blob: 41ac6d605e688a01035155c512abe9049650bb67 [file]
// Copyright 2025 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/default_browser/default_browser_manager.h"
#include <memory>
#include <string>
#include <utility>
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/default_browser/default_browser_features.h"
#include "chrome/browser/default_browser/default_browser_notification_observer.h"
#include "chrome/browser/default_browser/test_support/fake_shell_delegate.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/unowned_user_data/user_data_factory.h"
#if BUILDFLAG(IS_WIN)
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#endif // BUILDFLAG(IS_WIN)
namespace default_browser {
namespace {
#if BUILDFLAG(IS_WIN)
constexpr wchar_t kProgIdValue[] = L"ProgId";
constexpr wchar_t kRegistryPath[] =
L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\h"
L"ttp\\UserChoice";
#endif // BUILDFLAG(IS_WIN)
} // namespace
#if BUILDFLAG(IS_WIN)
class DefaultBrowserManagerWinBrowserTest : public InProcessBrowserTest {
protected:
DefaultBrowserManagerWinBrowserTest() {
scoped_feature_list_.InitWithFeatures(
/*enable_features*/ {kDefaultBrowserFramework,
kDefaultBrowserChangedOsNotification},
/*disabled_features=*/{});
}
~DefaultBrowserManagerWinBrowserTest() override = default;
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(
registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
base::win::RegKey key;
ASSERT_EQ(ERROR_SUCCESS,
key.Create(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE));
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(
browser()->profile());
}
void TearDownOnMainThread() override {
display_service_tester_.reset();
fake_shell_delegate_ptr_ = nullptr;
InProcessBrowserTest::TearDownOnMainThread();
}
void SetUpInProcessBrowserTestFixture() override {
global_feature_override_ =
GlobalFeatures::GetUserDataFactoryForTesting().AddOverrideForTesting(
base::BindLambdaForTesting([&](BrowserProcess& browser_process) {
auto fake_shell_delegate = std::make_unique<FakeShellDelegate>();
fake_shell_delegate_ptr_ = fake_shell_delegate.get();
return std::make_unique<DefaultBrowserManager>(
&browser_process, std::move(fake_shell_delegate),
base::BindLambdaForTesting(
[&]() { return browser()->profile(); }));
}));
}
// Helper to simulate the environment change that triggers the notification.
void TriggerNotification() {
// 1. Ensure Chrome believes it is NOT default.
fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
// 2. Set up the registry to look like Chrome was default initially (so the
// change to something else is significant).
CreateDefaultBrowserKey(L"ChromeHTML");
content::RunAllTasksUntilIdle();
// 3. Prepare to listen for the async monitor event.
base::test::TestFuture<DefaultBrowserState> monitor_future;
auto* manager = DefaultBrowserManager::From(g_browser_process);
base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
monitor_future.GetRepeatingCallback());
// 4. Simulate external registry change to a different browser.
ChangeDefaultBrowserProgId(L"VanadiumHTML");
// 5. Wait for the manager to detect the change and update state.
fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
EXPECT_EQ(monitor_future.Take(), shell_integration::NOT_DEFAULT);
// 6. Allow the notification tasks to post to the UI thread.
content::RunAllTasksUntilIdle();
}
void CreateDefaultBrowserKey(const std::wstring& prog_id) {
base::win::RegKey key;
ASSERT_EQ(ERROR_SUCCESS,
key.Create(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE));
ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kProgIdValue, prog_id.c_str()));
}
void ChangeDefaultBrowserProgId(const std::wstring& prog_id) {
base::win::RegKey key;
ASSERT_EQ(ERROR_SUCCESS,
key.Open(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE));
ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kProgIdValue, prog_id.c_str()));
}
raw_ptr<FakeShellDelegate> fake_shell_delegate_ptr_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
base::HistogramTester histogram_tester_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
ui::UserDataFactory::ScopedOverride global_feature_override_;
registry_util::RegistryOverrideManager registry_override_manager_;
};
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
ChangeIsDetectedAndObserverIsNotified) {
CreateDefaultBrowserKey(L"ChromeHTML");
DefaultBrowserManager* manager =
DefaultBrowserManager::From(g_browser_process);
ASSERT_TRUE(manager);
base::test::TestFuture<DefaultBrowserState> future;
base::CallbackListSubscription subscription =
manager->RegisterDefaultBrowserChanged(future.GetRepeatingCallback());
ChangeDefaultBrowserProgId(L"VanadiumHTML");
EXPECT_TRUE(future.Wait());
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
SubscriptionDestroyedPreventsCallback) {
CreateDefaultBrowserKey(L"ChromeHTML");
DefaultBrowserManager* manager =
DefaultBrowserManager::From(g_browser_process);
ASSERT_TRUE(manager);
base::test::TestFuture<DefaultBrowserState> future;
{
base::CallbackListSubscription subscription =
manager->RegisterDefaultBrowserChanged(future.GetRepeatingCallback());
}
ChangeDefaultBrowserProgId(L"VanadiumHTML");
content::RunAllTasksUntilIdle();
EXPECT_FALSE(future.IsReady());
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
AllRegisteredObserversAreNotified) {
CreateDefaultBrowserKey(L"ChromeHTML");
DefaultBrowserManager* manager =
DefaultBrowserManager::From(g_browser_process);
ASSERT_TRUE(manager);
base::test::TestFuture<DefaultBrowserState> future1;
base::test::TestFuture<DefaultBrowserState> future2;
base::CallbackListSubscription subscription1 =
manager->RegisterDefaultBrowserChanged(future1.GetRepeatingCallback());
base::CallbackListSubscription subscription2 =
manager->RegisterDefaultBrowserChanged(future2.GetRepeatingCallback());
ChangeDefaultBrowserProgId(L"VanadiumHTML");
EXPECT_TRUE(future1.Wait());
EXPECT_TRUE(future2.Wait());
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
SubsequentChangesAreAlsoDetected) {
CreateDefaultBrowserKey(L"ChromeHTML");
DefaultBrowserManager* manager =
DefaultBrowserManager::From(g_browser_process);
ASSERT_TRUE(manager);
{
base::test::TestFuture<DefaultBrowserState> future_change1;
base::CallbackListSubscription subscription =
manager->RegisterDefaultBrowserChanged(
future_change1.GetRepeatingCallback());
ChangeDefaultBrowserProgId(L"VanadiumHTML");
ASSERT_TRUE(future_change1.Wait()) << "First change was not detected.";
}
{
base::test::TestFuture<DefaultBrowserState> future_change2;
base::CallbackListSubscription subscription =
manager->RegisterDefaultBrowserChanged(
future_change2.GetRepeatingCallback());
ChangeDefaultBrowserProgId(L"ManganeseHTML");
EXPECT_TRUE(future_change2.Wait()) << "Second change was not detected.";
}
}
IN_PROC_BROWSER_TEST_F(
DefaultBrowserManagerWinBrowserTest,
SubsequentChangesAreAlsoDetectedWithTheSameSubscription) {
CreateDefaultBrowserKey(L"ChromeHTML");
DefaultBrowserManager* manager =
DefaultBrowserManager::From(g_browser_process);
ASSERT_TRUE(manager);
int call_count = 0;
base::RunLoop run_loop1;
base::RunLoop run_loop2;
// This callback will be invoked for each default browser change.
// On the first invocation, it will quit the first `run_loop`.
// On the second, it will quit the second `run_loop`.
auto subscription_callback = base::BindRepeating(
[](int* count, base::RunLoop* loop1, base::RunLoop* loop2,
DefaultBrowserState state) {
(*count)++;
if (*count == 1) {
loop1->Quit();
} else if (*count == 2) {
loop2->Quit();
}
},
&call_count, &run_loop1, &run_loop2);
// Register the single subscription that will be used for the entire test.
base::CallbackListSubscription subscription =
manager->RegisterDefaultBrowserChanged(subscription_callback);
// Trigger the first change.
ChangeDefaultBrowserProgId(L"VanadiumHTML");
run_loop1.Run();
EXPECT_EQ(call_count, 1) << "First change was not detected.";
// Trigger the second change. The same subscription should be notified again.
ChangeDefaultBrowserProgId(L"ManganeseHTML");
run_loop2.Run();
EXPECT_EQ(call_count, 2) << "Second change was not detected.";
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
RegistryChangeTriggersSystemNotification) {
TriggerNotification();
auto notification = display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId);
ASSERT_TRUE(notification.has_value());
EXPECT_EQ(notification->title(),
l10n_util::GetStringUTF16(IDS_DEFAULT_BROWSER_CHANGED_TITLE));
EXPECT_EQ(notification->message(),
l10n_util::GetStringUTF16(IDS_DEFAULT_BROWSER_CHANGED_MESSAGE));
histogram_tester_.ExpectUniqueSample(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Shown", 1, 1);
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
NoNotificationWhenChromeRemainsDefault) {
fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
CreateDefaultBrowserKey(L"ChromeHTML");
base::test::TestFuture<DefaultBrowserState> monitor_future;
auto* manager = DefaultBrowserManager::From(g_browser_process);
base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
monitor_future.GetRepeatingCallback());
ChangeDefaultBrowserProgId(L"ChromeHTML");
EXPECT_EQ(monitor_future.Take(), shell_integration::IS_DEFAULT);
content::RunAllTasksUntilIdle();
auto notification = display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId);
EXPECT_FALSE(notification.has_value())
<< "Notification shown even though Chrome is default.";
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
ClickAcceptTriggersSetterAndMetric) {
TriggerNotification();
ASSERT_TRUE(display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId));
display_service_tester_->SimulateClick(
NotificationHandler::Type::DEFAULT_BROWSER_CHANGED,
DefaultBrowserManager::kNotificationId,
/*action_index=*/0,
/*reply=*/std::nullopt);
histogram_tester_.ExpectUniqueSample(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
DefaultBrowserInteractionType::kAccepted, 1);
histogram_tester_.ExpectBucketCount(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
DefaultBrowserInteractionType::kDismissed, 0);
EXPECT_FALSE(display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId));
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
ClickNoThanksDismissesAndRecordsMetric) {
TriggerNotification();
ASSERT_TRUE(display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId));
display_service_tester_->SimulateClick(
NotificationHandler::Type::DEFAULT_BROWSER_CHANGED,
DefaultBrowserManager::kNotificationId,
/*action_index=*/1,
/*reply=*/std::nullopt);
histogram_tester_.ExpectUniqueSample(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
DefaultBrowserInteractionType::kDismissed, 1);
histogram_tester_.ExpectBucketCount(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
DefaultBrowserInteractionType::kAccepted, 0);
EXPECT_FALSE(display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId));
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
UserCloseDismissesAndRecordsMetric) {
TriggerNotification();
ASSERT_TRUE(display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId));
display_service_tester_->RemoveNotification(
NotificationHandler::Type::DEFAULT_BROWSER_CHANGED,
DefaultBrowserManager::kNotificationId,
/*by_user=*/true);
histogram_tester_.ExpectUniqueSample(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
DefaultBrowserInteractionType::kDismissed, 1);
histogram_tester_.ExpectBucketCount(
"DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
DefaultBrowserInteractionType::kAccepted, 0);
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
NoNotificationWhenRemainingNotDefault) {
fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
CreateDefaultBrowserKey(L"EdgeHTML");
content::RunAllTasksUntilIdle();
base::test::TestFuture<DefaultBrowserState> monitor_future;
auto* manager = DefaultBrowserManager::From(g_browser_process);
base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
monitor_future.GetRepeatingCallback());
ChangeDefaultBrowserProgId(L"VanadiumHTML");
EXPECT_EQ(monitor_future.Take(), shell_integration::NOT_DEFAULT);
content::RunAllTasksUntilIdle();
auto notification = display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId);
EXPECT_FALSE(notification.has_value());
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
NoNotificationWhenBecomingDefault) {
fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
CreateDefaultBrowserKey(L"EdgeHTML");
content::RunAllTasksUntilIdle();
base::test::TestFuture<DefaultBrowserState> monitor_future;
auto* manager = DefaultBrowserManager::From(g_browser_process);
base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
monitor_future.GetRepeatingCallback());
fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
ChangeDefaultBrowserProgId(L"ChromeHTML");
EXPECT_EQ(monitor_future.Take(), shell_integration::IS_DEFAULT);
content::RunAllTasksUntilIdle();
auto notification = display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId);
EXPECT_FALSE(notification.has_value());
}
IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
NotificationShownOnTransitionFromDefault) {
fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
CreateDefaultBrowserKey(L"ChromeHTML");
content::RunAllTasksUntilIdle();
base::test::TestFuture<DefaultBrowserState> monitor_future;
auto* manager = DefaultBrowserManager::From(g_browser_process);
base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
monitor_future.GetRepeatingCallback());
fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
ChangeDefaultBrowserProgId(L"VanadiumHTML");
EXPECT_EQ(monitor_future.Take(), shell_integration::NOT_DEFAULT);
content::RunAllTasksUntilIdle();
auto notification = display_service_tester_->GetNotification(
DefaultBrowserManager::kNotificationId);
EXPECT_TRUE(notification.has_value());
}
#endif // BUILDFLAG(IS_WIN)
} // namespace default_browser