| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/bind.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_browser_main.h" |
| #include "chrome/browser/chrome_browser_main_extra_parts.h" |
| #include "chrome/browser/profiles/profile_attributes_entry.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_test_util.h" |
| #include "chrome/browser/profiles/profile_window.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/profile_picker.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/test/browser_test.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Matcher; |
| using ::testing::Mock; |
| using ::testing::Property; |
| using ::testing::ValuesIn; |
| |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) |
| #error Not supported on this platform. |
| #endif |
| |
| namespace { |
| |
| class MockMainExtraParts : public ChromeBrowserMainExtraParts { |
| public: |
| MOCK_METHOD(void, PreProfileInit, ()); |
| MOCK_METHOD(void, PostProfileInit, (Profile*, bool)); |
| MOCK_METHOD(void, PreBrowserStart, ()); |
| MOCK_METHOD(void, PostBrowserStart, ()); |
| MOCK_METHOD(void, PreMainMessageLoopRun, ()); |
| }; |
| |
| const char kOtherProfileDirPath[] = "Other"; |
| |
| MATCHER_P(BaseNameEquals, |
| basename, |
| base::StrCat({negation ? "doesn't equal " : "equals ", basename})) { |
| return arg == base::FilePath::FromASCII(basename); |
| } |
| |
| Matcher<Profile*> HasBaseName(const char* basename) { |
| return Property("basename", &Profile::GetBaseName, BaseNameEquals(basename)); |
| } |
| |
| struct MultiProfileStartupTestParam { |
| // Whether features::kObserverBasedPostProfileInit should be enabled. |
| const bool should_enable_profile_observer; |
| |
| // Whether the profile picker should be shown on startup. |
| const bool should_show_profile_picker; |
| |
| struct PostInitExpectedCall { |
| // Matcher for the expected `profile` argument to `PostProfileInit()` |
| const Matcher<Profile*> profile_matcher; |
| |
| // Expected value for the `is_initial_profile` argument to |
| // `PostProfileInit()` |
| const bool is_initial_profile; |
| }; |
| |
| // Call expectations for the `PostProfileInit()` method. The expectations |
| // should themselves be listed in the expected call order. |
| // |
| // The first one is checked in `CreatedBrowserMainParts()` as part of startup, |
| // and the remaining ones in the test body. |
| const std::vector<PostInitExpectedCall> expected_post_profile_init_call_args; |
| }; |
| |
| const MultiProfileStartupTestParam kTestParams[] = { |
| {.should_enable_profile_observer = false, |
| .should_show_profile_picker = false, |
| .expected_post_profile_init_call_args = |
| {{HasBaseName(chrome::kInitialProfile), true}}}, |
| {.should_enable_profile_observer = false, |
| .should_show_profile_picker = true, |
| .expected_post_profile_init_call_args = |
| {{Property(&Profile::IsGuestSession, true), true}}}, |
| {.should_enable_profile_observer = true, |
| .should_show_profile_picker = false, |
| .expected_post_profile_init_call_args = |
| {{HasBaseName(chrome::kInitialProfile), true}, |
| {HasBaseName(kOtherProfileDirPath), false}}}, |
| {.should_enable_profile_observer = true, |
| .should_show_profile_picker = true, |
| .expected_post_profile_init_call_args = { |
| {HasBaseName(chrome::kInitialProfile), true}, |
| {HasBaseName(kOtherProfileDirPath), false}}}}; |
| |
| // Creates a new profile to be picked up on the actual test. |
| void SetUpSecondaryProfileForPreTest( |
| const base::FilePath& profile_dir_basename) { |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| base::FilePath profile_path = |
| profile_manager->user_data_dir().Append(profile_dir_basename); |
| |
| profiles::testing::CreateProfileSync(profile_manager, profile_path); |
| |
| // Mark newly created profile as active. |
| ProfileAttributesEntry* entry = |
| profile_manager->GetProfileAttributesStorage() |
| .GetProfileAttributesWithPath(profile_path); |
| ASSERT_NE(entry, nullptr); |
| entry->SetActiveTimeToNow(); |
| } |
| |
| void CreateBrowserForProfileDir(const base::FilePath& profile_dir_basename) { |
| profiles::testing::SwitchToProfileSync( |
| g_browser_process->profile_manager()->user_data_dir().Append( |
| profile_dir_basename)); |
| } |
| |
| } // namespace |
| |
| class ChromeMultiProfileStartupBrowserTestBase |
| : public InProcessBrowserTest, |
| public testing::WithParamInterface<MultiProfileStartupTestParam> { |
| public: |
| ChromeMultiProfileStartupBrowserTestBase() { |
| // Avoid providing a URL for the browser to open, allows the profile picker |
| // to be displayed on startup when it is enabled. |
| set_open_about_blank_on_browser_launch(false); |
| |
| if (GetParam().should_enable_profile_observer) { |
| feature_list_.InitAndEnableFeature( |
| features::kObserverBasedPostProfileInit); |
| } else { |
| feature_list_.InitWithFeatures({}, |
| {features::kObserverBasedPostProfileInit}); |
| } |
| } |
| |
| void CreatedBrowserMainParts(content::BrowserMainParts* parts) override { |
| InProcessBrowserTest::CreatedBrowserMainParts(parts); |
| |
| // Skip expectations preparation for the PRE_ step. |
| if (GetTestPreCount() != 0) |
| return; |
| |
| auto mock_part = std::make_unique<MockMainExtraParts>(); |
| mock_part_ = mock_part.get(); |
| static_cast<ChromeBrowserMainParts*>(parts)->AddParts(std::move(mock_part)); |
| |
| // At least one entry for the initial call is needed. |
| ASSERT_FALSE(GetParam().expected_post_profile_init_call_args.empty()); |
| |
| // The basic callbacks should be called only once. |
| EXPECT_CALL(*mock_part_, PreProfileInit()).Times(1); |
| EXPECT_CALL(*mock_part_, PreBrowserStart()).Times(1); |
| EXPECT_CALL(*mock_part_, PostBrowserStart()).Times(1); |
| EXPECT_CALL(*mock_part_, PreMainMessageLoopRun()).Times(1); |
| |
| { |
| const auto& call_args = GetParam().expected_post_profile_init_call_args; |
| InSequence s; |
| for (const auto& expected_args : call_args) { |
| EXPECT_CALL(*mock_part_, |
| PostProfileInit(expected_args.profile_matcher, |
| expected_args.is_initial_profile)); |
| } |
| } |
| } |
| |
| raw_ptr<MockMainExtraParts, DanglingUntriaged> mock_part_; |
| |
| protected: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(ChromeMultiProfileStartupBrowserTestBase, |
| PRE_PostProfileInitInvocation) { |
| SetUpSecondaryProfileForPreTest( |
| base::FilePath::FromASCII(kOtherProfileDirPath)); |
| g_browser_process->local_state()->SetBoolean( |
| prefs::kBrowserShowProfilePickerOnStartup, |
| GetParam().should_show_profile_picker); |
| |
| // Need to close the browser window manually so that the real test does not |
| // treat it as session restore. |
| CloseAllBrowsers(); |
| } |
| |
| // Make sure that the second profile creation causes `PostProfileInit()` to be |
| // called a second time. |
| IN_PROC_BROWSER_TEST_P(ChromeMultiProfileStartupBrowserTestBase, |
| PostProfileInitInvocation) { |
| EXPECT_EQ(2u, g_browser_process->profile_manager()->GetNumberOfProfiles()); |
| if (GetParam().should_show_profile_picker) { |
| EXPECT_EQ(0u, chrome::GetTotalBrowserCount()); |
| EXPECT_TRUE(ProfilePicker::IsOpen()); |
| } else { |
| EXPECT_EQ(1u, chrome::GetTotalBrowserCount()); |
| EXPECT_NE(base::FilePath::FromASCII(kOtherProfileDirPath), |
| browser()->profile()->GetPath().BaseName()); |
| EXPECT_FALSE(ProfilePicker::IsOpen()); |
| } |
| |
| // TODO(https://crbug.com/1288766): In some cases, profile creation is |
| // triggered by restoring the previously opened profile, and the test |
| // expectations in terms of `PostProfileInit()` calls can |
| // be met without opening browsers. We still open them for consistency, at |
| // least until we can make the test behaviour stricter. |
| if (GetParam().should_show_profile_picker) { |
| // No browser was previously open, as verified at the beginning of the test. |
| // So we start by opening the browser for the default profile. |
| CreateBrowserForProfileDir( |
| base::FilePath::FromASCII(chrome::kInitialProfile)); |
| } |
| CreateBrowserForProfileDir(base::FilePath::FromASCII(kOtherProfileDirPath)); |
| |
| EXPECT_EQ(2u, chrome::GetTotalBrowserCount()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ChromeMultiProfileStartupBrowserTestBase, |
| ValuesIn(kTestParams)); |