| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // 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_switches.h" |
| #include "ash/public/cpp/notification_utils.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.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/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 "chrome/browser/ash/app_restore/full_restore_service.h" |
| #include "chrome/browser/ash/crosapi/browser_data_migrator.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/environment_provider.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_chromeos.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/common/channel_info.h" |
| #include "chromeos/crosapi/cpp/crosapi_constants.h" |
| #include "chromeos/crosapi/cpp/lacros_startup_state.h" |
| #include "chromeos/startup/startup_switches.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_type.h" |
| #include "components/version_info/version_info.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/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 { |
| |
| namespace { |
| |
| // The actual Lacros launch mode. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class LacrosLaunchMode { |
| // Indicates that Lacros is disabled. |
| kLacrosDisabled = 0, |
| // Indicates that Lacros and Ash are both enabled and accessible by the user. |
| kSideBySide = 1, |
| // Similar to kSideBySide but Lacros is the primary browser. |
| kLacrosPrimary = 2, |
| // Lacros is the only browser and Ash is disabled. |
| kLacrosOnly = 3, |
| kMaxValue = kLacrosOnly |
| }; |
| |
| // The actual Lacros launch mode. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class LacrosLaunchModeAndSource { |
| // Either set by user or system/flags, indicates that Lacros is disabled. |
| kPossiblySetByUserLacrosDisabled = 0, |
| // Either set by user or system/flags, indicates that Lacros and Ash are both |
| // enabled and accessible by the user. |
| kPossiblySetByUserSideBySide = 1, |
| // Either set by user or system/flags, indicates that Lacros is the primary |
| // (but not only) browser. |
| kPossiblySetByUserLacrosPrimary = 2, |
| // Either set by user or system/flags, Lacros is the only browser and Ash is |
| // disabled. |
| kPossiblySetByUserLacrosOnly = 3, |
| // Enforced by the user, indicates that Lacros is disabled. |
| kForcedByUserLacrosDisabled = 4 + kPossiblySetByUserLacrosDisabled, |
| // Enforced by the user, indicates that Lacros and Ash are both enabled and |
| // accessible by the user. |
| kForcedByUserSideBySide = 4 + kPossiblySetByUserSideBySide, |
| // Enforced by the user, indicates that Lacros is the primary (but not only) |
| // browser. |
| kForcedByUserLacrosPrimary = 4 + kPossiblySetByUserLacrosPrimary, |
| // Enforced by the user, Lacros is the only browser and Ash is disabled. |
| kForcedByUserLacrosOnly = 4 + kPossiblySetByUserLacrosOnly, |
| // Enforced by policy, indicates that Lacros is disabled. |
| kForcedByPolicyLacrosDisabled = 8 + kPossiblySetByUserLacrosDisabled, |
| // Enforced by policy, indicates that Lacros and Ash are both enabled and |
| // accessible by the user. |
| kForcedByPolicySideBySide = 8 + kPossiblySetByUserSideBySide, |
| // Enforced by policy, indicates that Lacros is the primary (but not only) |
| // browser. |
| kForcedByPolicyLacrosPrimary = 8 + kPossiblySetByUserLacrosPrimary, |
| // Enforced by policy, Lacros is the only browser and Ash is disabled. |
| kForcedByPolicyLacrosOnly = 8 + kPossiblySetByUserLacrosOnly, |
| |
| kMaxValue = kForcedByPolicyLacrosOnly |
| }; |
| |
| using LaunchParamsFromBackground = BrowserManager::LaunchParamsFromBackground; |
| |
| // Pointer to the global instance of BrowserManager. |
| BrowserManager* g_instance = nullptr; |
| |
| constexpr char kLacrosCannotLaunchNotificationID[] = |
| "lacros_cannot_launch_notification_id"; |
| constexpr char kLacrosLauncherNotifierID[] = "lacros_launcher"; |
| |
| // To be sure the lacros is running with neutral priority |
| class ThreadPriorityDelegate : public base::LaunchOptions::PreExecDelegate { |
| public: |
| void RunAsyncSafe() override { |
| base::PlatformThread::SetCurrentThreadPriority( |
| base::ThreadPriority::NORMAL); |
| } |
| }; |
| |
| base::FilePath LacrosLogPath() { |
| return browser_util::GetUserDataDir().Append("lacros.log"); |
| } |
| |
| base::FilePath LacrosPreviousLogPath() { |
| return browser_util::GetUserDataDir().Append("lacros.log.PREVIOUS"); |
| } |
| |
| // Moves any existing lacros log file to lacros.log.PREVIOUS. Returns true if a |
| // log file existed before being moved, and false if no log file was found. |
| bool RotateLacrosLogs() { |
| // Remove lacros.log.PREVIOUS log entry if present. |
| base::FilePath previous_log_path = LacrosPreviousLogPath(); |
| unlink(previous_log_path.value().c_str()); |
| |
| base::FilePath log_path = LacrosLogPath(); |
| // Handle edge case where previous code created a symbolic link that could not |
| // correctly resolve by deleting that symbolic link. |
| if (base::IsLink(log_path)) { |
| unlink(log_path.value().c_str()); |
| return true; |
| } |
| |
| // If there is an existing log entry rename it to lacros.log.PREVIOUS. |
| if (base::PathExists(log_path)) { |
| base::Move(log_path, previous_log_path); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // 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 cleared_user_data_dir) { |
| LaunchParamsFromBackground params; |
| |
| // TODO(crbug/1198528): remove use_new_account_manager parameter. |
| // This code wipes the Lacros --user-data-dir exactly once due to an |
| // incompatible account_manager change. This code can be removed when ash is |
| // newer than M92, as we can then assume that all relevant users have been |
| // migrated. |
| // |
| // If we want to use the new account manager, and we haven't yet cleared the |
| // user data dir, do so. |
| if (!cleared_user_data_dir) { |
| params.use_new_account_manager = |
| base::DeletePathRecursively(browser_util::GetUserDataDir()); |
| } else { |
| params.use_new_account_manager = true; |
| } |
| |
| 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(browser_util::GetUserDataDir(), |
| &error)) { |
| LOG(ERROR) << "Failed to make directory " |
| << browser_util::GetUserDataDir() |
| << 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); |
| 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) { |
| // 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. |
| constexpr base::TimeDelta kGracefulShutdownTimeout = base::Seconds(5); |
| if (process.WaitForExitWithTimeout(kGracefulShutdownTimeout, 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); |
| } |
| |
| // Returns the initial browser action. No browser will be opened in the |
| // following circumstances: |
| // 1. Lacros-chrome is initialized in the web Kiosk session |
| // 2. Full restore is responsible for restoring/launching Lacros. |
| browser_util::InitialBrowserAction GetInitialBrowserAction() { |
| return browser_util::InitialBrowserAction( |
| user_manager::UserManager::Get()->IsLoggedInAsWebKioskApp() || |
| ash::full_restore::IsFullRestoreAvailableForLacros() |
| ? mojom::InitialBrowserAction::kDoNotOpenWindow |
| : mojom::InitialBrowserAction::kUseStartupPreference); |
| } |
| |
| bool IsKeepAliveDisabledForTesting() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kDisableLacrosKeepAliveForTesting); |
| } |
| |
| bool IsLoginLacrosOpeningDisabledForTesting() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kDisableLoginLacrosOpening); |
| } |
| |
| } // namespace |
| |
| // To be sure the lacros is running with neutral priority. |
| class LacrosThreadPriorityDelegate |
| : public base::LaunchOptions::PreExecDelegate { |
| public: |
| void RunAsyncSafe() override { |
| // SetCurrentThreadPriority() needs file I/O on /proc and /sys. |
| base::ScopedAllowBlocking allow_blocking; |
| base::PlatformThread::SetCurrentThreadPriority( |
| base::ThreadPriority::NORMAL); |
| } |
| }; |
| |
| // 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>()) { |
| DCHECK(!g_instance); |
| g_instance = 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); |
| |
| // CrosapiManager may not be initialized on unit testing. |
| if (CrosapiManager::IsInitialized()) { |
| CrosapiManager::Get() |
| ->crosapi_ash() |
| ->browser_service_host_ash() |
| ->AddObserver(this); |
| } |
| |
| std::string socket_path = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| chromeos::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(/*ignored=*/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; |
| } |
| |
| void BrowserManager::NewWindow(bool incognito, |
| bool should_trigger_session_restore) { |
| // If `should_trigger_session_restore` is set to true the new lacros window |
| // should be treated like the start of a new session. Ensure this is the case |
| // by deferring to the browser startup preferences. Otherwise we open the |
| // window with the default NTP. |
| // Incognito's default behavior is to open to a NTP regardless of session |
| // restore settings so the same browser action is used regardless of the value |
| // of `should_trigger_session_restore`. |
| constexpr mojom::InitialBrowserAction kBrowserActions[2][2] = { |
| {mojom::InitialBrowserAction::kOpenNewTabPageWindow, |
| mojom::InitialBrowserAction::kUseStartupPreference}, |
| {mojom::InitialBrowserAction::kOpenIncognitoWindow, |
| mojom::InitialBrowserAction::kOpenIncognitoWindow}}; |
| |
| auto result = MaybeStart(browser_util::InitialBrowserAction( |
| kBrowserActions[incognito][should_trigger_session_restore])); |
| |
| if (result != MaybeStartResult::kRunning) |
| return; |
| |
| if (!browser_service_.has_value()) { |
| LOG(ERROR) << "BrowserService was disconnected"; |
| return; |
| } |
| browser_service_->service->NewWindow( |
| incognito, should_trigger_session_restore, base::DoNothing()); |
| } |
| |
| bool BrowserManager::NewWindowForDetachingTabSupported() const { |
| return browser_service_.has_value() && |
| browser_service_->interface_version >= |
| crosapi::mojom::BrowserService:: |
| kNewWindowForDetachingTabMinVersion; |
| } |
| |
| void BrowserManager::NewWindowForDetachingTab( |
| const std::u16string& tab_id_str, |
| const std::u16string& group_id_str, |
| NewWindowForDetachingTabCallback callback) { |
| // Chrome OS uses different user model where clicking the chrome icon always |
| // opens a new tab page, and it doesn't matter whether lacros is launching |
| // for the first time or not. |
| auto result = MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kOpenNewTabPageWindow)); |
| if (result != MaybeStartResult::kRunning) { |
| std::move(callback).Run(mojom::CreationResult::kBrowserNotRunning, |
| std::string() /*new_window*/); |
| return; |
| } |
| |
| if (!browser_service_.has_value()) { |
| std::move(callback).Run(mojom::CreationResult::kServiceDisconnected, |
| std::string() /*new_window*/); |
| return; |
| } |
| |
| if (!NewWindowForDetachingTabSupported()) { |
| std::move(callback).Run(mojom::CreationResult::kUnsupported, |
| std::string() /*new_window*/); |
| return; |
| } |
| browser_service_->service->NewWindowForDetachingTab(tab_id_str, group_id_str, |
| std::move(callback)); |
| } |
| |
| bool BrowserManager::NewFullscreenWindowSupported() const { |
| return browser_service_.has_value() && |
| browser_service_->interface_version >= |
| crosapi::mojom::BrowserService::kNewFullscreenWindowMinVersion; |
| } |
| |
| void BrowserManager::NewFullscreenWindow(const GURL& url, |
| NewFullscreenWindowCallback callback) { |
| auto result = MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kDoNotOpenWindow)); |
| if (result != MaybeStartResult::kRunning) { |
| std::move(callback).Run(mojom::CreationResult::kBrowserNotRunning); |
| return; |
| } |
| |
| if (!browser_service_.has_value()) { |
| LOG(ERROR) << "BrowserService was disconnected"; |
| std::move(callback).Run(mojom::CreationResult::kServiceDisconnected); |
| return; |
| } |
| |
| if (!NewFullscreenWindowSupported()) { |
| std::move(callback).Run(mojom::CreationResult::kUnsupported); |
| return; |
| } |
| browser_service_->service->NewFullscreenWindow(url, std::move(callback)); |
| } |
| |
| void BrowserManager::NewTab() { |
| auto result = MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kOpenNewTabPageWindow)); |
| if (result != MaybeStartResult::kRunning) |
| return; |
| |
| if (!browser_service_.has_value()) { |
| LOG(ERROR) << "BrowserService was disconnected"; |
| return; |
| } |
| browser_service_->service->NewTab(base::DoNothing()); |
| } |
| |
| void BrowserManager::OpenUrl(const GURL& url) { |
| auto result = MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kOpenWindowWithUrls, {url})); |
| if (result != MaybeStartResult::kRunning) |
| return; |
| |
| if (!browser_service_.has_value()) { |
| LOG(ERROR) << "BrowserService was disconnected"; |
| return; |
| } |
| if (browser_service_->interface_version < |
| mojom::BrowserService::kOpenUrlMinVersion) { |
| LOG(ERROR) << "BrowserService does not support OpenUrl"; |
| return; |
| } |
| browser_service_->service->OpenUrl(url, base::DoNothing()); |
| } |
| |
| void BrowserManager::RestoreTab() { |
| auto result = MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kRestoreLastSession)); |
| if (result != MaybeStartResult::kRunning) |
| return; |
| |
| if (!browser_service_.has_value()) { |
| LOG(ERROR) << "BrowserService was disconnected"; |
| return; |
| } |
| browser_service_->service->RestoreTab(base::DoNothing()); |
| } |
| |
| bool BrowserManager::HandleTabScrubbingSupported() const { |
| return browser_service_.has_value() && |
| browser_service_->interface_version >= |
| crosapi::mojom::BrowserService::kHandleTabScrubbingMinVersion; |
| } |
| |
| void BrowserManager::HandleTabScrubbing(float x_offset) { |
| // If Lacros isn't running, bail out. |
| if (!IsRunning()) |
| return; |
| |
| if (!browser_service_.has_value()) |
| return; |
| |
| if (!HandleTabScrubbingSupported()) |
| return; |
| |
| browser_service_->service->HandleTabScrubbing(x_offset); |
| } |
| |
| void BrowserManager::InitializeAndStart() { |
| 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()); |
| |
| // Must be checked after user session start because it depends on user type. |
| if (is_lacros_enabled) { |
| component_update_observation_.Observe(component_update_service_); |
| SetState(State::MOUNTING); |
| browser_loader_->Load(base::BindOnce(&BrowserManager::OnLoadComplete, |
| weak_factory_.GetWeakPtr(), |
| GetInitialBrowserAction())); |
| } else { |
| SetState(State::UNAVAILABLE); |
| browser_loader_->Unload(); |
| } |
| |
| // Post `DryRunToCollectUMA()` to send UMA stats about sizes of files/dirs |
| // inside the profile data directory. |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&ash::BrowserDataMigratorImpl::DryRunToCollectUMA, |
| ProfileManager::GetPrimaryUserProfile()->GetPath())); |
| } |
| |
| bool BrowserManager::GetFeedbackDataSupported() const { |
| return browser_service_.has_value() && |
| browser_service_->interface_version >= |
| crosapi::mojom::BrowserService::kGetFeedbackDataMinVersion; |
| } |
| |
| 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::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; |
| |
| // The lacros-chrome process may have already been terminated as the result of |
| // a previous mojo pipe disconnection in `OnMojoDisconnected()` and has not |
| // yet been restarted. Ensure the lacros process is still valid before |
| // proceeding. |
| if (!lacros_process_.IsValid()) |
| return; |
| |
| // Signal the the lacros process to terminate. This will result in mojo |
| // disconnecting and a callback into `OnMojoDisconnected()`. This will post a |
| // task that waits for a successful lacros-chrome exit on a separate thread. |
| lacros_process_.Terminate(/*ignored=*/0, /*wait=*/false); |
| } |
| |
| void BrowserManager::SetState(State state) { |
| if (state_ == state) |
| return; |
| state_ = state; |
| |
| for (auto& observer : observers_) { |
| if (state == State::TERMINATING) { |
| observer.OnMojoDisconnected(); |
| } |
| observer.OnStateChanged(); |
| } |
| |
| LaunchForKeepAliveIfNecessary(); |
| } |
| |
| 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; |
| |
| BrowserManager::MaybeStartResult BrowserManager::MaybeStart( |
| browser_util::InitialBrowserAction initial_browser_action) { |
| if (!browser_util::IsLacrosEnabled()) |
| return MaybeStartResult::kNotStarted; |
| |
| if (!browser_util::IsLacrosAllowedToLaunch()) { |
| std::unique_ptr<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), |
| message_center::RichNotificationData(), |
| base::MakeRefCounted< |
| message_center::HandleNotificationClickDelegate>( |
| base::RepeatingClosure()), |
| gfx::kNoneIcon, |
| message_center::SystemNotificationWarningLevel::NORMAL); |
| |
| SystemNotificationHelper::GetInstance()->Display(*notification); |
| return MaybeStartResult::kNotStarted; |
| } |
| |
| if (!IsReady()) { |
| LOG(WARNING) << "lacros component image not yet available"; |
| return MaybeStartResult::kNotStarted; |
| } |
| DCHECK(!lacros_path_.empty()); |
| DCHECK(lacros_selection_.has_value()); |
| |
| if (shutdown_requested_) { |
| LOG(WARNING) << "lacros-chrome is preparing for system shutdown"; |
| return MaybeStartResult::kNotStarted; |
| } |
| |
| if (state_ == State::TERMINATING) { |
| LOG(WARNING) << "lacros-chrome is terminating, so cannot start now"; |
| return MaybeStartResult::kNotStarted; |
| } |
| |
| if (state_ == State::CREATING_LOG_FILE || state_ == State::STARTING) { |
| LOG(WARNING) << "lacros-chrome is in the process of launching"; |
| return MaybeStartResult::kStarting; |
| } |
| |
| // If lacros-chrome is not running, launch it. |
| if (state_ == State::STOPPED) { |
| // If an update is available, load the most up-to-date installed version and |
| // let the load complete callback start the browser. |
| if (update_available_) { |
| update_available_ = false; |
| |
| SetState(State::MOUNTING); |
| lacros_path_ = base::FilePath(); |
| lacros_selection_ = absl::nullopt; |
| browser_loader_->Load(base::BindOnce(&BrowserManager::OnLoadComplete, |
| weak_factory_.GetWeakPtr(), |
| std::move(initial_browser_action))); |
| } else { |
| Start(std::move(initial_browser_action)); |
| } |
| return MaybeStartResult::kStarting; |
| } |
| |
| return MaybeStartResult::kRunning; |
| } |
| |
| void BrowserManager::Start( |
| browser_util::InitialBrowserAction initial_browser_action) { |
| DCHECK_EQ(state_, State::STOPPED); |
| DCHECK(!lacros_path_.empty()); |
| DCHECK(!shutdown_requested_); |
| // Ensure we're not trying to open a window before the shelf is initialized. |
| DCHECK(ChromeShelfController::instance()); |
| |
| // Always reset the |relaunch_requested_| flag when launching Lacros. |
| relaunch_requested_ = false; |
| |
| SetState(State::CREATING_LOG_FILE); |
| |
| // TODO(ythjkt): After M92 cherry-pick, clean up the following code by moving |
| // the data wipe check logic from `BrowserDataMigrator` to browser_util. |
| const std::string user_id_hash = ash::ProfileHelper::GetUserIdHashFromProfile( |
| ProfileManager::GetPrimaryUserProfile()); |
| // Check if user data directory needs to be wiped for a backward incompatible |
| // update. |
| bool cleared_user_data_dir = !browser_util::IsDataWipeRequired(user_id_hash); |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&DoLacrosBackgroundWorkPreLaunch, lacros_path_, |
| cleared_user_data_dir), |
| base::BindOnce(&BrowserManager::StartWithLogFile, |
| weak_factory_.GetWeakPtr(), |
| std::move(initial_browser_action))); |
| } |
| |
| void BrowserManager::StartWithLogFile( |
| browser_util::InitialBrowserAction initial_browser_action, |
| LaunchParamsFromBackground params) { |
| DCHECK_EQ(state_, State::CREATING_LOG_FILE); |
| |
| if (shutdown_requested_) { |
| // StartWithLogFile() may have been posted before Shutdown() has been |
| // signalled by the system. Ensure that we do not start lacros-chrome in |
| // this case. |
| LOG(ERROR) << "Start attempted after Shutdown() called."; |
| SetState(State::STOPPED); |
| return; |
| } |
| |
| if (!params.use_new_account_manager) { |
| // If `use_new_account_manager` is false, that means deleting old lacros |
| // data directory failed. In such a case, do not launch lacros. |
| LOG(ERROR) << "Failed to delete old user data dir."; |
| SetState(State::STOPPED); |
| return; |
| } |
| |
| 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()); |
| |
| std::string chrome_path = lacros_path_.MaybeAsASCII() + "/chrome"; |
| LOG(WARNING) << "Launching lacros-chrome at " << chrome_path; |
| |
| DCHECK(lacros_selection_.has_value()); |
| version_info::Channel 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); |
| |
| std::string additional_env = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| chromeos::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 = |
| browser_util::GetUserDataDir().Append("crash_dumps").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-crashpad", |
| "--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"); |
| |
| std::string additional_flags = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| chromeos::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); |
| |
| // 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"); |
| |
| // 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)); |
| } |
| |
| base::ScopedFD startup_fd = browser_util::CreateStartupData( |
| environment_provider_.get(), std::move(initial_browser_action), |
| !keep_alive_features_.empty()); |
| 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); |
| } |
| |
| // Set up Mojo channel. |
| base::CommandLine command_line(argv); |
| LOG(WARNING) << "Launching lacros with command: " |
| << command_line.GetCommandLineString(); |
| |
| // Lacros-chrome starts with NORMAL priority |
| LacrosThreadPriorityDelegate thread_priority_delegate; |
| options.pre_exec_delegate = &thread_priority_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())); |
| |
| // Append a fake switch for backward compatibility. |
| // TODO(crbug.com/1188020): Remove this after M93 Lacros is spread enough. |
| command_line.AppendSwitchASCII(mojo::PlatformChannel::kHandleSwitch, "-1"); |
| |
| // 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"; |
| SetState(State::STOPPED); |
| return; |
| } |
| SetState(State::STARTING); |
| LOG(WARNING) << "Launched lacros-chrome with pid " << lacros_process_.Pid(); |
| channel.RemoteProcessLaunchAttempted(); |
| } |
| |
| void BrowserManager::OnBrowserServiceConnected( |
| CrosapiId id, |
| mojo::RemoteSetElementId mojo_id, |
| mojom::BrowserService* browser_service, |
| uint32_t browser_service_version) { |
| if (id != crosapi_id_ && id != legacy_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()); |
| } |
| |
| 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::OnMojoDisconnected() { |
| DCHECK(state_ == State::STARTING || state_ == State::RUNNING); |
| DCHECK(lacros_process_.IsValid()); |
| LOG(WARNING) |
| << "Mojo to lacros-chrome is disconnected. Terminating lacros-chrome"; |
| |
| browser_service_.reset(); |
| crosapi_id_.reset(); |
| legacy_crosapi_id_.reset(); |
| base::ThreadPool::PostTaskAndReply( |
| FROM_HERE, {base::WithBaseSyncPrimitives()}, |
| base::BindOnce(&TerminateLacrosChrome, std::move(lacros_process_)), |
| base::BindOnce(&BrowserManager::OnLacrosChromeTerminated, |
| weak_factory_.GetWeakPtr())); |
| |
| SetState(State::TERMINATING); |
| } |
| |
| void BrowserManager::OnLacrosChromeTerminated() { |
| DCHECK_EQ(state_, State::TERMINATING); |
| LOG(WARNING) << "Lacros-chrome is terminated"; |
| 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. |
| SetLaunchOnLoginPref(false); |
| |
| is_terminated_ = true; |
| |
| if (!shutdown_requested_ && relaunch_requested_) { |
| MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kRestoreLastSession)); |
| } |
| } |
| |
| void BrowserManager::OnSessionStateChanged() { |
| // Wait for session to become active. |
| auto* session_manager = session_manager::SessionManager::Get(); |
| if (session_manager->session_state() != |
| session_manager::SessionState::ACTIVE) { |
| LOG(WARNING) |
| << "Session not yet active. Lacros-chrome will not be launched yet"; |
| return; |
| } |
| |
| InitializeAndStart(); |
| } |
| |
| void BrowserManager::OnStoreLoaded(policy::CloudPolicyStore* store) { |
| // A new policy got installed for the current user, so we need to pass it to |
| // the Lacros browser. |
| std::string policy_blob; |
| 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::OnEvent(Events event, const std::string& id) { |
| // Track whether an update has been installed and should be loaded next time |
| // the browser is started. |
| if (event == Events::COMPONENT_UPDATED && |
| id == browser_util::GetLacrosComponentInfo().crx_id) { |
| update_available_ = true; |
| } |
| } |
| |
| void BrowserManager::OnLoadComplete( |
| browser_util::InitialBrowserAction initial_browser_action, |
| const base::FilePath& path, |
| LacrosSelection selection) { |
| DCHECK_EQ(state_, State::MOUNTING); |
| |
| lacros_path_ = path; |
| lacros_selection_ = absl::optional<LacrosSelection>(selection); |
| SetState(path.empty() ? State::UNAVAILABLE : State::STOPPED); |
| |
| // TODO(crbug.com/1266010): In the event the load operation failed, we should |
| // launch the last successfully loaded image. |
| const bool success = !path.empty(); |
| for (auto& observer : observers_) { |
| observer.OnLoadComplete(success); |
| } |
| |
| // Start Lacros browser automatically on login, if |
| // 1) Lacros was opened in the previous session. |
| // 2) Lacros is the primary web browser. |
| // This can be suppressed on commandline flag for testing. |
| if (state_ == State::STOPPED && !shutdown_requested_ && |
| (GetLaunchOnLoginPref() || (browser_util::IsLacrosPrimaryBrowser() && |
| !IsLoginLacrosOpeningDisabledForTesting()))) { |
| Start(std::move(initial_browser_action)); |
| } |
| } |
| |
| void BrowserManager::PrepareLacrosPolicies() { |
| policy::CloudPolicyStore* store = GetDeviceAccountPolicyStore(); |
| if (!store) |
| return; |
| |
| if (!store->policy_fetch_response()) |
| return; |
| const std::string policy_blob = |
| store->policy_fetch_response()->SerializeAsString(); |
| SetDeviceAccountPolicy(policy_blob); |
| // The lifetime of `BrowserManager` is longer than lifetime of policy store. |
| // That is why `CloudPolicyStore::RemoveObserver()` is called during |
| // `CloudPolicyStore::Observer::OnStoreDestruction()`. |
| store->AddObserver(this); |
| } |
| |
| policy::CloudPolicyStore* BrowserManager::GetDeviceAccountPolicyStore() { |
| const user_manager::User* user = |
| user_manager::UserManager::Get()->GetPrimaryUser(); |
| DCHECK(user); |
| |
| switch (user->GetType()) { |
| case user_manager::USER_TYPE_REGULAR: { |
| Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(user); |
| DCHECK(profile); |
| policy::CloudPolicyManager* user_cloud_policy_manager = |
| profile->GetUserCloudPolicyManagerAsh(); |
| if (!user_cloud_policy_manager) |
| return nullptr; |
| return user_cloud_policy_manager->core()->store(); |
| } |
| case user_manager::USER_TYPE_PUBLIC_ACCOUNT: |
| case user_manager::USER_TYPE_WEB_KIOSK_APP: { |
| policy::DeviceLocalAccountPolicyBroker* broker = |
| g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetDeviceLocalAccountPolicyService() |
| ->GetBrokerForUser(user->GetAccountId().GetUserEmail()); |
| return broker ? broker->core()->store() : nullptr; |
| } |
| default: |
| return nullptr; |
| } |
| } |
| |
| 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) { |
| 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) { |
| // If browser is not running, we have to launch it. |
| LaunchForKeepAliveIfNecessary(); |
| UpdateKeepAliveInBrowserIfNecessary(true); |
| } |
| } |
| |
| void BrowserManager::StopKeepAlive(Feature feature) { |
| keep_alive_features_.erase(feature); |
| if (keep_alive_features_.empty()) |
| UpdateKeepAliveInBrowserIfNecessary(false); |
| } |
| |
| void BrowserManager::LaunchForKeepAliveIfNecessary() { |
| if (state_ == State::STOPPED && !shutdown_requested_ && |
| !keep_alive_features_.empty()) { |
| CHECK(browser_util::IsLacrosEnabled()); |
| CHECK(browser_util::IsLacrosAllowedToLaunch()); |
| MaybeStart(browser_util::InitialBrowserAction( |
| mojom::InitialBrowserAction::kDoNotOpenWindow)); |
| } |
| } |
| |
| 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); |
| } |
| |
| bool BrowserManager::IsReady() const { |
| return state_ != State::NOT_INITIALIZED && state_ != State::MOUNTING && |
| state_ != State::UNAVAILABLE; |
| } |
| |
| void BrowserManager::RecordLacrosLaunchMode() { |
| LacrosLaunchMode lacros_mode; |
| LacrosLaunchModeAndSource lacros_mode_and_source; |
| |
| if (!browser_util::IsAshWebBrowserEnabled(chrome::GetChannel())) { |
| // 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(); |
| |
| // Unit tests can come here before the source is known. |
| if (source == crosapi::browser_util::LacrosLaunchSwitchSource::kUnknown) |
| return; |
| |
| 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); |
| } |
| |
| } // namespace crosapi |