| // Copyright 2024 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/glic/glic_profile_manager.h" |
| |
| #include <memory> |
| #include <string> |
| #include <type_traits> |
| |
| #include "base/memory/memory_pressure_monitor.h" |
| #include "base/test/test_future.h" |
| #include "chrome/browser/actor/actor_keyed_service_factory.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/contextual_cueing/contextual_cueing_service.h" |
| #include "chrome/browser/glic/public/glic_keyed_service.h" |
| #include "chrome/browser/glic/public/glic_keyed_service_factory.h" |
| #include "chrome/browser/glic/test_support/glic_test_environment.h" |
| #include "chrome/browser/glic/test_support/glic_test_util.h" |
| #include "chrome/browser/glic/widget/glic_window_controller.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_test_util.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/test_browser_window.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/test/browser_test.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/ozone_buildflags.h" |
| |
| namespace glic { |
| namespace { |
| |
| class MockGlicKeyedService : public GlicKeyedService { |
| public: |
| MockGlicKeyedService( |
| content::BrowserContext* browser_context, |
| signin::IdentityManager* identity_manager, |
| ProfileManager* profile_manager, |
| GlicProfileManager* glic_profile_manager, |
| contextual_cueing::ContextualCueingService* contextual_cueing_service, |
| actor::ActorKeyedService* actor_keyed_service) |
| : GlicKeyedService(Profile::FromBrowserContext(browser_context), |
| identity_manager, |
| profile_manager, |
| glic_profile_manager, |
| contextual_cueing_service, |
| actor_keyed_service) {} |
| MOCK_METHOD(void, ClosePanel, (), (override)); |
| |
| bool IsWindowDetached() const override { return detached_; } |
| void SetWindowDetached() { detached_ = true; } |
| |
| bool IsWindowShowing() const override { return showing_; } |
| void SetWindowShowing() { showing_ = true; } |
| |
| private: |
| bool detached_ = false; |
| bool showing_ = false; |
| }; |
| |
| class GlicProfileManagerBrowserTest : public InProcessBrowserTest { |
| public: |
| GlicProfileManagerBrowserTest() { |
| scoped_feature_list_.InitAndDisableFeature( |
| features::kDestroyProfileOnBrowserClose); |
| |
| create_services_subscription_ = |
| BrowserContextDependencyManager::GetInstance() |
| ->RegisterCreateServicesCallbackForTesting(base::BindRepeating( |
| &GlicProfileManagerBrowserTest::SetTestingFactory, |
| base::Unretained(this))); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| } |
| |
| MockGlicKeyedService* GetMockGlicKeyedService(Profile* profile) { |
| auto* service = GlicKeyedServiceFactory::GetGlicKeyedService(profile); |
| return static_cast<MockGlicKeyedService*>(service); |
| } |
| |
| Profile* CreateNewProfile() { |
| auto* profile_manager = g_browser_process->profile_manager(); |
| auto new_path = profile_manager->GenerateNextProfileDirectoryPath(); |
| profiles::testing::CreateProfileSync(profile_manager, new_path); |
| return profile_manager->GetProfile(new_path); |
| } |
| |
| protected: |
| void SetTestingFactory(content::BrowserContext* context) { |
| GlicKeyedServiceFactory::GetInstance()->SetTestingFactory( |
| context, base::BindRepeating( |
| &GlicProfileManagerBrowserTest::CreateMockGlicKeyedService, |
| base::Unretained(this))); |
| } |
| |
| std::unique_ptr<KeyedService> CreateMockGlicKeyedService( |
| content::BrowserContext* context) { |
| auto* identitity_manager = IdentityManagerFactory::GetForProfile( |
| Profile::FromBrowserContext(context)); |
| auto* actor_keyed_service = |
| actor::ActorKeyedServiceFactory::GetActorKeyedService(context); |
| return std::make_unique<MockGlicKeyedService>( |
| context, identitity_manager, g_browser_process->profile_manager(), |
| GlicProfileManager::GetInstance(), |
| /*contextual_cueing_service=*/nullptr, actor_keyed_service); |
| } |
| |
| GlicTestEnvironment glic_test_environment_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::CallbackListSubscription create_services_subscription_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicProfileManagerBrowserTest, |
| SetActiveGlic_SameProfile) { |
| auto* service0 = GetMockGlicKeyedService(browser()->profile()); |
| GlicProfileManager::GetInstance()->SetActiveGlic(service0); |
| // Opening glic twice for the same profile shouldn't cause it to close. |
| EXPECT_CALL(*service0, ClosePanel()).Times(0); |
| GlicProfileManager::GetInstance()->SetActiveGlic(service0); |
| } |
| |
| // TODO(crbug.com/448406730): Re-enable after testing the logic of close panel |
| // being now handled by EmbedderDelegate. |
| IN_PROC_BROWSER_TEST_F(GlicProfileManagerBrowserTest, |
| DISABLED_SetActiveGlic_DifferentProfiles) { |
| auto* service0 = GetMockGlicKeyedService(browser()->profile()); |
| |
| auto* profile1 = CreateNewProfile(); |
| auto* service1 = GetMockGlicKeyedService(profile1); |
| |
| auto* profile_manager = GlicProfileManager::GetInstance(); |
| profile_manager->SetActiveGlic(service0); |
| |
| // Tell the mock glic to pretend that the window is open (otherwise, we won't |
| // attempt to close it). |
| service0->SetWindowShowing(); |
| |
| // Opening glic from a second profile should make the profile manager close |
| // the first one. |
| EXPECT_CALL(*service0, ClosePanel()); |
| profile_manager->SetActiveGlic(service1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlicProfileManagerBrowserTest, |
| ProfileForLaunch_WithDetachedGlic) { |
| auto* service0 = GetMockGlicKeyedService(browser()->profile()); |
| |
| // Setup Profile 1 |
| auto* profile1 = CreateNewProfile(); |
| |
| auto* profile_manager = GlicProfileManager::GetInstance(); |
| // Profile 0 is the last used Glic and Profile 1 is the last used window. |
| // Profile 1 should be selected for launch. |
| profile_manager->SetActiveGlic(service0); |
| CreateBrowser(profile1); |
| EXPECT_EQ(profile1, profile_manager->GetProfileForLaunch()); |
| |
| // Simulate showing detached for Profile 0. |
| // Profile 0 should now be selected for launch. |
| service0->SetWindowDetached(); |
| EXPECT_EQ(browser()->profile(), profile_manager->GetProfileForLaunch()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlicProfileManagerBrowserTest, |
| ProfileForLaunch_BasedOnActivationOrder) { |
| // Setup Profile 1 |
| auto* profile1 = CreateNewProfile(); |
| |
| // Applies to next profile. |
| glic_test_environment_.SetForceSigninAndModelExecutionCapability(false); |
| |
| // Setup Profile 2 (not glic compliant) |
| auto* profile2 = CreateNewProfile(); |
| |
| auto* profile_manager = GlicProfileManager::GetInstance(); |
| // profile0 is the most recently used profile |
| EXPECT_EQ(browser()->profile(), profile_manager->GetProfileForLaunch()); |
| |
| // profile1 is the most recently used profile |
| [[maybe_unused]] auto* browser1 = CreateBrowser(profile1); |
| EXPECT_EQ(profile1, profile_manager->GetProfileForLaunch()); |
| |
| // profile2 is the most recently used profile but it isn't |
| // compliant, so still using profile1 |
| CreateBrowser(profile2); |
| EXPECT_EQ(profile1, profile_manager->GetProfileForLaunch()); |
| |
| #if !(BUILDFLAG(IS_OZONE_WAYLAND)) |
| // profile0 is the most recently used profile |
| browser()->window()->Activate(); |
| ui_test_utils::WaitForBrowserSetLastActive(browser()); |
| EXPECT_EQ(browser()->profile(), profile_manager->GetProfileForLaunch()); |
| #endif |
| } |
| |
| class GlicProfileManagerPreloadingTest |
| : public InProcessBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| explicit GlicProfileManagerPreloadingTest(const std::string& delay_ms) { |
| if (IsPrewarmingEnabled()) { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| /*enabled_features=*/{{features::kGlicWarming, |
| {{features::kGlicWarmingDelayMs.name, |
| delay_ms}, |
| {features::kGlicWarmingJitterMs.name, "0"}}}}, |
| /*disabled_features=*/{}); |
| } |
| |
| // We initialize memory pressure to moderate to prevent any premature |
| // preloading. |
| GlicProfileManager::ForceMemoryPressureForTesting( |
| base::MEMORY_PRESSURE_LEVEL_MODERATE); |
| GlicProfileManager::ForceConnectionTypeForTesting( |
| network::mojom::ConnectionType::CONNECTION_WIFI); |
| } |
| |
| GlicProfileManagerPreloadingTest() : GlicProfileManagerPreloadingTest("0") {} |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| GlicProfileManager::ForceProfileForLaunchForTesting(browser()->profile()); |
| } |
| |
| void TearDown() override { |
| GlicProfileManager::ForceProfileForLaunchForTesting(std::nullopt); |
| GlicProfileManager::ForceMemoryPressureForTesting(std::nullopt); |
| GlicProfileManager::ForceConnectionTypeForTesting(std::nullopt); |
| InProcessBrowserTest::TearDown(); |
| } |
| |
| bool IsPrewarmingEnabled() const { return GetParam(); } |
| |
| void ResetMemoryPressure() { |
| GlicProfileManager::ForceMemoryPressureForTesting( |
| base::MEMORY_PRESSURE_LEVEL_NONE); |
| } |
| |
| GlicPrewarmingChecksResult WaitForShouldPreload() { |
| base::test::TestFuture<GlicPrewarmingChecksResult> future; |
| GlicProfileManager::GetInstance()->ShouldPreloadForProfile( |
| browser()->profile(), future.GetCallback()); |
| return future.Get(); |
| } |
| |
| void SetConnectionType(network::mojom::ConnectionType connection_type) { |
| GlicProfileManager::ForceConnectionTypeForTesting(connection_type); |
| } |
| |
| private: |
| GlicTestEnvironment glic_test_environment_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest, |
| ShouldPreloadForProfile_Success) { |
| ResetMemoryPressure(); |
| const bool should_preload = IsPrewarmingEnabled(); |
| EXPECT_EQ(WaitForShouldPreload(), |
| should_preload ? GlicPrewarmingChecksResult::kSuccess |
| : GlicPrewarmingChecksResult::kWarmingDisabled); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest, |
| ShouldPreloadForProfile_NotSupportedProfile) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| ResetMemoryPressure(); |
| GlicProfileManager::ForceProfileForLaunchForTesting(std::nullopt); |
| SetModelExecutionCapability(browser()->profile(), false); |
| EXPECT_EQ(WaitForShouldPreload(), |
| GlicPrewarmingChecksResult::kProfileNotEligible); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest, |
| ShouldPreloadForProfile_WillBeDestroyed) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| ResetMemoryPressure(); |
| browser()->profile()->NotifyWillBeDestroyed(); |
| EXPECT_EQ(WaitForShouldPreload(), GlicPrewarmingChecksResult::kProfileGone); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest, |
| ShouldPreloadForProfile_MemoryPressure) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| // Note: we keep memory pressure at moderate here. |
| EXPECT_EQ(WaitForShouldPreload(), |
| GlicPrewarmingChecksResult::kUnderMemoryPressure); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest, |
| ShouldPreloadForProfile_Cellular) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| ResetMemoryPressure(); |
| SetConnectionType(network::mojom::ConnectionType::CONNECTION_2G); |
| EXPECT_EQ(WaitForShouldPreload(), |
| GlicPrewarmingChecksResult::kCellularConnection); |
| } |
| |
| // See *Deferred* below. Checks that we don't defer preloading when there's no |
| // delay. |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerPreloadingTest, |
| ShouldPreloadForProfile_DoNotDefer) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| ResetMemoryPressure(); |
| auto* service = |
| GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile()); |
| service->TryPreload(); |
| // Since we have no delay, running until idle should mean that we do warm |
| // (provided warming is enabled). |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(service->GetSingleInstanceWindowController().IsWarmed()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| GlicProfileManagerPreloadingTest, |
| ::testing::Bool()); |
| |
| class GlicProfileManagerDeferredPreloadingTest |
| : public GlicProfileManagerPreloadingTest { |
| public: |
| // This sets the delay to 500 ms. |
| GlicProfileManagerDeferredPreloadingTest() |
| : GlicProfileManagerPreloadingTest(/*delay_ms=*/"500") {} |
| ~GlicProfileManagerDeferredPreloadingTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // This is really a keyed service test, but it is convenient to locate it here |
| // for now. It just checks that if we have a preload delay, that we won't |
| // preload immediately. |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerDeferredPreloadingTest, |
| ShouldPreloadForProfile_Defer) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| ResetMemoryPressure(); |
| auto* service = |
| GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile()); |
| service->TryPreload(); |
| // Since we shouldn't preload until after the delay, we shouldn't be warmed |
| // after running until idle. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(service->GetSingleInstanceWindowController().IsWarmed()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicProfileManagerDeferredPreloadingTest, |
| ShouldPreloadForProfile_DeferWithProfileDeletion) { |
| if (!IsPrewarmingEnabled()) { |
| GTEST_SKIP() << "This test only applies if prewarming is enabled."; |
| } |
| ResetMemoryPressure(); |
| auto* service = |
| GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile()); |
| base::RunLoop run_loop; |
| service->AddPreloadCallback(run_loop.QuitClosure()); |
| service->TryPreload(); |
| service->reset_profile_for_test(); |
| run_loop.Run(); |
| EXPECT_FALSE(service->GetSingleInstanceWindowController().IsWarmed()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| GlicProfileManagerDeferredPreloadingTest, |
| ::testing::Bool()); |
| |
| } // namespace |
| } // namespace glic |