blob: 1c102e732b3ae69acf2688bfbb059d1ec8d966a6 [file]
// 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/startup/startup_launch_manager.h"
#include <optional>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/startup/startup_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
using auto_launch_util::StartupLaunchMode;
namespace {
// This method sets the pref to trial group value if user has not explicitly set
// it, and returns the infobar type to show.
std::optional<StartupLaunchInfoBarManager::InfoBarType>
UpdatePrefAndGetInfoBarType(PrefService* local_state) {
if (!local_state->FindPreference(prefs::kForegroundLaunchOnLogin)
->IsDefaultValue()) {
return std::nullopt;
}
// Update the pref's default value as this has lower priority than a user-set
// value.
const auto trial_group = features::GetLaunchOnStartupDefaultPreference();
switch (trial_group) {
case features::LaunchOnStartupDefaultPreference::kDisabled:
local_state->SetDefaultPrefValue(prefs::kForegroundLaunchOnLogin,
base::Value(false));
return StartupLaunchInfoBarManager::InfoBarType::kForegroundOptIn;
case features::LaunchOnStartupDefaultPreference::kEnabled:
local_state->SetDefaultPrefValue(prefs::kForegroundLaunchOnLogin,
base::Value(true));
return StartupLaunchInfoBarManager::InfoBarType::kForegroundOptOut;
}
}
bool ShouldShowInfoBars() {
constexpr int kMaxPromptCount = 5;
constexpr int kRepromptDurationDays = 21;
PrefService* local_state = g_browser_process->local_state();
const bool is_accepted =
local_state->GetBoolean(prefs::kStartupLaunchInfobarAccepted);
const int declined_count =
local_state->GetInteger(prefs::kStartupLaunchInfobarDeclinedCount);
const base::Time last_declined_time =
local_state->GetTime(prefs::kStartupLaunchInfobarLastDeclinedTime);
// If infobar is already accepted once, or declined more than maximum allowed
// declines, we don't show the infobar.
if (is_accepted || declined_count >= kMaxPromptCount) {
return false;
}
// Show if the user has never declined the prompt.
if (declined_count == 0) {
return true;
}
// Show if it has been long enough since the last declined time
return (base::Time::Now() - last_declined_time) >
base::Days(kRepromptDurationDays);
}
} // namespace
StartupLaunchManager::Client::Client(StartupLaunchReason launch_reason)
: launch_reason_(launch_reason) {
// Acquires a shared write lock to prevent StartupLaunchManager from
// processing the final launch configuration until this client has
// initialized.
auto* launch_manager = StartupLaunchManager::From(g_browser_process);
launch_manager->AcquireSharedWriteLock();
}
StartupLaunchManager::Client::~Client() {
// If the client is destroyed before being fully initialized, release the lock
// to prevent launch manager from hanging.
auto* launch_manager = StartupLaunchManager::From(g_browser_process);
if (launch_manager && !launch_enabled_.has_value()) {
launch_manager->ReleaseSharedWriteLock();
}
}
void StartupLaunchManager::Client::SetLaunchOnStartup(bool enable_launch) {
// Do nothing if the state hasn't changed.
if (launch_enabled_ == enable_launch) {
return;
}
// If we are registering for the first time, release the startup launch
// lock so that StartupLaunchManager can flush to the registry.
const bool release_lock = !launch_enabled_.has_value();
launch_enabled_.emplace(enable_launch);
auto* launch_manager = StartupLaunchManager::From(g_browser_process);
if (enable_launch) {
launch_manager->RegisterLaunchOnStartup(launch_reason_);
} else {
launch_manager->UnregisterLaunchOnStartup(launch_reason_);
}
if (release_lock) {
launch_manager->ReleaseSharedWriteLock();
}
}
DEFINE_USER_DATA(StartupLaunchManager);
StartupLaunchManager::StartupLaunchManager(BrowserProcess* browser_process)
: task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
scoped_unowned_user_data_(browser_process->GetUnownedUserDataHost(),
*this) {
// Acquire a lock so that any writes to registry are deferred until `Init()`
// is called.
AcquireSharedWriteLock();
if (features::IsForegroundLaunchEnabled()) {
PrefService* local_state = g_browser_process->local_state();
// Update the pref as per the trial group.
infobar_type_ = UpdatePrefAndGetInfoBarType(local_state);
// Register a callback that will run when this pref is changed.
foreground_launch_on_login_.Init(
prefs::kForegroundLaunchOnLogin, local_state,
base::BindRepeating(&StartupLaunchManager::OnLaunchOnStartupPrefChanged,
base::Unretained(this)));
// Initialize StartupLaunchManager to use current value of the pref.
UpdateForegroundLaunchRegistration();
} else {
// Removes foreground launch if feature flag is disabled, but keeps the pref
// unchanged. This allows us to resume the experiment if it needs to be
// paused anytime.
UnregisterLaunchOnStartup(StartupLaunchReason::kForeground);
}
}
StartupLaunchManager::~StartupLaunchManager() = default;
void StartupLaunchManager::SetInfoBarManager(
std::unique_ptr<StartupLaunchInfoBarManager> manager) {
infobar_manager_observation_.Reset();
if (is_showing_infobar_ && infobar_manager_) {
infobar_manager_->CloseAllInfoBars();
is_showing_infobar_ = false;
}
infobar_manager_ = std::move(manager);
if (infobar_manager_) {
infobar_manager_observation_.Observe(infobar_manager_.get());
}
}
void StartupLaunchManager::MaybeShowInfoBars() {
if (infobar_type_.has_value() && ShouldShowInfoBars()) {
infobar_manager_->ShowInfoBars(*infobar_type_);
is_showing_infobar_ = true;
}
}
void StartupLaunchManager::OnInfoBarDismissed() {
is_showing_infobar_ = false;
}
// static
StartupLaunchManager* StartupLaunchManager::From(
BrowserProcess* browser_process) {
return browser_process ? Get(browser_process->GetUnownedUserDataHost())
: nullptr;
}
void StartupLaunchManager::AcquireSharedWriteLock() {
++lock_counter_;
}
void StartupLaunchManager::ReleaseSharedWriteLock() {
// No-op if locks were already forcefully released.
if (lock_counter_ == 0) {
return;
}
// After releasing the last lock, cancel the force release lock task and write
// the final configuration to registry.
if (--lock_counter_ == 0) {
fallback_timer_.Stop();
UpdateLaunchOnStartup(GetStartupLaunchMode());
}
}
void StartupLaunchManager::CommitLaunchOnStartupState() {
// Release the lock acquired in the constructor.
ReleaseSharedWriteLock();
// Set up a task to release all shared write locks, and writing the pending
// changes to the registry if any client fails to do their initial
// registration for one minute.
fallback_timer_.Start(FROM_HERE, base::Minutes(1), this,
&StartupLaunchManager::ForceReleaseAllLocks);
}
// TODO(crbug.com/467376419): Record count of such occurrences.
void StartupLaunchManager::ForceReleaseAllLocks() {
// No-op if the locks are already released.
if (lock_counter_ == 0) {
return;
}
lock_counter_ = 0;
UpdateLaunchOnStartup(GetStartupLaunchMode());
}
void StartupLaunchManager::OnLaunchOnStartupPrefChanged() {
UpdateForegroundLaunchRegistration();
constexpr char histogram_name[] =
"Startup.Launch.Foreground.PreferenceChanged";
base::UmaHistogramBoolean(histogram_name,
foreground_launch_on_login_.GetValue());
}
void StartupLaunchManager::UpdateForegroundLaunchRegistration() {
if (foreground_launch_on_login_.GetValue()) {
RegisterLaunchOnStartup(StartupLaunchReason::kForeground);
} else {
UnregisterLaunchOnStartup(StartupLaunchReason::kForeground);
}
if (is_showing_infobar_) {
infobar_manager_->CloseAllInfoBars();
is_showing_infobar_ = false;
}
}
std::optional<StartupLaunchMode> StartupLaunchManager::GetStartupLaunchMode()
const {
if (registered_launch_reasons_.empty()) {
return std::nullopt;
}
// Foreground launch takes precedence as - having foreground launch enabled is
// the same as having both foreground and background launches enabled
// simultaneously.
if (registered_launch_reasons_.Has(StartupLaunchReason::kForeground)) {
return StartupLaunchMode::kForeground;
}
return StartupLaunchMode::kBackground;
}
void StartupLaunchManager::RegisterLaunchOnStartup(StartupLaunchReason reason) {
if (lock_counter_ > 0) {
registered_launch_reasons_.Put(reason);
return;
}
const auto previous_startup_mode = GetStartupLaunchMode();
registered_launch_reasons_.Put(reason);
const auto current_startup_mode = GetStartupLaunchMode();
if (previous_startup_mode != current_startup_mode) {
UpdateLaunchOnStartup(current_startup_mode);
}
}
void StartupLaunchManager::UnregisterLaunchOnStartup(
StartupLaunchReason reason) {
if (lock_counter_ > 0) {
registered_launch_reasons_.Remove(reason);
return;
}
const auto previous_startup_mode = GetStartupLaunchMode();
registered_launch_reasons_.Remove(reason);
const auto current_startup_mode = GetStartupLaunchMode();
if (!current_startup_mode.has_value() ||
previous_startup_mode != current_startup_mode) {
UpdateLaunchOnStartup(current_startup_mode);
}
}
void StartupLaunchManager::UpdateLaunchOnStartup(
std::optional<StartupLaunchMode> startup_launch_mode) {
// This functionality is only defined for default profile, currently.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUserDataDir)) {
return;
}
task_runner_->PostTask(
FROM_HERE, startup_launch_mode.has_value()
? base::BindOnce(auto_launch_util::EnableStartAtLogin,
*startup_launch_mode)
: base::BindOnce(auto_launch_util::DisableStartAtLogin));
}