[ProfilePicker][Glic] Create the ProfilePickerGlicFlowController
Creates the `ProfilePickerGlicFlowController` that will be
instantiated when opening the Glic version of the Profile Picker.
Currently only supports redirecting the selected profile to the input
callback (regular Profile Picker version opens a Browser).
It will later need to also support filtering out the list of Profiles
that are shown.
Bug: b:388211126
Change-Id: I819446a2c951053d494bb4730c1f638f34278e2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6142093
Reviewed-by: Nicolas Dossou-Gbété <dgn@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Commit-Queue: Ryan Sultanem <rsult@google.com>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1404780}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a6d93fe8b..04a41ce 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3201,6 +3201,8 @@
"views/profiles/profile_picker_dice_sign_in_provider.h",
"views/profiles/profile_picker_dice_sign_in_toolbar.cc",
"views/profiles/profile_picker_dice_sign_in_toolbar.h",
+ "views/profiles/profile_picker_glic_flow_controller.cc",
+ "views/profiles/profile_picker_glic_flow_controller.h",
"views/promos/autofill_bubble_signin_promo_view.cc",
"views/promos/autofill_bubble_signin_promo_view.h",
"webui/signin/batch_upload_handler.cc",
diff --git a/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider_browsertest.cc
index 33f84aa..2bfd79e3 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider_browsertest.cc
@@ -6,7 +6,6 @@
#include "base/functional/callback_helpers.h"
#include "base/test/mock_callback.h"
-#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/nuke_profile_directory_utils.h"
#include "chrome/browser/profiles/profile.h"
@@ -14,6 +13,7 @@
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h"
#include "chrome/browser/ui/views/profiles/profile_picker_web_contents_host.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/profile_deletion_observer.h"
@@ -34,30 +34,6 @@
const char kExpectedSigninBaseUrl[] =
"https://accounts.google.com/signin/chrome/sync";
-class MockHost : public ProfilePickerWebContentsHost {
- public:
- MOCK_METHOD(void,
- ShowScreen,
- (content::WebContents * contents,
- const GURL& url,
- base::OnceClosure navigation_finished_closure));
- MOCK_METHOD(void,
- ShowScreenInPickerContents,
- (const GURL& url, base::OnceClosure navigation_finished_closure));
- MOCK_METHOD(bool, ShouldUseDarkColors, (), (const));
- MOCK_METHOD(content::WebContents*, GetPickerContents, (), (const));
- MOCK_METHOD(void, SetNativeToolbarVisible, (bool visible));
- MOCK_METHOD(SkColor, GetPreferredBackgroundColor, (), (const));
- MOCK_METHOD(content::WebContentsDelegate*, GetWebContentsDelegate, ());
- MOCK_METHOD(web_modal::WebContentsModalDialogHost*,
- GetWebContentsModalDialogHost,
- ());
- MOCK_METHOD(void, Reset, (StepSwitchFinishedCallback callback));
- MOCK_METHOD(void,
- ShowForceSigninErrorDialog,
- (const ForceSigninUIError& error, bool success));
-};
-
Profile* GetContentsProfile(content::WebContents* contents) {
return Profile::FromBrowserContext(contents->GetBrowserContext());
}
@@ -69,11 +45,10 @@
ProfilePickerDiceSignInProviderBrowserTest() = default;
~ProfilePickerDiceSignInProviderBrowserTest() override = default;
- testing::NiceMock<MockHost>* host() { return &host_; }
+ testing::NiceMock<MockProfilePickerWebContentsHost>* host() { return &host_; }
private:
- testing::NiceMock<MockHost> host_;
- base::test::ScopedFeatureList scoped_feature_list_;
+ testing::NiceMock<MockProfilePickerWebContentsHost> host_;
};
IN_PROC_BROWSER_TEST_F(ProfilePickerDiceSignInProviderBrowserTest,
diff --git a/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller.cc b/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller.cc
new file mode 100644
index 0000000..078806ee
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller.cc
@@ -0,0 +1,83 @@
+// 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/ui/views/profiles/profile_picker_glic_flow_controller.h"
+
+#include "base/functional/bind.h"
+#include "base/notreached.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/views/profiles/profile_management_step_controller.h"
+#include "chrome/common/webui_url_constants.h"
+
+namespace {
+
+GURL GetProfilePickerGlicURL() {
+ const GURL base_url = GURL(chrome::kChromeUIProfilePickerUrl);
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(chrome::kChromeUIProfilePickerGlicQuery);
+ return base_url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+ProfilePickerGlicFlowController::ProfilePickerGlicFlowController(
+ ProfilePickerWebContentsHost* host,
+ ClearHostClosure clear_host_callback,
+ base::OnceCallback<void(Profile*)> picked_profile_callback)
+ : ProfileManagementFlowController(host, std::move(clear_host_callback)),
+ picked_profile_callback_(std::move(picked_profile_callback)) {
+ CHECK(picked_profile_callback_);
+}
+
+ProfilePickerGlicFlowController::~ProfilePickerGlicFlowController() {
+ if (picked_profile_callback_) {
+ Clear();
+ }
+}
+
+void ProfilePickerGlicFlowController::Init(
+ StepSwitchFinishedCallback step_switch_finished_callback) {
+ RegisterStep(Step::kProfilePicker,
+ ProfileManagementStepController::CreateForProfilePickerApp(
+ host(), GetProfilePickerGlicURL()));
+ SwitchToStep(Step::kProfilePicker, /*reset_state=*/true,
+ std::move(step_switch_finished_callback));
+}
+
+void ProfilePickerGlicFlowController::PickProfile(
+ const base::FilePath& profile_path,
+ ProfilePicker::ProfilePickingArgs args) {
+ g_browser_process->profile_manager()->LoadProfileByPath(
+ profile_path, /*incognito=*/false,
+ base::BindOnce(&ProfilePickerGlicFlowController::OnPickedProfileLoaded,
+ base::Unretained(this)));
+}
+
+void ProfilePickerGlicFlowController::OnPickedProfileLoaded(Profile* profile) {
+ if (!profile) {
+ Clear();
+ return;
+ }
+
+ // TODO(crbug.com/388211126): Add a new specific keep alive in order not to
+ // rely on `kWaitingForFirstBrowserWindow` that is not accurate and has
+ // special conditions.
+
+ // Return the loaded `profile` to the caller.
+ std::move(picked_profile_callback_).Run(profile);
+
+ // Close the picker.
+ ExitFlow();
+}
+
+void ProfilePickerGlicFlowController::Clear() {
+ std::move(picked_profile_callback_).Run(nullptr);
+ ExitFlow();
+}
+
+void ProfilePickerGlicFlowController::CancelPostSignInFlow() {
+ NOTREACHED() << "The glic flow controller is not expected to support this "
+ "part of the flow as it does not support signing in.";
+}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller.h b/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller.h
new file mode 100644
index 0000000..e3a99bd
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_GLIC_FLOW_CONTROLLER_H_
+#define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_GLIC_FLOW_CONTROLLER_H_
+
+#include "base/functional/callback_forward.h"
+#include "chrome/browser/ui/views/profiles/profile_management_flow_controller.h"
+
+class Profile;
+
+// Profile management flow controller that will run the Glic version of the
+// Profile Picker.
+class ProfilePickerGlicFlowController : public ProfileManagementFlowController {
+ public:
+ // `picked_profile_callback` will always be called, and may be called with
+ // a nullptr profile in case the profile failed to load or the `host` was
+ // closed without any selection.
+ ProfilePickerGlicFlowController(
+ ProfilePickerWebContentsHost* host,
+ ClearHostClosure clear_host_callback,
+ base::OnceCallback<void(Profile*)> picked_profile_callback);
+ ~ProfilePickerGlicFlowController() override;
+
+ // ProfileManagementFlowController:
+ void Init(StepSwitchFinishedCallback step_switch_finished_callback =
+ StepSwitchFinishedCallback()) override;
+ // Loads the profile with `profile_path` asynchronously then attempts to run
+ // the `picked_profile_callback_` callback with the loaded profile.
+ // `args` are actually not used in this implementation.
+ void PickProfile(const base::FilePath& profile_path,
+ ProfilePicker::ProfilePickingArgs args) override;
+
+ private:
+ // ProfileManagementFlowController:
+ void CancelPostSignInFlow() override;
+
+ // Callback after loading the picked profile.
+ void OnPickedProfileLoaded(Profile* profile);
+
+ // Makes sure to clear the current flow and return a nullptr profile to the
+ // callback.
+ void Clear();
+
+ base::OnceCallback<void(Profile*)> picked_profile_callback_;
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_GLIC_FLOW_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller_browsertest.cc
new file mode 100644
index 0000000..c632ec7
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_picker_glic_flow_controller_browsertest.cc
@@ -0,0 +1,139 @@
+// 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/ui/views/profiles/profile_picker_glic_flow_controller.h"
+
+#include "base/files/file_path.h"
+#include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
+#include "base/test/mock_callback.h"
+#include "base/test/test_future.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_destroyer.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/profile_test_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/profile_destruction_waiter.h"
+#include "chrome/test/base/profile_waiter.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class ProfilePickerGlicFlowControllerBrowserTest : public InProcessBrowserTest {
+ public:
+ ProfilePickerGlicFlowControllerBrowserTest() = default;
+ ~ProfilePickerGlicFlowControllerBrowserTest() override = default;
+
+ testing::NiceMock<MockProfilePickerWebContentsHost>* host() { return &host_; }
+
+ private:
+ testing::NiceMock<MockProfilePickerWebContentsHost> host_;
+};
+
+IN_PROC_BROWSER_TEST_F(ProfilePickerGlicFlowControllerBrowserTest,
+ InitController) {
+ EXPECT_CALL(*host(), ShowScreenInPickerContents(
+ GURL("chrome://profile-picker/?glic"), testing::_));
+
+ ProfilePickerGlicFlowController controller(
+ host(), ClearHostClosure(base::DoNothing()), base::DoNothing());
+ controller.Init();
+}
+
+IN_PROC_BROWSER_TEST_F(ProfilePickerGlicFlowControllerBrowserTest,
+ PickProfileWithProfileNotLoaded) {
+ // Create a new Profile and destroy it immediately so that it is not loaded.
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ base::FilePath new_profile_path =
+ profile_manager->GenerateNextProfileDirectoryPath();
+ Profile* new_profile =
+ &profiles::testing::CreateProfileSync(profile_manager, new_profile_path);
+ ProfileDestructionWaiter profile_destruction_waiter(new_profile);
+ Browser* new_browser = CreateBrowser(new_profile);
+ CloseBrowserSynchronously(new_browser);
+ profile_destruction_waiter.Wait();
+
+ base::MockCallback<base::OnceClosure> clear_host_callback;
+ EXPECT_CALL(clear_host_callback, Run());
+
+ base::MockCallback<base::OnceCallback<void(Profile*)>>
+ picked_profile_callback;
+ ProfileWaiter profile_waiter;
+ // Make sure that the loaded Profile is valid and corresponds to the newly
+ // created one.
+ EXPECT_CALL(picked_profile_callback, Run(testing::_))
+ .WillOnce([&new_profile_path, &profile_manager](Profile* profile) {
+ ASSERT_TRUE(profile);
+ EXPECT_EQ(profile->GetPath(), new_profile_path);
+ EXPECT_TRUE(profile_manager->HasKeepAliveForTesting(
+ profile, ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow));
+ });
+
+ ProfilePickerGlicFlowController controller(
+ host(), ClearHostClosure(clear_host_callback.Get()),
+ picked_profile_callback.Get());
+ controller.PickProfile(new_profile_path, ProfilePicker::ProfilePickingArgs());
+
+ profile_waiter.WaitForProfileAdded();
+}
+
+IN_PROC_BROWSER_TEST_F(ProfilePickerGlicFlowControllerBrowserTest,
+ PickProfileWithCurrentProfile) {
+ base::MockCallback<base::OnceClosure> clear_host_callback;
+ EXPECT_CALL(clear_host_callback, Run());
+
+ base::MockCallback<base::OnceCallback<void(Profile*)>>
+ picked_profile_callback;
+ // Return the currently active profile right away if it is already loaded.
+ EXPECT_CALL(picked_profile_callback, Run(browser()->profile()));
+
+ ProfilePickerGlicFlowController controller(
+ host(), ClearHostClosure(clear_host_callback.Get()),
+ picked_profile_callback.Get());
+ controller.PickProfile(browser()->profile()->GetPath(),
+ ProfilePicker::ProfilePickingArgs());
+}
+
+IN_PROC_BROWSER_TEST_F(ProfilePickerGlicFlowControllerBrowserTest,
+ PickProfileWithNonExistingProfile) {
+ base::MockCallback<base::OnceClosure> clear_host_callback;
+ EXPECT_CALL(clear_host_callback, Run());
+
+ base::MockCallback<base::OnceCallback<void(Profile*)>>
+ picked_profile_callback;
+ // Callback returns a nullptr profile for non existing profiles.
+ EXPECT_CALL(picked_profile_callback, Run(nullptr));
+
+ ProfilePickerGlicFlowController controller(
+ host(), ClearHostClosure(clear_host_callback.Get()),
+ picked_profile_callback.Get());
+ // Next profile directly is guaranteed not to be tied to an existing profile
+ // as it was not created yet.
+ base::FilePath non_profile_file_path =
+ g_browser_process->profile_manager()->GenerateNextProfileDirectoryPath();
+ controller.PickProfile(non_profile_file_path,
+ ProfilePicker::ProfilePickingArgs());
+}
+
+IN_PROC_BROWSER_TEST_F(ProfilePickerGlicFlowControllerBrowserTest,
+ ClearingControllerShouldCallTheInputCallbacks) {
+ base::MockCallback<base::OnceClosure> clear_host_callback;
+ EXPECT_CALL(clear_host_callback, Run());
+
+ base::MockCallback<base::OnceCallback<void(Profile*)>>
+ picked_profile_callback;
+ // Callback returns a nullptr profile if no profile was selected.
+ EXPECT_CALL(picked_profile_callback, Run(nullptr));
+
+ {
+ ProfilePickerGlicFlowController controller(
+ host(), ClearHostClosure(clear_host_callback.Get()),
+ picked_profile_callback.Get());
+ }
+ // Controller is destroyed without any profile selection.
+}
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.cc b/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.cc
index 5e17ee7..4dcdb62 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.cc
@@ -255,6 +255,9 @@
run_loop_.QuitClosure());
}
+MockProfilePickerWebContentsHost::MockProfilePickerWebContentsHost() = default;
+MockProfilePickerWebContentsHost::~MockProfilePickerWebContentsHost() = default;
+
// -- Other utils --------------------------------------------------------------
namespace profiles::testing {
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h b/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h
index d518336..6dccfdb1 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_test_utils.h
@@ -15,6 +15,7 @@
#include "chrome/browser/ui/views/profiles/profile_picker_view.h"
#include "chrome/browser/ui/views/profiles/profile_picker_web_contents_host.h"
#include "content/public/browser/web_contents_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "ui/views/view_observer.h"
class Profile;
@@ -123,4 +124,32 @@
base::RunLoop run_loop_;
};
+// Mock implementation of the `ProfilePickerWebContentsHost`.
+class MockProfilePickerWebContentsHost : public ProfilePickerWebContentsHost {
+ public:
+ MockProfilePickerWebContentsHost();
+ ~MockProfilePickerWebContentsHost();
+
+ MOCK_METHOD(void,
+ ShowScreen,
+ (content::WebContents * contents,
+ const GURL& url,
+ base::OnceClosure navigation_finished_closure));
+ MOCK_METHOD(void,
+ ShowScreenInPickerContents,
+ (const GURL& url, base::OnceClosure navigation_finished_closure));
+ MOCK_METHOD(bool, ShouldUseDarkColors, (), (const));
+ MOCK_METHOD(content::WebContents*, GetPickerContents, (), (const));
+ MOCK_METHOD(void, SetNativeToolbarVisible, (bool visible));
+ MOCK_METHOD(SkColor, GetPreferredBackgroundColor, (), (const));
+ MOCK_METHOD(content::WebContentsDelegate*, GetWebContentsDelegate, ());
+ MOCK_METHOD(web_modal::WebContentsModalDialogHost*,
+ GetWebContentsModalDialogHost,
+ ());
+ MOCK_METHOD(void, Reset, (StepSwitchFinishedCallback callback));
+ MOCK_METHOD(void,
+ ShowForceSigninErrorDialog,
+ (const ForceSigninUIError& error, bool success));
+};
+
#endif // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_PICKER_VIEW_TEST_UTILS_H_
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index d12c2cb..76a78d7 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -540,6 +540,7 @@
"chrome://profile-customization";
inline constexpr char kChromeUIProfilePickerHost[] = "profile-picker";
inline constexpr char kChromeUIProfilePickerStartupQuery[] = "startup";
+inline constexpr char kChromeUIProfilePickerGlicQuery[] = "glic";
inline constexpr char kChromeUIProfilePickerUrl[] = "chrome://profile-picker/";
#endif
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5e5e74b..5607bfb 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5436,6 +5436,7 @@
"../browser/ui/startup/default_browser_prompt/default_browser_prompt_browsertest.cc",
"../browser/ui/startup/first_run_service_browsertest.cc",
"../browser/ui/views/profiles/profile_picker_dice_sign_in_provider_browsertest.cc",
+ "../browser/ui/views/profiles/profile_picker_glic_flow_controller_browsertest.cc",
"../browser/ui/views/profiles/profile_picker_view_browsertest.cc",
"../browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc",