// Copyright 2013 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/profiles/profile_window.h"

#include <stddef.h>

#include "base/command_line.h"
#include "base/debug/stack_trace.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/about_flags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/lifetime/application_lifetime_desktop.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/account_reconcilor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_ui_util.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/dialogs/browser_dialogs.h"
#include "chrome/browser/ui/startup/startup_tab_provider.h"
#include "chrome/browser/ui/startup/startup_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/webui/flags/pref_service_flags_storage.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#endif  // !defined (OS_ANDROID)

#if !BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/profiles/profile_picker.h"
#endif  // !BUILDFLAG(IS_CHROMEOS)

using base::UserMetricsAction;
using content::BrowserThread;

namespace {

// Helper function to run a callback on a profile once it's initialized.
void ProfileLoadedCallback(base::OnceCallback<void(Profile*)> callback,
                           Profile* profile) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!profile) {
    return;
  }
  if (callback) {
    std::move(callback).Run(profile);
  }
}

}  // namespace

namespace profiles {

void FindOrCreateNewWindowForProfile(
    Profile* profile,
    chrome::startup::IsProcessStartup process_startup,
    chrome::startup::IsFirstRun is_first_run,
    bool always_create,
    bool open_command_line_urls) {
  DCHECK(profile);
  TRACE_EVENT1("browser", "FindOrCreateNewWindowForProfile", "profile_path",
               profile->GetPath());

  if (!always_create) {
    const bool match_original_profiles =
        IncognitoModePrefs::GetAvailability(profile->GetPrefs()) ==
        policy::IncognitoModeAvailability::kForced;
    BrowserWindowInterface* browser =
        ProfileBrowserCollection::GetForProfile(profile)->FindTabbedBrowser(
            match_original_profiles);
    if (browser) {
      browser->GetWindow()->Activate();
      return;
    }
  }

  base::RecordAction(UserMetricsAction("NewWindow"));
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  StartupBrowserCreator browser_creator;

#if !BUILDFLAG(IS_CHROMEOS)
  if (open_command_line_urls) {
    auto* current_command_line = base::CommandLine::ForCurrentProcess();
    StartupTabProviderImpl startup_tab_provider;
    StartupTabs tabs = startup_tab_provider.GetCommandLineTabs(
        *current_command_line, base::FilePath(), profile);
    for (const auto& tab : tabs) {
      if (tab.url.is_valid()) {
        command_line.AppendArg(tab.url.spec());
      }
    }
  }
#endif  // !BUILDFLAG(IS_CHROMEOS)

  // This is not a browser launch from the user; don't record the launch mode.
  browser_creator.LaunchBrowser(command_line, profile, base::FilePath(),
                                process_startup, is_first_run,
                                /*restore_tabbed_browser=*/true);
}

void OpenBrowserWindowForProfile(
    base::OnceCallback<void(BrowserWindowInterface*)> callback,
    bool always_create,
    bool is_new_profile,
    bool open_command_line_urls,
    Profile* profile) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  TRACE_EVENT1("browser", "OpenBrowserWindowForProfile", "profile_path",
               profile->GetPath().AsUTF8Unsafe());
  // `error_closure_runner` runs the callback  with nullptr to signal an error
  // if the function reaches a return statement without consuming callback. If
  // the callback is consumed by std::move(), then `callback` will be empty
  // after that and the scoped cleanup does nothing.
  absl::Cleanup error_closure_runner([&callback] {
    if (callback) {
      std::move(callback).Run(nullptr);
    }
  });
  chrome::startup::IsProcessStartup process_startup =
      chrome::startup::IsProcessStartup::kNo;
  chrome::startup::IsFirstRun is_first_run = chrome::startup::IsFirstRun::kNo;

  // If this is a brand new profile, then start a first run window.
  if (is_new_profile) {
    process_startup = chrome::startup::IsProcessStartup::kYes;
    is_first_run = chrome::startup::IsFirstRun::kYes;
  }

#if !BUILDFLAG(IS_CHROMEOS)
  if (!profile->IsGuestSession()) {
    ProfileAttributesEntry* entry =
        g_browser_process->profile_manager()
            ->GetProfileAttributesStorage()
            .GetProfileAttributesWithPath(profile->GetPath());
    if (entry && entry->IsSigninRequired()) {
      ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
          ProfilePicker::EntryPoint::kProfileLocked));
      return;
    }
  }
#endif  // !BUILDFLAG(IS_CHROMEOS)

  // If |always_create| is false, and we have a |callback| to run, check
  // whether a browser already exists so that we can run the callback. We don't
  // want to rely on the observer listening to OnBrowserSetLastActive in this
  // case, as you could manually activate an incorrect browser and trigger
  // a false positive.
  if (!always_create) {
    const bool match_original_profiles =
        IncognitoModePrefs::GetAvailability(profile->GetPrefs()) ==
        policy::IncognitoModeAvailability::kForced;
    BrowserWindowInterface* browser =
        ProfileBrowserCollection::GetForProfile(profile)->FindTabbedBrowser(
            match_original_profiles);
    if (browser) {
      browser->GetWindow()->Activate();
      if (callback) {
        std::move(callback).Run(browser);
      }
      return;
    }
  }

  // If there is a callback, create an observer to make sure it is only
  // run when the browser has been completely created. This observer will
  // delete itself once that happens. This should not leak, because we are
  // passing |always_create| = true to FindOrCreateNewWindow below, which ends
  // up calling LaunchBrowser and opens a new window. If for whatever reason
  // that fails, either something has crashed, or the observer will be cleaned
  // up when a different browser for this profile is opened.
  if (callback) {
    new BrowserAddedForProfileObserver(profile, std::move(callback));
  }

  // We already dealt with the case when |always_create| was false and a browser
  // existed, which means that here a browser definitely needs to be created.
  // Passing true for |always_create| means we won't duplicate the code that
  // tries to find a browser.
  profiles::FindOrCreateNewWindowForProfile(
      profile, process_startup, is_first_run, true, open_command_line_urls);
}

#if !BUILDFLAG(IS_ANDROID)

void LoadProfileAsync(const base::FilePath& path,
                      base::OnceCallback<void(Profile*)> callback) {
  g_browser_process->profile_manager()->CreateProfileAsync(
      path, base::BindOnce(&ProfileLoadedCallback, std::move(callback)));
}

void SwitchToProfile(const base::FilePath& path,
                     bool always_create,
                     base::OnceCallback<void(BrowserWindowInterface*)> callback,
                     bool open_command_line_urls) {
  base::OnceCallback<void(Profile*)> open_browser_callback =
      base::BindOnce(&profiles::OpenBrowserWindowForProfile,
                     std::move(callback), always_create,
                     /*is_new_profile=*/false, open_command_line_urls);
  g_browser_process->profile_manager()->CreateProfileAsync(
      path,
      base::BindOnce(&ProfileLoadedCallback, std::move(open_browser_callback)));
}

void SwitchToGuestProfile(
    base::OnceCallback<void(BrowserWindowInterface*)> callback) {
  SwitchToProfile(ProfileManager::GetGuestProfilePath(),
                  /*always_create=*/false, std::move(callback));
}
#endif

bool HasProfileSwitchTargets(Profile* profile) {
  size_t min_profiles = profile->IsGuestSession() ? 1 : 2;
  size_t number_of_profiles =
      g_browser_process->profile_manager()->GetNumberOfProfiles();
  return number_of_profiles >= min_profiles;
}

void CloseProfileWindows(Profile* profile) {
  DCHECK(profile);
  chrome::CloseAllBrowsersWithProfile(profile, /*skip_beforeunload=*/false);
}

BrowserAddedForProfileObserver::BrowserAddedForProfileObserver(
    Profile* profile,
    base::OnceCallback<void(BrowserWindowInterface*)> callback)
    : profile_(profile->GetWeakPtr()), callback_(std::move(callback)) {
  DCHECK(callback_);
  browser_collection_observation_.Observe(
      GlobalBrowserCollection::GetInstance());
  profile_observation_.Observe(profile);
}

BrowserAddedForProfileObserver::~BrowserAddedForProfileObserver() = default;

void BrowserAddedForProfileObserver::OnBrowserCreated(
    BrowserWindowInterface* browser) {
  if (browser_) {
    // Do not run the callback twice.
    return;
  }

  if (!profile_) {
    // The profile has been deleted.
    return;
  }

  // A browser matches if it is associated with the exact profile being
  // observed, or if incognito mode is forced by policy for the profile and the
  // browser is an off-the-record window for that original profile.
  const bool is_matching_browser =
      (browser->GetProfile() == profile_.get()) ||
      (IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) ==
           policy::IncognitoModeAvailability::kForced &&
       browser->GetProfile()->GetOriginalProfile() ==
           profile_->GetOriginalProfile());

  if (!is_matching_browser) {
    // This is a different profile.
    return;
  }

  browser_ = browser;
  // By the time the browser is added a tab (or multiple) are about to be added.
  // Post the callback to the message loop so it gets executed after the tabs
  // are created.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&BrowserAddedForProfileObserver::NotifyBrowserCreatedAnDie,
                     base::Unretained(this)));
}

void BrowserAddedForProfileObserver::OnBrowserClosed(
    BrowserWindowInterface* browser) {
  // The browser was closed before the callback could run.
  if (browser == browser_) {
    browser_collection_observation_.Reset();
    browser_ = nullptr;
  }
}

void BrowserAddedForProfileObserver::OnProfileWillBeDestroyed(
    Profile* profile) {
  // The profile is being destroyed before a browser was created for it.
  // Self-delete since the observer can no longer serve its purpose.
  browser_collection_observation_.Reset();
  profile_observation_.Reset();
  std::move(callback_).Run(nullptr);
  delete this;
}

void BrowserAddedForProfileObserver::NotifyBrowserCreatedAnDie() {
  // If the browser exists, the profile has to exist too.
  CHECK(profile_ || !browser_);
  std::move(callback_).Run(browser_);
  delete this;
}

}  // namespace profiles
