// Copyright 2012 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_STARTUP_STARTUP_BROWSER_CREATOR_H_
#define CHROME_BROWSER_UI_STARTUP_STARTUP_BROWSER_CREATOR_H_

#include <memory>
#include <vector>

#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/ui/startup/startup_types.h"

class BrowserWindowInterface;
class GURL;
class PrefRegistrySimple;
class Profile;

namespace base {
class CommandLine;
}

namespace web_app {
namespace integration_tests {
class WebAppIntegrationTestDriver;
}
FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineTab);
FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineWindowByUrl);
FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineWindowByAppId);
}  // namespace web_app

// Indicates how Chrome should start up the first profile.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class StartupProfileMode {
  // Regular startup with a browser window.
  kBrowserWindow = 0,
  // Profile picker window should be shown on startup.
  kProfilePicker = 1,
  // Chrome cannot start because no profiles are available.
  kError = 2,

  kMaxValue = kError,
};

// Bundles the startup profile path together with a `StartupProfileMode`.
// Depending on `mode`, `path` is either:
// - regular profile path for `kBrowserWindow`; if the guest mode is requested,
//   may contain either the default profile path or the guest profile path
// - empty profile path for `kProfilePicker` and `kError`
// TODO(crbug.com/40157821): return a guest profile path for the Guest
// mode.
struct StartupProfilePathInfo {
  base::FilePath path;
  StartupProfileMode mode = StartupProfileMode::kError;
};

// Bundles the startup profile together with a StartupProfileMode.
// Depending on the `mode` value, `profile` is either:
// - regular profile for `kBrowserWindow`; if the Guest mode is requested,
//   may contain either the default profile path or the guest profile path
// - nullptr for `kProfilePicker` and `kError`
// TODO(crbug.com/40157821): return a guest profile for the Guest mode.
struct StartupProfileInfo {
  raw_ptr<Profile, LeakedDanglingUntriaged> profile;
  StartupProfileMode mode;
};

// class containing helpers for BrowserMain to spin up a new instance and
// initialize the profile.
class StartupBrowserCreator {
 public:
  typedef std::vector<Profile*> Profiles;

  StartupBrowserCreator();
  StartupBrowserCreator(const StartupBrowserCreator&) = delete;
  StartupBrowserCreator& operator=(const StartupBrowserCreator&) = delete;
  ~StartupBrowserCreator();

  // Adds urls to be opened during first run. This overrides the standard
  // tabs shown at first run.
  // Invalid URLs (per `GURL::is_valid()`) are skipped.
  void AddFirstRunTabs(const std::vector<GURL>& urls);

  // This function is equivalent to ProcessCommandLine but should only be
  // called during actual process startup.
  bool Start(const base::CommandLine& cmd_line,
             const base::FilePath& cur_dir,
             StartupProfileInfo profile_info,
             const Profiles& last_opened_profiles);

  // This function performs command-line handling and is invoked only after
  // start up (for example when we get a start request for another process).
  // |command_line| holds the command line we need to process.
  // |cur_dir| is the current working directory that the original process was
  // invoked from.
  // |profile_path_info| contains the directory that contains the profile that
  // the command line arguments will be executed under.
  static void ProcessCommandLineAlreadyRunning(
      const base::CommandLine& command_line,
      const base::FilePath& cur_dir,
      const StartupProfilePathInfo& profile_path_info);

  // Opens the set of startup pages from the current session startup prefs.
  static void OpenStartupPages(
      BrowserWindowInterface* browser,
      chrome::startup::IsProcessStartup process_startup);

  // Returns true if we're launching a profile synchronously. In that case, the
  // opened window should not cause a session restore.
  static bool InSynchronousProfileLaunch();

  // Launches a browser window associated with |profile|. |command_line| should
  // be the command line passed to this process. |cur_dir| can be empty, which
  // implies that the directory of the executable should be used.
  // `process_startup` indicates whether this is the first browser.
  // `is_first_run` indicates that this is a new profile.
  // `restore_tabbed_browser` should only be flipped false by Ash full restore
  // code path, suppressing restoring a normal browser when there were only PWAs
  // open in previous session. See crbug.com/40275406.
  void LaunchBrowser(const base::CommandLine& command_line,
                     Profile* profile,
                     const base::FilePath& cur_dir,
                     chrome::startup::IsProcessStartup process_startup,
                     chrome::startup::IsFirstRun is_first_run,
                     bool restore_tabbed_browser);

  // Launches browser for `last_opened_profiles` if it's not empty. Otherwise,
  // launches browser for `profile_info`. `restore_tabbed_browser` should
  // only be flipped false by Ash full restore code path, suppressing restoring
  // a normal browser when there were only PWAs open in previous session. See
  // crbug.com/40275406.
  void LaunchBrowserForLastProfiles(
      const base::CommandLine& command_line,
      const base::FilePath& cur_dir,
      chrome::startup::IsProcessStartup process_startup,
      chrome::startup::IsFirstRun is_first_run,
      StartupProfileInfo profile_info,
      const Profiles& last_opened_profiles,
      bool restore_tabbed_browser);

  // Returns true during browser process startup if the previous browser was
  // restarted. This only returns true before the first StartupBrowserCreator
  // destructs. WasRestarted() will update prefs::kWasRestarted to false, but
  // caches the value of kWasRestarted until StartupBrowserCreator's
  // dtor is called. After the dtor is called, this function returns the value
  // of the preference which is expected to be false as per above.
  static bool WasRestarted();

  // Returns true during browser process startup if the last session should be
  // restored from switches::kRestoreLastSession. Similar to WasRestarted(),
  // this only returns true before the first StartupBrowserCreator destructs.
  // After the first StartupBrowserCreator is destroyed, this function returns
  // false to ensure subsequent profile launches in the same process honor
  // their configured On Startup settings.
  static bool ShouldRestoreLastSession(const base::CommandLine& command_line);

  static SessionStartupPref GetSessionStartupPref(
      const base::CommandLine& command_line,
      const Profile* profile);

  // For faking that no profiles have been launched yet.
  static void ClearLaunchedProfilesForTesting();

  static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
  static void RegisterProfilePrefs(PrefRegistrySimple* registry);

  // Returns true if Chrome is intended to load a profile and launch without any
  // window.
  static bool ShouldLoadProfileWithoutWindow(
      const base::CommandLine& command_line);

 private:
  friend class StartupBrowserCreatorImpl;
  friend class StartupBrowserCreatorInfobarsTest;
  friend class StartupBrowserCreatorInfobarsWithoutStartupWindowTest;
  // TODO(crbug.com/40482804): Remove this when first_run_tabs gets refactored.
  friend class StartupTabProviderImpl;
  friend class web_app::integration_tests::WebAppIntegrationTestDriver;
  FRIEND_TEST_ALL_PREFIXES(BrowserTest, AppIdSwitch);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           ReadingWasRestartedAfterNormalStart);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           ReadingWasRestartedAfterRestart);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           ReadingShouldRestoreLastSessionAfterNormalStart);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           ReadingShouldRestoreLastSessionAfterRestart);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest, UpdateWithTwoProfiles);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest, LastUsedProfileActivated);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           ValidNotificationLaunchId);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           InvalidNotificationLaunchId);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserWithListAppsFeature,
                           ListAppsForAllProfiles);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserWithListAppsFeature,
                           ListAppsForGivenProfile);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTest,
                           OpenAppShortcutNoPref);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTest,
                           OpenAppShortcutTabPref);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTest,
                           OpenAppShortcutWindowPref);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTest,
                           OpenPolicyForcedAppShortcut);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTestWithLaunch,
                           OpenAppShortcutNoPref);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTestWithLaunch,
                           OpenAppShortcutTabPref);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTestWithLaunch,
                           OpenAppShortcutWindowPref);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorChromeAppShortcutTestWithLaunch,
                           OpenPolicyForcedAppShortcut);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest, OpenAppUrlShortcut);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           OpenAppUrlIncognitoShortcut);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserWithRealWebAppTest,
                           LastUsedProfilesWithRealWebApp);
  FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
                           CommandLineTab);
  FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
                           CommandLineWindowByUrl);
  FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
                           CommandLineWindowByAppId);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           LastUsedProfilesWithWebApp);
  FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                           KSameTabSwitchReplacesActiveTab);

  bool ProcessCmdLineImpl(const base::CommandLine& command_line,
                          const base::FilePath& cur_dir,
                          chrome::startup::IsProcessStartup process_startup,
                          StartupProfileInfo profile_info,
                          const Profiles& last_opened_profiles);

  // Launches the |last_used_profile| with the full command line, and the other
  // |last_opened_profiles| without the URLs to launch.
  void ProcessLastOpenedProfiles(
      const base::CommandLine& command_line,
      const base::FilePath& cur_dir,
      chrome::startup::IsProcessStartup process_startup,
      chrome::startup::IsFirstRun is_first_run,
      Profile* last_used_profile,
      const Profiles& last_opened_profiles);

  // This function performs command-line handling and is invoked only after
  // start up (for example when we get a start request for another process).
  // |command_line| holds the command line being processed.
  // |cur_dir| is the current working directory that the original process was
  // invoked from.
  // |profile| is the profile the apps will be launched in.
  static bool ProcessLoadApps(const base::CommandLine& command_line,
                              const base::FilePath& cur_dir,
                              Profile* profile);

  // Callback after a profile has been initialized. `profile` should be nullptr
  // if `mode` is `StartupProfileMode::kProfilePicker`.
  static void ProcessCommandLineWithProfile(
      const base::CommandLine& command_line,
      const base::FilePath& cur_dir,
      StartupProfileMode mode,
      Profile* profile);

  // Returns true once a profile was activated. Used by the
  // StartupBrowserCreatorTest.LastUsedProfileActivated test.
  static bool ActivatedProfile();

  // Additional tabs to open during first run.
  std::vector<GURL> first_run_tabs_;

  // True if we have already read and reset the preference kWasRestarted. (A
  // member variable instead of a static variable inside WasRestarted because
  // of testing.)
  static bool was_restarted_read_;

  // True if we have already read the kRestoreLastSession switch during process
  // startup. (A member variable instead of a static variable inside
  // ShouldRestoreLastSession because of testing.)
  static bool was_restore_last_session_read_;

  // Caches whether kRestoreLastSession was active during initial process
  // startup before the initial StartupBrowserCreator is destroyed.
  static bool restore_last_session_active_;

  static bool in_synchronous_profile_launch_;

  static bool is_launching_browser_for_last_profiles_;
};

// Returns true if |profile| has exited uncleanly and has not been launched
// after the unclean exit.
//
// TODO(crbug.com/479862082): consider moving this into
// profile_launch_observer.h.
bool HasPendingUncleanExit(Profile* profile);

// Returns the path that contains the profile that should be loaded on process
// startup. This can do blocking operations to check if the profile exists in
// the case of using --profile-directory and
// --ignore-profile-directory-if-not-exists together.
// When the profile picker is shown on startup, this returns the Guest profile
// path. On Mac, the startup profile path is also used to open URLs at startup,
// bypassing the profile picker, because the profile picker does not support it.
// TODO(crbug.com/40159795): Remove this parameter once the picker
// supports opening URLs.
StartupProfilePathInfo GetStartupProfilePath(
    const base::FilePath& cur_dir,
    const base::CommandLine& command_line,
    bool ignore_profile_picker);

#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
// Returns the profile that should be loaded on process startup. This is either
// the profile returned by GetStartupProfilePath, or the guest profile along
// with StartupProfileMode::kProfilePicker mode if the profile picker should be
// opened. Returns nullptr with kError if neither the regular profile nor the
// profile picker can be opened.
StartupProfileInfo GetStartupProfile(const base::FilePath& cur_dir,
                                     const base::CommandLine& command_line);

// Returns the profile that should be loaded on process startup when
// GetStartupProfile() returns kError. This may return kError if neither any
// profile nor the profile picker can be opened.
StartupProfileInfo GetFallbackStartupProfile();
#endif  // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)

#endif  // CHROME_BROWSER_UI_STARTUP_STARTUP_BROWSER_CREATOR_H_
