[go: nahoru, domu]

blob: fb8e7cd31458d7c532230053284339563ecbc9c9 [file] [log] [blame]
// Copyright 2018 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/system_web_apps/system_web_app_manager.h"
#include <iterator>
#include <memory>
#include <optional>
#include <ostream>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/webui/camera_app_ui/url_constants.h"
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/dcheck_is_on.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/one_shot_event.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/version.h"
#include "chrome/browser/ash/system_web_apps/apps/boca_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/camera_app/camera_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/connectivity_diagnostics_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/crosh_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/demo_mode_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/diagnostics_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/eche_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/file_manager_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/firmware_update_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/help_app/help_app_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/mall_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/media_app/media_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/os_feedback_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/os_flags_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/os_settings_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/os_url_handler_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_system_app_delegate.h"
#include "chrome/browser/ash/system_web_apps/apps/print_management_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/projector_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/recorder_app/recorder_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/sanitize_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/scanning_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/shimless_rma_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/shortcut_customization_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/terminal_system_web_app_info.h"
#include "chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_system_app_delegate.h"
#include "chrome/browser/ash/system_web_apps/color_helpers.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_background_task.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_icon_checker.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager_factory.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_background_task_info.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/manifest_update_manager.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_system_web_app_delegate_map_utils.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#include "url/origin.h"
#if !defined(OFFICIAL_BUILD)
#include "chrome/browser/ash/system_web_apps/apps/sample_system_web_app_info.h"
#endif // !defined(OFFICIAL_BUILD)
namespace ash {
const constexpr char kIconHealthMetricName[] =
"Webapp.SystemApps.IconsAreHealthyInSession";
const constexpr char kIconsFixedOnReinstallMetricName[] =
"Webapp.SystemApps.IconsFixedOnReinstall";
namespace {
SystemWebAppDelegateMap CreateSystemWebApps(Profile* profile) {
std::vector<std::unique_ptr<SystemWebAppDelegate>> info_vec;
// TODO(crbug.com/40118385): Currently unused, will be hooked up
// post-migration. We're making delegates for everything, and will then use
// them in place of SystemAppInfos.
info_vec.push_back(std::make_unique<CameraSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<DemoModeSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<DiagnosticsSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<OSSettingsSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<CroshSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<TerminalSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<HelpAppSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<MediaSystemAppDelegate>(profile));
info_vec.push_back(
std::make_unique<PrintManagementSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<ScanningSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<ShimlessRMASystemAppDelegate>(profile));
info_vec.push_back(
std::make_unique<ConnectivityDiagnosticsSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<EcheSystemAppDelegate>(profile));
info_vec.push_back(
std::make_unique<PersonalizationSystemAppDelegate>(profile));
info_vec.push_back(
std::make_unique<ShortcutCustomizationSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<OSFeedbackAppDelegate>(profile));
info_vec.push_back(std::make_unique<FileManagerSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<ProjectorSystemWebAppDelegate>(profile));
info_vec.push_back(
std::make_unique<OsUrlHandlerSystemWebAppDelegate>(profile));
info_vec.push_back(
std::make_unique<FirmwareUpdateSystemAppDelegate>(profile));
info_vec.push_back(std::make_unique<OsFlagsSystemWebAppDelegate>(profile));
info_vec.push_back(
std::make_unique<vc_background_ui::VcBackgroundUISystemAppDelegate>(
profile));
info_vec.push_back(std::make_unique<PrintPreviewCrosDelegate>(profile));
if (features::IsBocaEnabled()) {
info_vec.push_back(std::make_unique<BocaSystemAppDelegate>(profile));
}
info_vec.push_back(std::make_unique<MallSystemAppDelegate>(profile));
if (base::FeatureList::IsEnabled(ash::features::kSanitize)) {
info_vec.push_back(std::make_unique<SanitizeSystemAppDelegate>(profile));
}
if (base::FeatureList::IsEnabled(ash::features::kSanitize)) {
info_vec.push_back(std::make_unique<SanitizeSystemAppDelegate>(profile));
}
if (base::FeatureList::IsEnabled(ash::features::kConch)) {
info_vec.push_back(std::make_unique<RecorderSystemAppDelegate>(profile));
}
#if !defined(OFFICIAL_BUILD)
info_vec.push_back(std::make_unique<SampleSystemAppDelegate>(profile));
#endif // !defined(OFFICIAL_BUILD)
SystemWebAppDelegateMap delegate_map;
for (auto& info : info_vec) {
if (info->IsAppEnabled() ||
base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps)) {
// Gets `type` before std::move().
SystemWebAppType type = info->GetType();
delegate_map.emplace(type, std::move(info));
}
}
return delegate_map;
}
bool HasSystemWebAppScheme(const GURL& url) {
return url.SchemeIs(content::kChromeUIScheme) ||
url.SchemeIs(content::kChromeUIUntrustedScheme);
}
web_app::ExternalInstallOptions CreateInstallOptionsForSystemApp(
const SystemWebAppType type,
const SystemWebAppDelegate& delegate,
bool force_update,
bool is_disabled) {
DCHECK(delegate.GetInstallUrl().scheme() == content::kChromeUIScheme ||
delegate.GetInstallUrl().scheme() ==
content::kChromeUIUntrustedScheme);
web_app::ExternalInstallOptions install_options(
delegate.GetInstallUrl(), web_app::mojom::UserDisplayMode::kStandalone,
web_app::ExternalInstallSource::kSystemInstalled);
install_options.only_use_app_info_factory = true;
// This can be Unretained because it's referring to the delegate owning this
// factory method. The lifetime of that is the same as the
// SystemWebAppManager.
install_options.app_info_factory = base::BindRepeating(
&SystemWebAppDelegate::GetWebAppInfo, base::Unretained(&delegate));
install_options.add_to_applications_menu = delegate.ShouldShowInLauncher();
install_options.add_to_desktop = false;
install_options.add_to_quick_launch_bar = false;
install_options.add_to_search = delegate.ShouldShowInSearchAndShelf();
install_options.add_to_management = false;
install_options.is_disabled = is_disabled;
install_options.force_reinstall = force_update;
install_options.uninstall_and_replace =
delegate.GetAppIdsToUninstallAndReplace();
install_options.system_app_type = type;
install_options.handles_file_open_intents =
delegate.ShouldHandleFileOpenIntents();
const auto& search_terms = delegate.GetAdditionalSearchTerms();
base::ranges::transform(
search_terms, std::back_inserter(install_options.additional_search_terms),
&l10n_util::GetStringUTF8);
return install_options;
}
} // namespace
// static
const char SystemWebAppManager::kInstallResultHistogramName[];
const char SystemWebAppManager::kInstallDurationHistogramName[];
SystemWebAppManager::SystemWebAppManager(Profile* profile)
: profile_(profile),
provider_(web_app::WebAppProvider::GetForLocalAppsUnchecked(profile_)),
on_apps_synchronized_(new base::OneShotEvent()),
on_tasks_started_(new base::OneShotEvent()),
on_icon_check_completed_(new base::OneShotEvent()),
install_result_per_profile_histogram_name_(
std::string(kInstallResultHistogramName) + ".Profiles." +
web_app::GetProfileCategoryForLogging(profile)),
pref_service_(profile_->GetPrefs()),
icon_checker_(SystemWebAppIconChecker::Create(profile_)) {
DCHECK(provider_);
// Always create delegates because many System Web App WebUIs are disabled
// when the delegate is not present and we need them in tests. Tests can
// override the list of delegates with SetSystemAppsForTesting().
// Tests can override the list of delegates with `SetSystemAppsForTesting`.
system_app_delegates_ = CreateSystemWebApps(profile_);
#if defined(OFFICIAL_BUILD)
const bool is_official = true;
#else
const bool is_official = false;
#endif
const bool is_test =
base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType);
if (is_test || !is_official) {
// Tests and non-official builds should always update.
update_policy_ = UpdatePolicy::kAlwaysUpdate;
} else {
// Official builds should trigger updates whenever the version number
// changes.
update_policy_ = UpdatePolicy::kOnVersionChange;
}
// Ensure that system web apps have chrome://theme and
// chrome-untrusted://theme available.
content::URLDataSource::Add(profile, GetThemeSource(profile));
content::URLDataSource::Add(profile,
GetThemeSource(profile, /*untrusted=*/true));
}
SystemWebAppManager::~SystemWebAppManager() {
// SystemWebAppManager lifetime matches WebAppProvider lifetime (see
// BrowserContextDependencyManager) but we reset pointers to
// system_app_delegates_ for integrity with DCHECKs.
if (provider_->is_registry_ready()) {
ConnectProviderToSystemWebAppDelegateMap(nullptr);
}
}
// static
SystemWebAppManager* SystemWebAppManager::Get(Profile* profile) {
return SystemWebAppManagerFactory::GetForProfile(profile);
}
// static
web_app::WebAppProvider* SystemWebAppManager::GetWebAppProvider(
Profile* profile) {
return web_app::WebAppProvider::GetForLocalAppsUnchecked(profile);
}
// static
SystemWebAppManager* SystemWebAppManager::GetForTest(Profile* profile) {
// Running a nested base::RunLoop outside of tests causes a deadlock. Crash
// immediately instead of deadlocking for easier debugging (especially for
// TAST tests which use prod binaries).
CHECK_IS_TEST();
web_app::WebAppProvider* provider =
SystemWebAppManager::GetWebAppProvider(profile);
if (!provider) {
return nullptr;
}
SystemWebAppManager* swa_manager = Get(profile);
DCHECK(swa_manager);
if (provider->on_registry_ready().is_signaled()) {
return swa_manager;
}
base::RunLoop run_loop;
provider->on_registry_ready().Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
return swa_manager;
}
void SystemWebAppManager::StopBackgroundTasks() {
for (auto& task : tasks_) {
task->StopTask();
}
}
void SystemWebAppManager::StopBackgroundTasksForTesting() {
StopBackgroundTasks();
}
bool SystemWebAppManager::IsAppEnabled(SystemWebAppType type) const {
if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps)) {
return true;
}
const SystemWebAppDelegate* delegate =
GetSystemWebApp(system_app_delegates_, type);
if (!delegate) {
return false;
}
return delegate->IsAppEnabled();
}
void SystemWebAppManager::ScheduleStart() {
provider_->on_registry_ready().Post(
FROM_HERE, base::BindOnce(&SystemWebAppManager::Start,
weak_ptr_factory_.GetWeakPtr()));
}
void SystemWebAppManager::Start() {
TRACE_EVENT0("ui", "SystemWebAppManager::Start");
DCHECK(provider_->is_registry_ready());
// `Start` can be called multiple times in tests.
ui_manager_observation_.Reset();
ui_manager_observation_.Observe(&provider_->ui_manager());
ConnectProviderToSystemWebAppDelegateMap(&system_app_delegates_);
const base::TimeTicks install_start_time = base::TimeTicks::Now();
#if DCHECK_IS_ON()
// Check Origin Trials are defined correctly.
for (const auto& type_and_app_info : system_app_delegates_) {
for (const auto& origin_to_trial_names :
type_and_app_info.second->GetEnabledOriginTrials()) {
// Only allow force enabled origin trials on chrome:// and
// chrome-untrusted:// URLs.
const auto& scheme = origin_to_trial_names.first.scheme();
DCHECK(scheme == content::kChromeUIScheme ||
scheme == content::kChromeUIUntrustedScheme);
// TODO(crbug.com/40115403): Find some ways to validate supplied
// origin trial names. Ideally, construct them from some static const
// char*.
}
}
#endif // DCHECK_IS_ON()
previous_session_had_broken_icons_ =
pref_service_->GetBoolean(kSystemWebAppSessionHasBrokenIconsPrefName);
std::vector<web_app::ExternalInstallOptions> install_options_list;
const bool should_force_install_apps = ShouldForceInstallApps();
if (should_force_install_apps) {
UpdateLastAttemptedInfo();
}
const auto& disabled_system_apps =
provider_->policy_manager().GetDisabledSystemWebApps();
for (const auto& app : system_app_delegates_) {
bool is_disabled = base::Contains(disabled_system_apps, app.first);
install_options_list.push_back(CreateInstallOptionsForSystemApp(
app.first, *app.second, should_force_install_apps, is_disabled));
}
const bool exceeded_retries = CheckAndIncrementRetryAttempts();
if (exceeded_retries) {
LOG(ERROR)
<< "Exceeded SWA install retry attempts. Skipping installation, will "
"retry on next OS update or when locale changes.";
SCOPED_CRASH_KEY_BOOL("SystemWebAppManager", "broken_icons",
PreviousSessionHadBrokenIcons());
base::debug::DumpWithoutCrashing();
return;
}
// In tests, only install System Web Apps if `InstallSystemAppsForTesting()`
// or `SetSystemAppsForTesting()` has been called.
if (skip_app_installation_in_test_ &&
base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
install_options_list.clear();
}
provider_->externally_managed_app_manager().SynchronizeInstalledApps(
std::move(install_options_list),
web_app::ExternalInstallSource::kSystemInstalled,
base::BindOnce(&SystemWebAppManager::OnAppsSynchronized,
weak_ptr_factory_.GetWeakPtr(), should_force_install_apps,
install_start_time));
}
void SystemWebAppManager::Shutdown() {
shutting_down_ = true;
StopBackgroundTasks();
// Icon check might be in progress, destroying the icon_checker_ ensures that
// any pending callbacks are dropped.
icon_checker_.reset();
}
void SystemWebAppManager::InstallSystemAppsForTesting() {
{
// If system web app manager is still starting (explained below), we need
// to wait until it finishes before we can reset the states. This is to
// ensure the app synchronization request to web app system is complete
// before issuing another one in Start().
//
// In browser tests, SystemWebAppManager starts immediately after
// WebAppProvider is ready and calls SynchronizeInstalledApps.
//
// SynchronizeInstalledApps asynchronously handles the install request
// (awaits on web app system lock). If this operation isn't complete before
// test body starts, we'll issue a second SynchronizeInstalledApps request
// and violates the expectation (in web app system) that there can be only
// one in-flight synchronize request for each app install source.
//
// TODO(crbug.com/40250473): Ensure browsertests sets up system apps
// before SystemWebAppManager::Start(). Then, remove this method (or
// restrict its use to system web app feature test that needs to simulate
// system restart).
base::RunLoop run_loop;
on_apps_synchronized().Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
ResetForTesting(); // IN-TEST
skip_app_installation_in_test_ = false;
Start();
// Wait for the System Web Apps to install.
base::RunLoop run_loop;
on_apps_synchronized().Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
std::optional<webapps::AppId> SystemWebAppManager::GetAppIdForSystemApp(
SystemWebAppType type) const {
if (!provider_->is_registry_ready()) {
return std::nullopt;
}
return web_app::GetAppIdForSystemApp(provider_->registrar_unsafe(),
system_app_delegates_, type);
}
std::optional<SystemWebAppType> SystemWebAppManager::GetSystemAppTypeForAppId(
const webapps::AppId& app_id) const {
if (!provider_->is_registry_ready()) {
return std::nullopt;
}
return web_app::GetSystemAppTypeForAppId(provider_->registrar_unsafe(),
system_app_delegates_, app_id);
}
const SystemWebAppDelegate* SystemWebAppManager::GetSystemApp(
SystemWebAppType type) const {
return GetSystemWebApp(system_app_delegates_, type);
}
std::vector<webapps::AppId> SystemWebAppManager::GetAppIds() const {
std::vector<webapps::AppId> app_ids;
for (const auto& app_type_to_app_info : system_app_delegates_) {
std::optional<webapps::AppId> app_id =
GetAppIdForSystemApp(app_type_to_app_info.first);
if (app_id.has_value()) {
app_ids.push_back(app_id.value());
}
}
return app_ids;
}
bool SystemWebAppManager::IsSystemWebApp(const webapps::AppId& app_id) const {
DCHECK(provider_->is_registry_ready());
return web_app::IsSystemWebApp(provider_->registrar_unsafe(),
system_app_delegates_, app_id);
}
const std::vector<std::string>* SystemWebAppManager::GetEnabledOriginTrials(
const SystemWebAppDelegate* system_app,
const GURL& url) const {
DCHECK(system_app);
const auto& origin_to_origin_trials = system_app->GetEnabledOriginTrials();
auto iter_trials = origin_to_origin_trials.find(url::Origin::Create(url));
if (iter_trials == origin_to_origin_trials.end()) {
return nullptr;
}
return &iter_trials->second;
}
void SystemWebAppManager::OnReadyToCommitNavigation(
const webapps::AppId& app_id,
content::NavigationHandle* navigation_handle) {
// Perform tab-specific setup when a navigation in a System Web App is about
// to be committed.
if (!IsSystemWebApp(app_id)) {
return;
}
// No need to setup origin trials for intra-document navigation.
if (navigation_handle->IsSameDocument()) {
return;
}
const std::optional<SystemWebAppType> type = GetSystemAppTypeForAppId(app_id);
// This function should only be called when an navigation happens inside a
// System App. So the |app_id| should always have a valid associated System
// App type.
DCHECK(type.has_value());
auto* system_app = GetSystemApp(type.value());
DCHECK(system_app);
const std::vector<std::string>* trials =
GetEnabledOriginTrials(system_app, navigation_handle->GetURL());
if (trials) {
navigation_handle->ForceEnableOriginTrials(*trials);
}
}
std::optional<SystemWebAppType> SystemWebAppManager::GetSystemAppForURL(
const GURL& url) const {
if (!HasSystemWebAppScheme(url)) {
return std::nullopt;
}
if (!provider_->is_registry_ready()) {
return std::nullopt;
}
std::optional<webapps::AppId> app_id =
provider_->registrar_unsafe().FindAppWithUrlInScope(url);
if (!app_id.has_value()) {
return std::nullopt;
}
std::optional<SystemWebAppType> type =
GetSystemAppTypeForAppId(app_id.value());
if (!type.has_value()) {
return std::nullopt;
}
const SystemWebAppDelegate* delegate =
GetSystemWebApp(system_app_delegates_, type.value());
if (!delegate) {
return std::nullopt;
}
return type;
}
std::optional<SystemWebAppType>
SystemWebAppManager::GetCapturingSystemAppForURL(const GURL& url) const {
std::optional<SystemWebAppType> type = GetSystemAppForURL(url);
if (!type.has_value()) {
return std::nullopt;
}
const SystemWebAppDelegate* delegate =
GetSystemWebApp(system_app_delegates_, type.value());
if (!delegate->ShouldCaptureNavigations()) {
return std::nullopt;
}
// TODO(crbug://1051229): Expand ShouldCaptureNavigation to take a GURL, and
// move this into the camera one.
if (type == SystemWebAppType::CAMERA) {
GURL::Replacements replacements;
replacements.ClearQuery();
replacements.ClearRef();
if (url.ReplaceComponents(replacements).spec() !=
kChromeUICameraAppMainURL) {
return std::nullopt;
}
}
return type;
}
void SystemWebAppManager::OnWebAppUiManagerDestroyed() {
ui_manager_observation_.Reset();
}
void SystemWebAppManager::SetSystemAppsForTesting(
SystemWebAppDelegateMap system_apps) {
skip_app_installation_in_test_ = false;
system_app_delegates_ = std::move(system_apps);
}
const std::vector<std::unique_ptr<SystemWebAppBackgroundTask>>&
SystemWebAppManager::GetBackgroundTasksForTesting() {
return tasks_;
}
void SystemWebAppManager::SetUpdatePolicyForTesting(UpdatePolicy policy) {
update_policy_ = policy;
}
void SystemWebAppManager::ResetForTesting() {
CHECK_IS_TEST();
StopBackgroundTasks();
tasks_.clear();
icon_checker_ = SystemWebAppIconChecker::Create(profile_);
on_apps_synchronized_ = std::make_unique<base::OneShotEvent>();
on_icon_check_completed_ = std::make_unique<base::OneShotEvent>();
on_tasks_started_ = std::make_unique<base::OneShotEvent>();
}
const base::Version& SystemWebAppManager::CurrentVersion() const {
return version_info::GetVersion();
}
const std::string& SystemWebAppManager::CurrentLocale() const {
return g_browser_process->GetApplicationLocale();
}
bool SystemWebAppManager::PreviousSessionHadBrokenIcons() const {
return previous_session_had_broken_icons_;
}
void SystemWebAppManager::RecordSystemWebAppInstallDuration(
const base::TimeDelta& install_duration) const {
// Install duration should be non-negative. A low resolution clock could
// result in a |install_duration| of 0.
DCHECK_GE(install_duration.InMilliseconds(), 0);
if (!shutting_down_) {
base::UmaHistogramMediumTimes(kInstallDurationHistogramName,
install_duration);
}
}
void SystemWebAppManager::RecordSystemWebAppInstallResults(
const std::map<GURL, web_app::ExternallyManagedAppManager::InstallResult>&
install_results) const {
// Report install result codes. Exclude kSuccessAlreadyInstalled from metrics.
// This result means the installation pipeline is a no-op (which happens every
// time user logs in, and if there hasn't been a version upgrade). This skews
// the install success rate.
std::map<GURL, web_app::ExternallyManagedAppManager::InstallResult>
results_to_report;
base::ranges::copy_if(
install_results,
std::inserter(results_to_report, results_to_report.end()),
[](const auto& url_and_result) {
return url_and_result.second.code !=
webapps::InstallResultCode::kSuccessAlreadyInstalled;
});
for (const auto& url_and_result : results_to_report) {
// Record aggregate result.
base::UmaHistogramEnumeration(
kInstallResultHistogramName,
shutting_down_
? webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown
: url_and_result.second.code);
// Record per-profile result.
base::UmaHistogramEnumeration(
install_result_per_profile_histogram_name_,
shutting_down_
? webapps::InstallResultCode::kCancelledOnWebAppProviderShuttingDown
: url_and_result.second.code);
}
// Record per-app result.
for (const auto& type_and_app_info : system_app_delegates_) {
const GURL& install_url = type_and_app_info.second->GetInstallUrl();
const auto url_and_result = results_to_report.find(install_url);
if (url_and_result != results_to_report.cend()) {
const std::string app_histogram_name =
std::string(kInstallResultHistogramName) + ".Apps." +
type_and_app_info.second->GetInternalName();
base::UmaHistogramEnumeration(
app_histogram_name, shutting_down_
? webapps::InstallResultCode::
kCancelledOnWebAppProviderShuttingDown
: url_and_result->second.code);
}
}
}
void SystemWebAppManager::OnAppsSynchronized(
bool did_force_install_apps,
const base::TimeTicks& install_start_time,
std::map<GURL, web_app::ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode> uninstall_results) {
const base::TimeDelta install_duration =
base::TimeTicks::Now() - install_start_time;
// TODO(qjw): Figure out where install_results come from, decide if
// installation failures need to be handled
pref_service_->SetString(prefs::kSystemWebAppLastUpdateVersion,
CurrentVersion().GetString());
pref_service_->SetString(prefs::kSystemWebAppLastInstalledLocale,
CurrentLocale());
// Report install duration only if the install pipeline actually installs
// all the apps (e.g. on version upgrade).
if (did_force_install_apps) {
RecordSystemWebAppInstallDuration(install_duration);
}
RecordSystemWebAppInstallResults(install_results);
for (const auto& it : system_app_delegates_) {
std::optional<SystemWebAppBackgroundTaskInfo> background_info =
it.second->GetTimerInfo();
if (background_info && it.second->IsAppEnabled()) {
tasks_.push_back(std::make_unique<SystemWebAppBackgroundTask>(
profile_, background_info.value()));
}
}
// May be called more than once in tests.
if (!on_apps_synchronized_->is_signaled()) {
on_apps_synchronized_->Signal();
DCHECK(provider_->is_registry_ready());
provider_->policy_manager().OnDisableListPolicyChanged();
// TODO(http://crbug/1173187): Don't create SWA background tasks that are
// associated with a disabled SWA.
}
if (!shutting_down_) {
// Start an icon health check.
icon_checker_->StartCheck(
GetAppIds(), base::BindOnce(&SystemWebAppManager::OnIconCheckResult,
weak_ptr_factory_.GetWeakPtr()));
}
// Start the tasks async to give any code running in an on_app_synchronized
// context a chance to finish first.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&SystemWebAppManager::StartBackgroundTasks,
weak_ptr_factory_.GetWeakPtr()));
}
void SystemWebAppManager::StartBackgroundTasks() const {
for (const auto& task : tasks_) {
task->StartTask();
}
// This happens as part of synchronize, and can also be called multiple times
// in testing.
if (!on_tasks_started_->is_signaled()) {
on_tasks_started_->Signal();
}
}
void SystemWebAppManager::OnIconCheckResult(
SystemWebAppIconChecker::IconState result) {
switch (result) {
case SystemWebAppIconChecker::IconState::kNoAppInstalled:
break;
case SystemWebAppIconChecker::IconState::kBroken:
base::UmaHistogramBoolean(kIconHealthMetricName, false);
if (PreviousSessionHadBrokenIcons()) {
base::UmaHistogramBoolean(kIconsFixedOnReinstallMetricName, false);
}
pref_service_->SetBoolean(kSystemWebAppSessionHasBrokenIconsPrefName,
true);
break;
case SystemWebAppIconChecker::IconState::kOk:
base::UmaHistogramBoolean(kIconHealthMetricName, true);
if (PreviousSessionHadBrokenIcons()) {
base::UmaHistogramBoolean(kIconsFixedOnReinstallMetricName, true);
}
pref_service_->ClearPref(kSystemWebAppSessionHasBrokenIconsPrefName);
pref_service_->ClearPref(prefs::kSystemWebAppInstallFailureCount);
break;
}
// Might get signaled multiple times in tests.
if (!on_icon_check_completed_->is_signaled()) {
on_icon_check_completed_->Signal();
}
}
bool SystemWebAppManager::ShouldForceInstallApps() const {
if (base::FeatureList::IsEnabled(features::kAlwaysReinstallSystemWebApps)) {
return true;
}
if (update_policy_ == UpdatePolicy::kAlwaysUpdate) {
return true;
}
if (PreviousSessionHadBrokenIcons()) {
return true;
}
base::Version current_installed_version(
pref_service_->GetString(prefs::kSystemWebAppLastUpdateVersion));
const std::string& current_installed_locale(
pref_service_->GetString(prefs::kSystemWebAppLastInstalledLocale));
// If Chrome version rolls back for some reason, ensure System Web Apps are
// always in sync with Chrome version.
const bool versionIsDifferent = !current_installed_version.IsValid() ||
current_installed_version != CurrentVersion();
// If system language changes, ensure System Web Apps launcher localization
// are in sync with current language.
const bool localeIsDifferent = current_installed_locale != CurrentLocale();
return versionIsDifferent || localeIsDifferent;
}
void SystemWebAppManager::UpdateLastAttemptedInfo() {
base::Version last_attempted_version(
pref_service_->GetString(prefs::kSystemWebAppLastAttemptedVersion));
const std::string& last_attempted_locale(
pref_service_->GetString(prefs::kSystemWebAppLastAttemptedLocale));
const bool is_retry = last_attempted_version.IsValid() &&
last_attempted_version == CurrentVersion() &&
last_attempted_locale == CurrentLocale();
if (!is_retry) {
pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount, 0);
}
pref_service_->SetString(prefs::kSystemWebAppLastAttemptedVersion,
CurrentVersion().GetString());
pref_service_->SetString(prefs::kSystemWebAppLastAttemptedLocale,
CurrentLocale());
pref_service_->CommitPendingWrite();
}
bool SystemWebAppManager::CheckAndIncrementRetryAttempts() {
int installation_failures =
pref_service_->GetInteger(prefs::kSystemWebAppInstallFailureCount);
bool reached_retry_limit = installation_failures > kInstallFailureAttempts;
if (!reached_retry_limit) {
pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount,
installation_failures + 1);
pref_service_->CommitPendingWrite();
return false;
}
return true;
}
void SystemWebAppManager::ConnectProviderToSystemWebAppDelegateMap(
const SystemWebAppDelegateMap* system_web_apps_delegate_map) const {
// TODO(crbug.com/40243506): Consider DCHECKing that provider_ is ready.
provider_->manifest_update_manager().SetSystemWebAppDelegateMap(
system_web_apps_delegate_map);
provider_->policy_manager().SetSystemWebAppDelegateMap(
system_web_apps_delegate_map);
}
} // namespace ash