[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",