blob: fe350d6b2dcde13c934ea98d829256b497bf2fa3 [file] [log] [blame]
// Copyright 2020 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/ash/crosapi/browser_manager.h"
#include <fcntl.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/base_switches.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/platform_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_file_value_serializer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/crosapi/browser_action.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
#include "chrome/browser/ash/crosapi/browser_loader.h"
#include "chrome/browser/ash/crosapi/browser_service_host_ash.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/crosapi_util.h"
#include "chrome/browser/ash/crosapi/desk_template_ash.h"
#include "chrome/browser/ash/crosapi/environment_provider.h"
#include "chrome/browser/ash/crosapi/files_app_launcher.h"
#include "chrome/browser/ash/crosapi/test_mojo_connection_manager.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_ash.h"
#include "chrome/browser/component_updater/cros_component_manager.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/logging_chrome.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/crosapi/cpp/lacros_startup_state.h"
#include "chromeos/crosapi/mojom/crosapi.mojom-shared.h"
#include "chromeos/startup/startup_switches.h"
#include "components/crash/core/app/crashpad.h"
#include "components/nacl/common/buildflags.h"
#include "components/nacl/common/nacl_switches.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/cloud/cloud_policy_store.h"
#include "components/policy/core/common/values_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/user_manager/user_type.h"
#include "components/version_info/version_info.h"
#include "content/public/common/content_switches.h"
#include "media/capture/capture_switches.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/temporary_shared_resource_path_chromeos.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/screen.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
// TODO(crbug.com/1101667): Currently, this source has log spamming
// by LOG(WARNING) for non critical errors to make it easy
// to debug and develop. Get rid of the log spamming
// when it gets stable enough.
namespace crosapi {
BASE_FEATURE(kLacrosLaunchAtLoginScreen,
"LacrosLaunchAtLoginScreen",
base::FEATURE_ENABLED_BY_DEFAULT);
namespace {
// Resources file sharing mode.
enum class ResourcesFileSharingMode {
kDefault = 0,
// Failed to handle cached shared resources properly.
kError = 1,
};
// The names of the UMA metrics to track Daily LaunchMode changes.
const char kLacrosLaunchModeDaily[] = "Ash.Lacros.Launch.Mode.Daily";
const char kLacrosLaunchModeAndSourceDaily[] =
"Ash.Lacros.Launch.ModeAndSource.Daily";
// The interval at which the daily UMA reporting function should be
// called. De-duping of events will be happening on the server side.
constexpr base::TimeDelta kDailyLaunchModeTimeDelta = base::Minutes(30);
using LaunchParamsFromBackground = BrowserManager::LaunchParamsFromBackground;
// Pointer to the global instance of BrowserManager.
BrowserManager* g_instance = nullptr;
// Global flag to disable most of BrowserManager for testing.
// Read by the BrowserManager constructor.
bool g_disabled_for_testing = false;
constexpr char kLacrosCannotLaunchNotificationID[] =
"lacros_cannot_launch_notification_id";
constexpr char kLacrosLauncherNotifierID[] = "lacros_launcher";
base::FilePath LacrosLogDirectory() {
if (base::FeatureList::IsEnabled(kLacrosLaunchAtLoginScreen) &&
session_manager::SessionManager::Get()->session_state() ==
session_manager::SessionState::LOGIN_PRIMARY) {
return base::FilePath("/var/log/lacros");
}
return browser_util::GetUserDataDir();
}
base::FilePath LacrosLogPath() {
return LacrosLogDirectory().Append("lacros.log");
}
base::FilePath LacrosPostLoginLogPath() {
return browser_util::GetUserDataDir().Append("lacros.log");
}
base::FilePath LacrosCrashDumpDirectory() {
return LacrosLogDirectory().Append("Crash Reports");
}
// Rotate existing Lacros's log file. Returns true if a log file existed before
// being moved, and false if no log file was found.
bool RotateLacrosLogs() {
base::FilePath log_path = LacrosLogPath();
if (!base::PathExists(log_path)) {
return false;
}
if (!logging::RotateLogFile(log_path)) {
PLOG(ERROR) << "Failed to rotate the log file: " << log_path.value()
<< ". Keeping using the same log file without rotating.";
}
return true;
}
void PreloadFile(base::FilePath file_path) {
DLOG(WARNING) << "Preloading " << file_path;
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
DPCHECK(file.IsValid());
if (!file.IsValid()) {
PLOG(WARNING) << "Failed opening " << file_path << " while preloading";
return;
}
int64_t file_size = file.GetLength();
if (file_size < 0) {
PLOG(WARNING) << "Failed getting size of " << file_path
<< "while preloading";
return;
}
if (readahead(file.GetPlatformFile(), 0, file_size) < 0) {
PLOG(WARNING) << "Failed preloading " << file_path;
return;
}
DLOG(WARNING) << "Preloaded " << file_path;
}
void PreloadLacrosFiles(const base::FilePath& lacros_dir) {
// These files are the Lacros equivalent of Ash's files preloaded at boot by
// ureadahead.
static constexpr const char* kPreloadFiles[] = {
"WidevineCdm/manifest.json",
"chrome",
"chrome_100_percent.pak",
"chrome_200_percent.pak",
"chrome_crashpad_handler",
"icudtl.dat",
"icudtl.dat.hash",
"nacl_helper",
"resources.pak",
"snapshot_blob.bin",
};
// Preload common files.
for (const char* file_name : kPreloadFiles) {
base::FilePath file_path = lacros_dir.Append(base::FilePath(file_name));
PreloadFile(file_path);
}
// Preload localization pack.
std::string locale = g_browser_process->GetApplicationLocale();
base::FilePath locale_path =
lacros_dir.Append(base::StringPrintf("locales/%s.pak", locale.c_str()));
PreloadFile(locale_path);
// Preload Widevine for the right architecture.
#if defined(ARCH_CPU_ARM_FAMILY)
base::FilePath libwidevine_path = lacros_dir.Append(
"WidevineCdm/_platform_specific/cros_arm/libwidevinecdm.so");
#else
base::FilePath libwidevine_path = lacros_dir.Append(
"WidevineCdm/_platform_specific/cros_x64/libwidevinecdm.so");
#endif
PreloadFile(libwidevine_path);
}
ResourcesFileSharingMode ClearOrMoveSharedResourceFileInternal(
bool clear_shared_resource_file,
base::FilePath shared_resource_path) {
// If shared resource pak doesn't exit, do nothing.
if (!base::PathExists(shared_resource_path)) {
return ResourcesFileSharingMode::kDefault;
}
// Clear shared resource file cache if `clear_shared_resource_file` is true.
if (clear_shared_resource_file) {
if (!base::DeleteFile(shared_resource_path)) {
LOG(ERROR) << "Failed to delete cached shared resource file.";
return ResourcesFileSharingMode::kError;
}
return ResourcesFileSharingMode::kDefault;
}
base::FilePath renamed_shared_resource_path =
ui::GetPathForTemporarySharedResourceFile(shared_resource_path);
// Move shared resource pak to `renamed_shared_resource_path`.
if (!base::Move(shared_resource_path, renamed_shared_resource_path)) {
LOG(ERROR) << "Failed to move cached shared resource file to temporary "
<< "location.";
return ResourcesFileSharingMode::kError;
}
return ResourcesFileSharingMode::kDefault;
}
ResourcesFileSharingMode ClearOrMoveSharedResourceFile(
bool clear_shared_resource_file) {
// Check 3 resource paks, resources.pak, chrome_100_percent.pak and
// chrome_200_percent.pak.
ResourcesFileSharingMode resources_file_sharing_mode =
ResourcesFileSharingMode::kDefault;
// Return kError if any of the resources failed to clear or move.
// Make sure that ClearOrMoveSharedResourceFileInternal() runs for all
// resources even if it already fails for some resource.
if (ClearOrMoveSharedResourceFileInternal(
clear_shared_resource_file, browser_util::GetUserDataDir().Append(
crosapi::kSharedResourcesPackName)) ==
ResourcesFileSharingMode::kError) {
resources_file_sharing_mode = ResourcesFileSharingMode::kError;
}
if (ClearOrMoveSharedResourceFileInternal(
clear_shared_resource_file,
browser_util::GetUserDataDir().Append(
crosapi::kSharedChrome100PercentPackName)) ==
ResourcesFileSharingMode::kError) {
resources_file_sharing_mode = ResourcesFileSharingMode::kError;
}
if (ClearOrMoveSharedResourceFileInternal(
clear_shared_resource_file,
browser_util::GetUserDataDir().Append(
crosapi::kSharedChrome200PercentPackName)) ==
ResourcesFileSharingMode::kError) {
resources_file_sharing_mode = ResourcesFileSharingMode::kError;
}
return resources_file_sharing_mode;
}
// This method runs some work on a background thread prior to launching lacros.
// The returns struct is used by the main thread as parameters to launch Lacros.
LaunchParamsFromBackground DoLacrosBackgroundWorkPreLaunch(
base::FilePath lacros_dir,
bool clear_shared_resource_file,
bool launching_at_login_screen) {
LaunchParamsFromBackground params;
if (!RotateLacrosLogs()) {
// If log file does not exist, most likely the user directory does not
// exist either. So create it here.
base::File::Error error;
if (!base::CreateDirectoryAndGetError(LacrosLogDirectory(), &error)) {
LOG(ERROR) << "Failed to make directory " << LacrosLogDirectory() << ": "
<< base::File::ErrorToString(error);
return params;
}
}
int fd = HANDLE_EINTR(
open(LacrosLogPath().value().c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644));
if (fd < 0) {
PLOG(ERROR) << "Failed to get file descriptor for " << LacrosLogPath();
return params;
}
params.logfd = base::ScopedFD(fd);
params.enable_resource_file_sharing =
base::FeatureList::IsEnabled(features::kLacrosResourcesFileSharing);
// If resource file sharing feature is disabled, clear the cached shared
// resource file anyway.
if (!params.enable_resource_file_sharing) {
clear_shared_resource_file = true;
}
// Clear shared resource file cache if it's initial lacros launch after ash
// reboot. If not, rename shared resource file cache to temporal name on
// Lacros launch.
if (ClearOrMoveSharedResourceFile(clear_shared_resource_file) ==
ResourcesFileSharingMode::kError) {
params.enable_resource_file_sharing = false;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kLacrosChromeAdditionalArgsFile)) {
const base::FilePath path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
ash::switches::kLacrosChromeAdditionalArgsFile);
std::string data;
if (!base::ReadFileToString(path, &data)) {
PLOG(WARNING) << "Unable to read from lacros additional args file "
<< path.value();
}
std::vector<base::StringPiece> delimited_flags =
base::SplitStringPieceUsingSubstr(data, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
for (const auto& flag : delimited_flags) {
if (flag[0] != '#') {
params.lacros_additional_args.emplace_back(flag);
}
}
}
// When launching at login screen, we can take advantage of the time before
// the user inputs the password and logs in to preload Lacros-related files.
// This speeds up the perceived startup time, as they will be loaded anyway
// in the later stages of Lacros's lifetime.
if (launching_at_login_screen) {
PreloadLacrosFiles(lacros_dir);
}
return params;
}
std::string GetXdgRuntimeDir() {
// If ash-chrome was given an environment variable, use it.
std::unique_ptr<base::Environment> env = base::Environment::Create();
std::string xdg_runtime_dir;
if (env->GetVar("XDG_RUNTIME_DIR", &xdg_runtime_dir)) {
return xdg_runtime_dir;
}
// Otherwise provide the default for Chrome OS devices.
return "/run/chrome";
}
void TerminateLacrosChrome(base::Process process, base::TimeDelta timeout) {
// Here, lacros-chrome process may crashed, or be in the shutdown procedure.
// Give some amount of time for the collection. In most cases,
// this wait captures the process termination.
if (process.WaitForExitWithTimeout(timeout, nullptr)) {
return;
}
// Here, the process is not yet terminated.
// This happens if some critical error happens on the mojo connection,
// while both ash-chrome and lacros-chrome are still alive.
// Terminate the lacros-chrome.
bool success = process.Terminate(/*exit_code=*/0, /*wait=*/true);
LOG_IF(ERROR, !success) << "Failed to terminate the lacros-chrome.";
}
void SetLaunchOnLoginPref(bool launch_on_login) {
ProfileManager::GetPrimaryUserProfile()->GetPrefs()->SetBoolean(
browser_util::kLaunchOnLoginPref, launch_on_login);
}
bool GetLaunchOnLoginPref() {
return ProfileManager::GetPrimaryUserProfile()->GetPrefs()->GetBoolean(
browser_util::kLaunchOnLoginPref);
}
bool IsKeepAliveDisabledForTesting() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kDisableLacrosKeepAliveForTesting);
}
bool IsLoginLacrosOpeningDisabledForTesting() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kDisableLoginLacrosOpening);
}
void WarnThatLacrosNotAllowedToLaunch() {
LOG(WARNING) << "Lacros enabled but not allowed to launch";
message_center::Notification notification = ash::CreateSystemNotification(
message_center::NOTIFICATION_TYPE_SIMPLE,
kLacrosCannotLaunchNotificationID,
/*title=*/std::u16string(),
l10n_util::GetStringUTF16(IDS_LACROS_CANNOT_LAUNCH_MULTI_SIGNIN_MESSAGE),
/* display_source= */ std::u16string(), GURL(),
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT,
kLacrosLauncherNotifierID,
ash::NotificationCatalogName::kLacrosCannotLaunch),
message_center::RichNotificationData(),
base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
base::RepeatingClosure()),
gfx::kNoneIcon, message_center::SystemNotificationWarningLevel::NORMAL);
SystemNotificationHelper::GetInstance()->Display(notification);
}
void RecordDataVerForPrimaryUser() {
const std::string user_id_hash = ash::ProfileHelper::GetUserIdHashFromProfile(
ProfileManager::GetPrimaryUserProfile());
crosapi::browser_util::RecordDataVer(g_browser_process->local_state(),
user_id_hash,
version_info::GetVersion());
}
// The delegate keeps track of the most recent lacros-chrome binary version
// loaded by the BrowserLoader.
// It is the single source of truth for what is the most up-to-date launchable
// version of lacros-chrome. It should be queried when determining if loading a
// more recent lacros-chrome binary should be attempted.
class BrowserVersionServiceDelegate : public BrowserVersionServiceAsh::Delegate,
public BrowserManagerObserver {
public:
BrowserVersionServiceDelegate(
const ComponentUpdateService* component_update_service,
BrowserManager* browser_manager)
: component_update_service_(component_update_service) {
observation_.Observe(browser_manager);
}
BrowserVersionServiceDelegate(const BrowserVersionServiceDelegate&) = delete;
BrowserVersionServiceDelegate& operator=(
const BrowserVersionServiceDelegate&) = delete;
~BrowserVersionServiceDelegate() override = default;
// BrowserVersionServiceAsh::Delegate:
base::Version GetLatestLaunchableBrowserVersion() const override {
// If there is a newer browser available return the version of lacros-chrome
// maintained by the component manager. Otherwise return the current version
// loaded by the manager.
const auto component_version_number =
browser_util::GetInstalledLacrosComponentVersion(
component_update_service_);
return IsNewerBrowserAvailable() && component_version_number.IsValid()
? component_version_number
: browser_version_loaded_;
}
bool IsNewerBrowserAvailable() const override {
// If the browser loader is not able to load newer stateful component builds
// signal there is no update available.
if (!BrowserLoader::WillLoadStatefulComponentBuilds()) {
return false;
}
const auto component_version_number =
browser_util::GetInstalledLacrosComponentVersion(
component_update_service_);
return (!browser_version_loaded_.IsValid() &&
component_version_number.IsValid()) ||
(browser_version_loaded_.IsValid() &&
component_version_number.IsValid() &&
browser_version_loaded_ < component_version_number);
}
// crosapi::BrowserManagerObserver:
void OnLoadComplete(bool success, const base::Version& version) override {
browser_version_loaded_ = version;
}
private:
// Version number of the most recently loaded lacros-chrome browser. This
// can be used for version checking and version comparisons. It is in the
// format of:
// <major_version>.<minor_version>.<build>.<patch>
// For example, "86.0.4240.38".
// Set immediately after lacros has loaded. May be invalid if BrowserLoader
// fails to successfully load a lacros binary.
base::Version browser_version_loaded_;
const raw_ptr<const ComponentUpdateService> component_update_service_;
base::ScopedObservation<BrowserManager, BrowserManagerObserver> observation_{
this};
};
} // namespace
// To be sure the lacros is running with neutral thread type.
class LacrosThreadTypeDelegate : public base::LaunchOptions::PreExecDelegate {
public:
void RunAsyncSafe() override {
// TODO(crbug.com/1289736): Currently, this is causing some deadlock issue.
// It looks like inside the function, we seem to call async unsafe API.
// For the mitigation, disabling this temporarily.
// We should revisit here, and see the impact of performance.
// SetCurrentThreadType() needs file I/O on /proc and /sys.
// base::ScopedAllowBlocking allow_blocking;
// base::PlatformThread::SetCurrentThreadType(
// base::ThreadType::kDefault);
}
};
// static
BrowserManager* BrowserManager::Get() {
return g_instance;
}
BrowserManager::BrowserManager(
scoped_refptr<component_updater::CrOSComponentManager> manager)
: BrowserManager(std::make_unique<BrowserLoader>(manager),
g_browser_process->component_updater()) {}
BrowserManager::BrowserManager(
std::unique_ptr<BrowserLoader> browser_loader,
component_updater::ComponentUpdateService* update_service)
: browser_loader_(std::move(browser_loader)),
component_update_service_(update_service),
environment_provider_(std::make_unique<EnvironmentProvider>()),
launch_at_login_screen_(
// NOTE: We only want to pre-launch Lacros if Ash is launched in login
// manager mode. When the `kLoginUser` switch is passed, we are
// restarting the session for an already logged in user, either in
// production, or after PRE_ tests. In both of those cases, the user
// is already logged in, and we do not want Lacros to prelaunch.
// Originally introduced because of https://crbug.com/1432779, which
// causes PRE_ tests to restart back to login screen, but with the
// user still "logged in" (UserManager::IsUserLoggedIn() == true).
!base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kLoginUser) &&
base::FeatureList::IsEnabled(kLacrosLaunchAtLoginScreen)),
disabled_for_testing_(g_disabled_for_testing) {
DCHECK(!g_instance);
g_instance = this;
version_service_delegate_ =
std::make_unique<BrowserVersionServiceDelegate>(update_service, this);
// Wait to query the flag until the user has entered the session. Enterprise
// devices restart Chrome during login to apply flags. We don't want to run
// the flag-off cleanup logic until we know we have the final flag state.
if (session_manager::SessionManager::Get()) {
session_manager::SessionManager::Get()->AddObserver(this);
}
if (ash::SessionManagerClient::Get()) {
ash::SessionManagerClient::Get()->AddObserver(this);
}
if (CrosapiManager::IsInitialized()) {
CrosapiManager::Get()
->crosapi_ash()
->browser_service_host_ash()
->AddObserver(this);
} else {
CHECK_IS_TEST();
}
std::string socket_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kLacrosMojoSocketForTesting);
if (!socket_path.empty()) {
test_mojo_connection_manager_ =
std::make_unique<crosapi::TestMojoConnectionManager>(
base::FilePath(socket_path), environment_provider_.get());
}
}
BrowserManager::~BrowserManager() {
if (CrosapiManager::IsInitialized()) {
CrosapiManager::Get()
->crosapi_ash()
->browser_service_host_ash()
->RemoveObserver(this);
}
// Unregister, just in case the manager is destroyed before
// OnUserSessionStarted() is called.
if (session_manager::SessionManager::Get()) {
session_manager::SessionManager::Get()->RemoveObserver(this);
}
// Try to kill the lacros-chrome binary.
if (lacros_process_.IsValid()) {
lacros_process_.Terminate(/*exit_code=*/0, /*wait=*/false);
}
DCHECK_EQ(g_instance, this);
g_instance = nullptr;
}
bool BrowserManager::IsRunning() const {
return state_ == State::RUNNING;
}
bool BrowserManager::IsRunningOrWillRun() const {
return state_ == State::RUNNING || state_ == State::STARTING ||
state_ == State::CREATING_LOG_FILE || state_ == State::TERMINATING;
}
bool BrowserManager::IsInitialized() const {
return state_ != State::NOT_INITIALIZED;
}
void BrowserManager::NewWindow(bool incognito,
bool should_trigger_session_restore) {
int64_t target_display_id =
display::Screen::GetScreen()->GetDisplayForNewWindows().id();
PerformOrEnqueue(BrowserAction::NewWindow(
incognito, should_trigger_session_restore, target_display_id));
}
void BrowserManager::OpenForFullRestore(bool skip_crash_restore) {
PerformOrEnqueue(BrowserAction::OpenForFullRestore(skip_crash_restore));
}
void BrowserManager::NewWindowForDetachingTab(
const std::u16string& tab_id_str,
const std::u16string& group_id_str,
NewWindowForDetachingTabCallback callback) {
PerformOrEnqueue(BrowserAction::NewWindowForDetachingTab(
tab_id_str, group_id_str, std::move(callback)));
}
void BrowserManager::NewFullscreenWindow(const GURL& url,
NewFullscreenWindowCallback callback) {
int64_t target_display_id =
display::Screen::GetScreen()->GetDisplayForNewWindows().id();
PerformOrEnqueue(BrowserAction::NewFullscreenWindow(url, target_display_id,
std::move(callback)));
}
void BrowserManager::NewGuestWindow() {
int64_t target_display_id =
display::Screen::GetScreen()->GetDisplayForNewWindows().id();
PerformOrEnqueue(BrowserAction::NewGuestWindow(target_display_id));
}
void BrowserManager::NewTab() {
PerformOrEnqueue(BrowserAction::NewTab());
}
void BrowserManager::Launch() {
int64_t target_display_id =
display::Screen::GetScreen()->GetDisplayForNewWindows().id();
PerformOrEnqueue(BrowserAction::Launch(target_display_id));
}
void BrowserManager::OpenUrl(
const GURL& url,
crosapi::mojom::OpenUrlFrom from,
crosapi::mojom::OpenUrlParams::WindowOpenDisposition disposition) {
PerformOrEnqueue(
BrowserAction::OpenUrl(url, disposition, from, NavigateParams::RESPECT));
}
void BrowserManager::SwitchToTab(const GURL& url,
NavigateParams::PathBehavior path_behavior) {
PerformOrEnqueue(BrowserAction::OpenUrl(
url, crosapi::mojom::OpenUrlParams::WindowOpenDisposition::kSwitchToTab,
crosapi::mojom::OpenUrlFrom::kUnspecified, path_behavior));
}
void BrowserManager::RestoreTab() {
PerformOrEnqueue(BrowserAction::RestoreTab());
}
void BrowserManager::HandleTabScrubbing(float x_offset,
bool is_fling_scroll_event) {
PerformOrEnqueue(
BrowserAction::HandleTabScrubbing(x_offset, is_fling_scroll_event));
}
void BrowserManager::CreateBrowserWithRestoredData(
const std::vector<GURL>& urls,
const gfx::Rect& bounds,
const std::vector<tab_groups::TabGroupInfo>& tab_group_infos,
ui::WindowShowState show_state,
int32_t active_tab_index,
int32_t first_non_pinned_tab_index,
const std::string& app_name,
int32_t restore_window_id) {
PerformOrEnqueue(BrowserAction::CreateBrowserWithRestoredData(
urls, bounds, tab_group_infos, show_state, active_tab_index,
first_non_pinned_tab_index, app_name, restore_window_id));
}
void BrowserManager::InitializeAndStartIfNeeded() {
// If we already tried to load Lacros but for some reason it wasn't available
// (for example, in some tests), then we should return here to avoid failure.
if (state_ == State::UNAVAILABLE) {
return;
}
DCHECK_EQ(state_, State::NOT_INITIALIZED);
// Ensure this isn't run multiple times.
session_manager::SessionManager::Get()->RemoveObserver(this);
PrepareLacrosPolicies();
// Perform the UMA recording for the current Lacros mode of operation.
RecordLacrosLaunchMode();
const bool is_lacros_enabled = browser_util::IsLacrosEnabled();
// As a switch between Ash and Lacros mode requires an Ash restart plus
// profile migration, the state will not change while the system is up.
// At this point we are starting Lacros for the first time and with that the
// operation mode is 'locked in'.
crosapi::lacros_startup_state::SetLacrosStartupState(
is_lacros_enabled, browser_util::IsLacrosPrimaryBrowser());
if (is_lacros_enabled) {
if (browser_util::IsLacrosAllowedToLaunch()) {
// Start Lacros automatically on login, if
// 1) Lacros was opened in the previous session; or
// 2) Lacros is the primary web browser.
// This can be suppressed via commandline flag for testing.
if (GetLaunchOnLoginPref() ||
(browser_util::IsLacrosPrimaryBrowser() &&
!IsLoginLacrosOpeningDisabledForTesting())) {
pending_actions_.Push(BrowserAction::GetActionForSessionStart());
}
SetState(State::MOUNTING);
browser_loader_->Load(base::BindOnce(
&BrowserManager::OnLoadComplete, weak_factory_.GetWeakPtr(),
/*launching_at_login_screen=*/false));
} else {
SetState(State::UNAVAILABLE);
WarnThatLacrosNotAllowedToLaunch();
}
} else {
SetState(State::UNAVAILABLE);
browser_loader_->Unload(); // NOTE: This deletes the user data dir.
}
// Post `DryRunToCollectUMA()` to send UMA stats about sizes of files/dirs
// inside the profile data directory.
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ash::browser_data_migrator_util::DryRunToCollectUMA,
ProfileManager::GetPrimaryUserProfile()->GetPath()));
}
void BrowserManager::PrelaunchAtLoginScreen() {
// NOTE: This is a stripped-down version of |InitializeAndStartIfNeeded| which
// assumes Lacros is enabled and primary for the user that will log in. If it
// isn't, we can terminate it after login.
DCHECK_EQ(state_, State::NOT_INITIALIZED);
DCHECK_EQ(session_manager::SessionManager::Get()->session_state(),
session_manager::SessionState::LOGIN_PRIMARY);
DCHECK(!user_manager::UserManager::Get()->IsUserLoggedIn());
// Load and start Lacros.
SetState(State::MOUNTING);
browser_loader_->Load(base::BindOnce(&BrowserManager::OnLoadComplete,
weak_factory_.GetWeakPtr(),
/*launching_at_login_screen=*/true));
}
bool BrowserManager::GetFeedbackDataSupported() const {
return browser_service_.has_value() &&
browser_service_->interface_version >=
crosapi::mojom::BrowserService::kGetFeedbackDataMinVersion;
}
// TODO(neis): Create BrowserAction also for this and others, perhaps even
// UpdateKeepAlive.
void BrowserManager::GetFeedbackData(GetFeedbackDataCallback callback) {
DCHECK(GetFeedbackDataSupported());
browser_service_->service->GetFeedbackData(std::move(callback));
}
bool BrowserManager::GetHistogramsSupported() const {
return browser_service_.has_value() &&
browser_service_->interface_version >=
crosapi::mojom::BrowserService::kGetHistogramsMinVersion;
}
void BrowserManager::GetHistograms(GetHistogramsCallback callback) {
DCHECK(GetHistogramsSupported());
browser_service_->service->GetHistograms(std::move(callback));
}
bool BrowserManager::GetActiveTabUrlSupported() const {
return browser_service_.has_value() &&
browser_service_->interface_version >=
crosapi::mojom::BrowserService::kGetActiveTabUrlMinVersion;
}
void BrowserManager::GetActiveTabUrl(GetActiveTabUrlCallback callback) {
DCHECK(GetActiveTabUrlSupported());
browser_service_->service->GetActiveTabUrl(std::move(callback));
}
void BrowserManager::GetBrowserInformation(
const std::string& window_unique_id,
GetBrowserInformationCallback callback) {
crosapi::CrosapiManager::Get()
->crosapi_ash()
->desk_template_ash()
->GetBrowserInformation(window_unique_id, std::move(callback));
}
void BrowserManager::AddObserver(BrowserManagerObserver* observer) {
observers_.AddObserver(observer);
}
void BrowserManager::RemoveObserver(BrowserManagerObserver* observer) {
observers_.RemoveObserver(observer);
}
void BrowserManager::Shutdown() {
// Lacros KeepAlive should be disabled once Shutdown() has been signalled.
// Further calls to `UpdateKeepAliveInBrowserIfNecessary()` will no-op after
// `shutdown_requested_` has been set.
UpdateKeepAliveInBrowserIfNecessary(false);
shutdown_requested_ = true;
pending_actions_.Clear();
// The lacros-chrome process may have already been terminated as the result of
// a previous mojo pipe disconnection in `OnMojoDisconnected()` and not yet
// restarted. If, on the other hand, it is still valid, terminate it now.
if (lacros_process_.IsValid()) {
LOG(WARNING) << "Ash-chrome shutdown initiated. Terminating lacros-chrome";
lacros_process_.Terminate(/*exit_code=*/0, /*wait=*/false);
// Synchronously post a shutdown blocking task that waits for lacros-chrome
// to cleanly exit. Terminate() will eventually result in a callback into
// OnMojoDisconnected(), however this resolves asynchronously and there is a
// risk that ash exits before this is called.
// The 2.5s wait for a successful lacros exit stays below the 3s timeout
// after which ash is forcefully terminated by the session_manager.
HandleLacrosChromeTermination(base::Milliseconds(2500));
}
}
void BrowserManager::set_relaunch_requested_for_testing(
bool relaunch_requested) {
CHECK_IS_TEST();
relaunch_requested_ = relaunch_requested;
}
void BrowserManager::SetState(State state) {
if (state_ == state) {
return;
}
state_ = state;
for (auto& observer : observers_) {
if (state == State::TERMINATING) {
observer.OnMojoDisconnected();
}
observer.OnStateChanged();
}
}
BrowserManager::ScopedKeepAlive::~ScopedKeepAlive() {
manager_->StopKeepAlive(feature_);
}
BrowserManager::ScopedKeepAlive::ScopedKeepAlive(BrowserManager* manager,
Feature feature)
: manager_(manager), feature_(feature) {
manager_->StartKeepAlive(feature_);
}
std::unique_ptr<BrowserManager::ScopedKeepAlive> BrowserManager::KeepAlive(
Feature feature) {
// Using new explicitly because ScopedKeepAlive's constructor is private.
return base::WrapUnique(new ScopedKeepAlive(this, feature));
}
BrowserManager::BrowserServiceInfo::BrowserServiceInfo(
mojo::RemoteSetElementId mojo_id,
mojom::BrowserService* service,
uint32_t interface_version)
: mojo_id(mojo_id),
service(service),
interface_version(interface_version) {}
BrowserManager::BrowserServiceInfo::BrowserServiceInfo(
const BrowserServiceInfo&) = default;
BrowserManager::BrowserServiceInfo&
BrowserManager::BrowserServiceInfo::operator=(const BrowserServiceInfo&) =
default;
BrowserManager::BrowserServiceInfo::~BrowserServiceInfo() = default;
void BrowserManager::Start(bool launching_at_login_screen) {
DCHECK_EQ(state_, State::STOPPED);
DCHECK(!shutdown_requested_);
DCHECK(!lacros_path_.empty());
DCHECK(lacros_selection_.has_value());
if (!launching_at_login_screen) {
DCHECK(browser_util::IsLacrosAllowedToLaunch());
}
if (version_service_delegate_->IsNewerBrowserAvailable() &&
should_attempt_update_) {
SetState(State::MOUNTING);
lacros_path_ = base::FilePath();
lacros_selection_ = absl::nullopt;
should_attempt_update_ = false;
// OnLoadComplete will call Start again.
browser_loader_->Load(base::BindOnce(&BrowserManager::OnLoadComplete,
weak_factory_.GetWeakPtr(),
launching_at_login_screen));
return;
}
should_attempt_update_ = true;
// Always reset the |relaunch_requested_| flag when launching Lacros.
relaunch_requested_ = false;
SetState(State::CREATING_LOG_FILE);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&DoLacrosBackgroundWorkPreLaunch, lacros_path_,
is_initial_lacros_launch_after_reboot_,
launching_at_login_screen),
base::BindOnce(&BrowserManager::StartWithLogFile,
weak_factory_.GetWeakPtr()));
// Set false to prepare for the next Lacros launch.
is_initial_lacros_launch_after_reboot_ = false;
}
void BrowserManager::StartWithLogFile(LaunchParamsFromBackground params) {
DCHECK_EQ(state_, State::CREATING_LOG_FILE);
// Shutdown() might have been called after Start() posted the StartWithLogFile
// task, so we need to check `shutdown_requested_` again.
if (shutdown_requested_) {
LOG(ERROR) << "Start attempted after Shutdown() called.";
SetState(State::STOPPED);
return;
}
// If the user is already logged in and we are inside the session,
// call |RecordDataverForPrimaryUser| now.
// Otherwise, if we're pre-launching at login screen, this will be
// done later, once the user logs in and the session is started.
if (user_manager::UserManager::Get()->IsUserLoggedIn()) {
RecordDataVerForPrimaryUser();
}
std::string chrome_path = lacros_path_.MaybeAsASCII() + "/chrome";
LOG(WARNING) << "Launching lacros-chrome at " << chrome_path;
// If Ash is an unknown channel then this is not a production build and we
// should be using an unknown channel for Lacros as well. This prevents Lacros
// from picking up Finch experiments.
version_info::Channel update_channel = version_info::Channel::UNKNOWN;
if (chrome::GetChannel() != version_info::Channel::UNKNOWN) {
DCHECK(lacros_selection_.has_value());
update_channel = browser_util::GetLacrosSelectionUpdateChannel(
lacros_selection_.value());
// If we don't have channel information, we default to the "dev" channel.
if (update_channel == version_info::Channel::UNKNOWN) {
update_channel = browser_util::kLacrosDefaultChannel;
}
}
base::LaunchOptions options;
options.environment["EGL_PLATFORM"] = "surfaceless";
options.environment["XDG_RUNTIME_DIR"] = GetXdgRuntimeDir();
options.environment["CHROME_VERSION_EXTRA"] =
version_info::GetChannelString(update_channel);
if (base::FeatureList::IsEnabled(ash::features::kLacrosWaylandLogging)) {
options.environment["WAYLAND_DEBUG"] = "1";
}
// LsbRelease and LsbReleaseTime are used by sys_info in Lacros to determine
// hardware class.
std::unique_ptr<base::Environment> env = base::Environment::Create();
std::string lsb_release;
std::string lsb_release_time;
if (env->GetVar(base::kLsbReleaseKey, &lsb_release) &&
env->GetVar(base::kLsbReleaseTimeKey, &lsb_release_time)) {
options.environment[base::kLsbReleaseKey] = std::move(lsb_release);
options.environment[base::kLsbReleaseTimeKey] = std::move(lsb_release_time);
}
std::string additional_env =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kLacrosChromeAdditionalEnv);
base::StringPairs env_pairs;
if (base::SplitStringIntoKeyValuePairsUsingSubstr(additional_env, '=', "####",
&env_pairs)) {
for (const auto& env_pair : env_pairs) {
if (!env_pair.first.empty()) {
LOG(WARNING) << "Applying lacros env " << env_pair.first << "="
<< env_pair.second;
options.environment[env_pair.first] = env_pair.second;
}
}
}
options.kill_on_parent_death = true;
// Paths are UTF-8 safe on Chrome OS.
std::string user_data_dir = browser_util::GetUserDataDir().AsUTF8Unsafe();
std::string crash_dir = LacrosCrashDumpDirectory().AsUTF8Unsafe();
// Pass the locale via command line instead of via LacrosInitParams because
// the Lacros browser process needs it early in startup, before zygote fork.
std::string locale = g_browser_process->GetApplicationLocale();
// Static configuration should be enabled from Lacros rather than Ash. This
// vector should only be used for dynamic configuration.
// TODO(https://crbug.com/1145713): Remove existing static configuration.
std::vector<std::string> argv = {chrome_path,
"--ozone-platform=wayland",
"--user-data-dir=" + user_data_dir,
"--enable-gpu-rasterization",
"--lang=" + locale,
"--enable-webgl-image-chromium",
"--breakpad-dump-location=" + crash_dir};
// CrAS is the default audio server in Chrome OS.
if (base::SysInfo::IsRunningOnChromeOS()) {
argv.push_back("--use-cras");
}
#if BUILDFLAG(ENABLE_NACL)
// This switch is forwarded to nacl_helper and is needed before zygote fork.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kVerboseLoggingInNacl)) {
argv.push_back("--verbose-logging-in-nacl=" +
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVerboseLoggingInNacl));
}
#endif
std::string additional_flags =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kLacrosChromeAdditionalArgs);
std::vector<base::StringPiece> delimited_flags =
base::SplitStringPieceUsingSubstr(additional_flags, "####",
base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
for (const auto& flag : delimited_flags) {
argv.emplace_back(flag);
}
argv.insert(argv.end(), params.lacros_additional_args.begin(),
params.lacros_additional_args.end());
// Forward flag for zero copy video capture to Lacros if it is enabled.
if (switches::IsVideoCaptureUseGpuMemoryBufferEnabled()) {
argv.emplace_back(
base::StringPrintf("--%s", switches::kVideoCaptureUseGpuMemoryBuffer));
}
// If logfd is valid, enable logging and redirect stdout/stderr to logfd.
if (params.logfd.is_valid()) {
// The next flag will make chrome log only via stderr. See
// DetermineLoggingDestination in logging_chrome.cc.
argv.push_back("--enable-logging=stderr");
auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kLoggingLevel)) {
argv.push_back(base::StringPrintf(
"--%s=%s", switches::kLoggingLevel,
command_line->GetSwitchValueASCII(switches::kLoggingLevel).c_str()));
}
// TODO(crbug.com/1423163): Remove after root causing the issue.
argv.push_back(
"--vmodule=command_storage_backend=1,session_service_commands=1");
if (launch_at_login_screen_ &&
!command_line->HasSwitch(switches::kDisableLoggingRedirect)) {
// Redirect logs to cryptohome after login on non-test images.
argv.push_back(base::StringPrintf(
"--%s=%s", chromeos::switches::kCrosPostLoginLogFile,
LacrosPostLoginLogPath().value().c_str()));
}
// These options will assign stdout/stderr fds to logfd in the fd table of
// the new process.
options.fds_to_remap.push_back(
std::make_pair(params.logfd.get(), STDOUT_FILENO));
options.fds_to_remap.push_back(
std::make_pair(params.logfd.get(), STDERR_FILENO));
}
// For backward compatibility, we want to pass all the parameters at
// startup if we're not launching at login screen.
// Vice versa, if we're launching at login screen, we want to split
// the parameters in pre-login and post-login.
bool pass_post_login_params =
launch_at_login_screen_ &&
!user_manager::UserManager::Get()->IsUserLoggedIn();
base::ScopedFD startup_fd = browser_util::CreateStartupData(
environment_provider_.get(),
browser_util::InitialBrowserAction(
mojom::InitialBrowserAction::kDoNotOpenWindow),
!keep_alive_features_.empty(), lacros_selection_,
!pass_post_login_params);
if (startup_fd.is_valid()) {
// Hardcoded to use FD 3 to make the ash-chrome's behavior more predictable.
// Lacros-chrome should not depend on the hardcoded value though. Instead
// it should take a look at the value passed via the command line flag.
constexpr int kStartupDataFD = 3;
argv.push_back(base::StringPrintf(
"--%s=%d", chromeos::switches::kCrosStartupDataFD, kStartupDataFD));
options.fds_to_remap.emplace_back(startup_fd.get(), kStartupDataFD);
}
// If at login screen, open an anonymous pipe to pass post-login parameters to
// Lacros later on.
base::ScopedFD read_pipe_fd;
if (pass_post_login_params) {
bool success = base::CreatePipe(&read_pipe_fd, &postlogin_pipe_fd_);
DCHECK(success);
// Pass the read side of the pipe to the Lacros process.
constexpr int kPostLoginDataFD = 4;
argv.push_back(base::StringPrintf(
"--%s=%d", chromeos::switches::kCrosPostLoginDataFD, kPostLoginDataFD));
options.fds_to_remap.emplace_back(read_pipe_fd.get(), kPostLoginDataFD);
}
// Set up Mojo channel.
base::CommandLine command_line(argv);
// Lacros-chrome starts with kNormal type
LacrosThreadTypeDelegate thread_type_delegate;
options.pre_exec_delegate = &thread_type_delegate;
// Prepare to invite lacros-chrome to the Mojo universe of Crosapi.
mojo::PlatformChannel channel;
std::string channel_flag_value;
channel.PrepareToPassRemoteEndpoint(&options.fds_to_remap,
&channel_flag_value);
DCHECK(!channel_flag_value.empty());
command_line.AppendSwitchASCII(kCrosapiMojoPlatformChannelHandle,
channel_flag_value);
DCHECK(!crosapi_id_.has_value());
// Use new Crosapi mojo connection to detect process termination always.
crosapi_id_ = CrosapiManager::Get()->SendInvitation(
channel.TakeLocalEndpoint(),
base::BindOnce(&BrowserManager::OnMojoDisconnected,
weak_factory_.GetWeakPtr()));
if (crash_reporter::IsCrashpadEnabled()) {
command_line.AppendSwitch(switches::kEnableCrashpad);
}
if (params.enable_resource_file_sharing) {
// Pass a flag to enable resources file sharing to Lacros.
// To use resources file sharing feature on Lacros, it's required for ash to
// run with enabling the feature as well since the feature is based on some
// ash behavior(clear or move cached shared resource file at lacros launch).
command_line.AppendSwitch(switches::kEnableResourcesFileSharing);
}
LOG(WARNING) << "Launching lacros with command: "
<< command_line.GetCommandLineString();
// Create the lacros-chrome subprocess.
base::RecordAction(base::UserMetricsAction("Lacros.Launch"));
lacros_launch_time_ = base::TimeTicks::Now();
// If lacros_process_ already exists, because it does not call waitpid(2),
// the process will never be collected.
lacros_process_ = base::LaunchProcess(command_line, options);
if (!lacros_process_.IsValid()) {
LOG(ERROR) << "Failed to launch lacros-chrome";
// We give up, as this is most likely a permanent problem.
SetState(State::UNAVAILABLE);
return;
}
SetState(State::STARTING);
LOG(WARNING) << "Launched lacros-chrome with pid " << lacros_process_.Pid();
channel.RemoteProcessLaunchAttempted();
}
void BrowserManager::EmitLoginPromptVisibleCalled() {
OnLoginPromptVisible();
}
void BrowserManager::OnBrowserServiceConnected(
CrosapiId id,
mojo::RemoteSetElementId mojo_id,
mojom::BrowserService* browser_service,
uint32_t browser_service_version) {
if (id != crosapi_id_) {
// This BrowserService is unrelated to this instance. Skipping.
return;
}
is_terminated_ = false;
DCHECK(!browser_service_.has_value());
browser_service_ =
BrowserServiceInfo{mojo_id, browser_service, browser_service_version};
base::UmaHistogramMediumTimes("ChromeOS.Lacros.StartTime",
base::TimeTicks::Now() - lacros_launch_time_);
// Set the launch-on-login pref every time lacros-chrome successfully starts,
// instead of once during ash-chrome shutdown, so we have the right value
// even if ash-chrome crashes.
SetLaunchOnLoginPref(true);
LOG(WARNING) << "Connection to lacros-chrome is established.";
DCHECK_EQ(state_, State::STARTING);
SetState(State::RUNNING);
// There can be a chance that keep_alive status is updated between the
// process launching timing (where initial_keep_alive is set) and the
// crosapi mojo connection timing (i.e., this function).
// So, send it to lacros-chrome to update to fill the possible gap.
UpdateKeepAliveInBrowserIfNecessary(!keep_alive_features_.empty());
while (!pending_actions_.IsEmpty()) {
pending_actions_.Pop()->Perform(
{browser_service_.value().service,
browser_service_.value().interface_version});
DCHECK_EQ(state_, State::RUNNING);
}
}
void BrowserManager::OnBrowserServiceDisconnected(
CrosapiId id,
mojo::RemoteSetElementId mojo_id) {
// No need to check CrosapiId here, because |mojo_id| is unique within
// a process.
if (browser_service_.has_value() && browser_service_->mojo_id == mojo_id) {
browser_service_.reset();
}
}
void BrowserManager::OnBrowserRelaunchRequested(CrosapiId id) {
if (id != crosapi_id_) {
return;
}
relaunch_requested_ = true;
}
void BrowserManager::OnCoreConnected(policy::CloudPolicyCore* core) {}
void BrowserManager::OnRefreshSchedulerStarted(policy::CloudPolicyCore* core) {
core->refresh_scheduler()->AddObserver(this);
}
void BrowserManager::OnCoreDisconnecting(policy::CloudPolicyCore* core) {}
void BrowserManager::OnCoreDestruction(policy::CloudPolicyCore* core) {
core->RemoveObserver(this);
}
void BrowserManager::OnMojoDisconnected() {
LOG(WARNING)
<< "Mojo to lacros-chrome is disconnected. Terminating lacros-chrome";
HandleLacrosChromeTermination(base::Seconds(5));
}
void BrowserManager::HandleLacrosChromeTermination(base::TimeDelta timeout) {
// This may be called following a synchronous termination in `Shutdown()` or
// when the mojo pipe with the lacros-chrome process has disconnected. Early
// return if already handling lacros-chrome termination.
if (!lacros_process_.IsValid()) {
return;
}
DCHECK(state_ == State::STARTING || state_ == State::RUNNING);
DCHECK(lacros_process_.IsValid());
browser_service_.reset();
crosapi_id_.reset();
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&TerminateLacrosChrome, std::move(lacros_process_),
timeout),
base::BindOnce(&BrowserManager::OnLacrosChromeTerminated,
weak_factory_.GetWeakPtr()));
SetState(State::TERMINATING);
}
void BrowserManager::OnLacrosChromeTerminated() {
DCHECK_EQ(state_, State::TERMINATING);
LOG(WARNING) << "Lacros-chrome is terminated";
is_terminated_ = true;
SetState(State::STOPPED);
// TODO(https://crbug.com/1109366): Restart lacros-chrome if it exits
// abnormally (e.g. crashes). For now, assume the user meant to close it.
// Relaunch lacros-chrome if it was closed due to ash shutting down.
// Note that this only matters for side-by-side lacros.
SetLaunchOnLoginPref(shutdown_requested_);
if (relaunch_requested_) {
pending_actions_.Push(
BrowserAction::OpenForFullRestore(/*skip_crash_restore=*/true));
}
StartIfNeeded();
if (unload_requested_) {
LOG(WARNING) << "Unloading Lacros-chrome";
SetState(State::UNAVAILABLE);
browser_loader_->Unload();
}
}
void BrowserManager::OnLoginPromptVisible() {
if (launch_at_login_screen_ &&
session_manager::SessionManager::Get()->session_state() ==
session_manager::SessionState::LOGIN_PRIMARY) {
PrelaunchAtLoginScreen();
}
}
void BrowserManager::OnSessionStateChanged() {
if (disabled_for_testing_) {
CHECK_IS_TEST();
LOG(WARNING)
<< "BrowserManager disabled for testing, entering UNAVAILABLE state";
SetState(State::UNAVAILABLE);
return;
}
// Wait for session to become active.
auto* session_manager = session_manager::SessionManager::Get();
if (session_manager->session_state() !=
session_manager::SessionState::ACTIVE) {
return;
}
if (launch_at_login_screen_ && postlogin_pipe_fd_.is_valid()) {
// Resume Lacros launch after login, if it was pre-launched.
ResumeLaunch();
} else {
// Otherwise, just start Lacros normally, if appropriate.
InitializeAndStartIfNeeded();
}
// If "Go to files" on the migration error page was clicked, launch it here.
HandleGoToFiles();
}
void BrowserManager::OnStoreLoaded(policy::CloudPolicyStore* store) {
DCHECK(store);
// A new policy got installed for the current user, so we need to pass it to
// the Lacros browser.
std::string policy_blob;
if (store->policy_fetch_response()) {
const bool success =
store->policy_fetch_response()->SerializeToString(&policy_blob);
DCHECK(success);
}
SetDeviceAccountPolicy(policy_blob);
}
void BrowserManager::OnStoreError(policy::CloudPolicyStore* store) {
// Policy store failed, Lacros will use stale policy as well as Ash.
}
void BrowserManager::OnStoreDestruction(policy::CloudPolicyStore* store) {
store->RemoveObserver(this);
}
void BrowserManager::OnComponentPolicyUpdated(
const policy::ComponentPolicyMap& component_policy) {
environment_provider_->SetDeviceAccountComponentPolicy(
policy::CopyComponentPolicyMap(component_policy));
if (browser_service_.has_value()) {
browser_service_->service->UpdateComponentPolicy(
policy::CopyComponentPolicyMap(component_policy));
}
}
void BrowserManager::OnComponentPolicyServiceDestruction(
policy::ComponentCloudPolicyService* service) {
service->RemoveObserver(this);
}
void BrowserManager::OnFetchAttempt(
policy::CloudPolicyRefreshScheduler* scheduler) {
environment_provider_->SetLastPolicyFetchAttemptTimestamp(
scheduler->last_refresh());
if (browser_service_.has_value()) {
browser_service_->service->NotifyPolicyFetchAttempt();
}
}
void BrowserManager::OnRefreshSchedulerDestruction(
policy::CloudPolicyRefreshScheduler* scheduler) {
scheduler->RemoveObserver(this);
}
void BrowserManager::OnLoadComplete(bool launching_at_login_screen,
const base::FilePath& path,
LacrosSelection selection,
base::Version version) {
if (shutdown_requested_) {
LOG(ERROR) << "Load completed after Shutdown() called.";
return;
}
DCHECK_EQ(state_, State::MOUNTING);
lacros_path_ = path;
lacros_selection_ = absl::optional<LacrosSelection>(selection);
const bool success = !path.empty();
SetState(success ? State::STOPPED : State::UNAVAILABLE);
// TODO(crbug.com/1266010): In the event the load operation failed, we should
// launch the last successfully loaded image.
for (auto& observer : observers_) {
observer.OnLoadComplete(success, version);
}
StartIfNeeded(launching_at_login_screen);
}
void BrowserManager::StartIfNeeded(bool launching_at_login_screen) {
if (state_ == State::STOPPED && !shutdown_requested_) {
if (launching_at_login_screen || !pending_actions_.IsEmpty() ||
IsKeepAliveEnabled()) {
Start(launching_at_login_screen);
}
}
}
void BrowserManager::ResumeLaunch() {
// NOTE: This method runs some of the operations that would have normally been
// executed in |InitializeAndStartIfNeeded| (we call |PrelaunchAtLoginScreen|
// instead) and |StartWithLogFile|, because they required the user to be
// logged in.
DCHECK_EQ(session_manager::SessionManager::Get()->session_state(),
session_manager::SessionState::ACTIVE);
DCHECK(user_manager::UserManager::Get()->IsUserLoggedIn());
// Ensure this isn't run multiple times.
ash::SessionManagerClient::Get()->RemoveObserver(this);
// If Lacros is not enabled for the user, terminate it now.
const bool is_lacros_enabled = browser_util::IsLacrosEnabled();
if (!is_lacros_enabled) {
LOG(WARNING) << "Lacros is not enabled for the current user. Terminating "
"pre-launched instance";
unload_requested_ = true;
lacros_process_.Terminate(/*exit_code=*/0, /*wait=*/false);
return;
}
LOG(WARNING) << "Resuming lacros-chrome launch";
// Execute actions that we couldn't run when pre-launching at login screen,
// because they required the user to be logged in.
PrepareLacrosPolicies();
RecordLacrosLaunchMode();
crosapi::lacros_startup_state::SetLacrosStartupState(
is_lacros_enabled, browser_util::IsLacrosPrimaryBrowser());
RecordDataVerForPrimaryUser();
// Once Lacros starts and BrowserService is connected,
// the following action will be executed.
pending_actions_.Push(BrowserAction::GetActionForSessionStart());
// Write post-login parameters into the anonymous pipe.
bool write_success = browser_util::WritePostLoginData(
postlogin_pipe_fd_.get(), environment_provider_.get(),
browser_util::InitialBrowserAction(
mojom::InitialBrowserAction::kDoNotOpenWindow));
DPCHECK(write_success);
postlogin_pipe_fd_.reset();
// Post `DryRunToCollectUMA()` to send UMA stats about sizes of files/dirs
// inside the profile data directory.
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ash::browser_data_migrator_util::DryRunToCollectUMA,
ProfileManager::GetPrimaryUserProfile()->GetPath()));
}
void BrowserManager::HandleGoToFiles() {
// If "Go to files" on the migration error page was clicked, launch it here.
Profile* profile = ProfileManager::GetPrimaryUserProfile();
std::string user_id_hash =
ash::ProfileHelper::GetUserIdHashFromProfile(profile);
if (browser_util::WasGotoFilesClicked(g_browser_process->local_state(),
user_id_hash)) {
files_app_launcher_ = std::make_unique<FilesAppLauncher>(
apps::AppServiceProxyFactory::GetForProfile(profile));
files_app_launcher_->Launch(base::BindOnce(
browser_util::ClearGotoFilesClicked, g_browser_process->local_state(),
std::move(user_id_hash)));
}
}
void BrowserManager::PrepareLacrosPolicies() {
const user_manager::User* user =
user_manager::UserManager::Get()->GetPrimaryUser();
policy::CloudPolicyCore* core = nullptr;
policy::ComponentCloudPolicyService* component_policy_service = nullptr;
switch (user->GetType()) {
case user_manager::USER_TYPE_REGULAR:
case user_manager::USER_TYPE_CHILD: {
Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(user);
DCHECK(profile);
policy::CloudPolicyManager* user_cloud_policy_manager =
profile->GetUserCloudPolicyManagerAsh();
if (user_cloud_policy_manager) {
core = user_cloud_policy_manager->core();
component_policy_service =
user_cloud_policy_manager->component_policy_service();
}
break;
}
case user_manager::USER_TYPE_KIOSK_APP:
case user_manager::USER_TYPE_PUBLIC_ACCOUNT:
case user_manager::USER_TYPE_WEB_KIOSK_APP: {
policy::DeviceLocalAccountPolicyService* policy_service =
g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetDeviceLocalAccountPolicyService();
// `policy_service` can be nullptr, e.g. in unit tests.
if (policy_service) {
policy::DeviceLocalAccountPolicyBroker* broker =
policy_service->GetBrokerForUser(
user->GetAccountId().GetUserEmail());
if (broker) {
core = broker->core();
component_policy_service = broker->component_policy_service();
}
}
break;
}
case user_manager::USER_TYPE_GUEST:
case user_manager::USER_TYPE_ACTIVE_DIRECTORY:
case user_manager::USER_TYPE_ARC_KIOSK_APP:
break;
case user_manager::NUM_USER_TYPES:
NOTREACHED();
}
// The lifetime of `BrowserManager` is longer than lifetime of various
// classes, for which we register as an observer below. The RemoveObserver
// function is therefore called in various handlers invoked by those classes
// and not in the destructor.
if (core) {
core->AddObserver(this);
if (core->refresh_scheduler()) {
core->refresh_scheduler()->AddObserver(this);
}
policy::CloudPolicyStore* store = core->store();
if (store && store->policy_fetch_response()) {
const std::string policy_blob =
store->policy_fetch_response()->SerializeAsString();
SetDeviceAccountPolicy(policy_blob);
store->AddObserver(this);
}
}
if (component_policy_service) {
component_policy_service->AddObserver(this);
}
}
void BrowserManager::SetDeviceAccountPolicy(const std::string& policy_blob) {
environment_provider_->SetDeviceAccountPolicy(policy_blob);
if (browser_service_.has_value()) {
browser_service_->service->UpdateDeviceAccountPolicy(
std::vector<uint8_t>(policy_blob.begin(), policy_blob.end()));
}
}
LaunchParamsFromBackground::LaunchParamsFromBackground() = default;
LaunchParamsFromBackground::LaunchParamsFromBackground(
LaunchParamsFromBackground&&) = default;
LaunchParamsFromBackground::~LaunchParamsFromBackground() = default;
void BrowserManager::StartKeepAlive(Feature feature) {
DCHECK(browser_util::IsLacrosEnabled());
if (IsKeepAliveDisabledForTesting()) {
return;
}
DCHECK(keep_alive_features_.find(feature) == keep_alive_features_.end())
<< "Features should never be double registered.";
keep_alive_features_.insert(feature);
// If this is first KeepAlive instance, update the keep-alive in the browser.
if (keep_alive_features_.size() == 1) {
UpdateKeepAliveInBrowserIfNecessary(true);
}
StartIfNeeded();
}
void BrowserManager::StopKeepAlive(Feature feature) {
keep_alive_features_.erase(feature);
if (!IsKeepAliveEnabled()) {
UpdateKeepAliveInBrowserIfNecessary(false);
}
}
bool BrowserManager::IsKeepAliveEnabled() const {
return !keep_alive_features_.empty();
}
void BrowserManager::UpdateKeepAliveInBrowserIfNecessary(bool enabled) {
if (shutdown_requested_ || !browser_service_.has_value() ||
browser_service_->interface_version <
crosapi::mojom::BrowserService::kUpdateKeepAliveMinVersion) {
// Shutdown has started, the browser is not running now, or Lacros is too
// old. Just give up.
return;
}
browser_service_->service->UpdateKeepAlive(enabled);
}
void BrowserManager::RecordLacrosLaunchMode() {
LacrosLaunchMode lacros_mode;
LacrosLaunchModeAndSource lacros_mode_and_source;
if (!browser_util::IsAshWebBrowserEnabled()) {
// As Ash is disabled, Lacros is the only available browser.
lacros_mode = LacrosLaunchMode::kLacrosOnly;
lacros_mode_and_source =
LacrosLaunchModeAndSource::kPossiblySetByUserLacrosOnly;
} else if (browser_util::IsLacrosPrimaryBrowser()) {
// Lacros is the primary browser - but Ash is still available.
lacros_mode = LacrosLaunchMode::kLacrosPrimary;
lacros_mode_and_source =
LacrosLaunchModeAndSource::kPossiblySetByUserLacrosPrimary;
} else if (browser_util::IsLacrosEnabled()) {
// If Lacros is enabled but not primary or the only browser, the
// side by side mode is active.
lacros_mode = LacrosLaunchMode::kSideBySide;
lacros_mode_and_source =
LacrosLaunchModeAndSource::kPossiblySetByUserSideBySide;
} else {
lacros_mode = LacrosLaunchMode::kLacrosDisabled;
lacros_mode_and_source =
LacrosLaunchModeAndSource::kPossiblySetByUserLacrosDisabled;
}
UMA_HISTOGRAM_ENUMERATION("Ash.Lacros.Launch.Mode", lacros_mode);
crosapi::browser_util::LacrosLaunchSwitchSource source =
crosapi::browser_util::GetLacrosLaunchSwitchSource();
// Make sure we have always the policy loaded before we get here.
DCHECK(source != crosapi::browser_util::LacrosLaunchSwitchSource::kUnknown);
LacrosLaunchModeAndSource source_offset;
if (source ==
crosapi::browser_util::LacrosLaunchSwitchSource::kPossiblySetByUser) {
source_offset = LacrosLaunchModeAndSource::kPossiblySetByUserLacrosDisabled;
} else if (source ==
crosapi::browser_util::LacrosLaunchSwitchSource::kForcedByUser) {
source_offset = LacrosLaunchModeAndSource::kForcedByUserLacrosDisabled;
} else {
source_offset = LacrosLaunchModeAndSource::kForcedByPolicyLacrosDisabled;
}
// The states are comprised of the basic four Lacros options and the
// source of the mode selection (By user, by Policy, by System). These
// combinations are "nibbled together" here in one status value.
lacros_mode_and_source = static_cast<LacrosLaunchModeAndSource>(
static_cast<int>(source_offset) +
static_cast<int>(lacros_mode_and_source));
UMA_HISTOGRAM_ENUMERATION("Ash.Lacros.Launch.ModeAndSource",
lacros_mode_and_source);
LOG(WARNING) << "Using LacrosLaunchModeAndSource "
<< static_cast<int>(lacros_mode_and_source);
if (!lacros_mode_.has_value() || !lacros_mode_and_source_.has_value() ||
lacros_mode != *lacros_mode_ ||
lacros_mode_and_source != *lacros_mode_and_source_) {
// Remember new values.
lacros_mode_ = lacros_mode;
lacros_mode_and_source_ = lacros_mode_and_source;
// Call our Daily launch mode reporting once now to make sure we have an
// event. If it's a dupe, the server will de-dupe.
OnDailyLaunchModeTimer();
if (!daily_event_timer_.IsRunning()) {
daily_event_timer_.Start(FROM_HERE, kDailyLaunchModeTimeDelta, this,
&BrowserManager::OnDailyLaunchModeTimer);
}
}
}
void BrowserManager::PerformOrEnqueue(std::unique_ptr<BrowserAction> action) {
if (shutdown_requested_) {
LOG(WARNING) << "lacros-chrome is preparing for system shutdown";
action->Cancel(mojom::CreationResult::kBrowserNotRunning);
return;
}
switch (state_) {
case State::UNAVAILABLE:
LOG(ERROR) << "lacros unavailable";
action->Cancel(mojom::CreationResult::kBrowserNotRunning);
return;
case State::NOT_INITIALIZED:
case State::MOUNTING:
LOG(WARNING) << "lacros component image not yet available";
pending_actions_.PushOrCancel(std::move(action));
return;
case State::TERMINATING:
LOG(WARNING) << "lacros-chrome is terminating, so cannot start now";
pending_actions_.PushOrCancel(std::move(action));
return;
case State::CREATING_LOG_FILE:
case State::STARTING:
LOG(WARNING) << "lacros-chrome is in the process of launching";
pending_actions_.PushOrCancel(std::move(action));
return;
case State::STOPPED:
DCHECK(!IsKeepAliveEnabled());
DCHECK(pending_actions_.IsEmpty());
pending_actions_.PushOrCancel(std::move(action));
StartIfNeeded();
return;
case State::RUNNING:
if (!browser_service_.has_value()) {
LOG(ERROR) << "BrowserService was disconnected";
action->Cancel(mojom::CreationResult::kServiceDisconnected);
return;
}
action->Perform(
{browser_service_->service, browser_service_->interface_version});
return;
}
}
// Callback called when the daily event happens.
void BrowserManager::OnDailyLaunchModeTimer() {
UMA_HISTOGRAM_ENUMERATION(kLacrosLaunchModeDaily, *lacros_mode_);
UMA_HISTOGRAM_ENUMERATION(kLacrosLaunchModeAndSourceDaily,
*lacros_mode_and_source_);
}
// static
void BrowserManager::DisableForTesting() {
CHECK_IS_TEST();
g_disabled_for_testing = true;
}
// static
void BrowserManager::EnableForTesting() {
CHECK_IS_TEST();
g_disabled_for_testing = false;
}
BrowserManager::ScopedUnsetAllKeepAliveForTesting::
ScopedUnsetAllKeepAliveForTesting(BrowserManager* manager)
: manager_(manager) {
previous_keep_alive_features_ = std::move(manager_->keep_alive_features_);
manager_->keep_alive_features_.clear();
manager_->UpdateKeepAliveInBrowserIfNecessary(false);
}
BrowserManager::ScopedUnsetAllKeepAliveForTesting::
~ScopedUnsetAllKeepAliveForTesting() {
manager_->keep_alive_features_ = std::move(previous_keep_alive_features_);
manager_->UpdateKeepAliveInBrowserIfNecessary(
!manager_->keep_alive_features_.empty());
}
} // namespace crosapi