blob: 6b72bb64cd5d0082d267e966ccb4085768e2524f [file] [log] [blame]
// Copyright 2023 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/extensions/controlled_home_bubble_delegate.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_web_ui_override_registrar.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
std::unique_ptr<KeyedService> BuildOverrideRegistrar(
content::BrowserContext* context) {
return std::make_unique<extensions::ExtensionWebUIOverrideRegistrar>(context);
}
} // namespace
class ControlledHomeBubbleDelegateTest : public BrowserWithTestWindowTest {
public:
ControlledHomeBubbleDelegateTest() = default;
~ControlledHomeBubbleDelegateTest() override = default;
// Loads an extension that overrides the home page of a user.
scoped_refptr<const extensions::Extension> LoadExtensionOverridingHome(
const std::string& name = "extension",
extensions::mojom::ManifestLocation location =
extensions::mojom::ManifestLocation::kInternal) {
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder(name)
.SetManifestVersion(3)
.SetLocation(location)
.SetManifestKey(
"chrome_settings_overrides",
base::Value::Dict().Set("homepage", "http://www.google.com"))
.Build();
extension_service_->GrantPermissions(extension.get());
extension_service_->AddExtension(extension.get());
return extension;
}
// Returns true if the extension is enabled.
bool IsExtensionEnabled(const extensions::ExtensionId& id) {
return extension_registry_->enabled_extensions().GetByID(id);
}
// Returns true if the extension is disabled and has the specified
// `disable_reason`.
bool IsExtensionDisabled(
const extensions::ExtensionId& id,
extensions::disable_reason::DisableReason disable_reason) {
return extension_registry_->disabled_extensions().GetByID(id) &&
extension_prefs_->GetDisableReasons(id) == disable_reason;
}
// Returns true if the extension has been acknowledged by the user.
bool IsExtensionAcknowledged(const extensions::ExtensionId& id) {
bool was_acknowledged = false;
return extension_prefs_->ReadPrefAsBoolean(
id, ControlledHomeBubbleDelegate::kAcknowledgedPreference,
&was_acknowledged) &&
was_acknowledged;
}
// Acknowledges the extension in preferences.
void AcknowledgeExtension(const extensions::ExtensionId& id) {
extension_prefs_->UpdateExtensionPref(
id, ControlledHomeBubbleDelegate::kAcknowledgedPreference,
base::Value(true));
}
extensions::ExtensionService* extension_service() {
return extension_service_.get();
}
private:
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
// Prevent the Profile from getting deleted before TearDown() is complete,
// since WaitForStorageCleanup() relies on an active Profile. See the
// DestroyProfileOnBrowserClose flag.
profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
profile(), ProfileKeepAliveOrigin::kBrowserWindow);
// The two lines of magical incantation required to get the extension
// service to work inside a unit test and access the extension prefs.
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile()))
->CreateExtensionService(base::CommandLine::ForCurrentProcess(),
base::FilePath(), false);
// Set up the rest of the necessary systems.
extension_service_ =
extensions::ExtensionSystem::Get(profile())->extension_service();
extension_service_->Init();
extensions::ExtensionWebUIOverrideRegistrar::GetFactoryInstance()
->SetTestingFactory(profile(),
base::BindRepeating(&BuildOverrideRegistrar));
extensions::ExtensionWebUIOverrideRegistrar::GetFactoryInstance()->Get(
profile());
extension_prefs_ = extensions::ExtensionPrefs::Get(profile());
extension_registry_ = extensions::ExtensionRegistry::Get(profile());
}
void TearDown() override {
extension_service_ = nullptr;
extension_prefs_ = nullptr;
extension_registry_ = nullptr;
WaitForStorageCleanup();
// Clean up global state for the delegates. Since profiles are stored in
// global variables, they can be shared between tests and cause
// unpredictable behavior.
ControlledHomeBubbleDelegate::ClearProfileSetForTesting();
profile_keep_alive_.reset();
BrowserWithTestWindowTest::TearDown();
}
void WaitForStorageCleanup() {
content::StoragePartition* partition =
profile()->GetDefaultStoragePartition();
if (partition) {
partition->WaitForDeletionTasksForTesting();
}
}
base::AutoReset<bool> ignore_learn_more_{
ControlledHomeBubbleDelegate::IgnoreLearnMoreForTesting()};
raw_ptr<extensions::ExtensionService> extension_service_;
raw_ptr<extensions::ExtensionPrefs> extension_prefs_;
raw_ptr<extensions::ExtensionRegistry> extension_registry_;
std::unique_ptr<base::CommandLine> command_line_;
std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
};
// Though the test harness should compile on all platforms, the behavior for
// extensions to override the home page is limited to mac and windows.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
TEST_F(ControlledHomeBubbleDelegateTest, ClickingExecuteDisablesTheExtension) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome();
ASSERT_TRUE(extension);
ASSERT_TRUE(browser());
ASSERT_TRUE(profile());
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
EXPECT_FALSE(did_close_programmatically);
bubble_delegate->OnBubbleClosed(
ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE);
EXPECT_TRUE(IsExtensionDisabled(
extension->id(), extensions::disable_reason::DISABLE_USER_ACTION));
// Since the extension was disabled, it shouldn't be acknowledged in
// preferences.
EXPECT_FALSE(IsExtensionAcknowledged(extension->id()));
}
TEST_F(ControlledHomeBubbleDelegateTest,
ClickingDismissAcknowledgesTheExtension) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome();
ASSERT_TRUE(extension);
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
EXPECT_FALSE(did_close_programmatically);
bubble_delegate->OnBubbleClosed(
ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS_USER_ACTION);
// The extension should remain enabled and be acknowledged.
EXPECT_TRUE(IsExtensionEnabled(extension->id()));
EXPECT_TRUE(IsExtensionAcknowledged(extension->id()));
}
TEST_F(ControlledHomeBubbleDelegateTest,
DismissByDeactivationDoesNotDisableOrAcknowledge) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome();
ASSERT_TRUE(extension);
{
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
EXPECT_FALSE(did_close_programmatically);
bubble_delegate->OnBubbleClosed(
ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS_DEACTIVATION);
}
// The extension should remain enabled but *shouldn't* be acknowledged.
EXPECT_TRUE(IsExtensionEnabled(extension->id()));
EXPECT_FALSE(IsExtensionAcknowledged(extension->id()));
{
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
// Even though the extension hasn't been acknowledged, we shouldn't show the
// bubble twice in the same session.
EXPECT_FALSE(bubble_delegate->ShouldShow());
}
}
TEST_F(ControlledHomeBubbleDelegateTest,
ClickingLearnMoreAcknowledgesTheExtension) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome();
ASSERT_TRUE(extension);
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
EXPECT_FALSE(did_close_programmatically);
bubble_delegate->OnBubbleClosed(
ToolbarActionsBarBubbleDelegate::CLOSE_LEARN_MORE);
EXPECT_TRUE(IsExtensionEnabled(extension->id()));
EXPECT_TRUE(IsExtensionAcknowledged(extension->id()));
}
TEST_F(ControlledHomeBubbleDelegateTest, DisablingTheExtensionClosesTheBubble) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome();
ASSERT_TRUE(extension);
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
extension_service()->DisableExtension(
extension->id(), extensions::disable_reason::DISABLE_USER_ACTION);
// The bubble should close as part of the extension being unloaded.
EXPECT_TRUE(did_close_programmatically);
// And it should remain unacknowledged.
EXPECT_FALSE(IsExtensionAcknowledged(extension->id()));
}
TEST_F(ControlledHomeBubbleDelegateTest,
BubbleShouldntShowIfExtensionAcknowledged) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome();
ASSERT_TRUE(extension);
AcknowledgeExtension(extension->id());
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_FALSE(bubble_delegate->ShouldShow());
}
TEST_F(ControlledHomeBubbleDelegateTest,
ExecutingOnOneExtensionDoesntAffectAnotherExtension) {
scoped_refptr<const extensions::Extension> extension1 =
LoadExtensionOverridingHome("ext1");
scoped_refptr<const extensions::Extension> extension2 =
LoadExtensionOverridingHome("ext2");
ASSERT_TRUE(extension1);
ASSERT_TRUE(extension2);
{
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
// The most-recently-installed extension should control the home page
// (`extension2`).
EXPECT_EQ(extension2, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
// Close the bubble with the "execute" action.
bubble_delegate->OnBubbleClosed(
ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE);
EXPECT_TRUE(IsExtensionDisabled(
extension2->id(), extensions::disable_reason::DISABLE_USER_ACTION));
EXPECT_TRUE(IsExtensionEnabled(extension1->id()));
EXPECT_FALSE(IsExtensionAcknowledged(extension2->id()));
EXPECT_FALSE(IsExtensionAcknowledged(extension1->id()));
}
{
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
// Since `extension2` was removed, we shouldn't have acknowledged either
// extension and we can re-show the bubble if the homepage is controlled
// by another extension.
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension1, bubble_delegate->extension_for_testing());
}
}
TEST_F(ControlledHomeBubbleDelegateTest,
AcknowledgingOneExtensionDoesntAffectAnother) {
scoped_refptr<const extensions::Extension> extension1 =
LoadExtensionOverridingHome("ext1");
scoped_refptr<const extensions::Extension> extension2 =
LoadExtensionOverridingHome("ext2");
ASSERT_TRUE(extension1);
ASSERT_TRUE(extension2);
{
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension2, bubble_delegate->extension_for_testing());
bool did_close_programmatically = false;
auto close_callback = base::BindLambdaForTesting(
[&did_close_programmatically]() { did_close_programmatically = true; });
bubble_delegate->PendingShow();
bubble_delegate->OnBubbleShown(std::move(close_callback));
// Dismiss the bubble; this acknowledges the extension.
bubble_delegate->OnBubbleClosed(
ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS_USER_ACTION);
EXPECT_TRUE(IsExtensionEnabled(extension2->id()));
EXPECT_TRUE(IsExtensionAcknowledged(extension2->id()));
EXPECT_TRUE(IsExtensionEnabled(extension1->id()));
EXPECT_FALSE(IsExtensionAcknowledged(extension1->id()));
}
{
// The bubble shouldn't want to show (the extension that controls the home
// page was acknowledged).
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
EXPECT_FALSE(bubble_delegate->ShouldShow());
}
// Disable the extension that was acknowledged.
extension_service()->DisableExtension(
extension2->id(), extensions::disable_reason::DISABLE_USER_ACTION);
{
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
// Now a new extension controls the home page, so we should re-show the
// bubble.
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(extension1, bubble_delegate->extension_for_testing());
}
}
TEST_F(ControlledHomeBubbleDelegateTest,
PolicyExtensionsRequirePolicyIndicators) {
scoped_refptr<const extensions::Extension> extension =
LoadExtensionOverridingHome(
"ext", extensions::mojom::ManifestLocation::kExternalPolicy);
ASSERT_TRUE(extension);
auto bubble_delegate =
std::make_unique<ControlledHomeBubbleDelegate>(browser());
// We still show the bubble for policy-installed extensions, but it should
// have a policy decoration.
EXPECT_TRUE(bubble_delegate->ShouldShow());
EXPECT_EQ(u"", bubble_delegate->GetActionButtonText());
std::unique_ptr<ToolbarActionsBarBubbleDelegate::ExtraViewInfo> extra_view =
bubble_delegate->GetExtraViewInfo();
// Note: Mac vs Win message styling.
EXPECT_TRUE(extra_view->text == u"Installed by your administrator" ||
extra_view->text == u"Installed by Your Administrator");
EXPECT_FALSE(extra_view->is_learn_more);
}
#endif