blob: ef4063198a15b29faa1edac466f4ed154b637298 [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/passwords/password_generation_popup_controller_impl.h"
#include <memory>
#include <string>
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/passwords/password_generation_popup_controller.h"
#include "chrome/browser/ui/passwords/password_generation_popup_view.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/autofill/core/browser/ui/suggestion_hiding_reason.h"
#include "components/autofill/core/common/password_generation_util.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_generation_frame_helper.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/password_manager/core/browser/stub_password_manager_driver.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/browser/web_contents.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect_f.h"
#if !BUILDFLAG(IS_ANDROID)
#include "base/test/scoped_feature_list.h"
#include "components/password_manager/core/browser/features/password_features.h"
#endif // !BUILDFLAG(IS_ANDROID)
namespace password_manager {
namespace {
using autofill::password_generation::PasswordGenerationUIData;
using ::testing::_;
using ::testing::Return;
#if !BUILDFLAG(IS_ANDROID)
using password_manager::features::kPasswordGenerationExperiment;
using password_manager::prefs::kPasswordGenerationNudgePasswordDismissCount;
#endif // !BUILDFLAG(IS_ANDROID)
PasswordGenerationUIData CreatePasswordGenerationUIData() {
return PasswordGenerationUIData(
gfx::RectF(100, 20), /*max_length=*/20, u"element",
autofill::FieldRendererId(100),
/*is_generation_element_password_type=*/true, base::i18n::TextDirection(),
autofill::FormData(), /*input_field_empty=*/true);
}
class MockPasswordManagerDriver
: public password_manager::StubPasswordManagerDriver {
public:
MOCK_METHOD(void,
GeneratedPasswordAccepted,
(const std::u16string&),
(override));
MOCK_METHOD(void,
GeneratedPasswordAccepted,
(const autofill::FormData&,
autofill::FieldRendererId,
const std::u16string&),
(override));
MOCK_METHOD(PasswordGenerationFrameHelper*,
GetPasswordGenerationHelper,
(),
(override));
MOCK_METHOD(void,
PreviewGenerationSuggestion,
(const std::u16string& password),
(override));
MOCK_METHOD(void, ClearPreviewedForm, (), (override));
MOCK_METHOD(void, FocusNextFieldAfterPasswords, (), (override));
};
class MockPasswordGenerationPopupView : public PasswordGenerationPopupView {
public:
MOCK_METHOD(bool, Show, (), (override));
MOCK_METHOD(void, Hide, (), (override));
MOCK_METHOD(void, UpdateState, (), (override));
MOCK_METHOD(void, UpdateGeneratedPasswordValue, (), (override));
MOCK_METHOD(bool, UpdateBoundsAndRedrawPopup, (), (override));
MOCK_METHOD(void, PasswordSelectionUpdated, (), (override));
MOCK_METHOD(void, EditPasswordSelectionUpdated, (), (override));
MOCK_METHOD(void, NudgePasswordSelectionUpdated, (), (override));
};
class PasswordGenerationPopupControllerImplTest
: public ChromeRenderViewHostTestHarness {
public:
std::unique_ptr<MockPasswordManagerDriver> CreateDriver();
protected:
MockPasswordGenerationPopupView* popup_view() { return &view_; }
private:
MockPasswordGenerationPopupView view_;
};
std::unique_ptr<MockPasswordManagerDriver>
PasswordGenerationPopupControllerImplTest::CreateDriver() {
return std::make_unique<MockPasswordManagerDriver>();
}
} // namespace
TEST_F(PasswordGenerationPopupControllerImplTest, GetOrCreateTheSame) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller1 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller2 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
controller1, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
EXPECT_EQ(controller1.get(), controller2.get());
}
TEST_F(PasswordGenerationPopupControllerImplTest, GetOrCreateDifferentBounds) {
gfx::RectF rect(100, 20);
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller1 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, rect, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
rect = gfx::RectF(200, 30);
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller2 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
controller1, rect, ui_data, driver->AsWeakPtr(), /*observer=*/nullptr,
web_contents.get(), main_rfh(), /*pref_service=*/nullptr);
EXPECT_FALSE(controller1);
EXPECT_TRUE(controller2);
}
TEST_F(PasswordGenerationPopupControllerImplTest, GetOrCreateDifferentTabs) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller1 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller2 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
controller1, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
EXPECT_FALSE(controller1);
EXPECT_TRUE(controller2);
}
TEST_F(PasswordGenerationPopupControllerImplTest, GetOrCreateDifferentDrivers) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller1 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
driver = CreateDriver();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller2 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
controller1, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
EXPECT_FALSE(controller1);
EXPECT_TRUE(controller2);
}
TEST_F(PasswordGenerationPopupControllerImplTest,
GetOrCreateDifferentElements) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller1 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
ui_data.generation_element_id = autofill::FieldRendererId(200);
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller2 =
PasswordGenerationPopupControllerImpl::GetOrCreate(
controller1, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
EXPECT_FALSE(controller1);
EXPECT_TRUE(controller2);
}
TEST_F(PasswordGenerationPopupControllerImplTest, DestroyInPasswordAccepted) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
// Destroying the controller in GeneratedPasswordAccepted() should not cause a
// crash.
EXPECT_CALL(*driver,
GeneratedPasswordAccepted(_, autofill::FieldRendererId(100), _))
.WillOnce([controller](auto, auto, auto) {
controller->Hide(autofill::SuggestionHidingReason::kViewDestroyed);
});
controller->PasswordAccepted();
}
TEST_F(PasswordGenerationPopupControllerImplTest, GetElementTextDirection) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
ui_data.text_direction = base::i18n::TextDirection::RIGHT_TO_LEFT;
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
ASSERT_TRUE(controller);
EXPECT_EQ(controller->GetElementTextDirection(),
base::i18n::TextDirection::RIGHT_TO_LEFT);
}
TEST_F(PasswordGenerationPopupControllerImplTest,
PreviewIsTriggeredDuringGeneration) {
// The password generation helper is needed in the offer generation state and
// since the driver mock returns a raw pointer to it, we construct it first.
StubPasswordManagerClient client;
PasswordGenerationFrameHelper pw_generation_helper{&client,
/*driver=*/nullptr};
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
ON_CALL(*driver, GetPasswordGenerationHelper)
.WillByDefault(Return(&pw_generation_helper));
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
controller->SetViewForTesting(popup_view());
// In the offer generation state, suggestions are previewed on selection.
controller->Show(
PasswordGenerationPopupController::GenerationUIState::kOfferGeneration);
EXPECT_CALL(*driver, PreviewGenerationSuggestion);
static_cast<PasswordGenerationPopupController*>(controller.get())
->SetSelected();
}
TEST_F(PasswordGenerationPopupControllerImplTest,
PreviewIsTriggeredOnlyDuringOfferGeneration) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
controller->SetViewForTesting(popup_view());
// In the edit generated password state, no preview calls happen.
controller->Show(PasswordGenerationPopupController::GenerationUIState::
kEditGeneratedPassword);
EXPECT_CALL(*driver, PreviewGenerationSuggestion).Times(0);
static_cast<PasswordGenerationPopupController*>(controller.get())
->SetSelected();
}
TEST_F(PasswordGenerationPopupControllerImplTest, ClearsFormPreviewOnHide) {
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
EXPECT_CALL(*driver, ClearPreviewedForm());
controller->Hide(autofill::SuggestionHidingReason::kViewDestroyed);
}
#if !BUILDFLAG(IS_ANDROID)
TEST_F(PasswordGenerationPopupControllerImplTest,
AdvancesFieldFocusOnUseStrongPassword) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeaturesAndParameters(
{{kPasswordGenerationExperiment,
{{"password_generation_variation", "edit_password"}}}},
{});
PasswordGenerationUIData ui_data{CreatePasswordGenerationUIData()};
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
EXPECT_CALL(*driver,
GeneratedPasswordAccepted(_, autofill::FieldRendererId(100), _));
EXPECT_CALL(*driver, FocusNextFieldAfterPasswords);
controller->PasswordAccepted();
}
TEST_F(PasswordGenerationPopupControllerImplTest,
DoesNotAdvanceFieldFocusOnEditPassword) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeaturesAndParameters(
{{kPasswordGenerationExperiment,
{{"password_generation_variation", "edit_password"}}}},
{});
PasswordGenerationUIData ui_data{CreatePasswordGenerationUIData()};
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
// EditPasswordClicked() below results in calling view->Show(), hence the need
// to use the mock.
static_cast<PasswordGenerationPopupControllerImpl*>(controller.get())
->SetViewForTesting(popup_view());
EXPECT_CALL(*driver,
GeneratedPasswordAccepted(_, autofill::FieldRendererId(100), _));
EXPECT_CALL(*driver, FocusNextFieldAfterPasswords).Times(0);
controller->EditPasswordClicked();
}
TEST_F(PasswordGenerationPopupControllerImplTest,
PreviewsGeneratedPasswordOnShowInNudgePassword) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeaturesAndParameters(
{{kPasswordGenerationExperiment,
{{"password_generation_variation", "nudge_password"}}}},
{});
// The password generation helper is needed in the offer generation state and
// since the driver mock returns a raw pointer to it, we construct it first.
StubPasswordManagerClient client;
PasswordGenerationFrameHelper pw_generation_helper{&client,
/*driver=*/nullptr};
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
ON_CALL(*driver, GetPasswordGenerationHelper)
.WillByDefault(Return(&pw_generation_helper));
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupControllerImpl> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(),
/*pref_service=*/nullptr);
controller->SetViewForTesting(popup_view());
// TODO(crbug.com/40267532): Rewrite controller_->Show() function to allow
// testing expectations when the view doesn't exist. SetViewForTesting
// prevents that currently, hence the update view flow is being called.
ON_CALL(*popup_view(), UpdateBoundsAndRedrawPopup)
.WillByDefault(Return(true));
// In the nudge password experiment suggestion is previewed on show.
EXPECT_CALL(*driver, PreviewGenerationSuggestion);
controller->Show(
PasswordGenerationPopupController::GenerationUIState::kOfferGeneration);
}
TEST_F(PasswordGenerationPopupControllerImplTest,
IncrementsNudgePasswordDismissCountPrefOnHide) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeaturesAndParameters(
{{kPasswordGenerationExperiment,
{{"password_generation_variation", "nudge_password"}}}},
{});
TestingPrefServiceSimple pref_service;
pref_service.registry()->RegisterIntegerPref(
kPasswordGenerationNudgePasswordDismissCount, 0);
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(), &pref_service);
EXPECT_EQ(
pref_service.GetInteger(kPasswordGenerationNudgePasswordDismissCount), 0);
controller->Hide(autofill::SuggestionHidingReason::kUserAborted);
EXPECT_EQ(
pref_service.GetInteger(kPasswordGenerationNudgePasswordDismissCount), 1);
}
TEST_F(PasswordGenerationPopupControllerImplTest,
ResetsNudgePasswordDismissCountPrefOnPasswordAccepted) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeaturesAndParameters(
{{kPasswordGenerationExperiment,
{{"password_generation_variation", "nudge_password"}}}},
{});
TestingPrefServiceSimple pref_service;
pref_service.registry()->RegisterIntegerPref(
kPasswordGenerationNudgePasswordDismissCount, 4);
PasswordGenerationUIData ui_data = CreatePasswordGenerationUIData();
auto driver = CreateDriver();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
base::WeakPtr<PasswordGenerationPopupController> controller =
PasswordGenerationPopupControllerImpl::GetOrCreate(
/*previous=*/nullptr, ui_data.bounds, ui_data, driver->AsWeakPtr(),
/*observer=*/nullptr, web_contents.get(), main_rfh(), &pref_service);
EXPECT_EQ(
pref_service.GetInteger(kPasswordGenerationNudgePasswordDismissCount), 4);
controller->PasswordAccepted();
EXPECT_EQ(
pref_service.GetInteger(kPasswordGenerationNudgePasswordDismissCount), 0);
}
#endif // !BUILDFLAG(IS_ANDROID)
} // namespace password_manager