blob: d07531c6ca44697ce27e4dad22589caceddbf042 [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 <string>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/accessibility/accessibility_state_utils.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
#include "chrome/browser/ui/autofill/autofill_popup_view.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/content_autofill_driver_test_api.h"
#include "components/autofill/content/browser/content_autofill_router.h"
#include "components/autofill/content/browser/content_autofill_router_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_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/ui/popup_item_ids.h"
#include "components/autofill/core/browser/ui/suggestion.h"
#include "components/autofill/core/common/aliases.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/web_contents.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/accessibility/ax_active_popup.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_manager.h"
#include "ui/accessibility/ax_tree_manager_map.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.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"
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#include "content/public/browser/browser_accessibility_state.h"
#endif
#if BUILDFLAG(IS_ANDROID)
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/autofill/manual_filling_controller_impl.h"
#include "chrome/browser/autofill/mock_address_accessory_controller.h"
#include "chrome/browser/autofill/mock_credit_card_accessory_controller.h"
#include "chrome/browser/autofill/mock_manual_filling_view.h"
#include "chrome/browser/autofill/mock_password_accessory_controller.h"
#endif
using base::ASCIIToUTF16;
using base::WeakPtr;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
namespace autofill {
namespace {
ContentAutofillRouterTestApi test_api(ContentAutofillRouter* cadf) {
return ContentAutofillRouterTestApi(cadf);
}
class MockAutofillDriver : public ContentAutofillDriver {
public:
MockAutofillDriver(content::RenderFrameHost* rfh,
ContentAutofillDriverFactory* factory)
: ContentAutofillDriver(rfh, factory) {}
MockAutofillDriver(MockAutofillDriver&) = delete;
MockAutofillDriver& operator=(MockAutofillDriver&) = delete;
~MockAutofillDriver() override = default;
MOCK_METHOD(ui::AXTreeID, GetAxTreeId, (), (const override));
};
class MockBrowserAutofillManager : public BrowserAutofillManager {
public:
MockBrowserAutofillManager(AutofillDriver* driver,
ContentAutofillClient* client)
: BrowserAutofillManager(driver, client, "en-US") {}
MockBrowserAutofillManager(MockBrowserAutofillManager&) = delete;
MockBrowserAutofillManager& operator=(MockBrowserAutofillManager&) = delete;
~MockBrowserAutofillManager() override = default;
};
class MockAutofillExternalDelegate : public AutofillExternalDelegate {
public:
MockAutofillExternalDelegate(BrowserAutofillManager* autofill_manager,
AutofillDriver* autofill_driver)
: AutofillExternalDelegate(autofill_manager, autofill_driver) {}
~MockAutofillExternalDelegate() override = default;
void DidSelectSuggestion(const Suggestion& suggestion) override {}
bool RemoveSuggestion(const std::u16string& value,
PopupItemId popup_item_id,
Suggestion::BackendId backend_id) override {
return true;
}
base::WeakPtr<AutofillExternalDelegate> GetWeakPtr() {
return AutofillExternalDelegate::GetWeakPtr();
}
MOCK_METHOD(void, ClearPreviewedForm, (), (override));
MOCK_METHOD(void, OnPopupSuppressed, (), (override));
MOCK_METHOD(void, DidAcceptSuggestion, (const Suggestion&, int), (override));
};
class MockAutofillPopupView : public AutofillPopupView {
public:
MockAutofillPopupView() = default;
MockAutofillPopupView(MockAutofillPopupView&) = delete;
MockAutofillPopupView& operator=(MockAutofillPopupView&) = delete;
~MockAutofillPopupView() override = default;
MOCK_METHOD(void, Show, (AutoselectFirstSuggestion), (override));
MOCK_METHOD(void, Hide, (), (override));
MOCK_METHOD(bool,
HandleKeyPressEvent,
(const content::NativeWebKeyboardEvent&),
(override));
MOCK_METHOD(void, OnSuggestionsChanged, (), (override));
MOCK_METHOD(absl::optional<int32_t>, GetAxUniqueId, (), (override));
MOCK_METHOD(void, AxAnnounce, (const std::u16string&), (override));
base::WeakPtr<AutofillPopupView> GetWeakPtr() override {
return weak_ptr_factory_.GetWeakPtr();
}
private:
base::WeakPtrFactory<AutofillPopupView> weak_ptr_factory_{this};
};
class TestAutofillPopupController : public AutofillPopupControllerImpl {
public:
TestAutofillPopupController(
base::WeakPtr<AutofillExternalDelegate> external_delegate,
content::WebContents* web_contents,
const gfx::RectF& element_bounds,
base::RepeatingCallback<void(gfx::NativeWindow, Profile*)>
show_pwd_migration_warning_callback)
: AutofillPopupControllerImpl(
external_delegate,
web_contents,
nullptr,
element_bounds,
base::i18n::UNKNOWN_DIRECTION,
std::move(show_pwd_migration_warning_callback)) {}
~TestAutofillPopupController() override = default;
// Making protected functions public for testing
using AutofillPopupControllerImpl::AcceptSuggestion;
using AutofillPopupControllerImpl::AcceptSuggestionWithoutThreshold;
using AutofillPopupControllerImpl::element_bounds;
using AutofillPopupControllerImpl::FireControlsChangedEvent;
using AutofillPopupControllerImpl::GetLineCount;
using AutofillPopupControllerImpl::GetRootAXPlatformNodeForWebContents;
using AutofillPopupControllerImpl::GetSuggestionAt;
using AutofillPopupControllerImpl::GetSuggestionLabelsAt;
using AutofillPopupControllerImpl::GetSuggestionMainTextAt;
using AutofillPopupControllerImpl::GetWeakPtr;
using AutofillPopupControllerImpl::RemoveSuggestion;
using AutofillPopupControllerImpl::SelectSuggestion;
MOCK_METHOD(void, OnSuggestionsChanged, (), (override));
MOCK_METHOD(void, Hide, (PopupHidingReason reason), (override));
MOCK_METHOD(ui::AXPlatformNode*,
GetRootAXPlatformNodeForWebContents,
(),
(override));
void DoHide() { DoHide(PopupHidingReason::kTabGone); }
void DoHide(PopupHidingReason reason) {
AutofillPopupControllerImpl::Hide(reason);
}
};
} // namespace
class AutofillPopupControllerUnitTest : public ChromeRenderViewHostTestHarness {
public:
AutofillPopupControllerUnitTest()
: ChromeRenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~AutofillPopupControllerUnitTest() override = default;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
// Make sure RenderFrame is created.
NavigateAndCommit(GURL("about:blank"));
external_delegate_ = CreateExternalDelegate();
autofill_popup_view_ = std::make_unique<NiceMock<MockAutofillPopupView>>();
#if BUILDFLAG(IS_ANDROID)
autofill_popup_controller_ = new NiceMock<TestAutofillPopupController>(
external_delegate_->GetWeakPtr(), web_contents(), gfx::RectF(),
show_pwd_migration_warning_callback_.Get());
ManualFillingControllerImpl::CreateForWebContentsForTesting(
web_contents(), mock_pwd_controller_.AsWeakPtr(),
mock_address_controller_.AsWeakPtr(), mock_cc_controller_.AsWeakPtr(),
std::make_unique<NiceMock<MockManualFillingView>>());
#else
autofill_popup_controller_ = new NiceMock<TestAutofillPopupController>(
external_delegate_->GetWeakPtr(), web_contents(), gfx::RectF(),
base::DoNothing());
#endif
autofill_popup_controller_->SetViewForTesting(
autofill_popup_view()->GetWeakPtr());
}
void TearDown() override {
// This will make sure the controller and the view (if any) are both
// cleaned up.
if (autofill_popup_controller_) {
autofill_popup_controller_->DoHide();
}
external_delegate_.reset();
ChromeRenderViewHostTestHarness::TearDown();
}
virtual std::unique_ptr<NiceMock<MockAutofillExternalDelegate>>
CreateExternalDelegate() {
// Fake that |driver| has queried a form.
test_api(&autofill_router()).set_last_queried_source(autofill_driver());
return std::make_unique<NiceMock<MockAutofillExternalDelegate>>(
autofill_manager(), autofill_driver());
}
// Shows empty suggestions with the popup_item_id ids passed as
// `popup_item_ids`.
void ShowSuggestions(const std::vector<PopupItemId>& popup_item_ids) {
std::vector<Suggestion> suggestions;
suggestions.reserve(popup_item_ids.size());
for (PopupItemId popup_item_id : popup_item_ids) {
suggestions.emplace_back("", "", "", popup_item_id);
}
popup_controller().Show(std::move(suggestions),
AutoselectFirstSuggestion(false));
}
TestAutofillPopupController& popup_controller() {
return *autofill_popup_controller_;
}
NiceMock<MockAutofillExternalDelegate>* delegate() {
return external_delegate_.get();
}
MockAutofillPopupView* autofill_popup_view() {
return autofill_popup_view_.get();
}
content::NativeWebKeyboardEvent CreateTabKeyPressEvent() {
content::NativeWebKeyboardEvent event(
blink::WebInputEvent::Type::kRawKeyDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
event.dom_key = ui::DomKey::TAB;
event.dom_code = static_cast<int>(ui::DomCode::TAB);
event.native_key_code =
ui::KeycodeConverter::DomCodeToNativeKeycode(ui::DomCode::TAB);
event.windows_key_code = ui::VKEY_TAB;
return event;
}
protected:
TestContentAutofillClient* autofill_client() {
return autofill_client_injector_[web_contents()];
}
ContentAutofillRouter& autofill_router() {
return autofill_client()->GetAutofillDriverFactory()->autofill_router();
}
NiceMock<MockAutofillDriver>* autofill_driver() {
return autofill_driver_injector_[web_contents()];
}
BrowserAutofillManager* autofill_manager() {
return static_cast<BrowserAutofillManager*>(
autofill_driver()->autofill_manager());
}
TestAutofillClientInjector<TestContentAutofillClient>
autofill_client_injector_;
TestAutofillDriverInjector<NiceMock<MockAutofillDriver>>
autofill_driver_injector_;
test::AutofillUnitTestEnvironment autofill_test_environment_;
std::unique_ptr<NiceMock<MockAutofillExternalDelegate>> external_delegate_;
std::unique_ptr<NiceMock<MockAutofillPopupView>> autofill_popup_view_;
#if BUILDFLAG(IS_ANDROID)
NiceMock<MockPasswordAccessoryController> mock_pwd_controller_;
NiceMock<MockAddressAccessoryController> mock_address_controller_;
NiceMock<MockCreditCardAccessoryController> mock_cc_controller_;
base::MockCallback<base::RepeatingCallback<void(gfx::NativeWindow, Profile*)>>
show_pwd_migration_warning_callback_;
#endif
raw_ptr<NiceMock<TestAutofillPopupController>, DanglingUntriaged>
autofill_popup_controller_ = nullptr;
};
TEST_F(AutofillPopupControllerUnitTest, RemoveSuggestion) {
ShowSuggestions({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(external_delegate_.get());
// Remove the first entry. The popup should be redrawn since its size has
// changed.
EXPECT_CALL(popup_controller(), OnSuggestionsChanged());
EXPECT_TRUE(popup_controller().RemoveSuggestion(0));
Mock::VerifyAndClearExpectations(autofill_popup_view());
// Remove the next entry. The popup should then be hidden since there are
// no Autofill entries left.
EXPECT_CALL(popup_controller(), Hide(PopupHidingReason::kNoSuggestions));
EXPECT_TRUE(popup_controller().RemoveSuggestion(0));
}
TEST_F(AutofillPopupControllerUnitTest, UpdateDataListValues) {
ShowSuggestions({PopupItemId::kAddressEntry});
// Add one data list entry.
std::u16string value1 = u"data list value 1";
std::vector<std::u16string> data_list_values{value1};
std::u16string label1 = u"data list label 1";
std::vector<std::u16string> data_list_labels{label1};
popup_controller().UpdateDataListValues(data_list_values, data_list_labels);
ASSERT_EQ(3, popup_controller().GetLineCount());
Suggestion result0 = popup_controller().GetSuggestionAt(0);
EXPECT_EQ(value1, result0.main_text.value);
EXPECT_EQ(value1, popup_controller().GetSuggestionMainTextAt(0));
ASSERT_EQ(1u, result0.labels.size());
ASSERT_EQ(1u, result0.labels[0].size());
EXPECT_EQ(label1, result0.labels[0][0].value);
EXPECT_EQ(std::u16string(), result0.additional_label);
EXPECT_EQ(label1, popup_controller().GetSuggestionLabelsAt(0)[0][0].value);
EXPECT_EQ(PopupItemId::kDatalistEntry, result0.popup_item_id);
Suggestion result1 = popup_controller().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 = popup_controller().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).
std::u16string value2 = u"data list value 2";
data_list_values.push_back(value2);
std::u16string label2 = u"data list label 2";
data_list_labels.push_back(label2);
popup_controller().UpdateDataListValues(data_list_values, data_list_labels);
ASSERT_EQ(4, popup_controller().GetLineCount());
// Original one first, followed by new one, then separator.
EXPECT_EQ(value1, popup_controller().GetSuggestionAt(0).main_text.value);
EXPECT_EQ(value1, popup_controller().GetSuggestionMainTextAt(0));
ASSERT_EQ(1u, popup_controller().GetSuggestionAt(0).labels.size());
ASSERT_EQ(1u, popup_controller().GetSuggestionAt(0).labels[0].size());
EXPECT_EQ(label1, popup_controller().GetSuggestionAt(0).labels[0][0].value);
EXPECT_EQ(std::u16string(),
popup_controller().GetSuggestionAt(0).additional_label);
EXPECT_EQ(value2, popup_controller().GetSuggestionAt(1).main_text.value);
EXPECT_EQ(value2, popup_controller().GetSuggestionMainTextAt(1));
ASSERT_EQ(1u, popup_controller().GetSuggestionAt(1).labels.size());
ASSERT_EQ(1u, popup_controller().GetSuggestionAt(1).labels[0].size());
EXPECT_EQ(label2, popup_controller().GetSuggestionAt(1).labels[0][0].value);
EXPECT_EQ(std::u16string(),
popup_controller().GetSuggestionAt(1).additional_label);
EXPECT_EQ(PopupItemId::kSeparator,
popup_controller().GetSuggestionAt(2).popup_item_id);
// Clear all data list values.
data_list_values.clear();
popup_controller().UpdateDataListValues(data_list_values, data_list_labels);
ASSERT_EQ(1, popup_controller().GetLineCount());
EXPECT_EQ(PopupItemId::kAddressEntry,
popup_controller().GetSuggestionAt(0).popup_item_id);
}
TEST_F(AutofillPopupControllerUnitTest, PopupsWithOnlyDataLists) {
// Create the popup with a single datalist element.
ShowSuggestions({PopupItemId::kDatalistEntry});
// Replace the datalist element with a new one.
std::u16string value1 = u"data list value 1";
std::vector<std::u16string> data_list_values{value1};
std::u16string label1 = u"data list label 1";
std::vector<std::u16string> data_list_labels{label1};
popup_controller().UpdateDataListValues(data_list_values, data_list_labels);
ASSERT_EQ(1, popup_controller().GetLineCount());
EXPECT_EQ(value1, popup_controller().GetSuggestionAt(0).main_text.value);
ASSERT_EQ(1u, popup_controller().GetSuggestionAt(0).labels.size());
ASSERT_EQ(1u, popup_controller().GetSuggestionAt(0).labels[0].size());
EXPECT_EQ(label1, popup_controller().GetSuggestionAt(0).labels[0][0].value);
EXPECT_EQ(std::u16string(),
popup_controller().GetSuggestionAt(0).additional_label);
EXPECT_EQ(PopupItemId::kDatalistEntry,
popup_controller().GetSuggestionAt(0).popup_item_id);
// Clear datalist values and check that the popup becomes hidden.
EXPECT_CALL(popup_controller(), Hide(PopupHidingReason::kNoSuggestions));
data_list_values.clear();
popup_controller().UpdateDataListValues(data_list_values, data_list_values);
}
TEST_F(AutofillPopupControllerUnitTest, GetOrCreateAndroid) {
NiceMock<MockAutofillExternalDelegate> delegate(autofill_manager(),
autofill_driver());
WeakPtr<AutofillPopupControllerImpl> controller =
AutofillPopupControllerImpl::GetOrCreate(
WeakPtr<AutofillPopupControllerImpl>(), delegate.GetWeakPtr(),
web_contents(), nullptr, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION);
EXPECT_TRUE(controller.get());
controller->Hide(PopupHidingReason::kViewDestroyed);
controller = AutofillPopupControllerImpl::GetOrCreate(
WeakPtr<AutofillPopupControllerImpl>(), delegate.GetWeakPtr(),
web_contents(), nullptr, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION);
EXPECT_TRUE(controller.get());
WeakPtr<AutofillPopupControllerImpl> controller2 =
AutofillPopupControllerImpl::GetOrCreate(
controller, delegate.GetWeakPtr(), web_contents(), nullptr,
gfx::RectF(), base::i18n::UNKNOWN_DIRECTION);
EXPECT_EQ(controller.get(), controller2.get());
controller->Hide(PopupHidingReason::kViewDestroyed);
NiceMock<TestAutofillPopupController>* test_controller =
new NiceMock<TestAutofillPopupController>(delegate.GetWeakPtr(),
web_contents(), gfx::RectF(),
base::DoNothing());
EXPECT_CALL(*test_controller, Hide(PopupHidingReason::kViewDestroyed));
gfx::RectF bounds(0.f, 0.f, 1.f, 2.f);
base::WeakPtr<AutofillPopupControllerImpl> controller3 =
AutofillPopupControllerImpl::GetOrCreate(
test_controller->GetWeakPtr(), delegate.GetWeakPtr(), web_contents(),
nullptr, bounds, base::i18n::UNKNOWN_DIRECTION);
EXPECT_EQ(bounds, static_cast<AutofillPopupController*>(controller3.get())
->element_bounds());
controller3->Hide(PopupHidingReason::kViewDestroyed);
// Hide the test_controller to delete it.
test_controller->DoHide();
test_controller = new NiceMock<TestAutofillPopupController>(
delegate.GetWeakPtr(), web_contents(), gfx::RectF(), base::DoNothing());
EXPECT_CALL(*test_controller, Hide).Times(0);
const base::WeakPtr<AutofillPopupControllerImpl> controller4 =
AutofillPopupControllerImpl::GetOrCreate(
test_controller->GetWeakPtr(), delegate.GetWeakPtr(), web_contents(),
nullptr, bounds, base::i18n::UNKNOWN_DIRECTION);
EXPECT_EQ(bounds,
static_cast<const AutofillPopupController*>(controller4.get())
->element_bounds());
delete test_controller;
}
TEST_F(AutofillPopupControllerUnitTest, ProperlyResetController) {
ShowSuggestions(
{PopupItemId::kAutocompleteEntry, PopupItemId::kAutocompleteEntry});
// Now show a new popup with the same controller, but with fewer items.
WeakPtr<AutofillPopupControllerImpl> controller =
AutofillPopupControllerImpl::GetOrCreate(
popup_controller().GetWeakPtr(), delegate()->GetWeakPtr(), nullptr,
nullptr, gfx::RectF(), base::i18n::UNKNOWN_DIRECTION);
EXPECT_EQ(0, controller->GetLineCount());
}
TEST_F(AutofillPopupControllerUnitTest, HidingClearsPreview) {
// Create a new controller, because hiding destroys it and we can't destroy it
// twice.
StrictMock<MockAutofillExternalDelegate> delegate(autofill_manager(),
autofill_driver());
StrictMock<TestAutofillPopupController>* test_controller =
new StrictMock<TestAutofillPopupController>(delegate.GetWeakPtr(),
web_contents(), gfx::RectF(),
base::DoNothing());
EXPECT_CALL(delegate, ClearPreviewedForm());
// Hide() also deletes the object itself.
test_controller->DoHide();
}
TEST_F(AutofillPopupControllerUnitTest, DontHideWhenWaitingForData) {
EXPECT_CALL(*autofill_popup_view(), Hide).Times(0);
popup_controller().PinView();
// Hide() will not work for stale data or when focusing native UI.
popup_controller().DoHide(PopupHidingReason::kStaleData);
popup_controller().DoHide(PopupHidingReason::kEndEditing);
// Check the expectations now since TearDown will perform a successful hide.
Mock::VerifyAndClearExpectations(delegate());
Mock::VerifyAndClearExpectations(autofill_popup_view());
}
TEST_F(AutofillPopupControllerUnitTest, ShouldReportHidingPopupReason) {
// Create a new controller, because hiding destroys it and we can't destroy it
// twice (since we already hide it in the destructor).
NiceMock<MockAutofillExternalDelegate> delegate(autofill_manager(),
autofill_driver());
NiceMock<TestAutofillPopupController>* test_controller =
new NiceMock<TestAutofillPopupController>(delegate.GetWeakPtr(),
web_contents(), gfx::RectF(),
base::DoNothing());
base::HistogramTester histogram_tester;
// DoHide() invokes Hide() that also deletes the object itself.
test_controller->DoHide(PopupHidingReason::kTabGone);
histogram_tester.ExpectTotalCount("Autofill.PopupHidingReason", 1);
histogram_tester.ExpectBucketCount("Autofill.PopupHidingReason",
/*kTabGone=*/8, 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(AutofillPopupControllerUnitTest, SelectInvalidSuggestion) {
ShowSuggestions({PopupItemId::kAddressEntry});
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(0);
// The following should not crash:
popup_controller().AcceptSuggestion(1); // Out of bounds!
}
TEST_F(AutofillPopupControllerUnitTest, AcceptSuggestionRespectsTimeout) {
ShowSuggestions({PopupItemId::kAddressEntry});
// Calls before the threshold are ignored.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(0);
popup_controller().AcceptSuggestion(0);
task_environment()->FastForwardBy(base::Milliseconds(100));
popup_controller().AcceptSuggestion(0);
EXPECT_CALL(*delegate(), DidAcceptSuggestion);
task_environment()->FastForwardBy(base::Milliseconds(400));
popup_controller().AcceptSuggestion(0);
}
TEST_F(AutofillPopupControllerUnitTest, AcceptSuggestionWithoutThreshold) {
ShowSuggestions({PopupItemId::kAddressEntry});
// Calls are accepted immediately.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(1);
popup_controller().AcceptSuggestionWithoutThreshold(0);
}
TEST_F(AutofillPopupControllerUnitTest,
AcceptSuggestionTimeoutIsUpdatedOnPopupMove) {
ShowSuggestions({PopupItemId::kAddressEntry});
// Calls before the threshold are ignored.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(0);
popup_controller().AcceptSuggestion(0);
task_environment()->FastForwardBy(base::Milliseconds(100));
popup_controller().AcceptSuggestion(0);
task_environment()->FastForwardBy(base::Milliseconds(400));
// Show the suggestions again (simulating, e.g., a click somewhere slightly
// different).
ShowSuggestions({PopupItemId::kAddressEntry});
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(0);
popup_controller().AcceptSuggestion(0);
EXPECT_CALL(*delegate(), DidAcceptSuggestion);
// After waiting, suggestions are accepted again.
task_environment()->FastForwardBy(base::Milliseconds(500));
popup_controller().AcceptSuggestion(0);
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(AutofillPopupControllerUnitTest,
AcceptPwdSuggestionInvokesWarningAndroid) {
base::test::ScopedFeatureList scoped_feature_list(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions({PopupItemId::kPasswordEntry});
// Calls are accepted immediately.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(show_pwd_migration_warning_callback_, Run);
popup_controller().AcceptSuggestionWithoutThreshold(0);
}
TEST_F(AutofillPopupControllerUnitTest,
AcceptUsernameSuggestionInvokesWarningAndroid) {
base::test::ScopedFeatureList scoped_feature_list(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions({PopupItemId::kUsernameEntry});
// Calls are accepted immediately.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(show_pwd_migration_warning_callback_, Run);
popup_controller().AcceptSuggestionWithoutThreshold(0);
}
TEST_F(AutofillPopupControllerUnitTest,
AcceptPwdSuggestionNoWarningIfDisabledAndroid) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions({PopupItemId::kPasswordEntry});
// Calls are accepted immediately.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(show_pwd_migration_warning_callback_, Run).Times(0);
popup_controller().AcceptSuggestionWithoutThreshold(0);
}
TEST_F(AutofillPopupControllerUnitTest, AcceptAddressNoPwdWarningAndroid) {
base::test::ScopedFeatureList scoped_feature_list(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions({PopupItemId::kAddressEntry});
// Calls are accepted immediately.
EXPECT_CALL(*delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(show_pwd_migration_warning_callback_, Run).Times(0);
popup_controller().AcceptSuggestionWithoutThreshold(0);
}
#endif
#if !BUILDFLAG(IS_CHROMEOS_ASH)
class MockAxTreeManager : public ui::AXTreeManager {
public:
MockAxTreeManager() = default;
MockAxTreeManager(MockAxTreeManager&) = delete;
MockAxTreeManager& operator=(MockAxTreeManager&) = delete;
~MockAxTreeManager() override = default;
MOCK_METHOD(ui::AXNode*,
GetNodeFromTree,
(const ui::AXTreeID& tree_id, const int32_t node_id),
(const override));
MOCK_METHOD(ui::AXPlatformNodeDelegate*,
GetDelegate,
(const ui::AXTreeID tree_id, const int32_t node_id),
(const override));
MOCK_METHOD(ui::AXPlatformNodeDelegate*,
GetRootDelegate,
(const ui::AXTreeID tree_id),
(const override));
MOCK_METHOD(ui::AXTreeID, GetTreeID, (), (const override));
MOCK_METHOD(ui::AXTreeID, GetParentTreeID, (), (const override));
MOCK_METHOD(ui::AXNode*, GetRootAsAXNode, (), (const override));
MOCK_METHOD(ui::AXNode*, GetParentNodeFromParentTree, (), (const override));
};
class MockAxPlatformNodeDelegate : public ui::AXPlatformNodeDelegate {
public:
MockAxPlatformNodeDelegate() = default;
MockAxPlatformNodeDelegate(MockAxPlatformNodeDelegate&) = delete;
MockAxPlatformNodeDelegate& operator=(MockAxPlatformNodeDelegate&) = delete;
~MockAxPlatformNodeDelegate() override = default;
MOCK_METHOD(ui::AXPlatformNode*, GetFromNodeID, (int32_t id), (override));
MOCK_METHOD(ui::AXPlatformNode*,
GetFromTreeIDAndNodeID,
(const ui::AXTreeID& tree_id, int32_t id),
(override));
};
class MockAxPlatformNode : public ui::AXPlatformNodeBase {
public:
MockAxPlatformNode() = default;
MockAxPlatformNode(MockAxPlatformNode&) = delete;
MockAxPlatformNode& operator=(MockAxPlatformNode&) = delete;
~MockAxPlatformNode() override = default;
MOCK_METHOD(ui::AXPlatformNodeDelegate*, GetDelegate, (), (const override));
};
class AutofillPopupControllerAccessibilityUnitTest
: public AutofillPopupControllerUnitTest {
public:
static constexpr int kAxUniqueId = 123;
AutofillPopupControllerAccessibilityUnitTest()
: accessibility_mode_setter_(ui::AXMode::kScreenReader) {}
AutofillPopupControllerAccessibilityUnitTest(
AutofillPopupControllerAccessibilityUnitTest&) = delete;
AutofillPopupControllerAccessibilityUnitTest& operator=(
AutofillPopupControllerAccessibilityUnitTest&) = delete;
~AutofillPopupControllerAccessibilityUnitTest() override = default;
void SetUp() override {
AutofillPopupControllerUnitTest::SetUp();
ON_CALL(*autofill_driver(), GetAxTreeId())
.WillByDefault(Return(test_tree_id_));
ON_CALL(popup_controller(), GetRootAXPlatformNodeForWebContents)
.WillByDefault(Return(&mock_ax_platform_node_));
ON_CALL(mock_ax_platform_node_, GetDelegate)
.WillByDefault(Return(&mock_ax_platform_node_delegate_));
ON_CALL(*autofill_popup_view_, GetAxUniqueId)
.WillByDefault(Return(absl::optional<int32_t>(kAxUniqueId)));
ON_CALL(mock_ax_platform_node_delegate_, GetFromTreeIDAndNodeID)
.WillByDefault(Return(&mock_ax_platform_node_));
}
void TearDown() override {
// This needs to bo reset explicit because having the mode set to
// `kScreenReader` causes mocked functions to get called with
// `mock_ax_platform_node_delegate` after it has been destroyed.
accessibility_mode_setter_.ResetMode();
AutofillPopupControllerUnitTest::TearDown();
}
protected:
content::testing::ScopedContentAXModeSetter accessibility_mode_setter_;
MockAxPlatformNodeDelegate mock_ax_platform_node_delegate_;
MockAxPlatformNode mock_ax_platform_node_;
ui::AXTreeID test_tree_id_ = ui::AXTreeID::CreateNewAXTreeID();
};
// Test for successfully firing controls changed event for popup show/hide.
TEST_F(AutofillPopupControllerAccessibilityUnitTest,
FireControlsChangedEventDuringShowAndHide) {
ShowSuggestions({PopupItemId::kAddressEntry});
// Manually fire the event for popup show since setting the test view results
// in the fire controls changed event not being sent.
popup_controller().FireControlsChangedEvent(true);
EXPECT_EQ(kAxUniqueId, ui::GetActivePopupAxUniqueId());
popup_controller().DoHide();
EXPECT_EQ(absl::nullopt, ui::GetActivePopupAxUniqueId());
}
// Test for attempting to fire controls changed event when ax tree manager
// fails to retrieve the ax platform node associated with the popup.
// No event is fired and global active popup ax unique id is not set.
TEST_F(AutofillPopupControllerAccessibilityUnitTest,
FireControlsChangedEventNoAxPlatformNode) {
EXPECT_CALL(mock_ax_platform_node_delegate_, GetFromTreeIDAndNodeID)
.WillOnce(Return(nullptr));
ShowSuggestions({PopupItemId::kAddressEntry});
// Manually fire the event for popup show since setting the test view results
// in the fire controls changed event not being sent.
popup_controller().FireControlsChangedEvent(true);
EXPECT_EQ(absl::nullopt, ui::GetActivePopupAxUniqueId());
}
// Test for attempting to fire controls changed event when failing to retrieve
// the autofill popup's ax unique id. No event is fired and the global active
// popup ax unique id is not set.
TEST_F(AutofillPopupControllerAccessibilityUnitTest,
FireControlsChangedEventNoPopupAxUniqueId) {
EXPECT_CALL(*autofill_popup_view_, GetAxUniqueId)
.WillOnce(testing::Return(absl::nullopt));
ShowSuggestions({PopupItemId::kAddressEntry});
// Manually fire the event for popup show since setting the test view results
// in the fire controls changed event not being sent.
popup_controller().FireControlsChangedEvent(true);
EXPECT_EQ(absl::nullopt, ui::GetActivePopupAxUniqueId());
}
#endif
} // namespace autofill