| // Copyright 2018 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/web_applications/system_web_app_manager.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/version.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/components/app_registrar.h" |
| #include "chrome/browser/web_applications/components/app_registry_controller.h" |
| #include "chrome/browser/web_applications/components/external_install_options.h" |
| #include "chrome/browser/web_applications/components/os_integration_manager.h" |
| #include "chrome/browser/web_applications/components/web_app_constants.h" |
| #include "chrome/browser/web_applications/components/web_app_install_utils.h" |
| #include "chrome/browser/web_applications/components/web_app_ui_manager.h" |
| #include "chrome/browser/web_applications/components/web_app_utils.h" |
| #include "chrome/browser/web_applications/components/web_application_info.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/user_manager/user_manager.h" |
| #include "components/version_info/version_info.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" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/public/cpp/app_list/internal_app_id_constants.h" |
| #include "base/values.h" |
| #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h" |
| #include "chrome/browser/chromeos/web_applications/camera_system_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/connectivity_diagnostics_system_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/diagnostics_system_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/help_app_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/media_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/os_settings_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/print_management_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/scanning_system_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/terminal_source.h" |
| #include "chrome/browser/chromeos/web_applications/terminal_system_web_app_info.h" |
| #include "chrome/browser/web_applications/components/web_app_id_constants.h" |
| #include "chromeos/components/camera_app_ui/url_constants.h" |
| #include "chromeos/components/connectivity_diagnostics/url_constants.h" |
| #include "chromeos/components/help_app_ui/url_constants.h" |
| #include "chromeos/components/media_app_ui/url_constants.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/constants/chromeos_pref_names.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "extensions/common/constants.h" |
| |
| #if !defined(OFFICIAL_BUILD) |
| #include "chrome/browser/chromeos/web_applications/file_manager_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/sample_system_web_app_info.h" |
| #include "chrome/browser/chromeos/web_applications/telemetry_extension_web_app_info.h" |
| #endif // !defined(OFFICIAL_BUILD) |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| namespace web_app { |
| |
| namespace { |
| |
| // Copy the origin trial name from runtime_enabled_features.json5, to avoid |
| // complex dependencies. |
| const char kFileHandlingOriginTrial[] = "FileHandling"; |
| |
| // Number of attempts to install a given version & locale of the SWAs before |
| // bailing out. |
| const int kInstallFailureAttempts = 3; |
| |
| // Use #if defined to avoid compiler error on unused function. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // A convenience method to create OriginTrialsMap. Note, we only support simple |
| // cases for chrome:// and chrome-untrusted:// URLs. We don't support complex |
| // cases such as about:blank (which inherits origins from the embedding frame). |
| url::Origin GetOrigin(const char* url) { |
| GURL gurl = GURL(url); |
| DCHECK(gurl.is_valid()); |
| |
| url::Origin origin = url::Origin::Create(gurl); |
| DCHECK(!origin.opaque()); |
| |
| return origin; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps( |
| Profile* profile) { |
| base::flat_map<SystemAppType, SystemAppInfo> infos; |
| // TODO(calamity): Split this into per-platform functions. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // SystemAppInfo's |name| field should be defined. These names are persisted |
| // to logs and should not be renamed. |
| // If new names are added, update tool/metrics/histograms/histograms.xml: |
| // "SystemWebAppName" |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::CAMERA)) { |
| infos.emplace( |
| SystemAppType::CAMERA, |
| SystemAppInfo( |
| "Camera", GURL("chrome://camera-app/views/main.html"), |
| base::BindRepeating(&CreateWebAppInfoForCameraSystemWebApp))); |
| if (!profile->GetPrefs()->GetBoolean( |
| chromeos::prefs::kHasCameraAppMigratedToSWA)) { |
| infos.at(SystemAppType::CAMERA).uninstall_and_replace = { |
| extension_misc::kCameraAppId}; |
| } |
| // We need "FileHandling" to use File Handling API to set launch directory. |
| // And we need "NativeFileSystem2" to use Native File System API. |
| infos.at(SystemAppType::CAMERA).enabled_origin_trials = |
| OriginTrialsMap({{GetOrigin("chrome://camera-app"), |
| {"FileHandling", "NativeFileSystem2"}}}); |
| infos.at(SystemAppType::CAMERA).capture_navigations = true; |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::DIAGNOSTICS)) { |
| infos.emplace( |
| SystemAppType::DIAGNOSTICS, |
| SystemAppInfo( |
| "Diagnostics", GURL("chrome://diagnostics"), |
| base::BindRepeating(&CreateWebAppInfoForDiagnosticsSystemWebApp))); |
| } |
| |
| infos.emplace(SystemAppType::SETTINGS, |
| SystemAppInfo("OSSettings", GURL(chrome::kChromeUISettingsURL), |
| base::BindRepeating( |
| &CreateWebAppInfoForOSSettingsSystemWebApp))); |
| infos.at(SystemAppType::SETTINGS).uninstall_and_replace = { |
| web_app::kSettingsAppId, ash::kInternalAppIdSettings}; |
| // Large enough to see the heading text "Settings" in the top-left. |
| infos.at(SystemAppType::SETTINGS).minimum_window_size = {300, 100}; |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::TERMINAL)) { |
| infos.emplace( |
| SystemAppType::TERMINAL, |
| SystemAppInfo( |
| "Terminal", GURL(chrome::kChromeUIUntrustedTerminalURL), |
| base::BindRepeating(&CreateWebAppInfoForTerminalSystemWebApp))); |
| infos.at(SystemAppType::TERMINAL).single_window = false; |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::HELP)) { |
| infos.emplace( |
| SystemAppType::HELP, |
| SystemAppInfo("Help", GURL("chrome://help-app/pwa.html"), |
| base::BindRepeating(&CreateWebAppInfoForHelpWebApp))); |
| infos.at(SystemAppType::HELP).additional_search_terms = { |
| IDS_GENIUS_APP_NAME, IDS_HELP_APP_PERKS, IDS_HELP_APP_OFFERS}; |
| infos.at(SystemAppType::HELP).minimum_window_size = {600, 320}; |
| infos.at(SystemAppType::HELP).capture_navigations = true; |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::MEDIA)) { |
| infos.emplace( |
| SystemAppType::MEDIA, |
| SystemAppInfo("Media", GURL("chrome://media-app/pwa.html"), |
| base::BindRepeating(&CreateWebAppInfoForMediaWebApp))); |
| infos.at(SystemAppType::MEDIA).include_launch_directory = true; |
| infos.at(SystemAppType::MEDIA).show_in_launcher = false; |
| infos.at(SystemAppType::MEDIA).show_in_search = false; |
| infos.at(SystemAppType::MEDIA).enabled_origin_trials = |
| OriginTrialsMap({{GetOrigin("chrome://media-app"), |
| {"FileHandling", "NativeFileSystem2"}}}); |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::PRINT_MANAGEMENT)) { |
| infos.emplace( |
| SystemAppType::PRINT_MANAGEMENT, |
| SystemAppInfo( |
| "PrintManagement", GURL("chrome://print-management/pwa.html"), |
| base::BindRepeating(&CreateWebAppInfoForPrintManagementApp))); |
| infos.at(SystemAppType::PRINT_MANAGEMENT).show_in_launcher = false; |
| infos.at(SystemAppType::PRINT_MANAGEMENT).minimum_window_size = {600, 320}; |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::SCANNING)) { |
| infos.emplace(SystemAppType::SCANNING, |
| SystemAppInfo("Scanning", GURL("chrome://scanning"), |
| base::BindRepeating( |
| &CreateWebAppInfoForScanningSystemWebApp))); |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled( |
| SystemAppType::CONNECTIVITY_DIAGNOSTICS)) { |
| infos.emplace( |
| SystemAppType::CONNECTIVITY_DIAGNOSTICS, |
| SystemAppInfo( |
| "ConnectivityDiagnostics", |
| GURL(chromeos::kChromeUIConnectivityDiagnosticsUrl), |
| base::BindRepeating( |
| &CreateWebAppInfoForConnectivityDiagnosticsSystemWebApp))); |
| } |
| |
| #if !defined(OFFICIAL_BUILD) |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::TELEMETRY)) { |
| infos.emplace( |
| SystemAppType::TELEMETRY, |
| SystemAppInfo( |
| "Telemetry", GURL("chrome://telemetry-extension"), |
| base::BindRepeating(&CreateWebAppInfoForTelemetryExtension))); |
| } |
| |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::FILE_MANAGER)) { |
| infos.emplace( |
| SystemAppType::FILE_MANAGER, |
| SystemAppInfo("File Manager", GURL("chrome://file-manager"), |
| base::BindRepeating(&CreateWebAppInfoForFileManager))); |
| infos.at(SystemAppType::FILE_MANAGER).capture_navigations = true; |
| infos.at(SystemAppType::FILE_MANAGER).single_window = false; |
| } |
| |
| infos.emplace( |
| SystemAppType::SAMPLE, |
| SystemAppInfo( |
| "Sample", GURL("chrome://sample-system-web-app/pwa.html"), |
| base::BindRepeating(&CreateWebAppInfoForSampleSystemWebApp))); |
| // Frobulate is the name for Sample Origin Trial API, and has no impact on the |
| // Web App's functionality. Here we use it to demonstrate how to enable origin |
| // trials for a System Web App. |
| infos.at(SystemAppType::SAMPLE).enabled_origin_trials = OriginTrialsMap( |
| {{GetOrigin("chrome://sample-system-web-app"), {"Frobulate"}}, |
| {GetOrigin("chrome-untrusted://sample-system-web-app"), {"Frobulate"}}}); |
| infos.at(SystemAppType::SAMPLE).capture_navigations = true; |
| #endif // !defined(OFFICIAL_BUILD) |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| return infos; |
| } |
| |
| bool HasSystemWebAppScheme(const GURL& url) { |
| return url.SchemeIs(content::kChromeUIScheme) || |
| url.SchemeIs(content::kChromeUIUntrustedScheme); |
| } |
| |
| ExternalInstallOptions CreateInstallOptionsForSystemApp( |
| const SystemAppInfo& info, |
| bool force_update, |
| bool is_disabled) { |
| DCHECK(info.install_url.scheme() == content::kChromeUIScheme || |
| info.install_url.scheme() == content::kChromeUIUntrustedScheme); |
| |
| ExternalInstallOptions install_options( |
| info.install_url, DisplayMode::kStandalone, |
| ExternalInstallSource::kSystemInstalled); |
| install_options.only_use_app_info_factory = !!info.app_info_factory; |
| install_options.app_info_factory = info.app_info_factory; |
| install_options.add_to_applications_menu = info.show_in_launcher; |
| install_options.add_to_desktop = false; |
| install_options.add_to_quick_launch_bar = false; |
| install_options.add_to_search = info.show_in_search; |
| install_options.add_to_management = false; |
| install_options.is_disabled = is_disabled; |
| install_options.bypass_service_worker_check = true; |
| install_options.force_reinstall = force_update; |
| install_options.uninstall_and_replace = info.uninstall_and_replace; |
| |
| const auto& search_terms = info.additional_search_terms; |
| std::transform(search_terms.begin(), search_terms.end(), |
| std::back_inserter(install_options.additional_search_terms), |
| [](int term) { return l10n_util::GetStringUTF8(term); }); |
| return install_options; |
| } |
| |
| std::set<SystemAppType> GetDisabledSystemWebApps() { |
| std::set<SystemAppType> disabled_system_apps; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| PrefService* const local_state = g_browser_process->local_state(); |
| if (!local_state) // Sometimes it's not available in tests. |
| return disabled_system_apps; |
| |
| const base::ListValue* disabled_system_features_pref = |
| local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList); |
| if (!disabled_system_features_pref) |
| return disabled_system_apps; |
| |
| for (const auto& entry : *disabled_system_features_pref) { |
| switch (entry.GetInt()) { |
| case policy::SystemFeature::CAMERA: |
| disabled_system_apps.insert(SystemAppType::CAMERA); |
| break; |
| case policy::SystemFeature::OS_SETTINGS: |
| disabled_system_apps.insert(SystemAppType::SETTINGS); |
| break; |
| case policy::SystemFeature::SCANNING: |
| disabled_system_apps.insert(SystemAppType::SCANNING); |
| break; |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| return disabled_system_apps; |
| } |
| |
| } // namespace |
| |
| SystemAppInfo::SystemAppInfo(const std::string& internal_name, |
| const GURL& install_url) |
| : internal_name(internal_name), install_url(install_url) {} |
| |
| SystemAppInfo::SystemAppInfo(const std::string& internal_name, |
| const GURL& install_url, |
| const WebApplicationInfoFactory& app_info_factory) |
| : internal_name(internal_name), |
| install_url(install_url), |
| app_info_factory(app_info_factory) {} |
| |
| SystemAppInfo::SystemAppInfo(const SystemAppInfo& other) = default; |
| |
| SystemAppInfo::~SystemAppInfo() = default; |
| |
| // static |
| const char SystemWebAppManager::kInstallResultHistogramName[]; |
| const char SystemWebAppManager::kInstallDurationHistogramName[]; |
| |
| // static |
| bool SystemWebAppManager::IsAppEnabled(SystemAppType type) { |
| if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps)) |
| return true; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| switch (type) { |
| case SystemAppType::SETTINGS: |
| return true; |
| case SystemAppType::CAMERA: |
| return base::FeatureList::IsEnabled( |
| chromeos::features::kCameraSystemWebApp) && |
| !user_manager::UserManager::Get()->IsLoggedInAsGuest(); |
| case SystemAppType::TERMINAL: |
| return true; |
| case SystemAppType::MEDIA: |
| return base::FeatureList::IsEnabled(chromeos::features::kMediaApp); |
| case SystemAppType::HELP: |
| return true; |
| case SystemAppType::PRINT_MANAGEMENT: |
| return base::FeatureList::IsEnabled( |
| chromeos::features::kPrintJobManagementApp); |
| case SystemAppType::SCANNING: |
| return base::FeatureList::IsEnabled(chromeos::features::kScanningUI); |
| case SystemAppType::DIAGNOSTICS: |
| return base::FeatureList::IsEnabled(chromeos::features::kDiagnosticsApp); |
| case SystemAppType::CONNECTIVITY_DIAGNOSTICS: |
| return base::FeatureList::IsEnabled( |
| chromeos::features::kConnectivityDiagnosticsWebUi); |
| #if !defined(OFFICIAL_BUILD) |
| case SystemAppType::TELEMETRY: |
| return base::FeatureList::IsEnabled( |
| chromeos::features::kTelemetryExtension); |
| case SystemAppType::FILE_MANAGER: |
| return base::FeatureList::IsEnabled(chromeos::features::kFilesSWA); |
| case SystemAppType::SAMPLE: |
| NOTREACHED(); |
| return false; |
| #endif // !defined(OFFICIAL_BUILD) |
| } |
| #else |
| return false; |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| SystemWebAppManager::SystemWebAppManager(Profile* profile) |
| : profile_(profile), |
| on_apps_synchronized_(new base::OneShotEvent()), |
| install_result_per_profile_histogram_name_( |
| std::string(kInstallResultHistogramName) + ".Profiles." + |
| GetProfileCategoryForLogging(profile)), |
| pref_service_(profile_->GetPrefs()) { |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) { |
| // Always update in tests. |
| update_policy_ = UpdatePolicy::kAlwaysUpdate; |
| |
| // Populate with real system apps if the test asks for it. |
| if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps)) |
| system_app_infos_ = CreateSystemWebApps(profile_); |
| |
| return; |
| } |
| |
| #if defined(OFFICIAL_BUILD) |
| // Official builds should trigger updates whenever the version number changes. |
| update_policy_ = UpdatePolicy::kOnVersionChange; |
| #else |
| // Dev builds should update every launch. |
| update_policy_ = UpdatePolicy::kAlwaysUpdate; |
| #endif |
| |
| system_app_infos_ = CreateSystemWebApps(profile_); |
| } |
| |
| SystemWebAppManager::~SystemWebAppManager() = default; |
| |
| void SystemWebAppManager::Shutdown() { |
| shutting_down_ = true; |
| } |
| |
| void SystemWebAppManager::SetSubsystems( |
| PendingAppManager* pending_app_manager, |
| AppRegistrar* registrar, |
| AppRegistryController* registry_controller, |
| WebAppUiManager* ui_manager, |
| OsIntegrationManager* os_integration_manager) { |
| pending_app_manager_ = pending_app_manager; |
| registrar_ = registrar; |
| registry_controller_ = registry_controller; |
| ui_manager_ = ui_manager; |
| os_integration_manager_ = os_integration_manager; |
| } |
| |
| void SystemWebAppManager::Start() { |
| 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_infos_) { |
| for (const auto& origin_to_trial_names : |
| type_and_app_info.second.enabled_origin_trials) { |
| // 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(https://crbug.com/1043843): Find some ways to validate supplied origin |
| // trial names. Ideally, construct them from some static const char*. |
| #endif // DCHECK_IS_ON() |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // Set up terminal data source. Terminal source is needed for install. |
| // TODO(crbug.com/1080384): Move once chrome-untrusted has WebUIControllers. |
| if (SystemWebAppManager::IsAppEnabled(SystemAppType::TERMINAL)) { |
| content::URLDataSource::Add(profile_, |
| TerminalSource::ForTerminal(profile_)); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| std::vector<ExternalInstallOptions> install_options_list; |
| const bool should_force_install_apps = ShouldForceInstallApps(); |
| if (should_force_install_apps) { |
| UpdateLastAttemptedInfo(); |
| } |
| |
| const auto disabled_system_apps = GetDisabledSystemWebApps(); |
| |
| for (const auto& app : system_app_infos_) { |
| install_options_list.push_back(CreateInstallOptionsForSystemApp( |
| app.second, should_force_install_apps, |
| base::Contains(disabled_system_apps, app.first))); |
| } |
| |
| const bool exceeded_retries = CheckAndIncrementRetryAttempts(); |
| if (!exceeded_retries) { |
| pending_app_manager_->SynchronizeInstalledApps( |
| std::move(install_options_list), |
| ExternalInstallSource::kSystemInstalled, |
| base::BindOnce(&SystemWebAppManager::OnAppsSynchronized, |
| weak_ptr_factory_.GetWeakPtr(), |
| should_force_install_apps, install_start_time)); |
| } |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| PrefService* const local_state = g_browser_process->local_state(); |
| if (local_state) { // Sometimes it's not available in tests. |
| local_state_pref_change_registrar_.Init(local_state); |
| |
| // Sometimes this function gets called twice in tests. |
| if (!local_state_pref_change_registrar_.IsObserved( |
| policy::policy_prefs::kSystemFeaturesDisableList)) { |
| local_state_pref_change_registrar_.Add( |
| policy::policy_prefs::kSystemFeaturesDisableList, |
| base::Bind(&SystemWebAppManager::OnAppsPolicyChanged, |
| base::Unretained(this))); |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| void SystemWebAppManager::InstallSystemAppsForTesting() { |
| on_apps_synchronized_.reset(new base::OneShotEvent()); |
| system_app_infos_ = CreateSystemWebApps(profile_); |
| 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(); |
| } |
| |
| const base::flat_map<SystemAppType, SystemAppInfo>& |
| SystemWebAppManager::GetRegisteredSystemAppsForTesting() const { |
| return system_app_infos_; |
| } |
| |
| base::Optional<AppId> SystemWebAppManager::GetAppIdForSystemApp( |
| SystemAppType id) const { |
| auto app_url_it = system_app_infos_.find(id); |
| |
| if (app_url_it == system_app_infos_.end()) |
| return base::Optional<AppId>(); |
| |
| return registrar_->LookupExternalAppId(app_url_it->second.install_url); |
| } |
| |
| base::Optional<SystemAppType> SystemWebAppManager::GetSystemAppTypeForAppId( |
| AppId app_id) const { |
| auto it = app_id_to_app_type_.find(app_id); |
| if (it == app_id_to_app_type_.end()) |
| return base::nullopt; |
| |
| return it->second; |
| } |
| |
| std::vector<AppId> SystemWebAppManager::GetAppIds() const { |
| std::vector<AppId> app_ids; |
| for (const auto& app_id_to_app_type : app_id_to_app_type_) { |
| app_ids.push_back(app_id_to_app_type.first); |
| } |
| return app_ids; |
| } |
| |
| bool SystemWebAppManager::IsSystemWebApp(const AppId& app_id) const { |
| return app_id_to_app_type_.contains(app_id); |
| } |
| |
| bool SystemWebAppManager::IsSingleWindow(SystemAppType type) const { |
| auto it = system_app_infos_.find(type); |
| if (it == system_app_infos_.end()) |
| return false; |
| |
| return it->second.single_window; |
| } |
| |
| bool SystemWebAppManager::AppShouldReceiveLaunchDirectory( |
| SystemAppType type) const { |
| auto it = system_app_infos_.find(type); |
| if (it == system_app_infos_.end()) |
| return false; |
| return it->second.include_launch_directory; |
| } |
| |
| const std::vector<std::string>* SystemWebAppManager::GetEnabledOriginTrials( |
| const SystemAppType type, |
| const GURL& url) { |
| const auto& origin_to_origin_trials = |
| system_app_infos_.at(type).enabled_origin_trials; |
| 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; |
| } |
| |
| bool SystemWebAppManager::AppHasFileHandlingOriginTrial(SystemAppType type) { |
| const auto& info = system_app_infos_.at(type); |
| const std::vector<std::string>* trials = |
| GetEnabledOriginTrials(type, info.install_url); |
| return trials && base::Contains(*trials, kFileHandlingOriginTrial); |
| } |
| |
| void SystemWebAppManager::OnReadyToCommitNavigation( |
| const AppId& app_id, |
| content::NavigationHandle* navigation_handle) { |
| // No need to setup origin trials for intra-document navigation. |
| if (navigation_handle->IsSameDocument()) |
| return; |
| |
| const base::Optional<SystemAppType> 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()); |
| |
| const std::vector<std::string>* trials = |
| GetEnabledOriginTrials(type.value(), navigation_handle->GetURL()); |
| if (trials) |
| navigation_handle->ForceEnableOriginTrials(*trials); |
| } |
| |
| std::vector<std::string> SystemWebAppManager::GetAdditionalSearchTerms( |
| SystemAppType type) const { |
| auto it = system_app_infos_.find(type); |
| if (it == system_app_infos_.end()) |
| return {}; |
| |
| const auto& search_terms = it->second.additional_search_terms; |
| |
| std::vector<std::string> search_terms_strings; |
| std::transform(search_terms.begin(), search_terms.end(), |
| std::back_inserter(search_terms_strings), |
| [](int term) { return l10n_util::GetStringUTF8(term); }); |
| return search_terms_strings; |
| } |
| |
| bool SystemWebAppManager::ShouldShowInLauncher(SystemAppType type) const { |
| auto it = system_app_infos_.find(type); |
| if (it == system_app_infos_.end()) |
| return false; |
| return it->second.show_in_launcher; |
| } |
| |
| bool SystemWebAppManager::ShouldShowInSearch(SystemAppType type) const { |
| auto it = system_app_infos_.find(type); |
| if (it == system_app_infos_.end()) |
| return false; |
| return it->second.show_in_search; |
| } |
| |
| base::Optional<SystemAppType> SystemWebAppManager::GetCapturingSystemAppForURL( |
| const GURL& url) const { |
| if (!HasSystemWebAppScheme(url)) |
| return base::nullopt; |
| |
| base::Optional<AppId> app_id = registrar_->FindAppWithUrlInScope(url); |
| if (!app_id.has_value()) |
| return base::nullopt; |
| |
| base::Optional<SystemAppType> type = GetSystemAppTypeForAppId(app_id.value()); |
| if (!type.has_value()) |
| return base::nullopt; |
| |
| const auto it = system_app_infos_.find(type); |
| if (it == system_app_infos_.end()) |
| return base::nullopt; |
| |
| if (!it->second.capture_navigations) |
| return base::nullopt; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (type == SystemAppType::CAMERA && |
| url.spec() != chromeos::kChromeUICameraAppMainURL) |
| return base::nullopt; |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| return type; |
| } |
| |
| gfx::Size SystemWebAppManager::GetMinimumWindowSize(const AppId& app_id) const { |
| auto app_type_it = app_id_to_app_type_.find(app_id); |
| if (app_type_it == app_id_to_app_type_.end()) |
| return gfx::Size(); |
| const SystemAppType& app_type = app_type_it->second; |
| auto app_info_it = system_app_infos_.find(app_type); |
| if (app_info_it == system_app_infos_.end()) |
| return gfx::Size(); |
| return app_info_it->second.minimum_window_size; |
| } |
| |
| void SystemWebAppManager::SetSystemAppsForTesting( |
| base::flat_map<SystemAppType, SystemAppInfo> system_apps) { |
| system_app_infos_ = std::move(system_apps); |
| } |
| |
| void SystemWebAppManager::SetUpdatePolicyForTesting(UpdatePolicy policy) { |
| update_policy_ = policy; |
| } |
| |
| void SystemWebAppManager::ResetOnAppsSynchronizedForTesting() { |
| on_apps_synchronized_ = std::make_unique<base::OneShotEvent>(); |
| } |
| |
| // static |
| void SystemWebAppManager::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterStringPref(prefs::kSystemWebAppLastUpdateVersion, ""); |
| registry->RegisterStringPref(prefs::kSystemWebAppLastInstalledLocale, ""); |
| registry->RegisterStringPref(prefs::kSystemWebAppLastAttemptedVersion, ""); |
| registry->RegisterStringPref(prefs::kSystemWebAppLastAttemptedLocale, ""); |
| registry->RegisterIntegerPref(prefs::kSystemWebAppInstallFailureCount, 0); |
| } |
| |
| const base::Version& SystemWebAppManager::CurrentVersion() const { |
| return version_info::GetVersion(); |
| } |
| |
| const std::string& SystemWebAppManager::CurrentLocale() const { |
| return g_browser_process->GetApplicationLocale(); |
| } |
| |
| 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, InstallResultCode>& 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, InstallResultCode> results_to_report; |
| std::copy_if(install_results.begin(), install_results.end(), |
| std::inserter(results_to_report, results_to_report.end()), |
| [](const auto& url_and_result) { |
| return url_and_result.second != |
| InstallResultCode::kSuccessAlreadyInstalled; |
| }); |
| |
| for (const auto& url_and_result : results_to_report) { |
| // Record aggregate result. |
| base::UmaHistogramEnumeration( |
| kInstallResultHistogramName, |
| shutting_down_ |
| ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown |
| : url_and_result.second); |
| |
| // Record per-profile result. |
| base::UmaHistogramEnumeration( |
| install_result_per_profile_histogram_name_, |
| shutting_down_ |
| ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown |
| : url_and_result.second); |
| } |
| |
| // Record per-app result. |
| for (const auto& type_and_app_info : system_app_infos_) { |
| const GURL& install_url = type_and_app_info.second.install_url; |
| 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.internal_name; |
| base::UmaHistogramEnumeration( |
| app_histogram_name, |
| shutting_down_ |
| ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown |
| : url_and_result->second); |
| } |
| } |
| } |
| |
| void SystemWebAppManager::OnAppsSynchronized( |
| bool did_force_install_apps, |
| const base::TimeTicks& install_start_time, |
| std::map<GURL, InstallResultCode> install_results, |
| std::map<GURL, bool> uninstall_results) { |
| // TODO(crbug.com/1053371): Clean up File Handler install. We install SWA file |
| // handlers here, because the code that registers file handlers for regular |
| // Web Apps, does not run when for apps installed in the background. |
| for (const auto& it : system_app_infos_) { |
| const SystemAppType& type = it.first; |
| base::Optional<AppId> app_id = GetAppIdForSystemApp(type); |
| if (!app_id) |
| continue; |
| |
| if (AppHasFileHandlingOriginTrial(type)) { |
| os_integration_manager_->ForceEnableFileHandlingOriginTrial( |
| app_id.value()); |
| } else { |
| os_integration_manager_->DisableForceEnabledFileHandlingOriginTrial( |
| app_id.value()); |
| } |
| } |
| |
| 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()); |
| pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount, 0); |
| |
| // 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); |
| |
| // Build the map from installed app id to app type. |
| for (const auto& it : system_app_infos_) { |
| const SystemAppType& app_type = it.first; |
| base::Optional<AppId> app_id = |
| registrar_->LookupExternalAppId(it.second.install_url); |
| if (app_id.has_value()) |
| app_id_to_app_type_[app_id.value()] = app_type; |
| } |
| |
| // May be called more than once in tests. |
| if (!on_apps_synchronized_->is_signaled()) { |
| on_apps_synchronized_->Signal(); |
| OnAppsPolicyChanged(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| bool is_camera_app_installed = |
| system_app_infos_.find(SystemAppType::CAMERA) != system_app_infos_.end(); |
| profile_->GetPrefs()->SetBoolean(chromeos::prefs::kHasCameraAppMigratedToSWA, |
| is_camera_app_installed); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| bool SystemWebAppManager::ShouldForceInstallApps() const { |
| if (base::FeatureList::IsEnabled(features::kAlwaysReinstallSystemWebApps)) |
| return true; |
| |
| if (update_policy_ == UpdatePolicy::kAlwaysUpdate) |
| 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::OnAppsPolicyChanged() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (!on_apps_synchronized_->is_signaled()) |
| return; |
| |
| auto disabled_system_apps = GetDisabledSystemWebApps(); |
| |
| for (const auto& id_and_type : app_id_to_app_type_) { |
| const bool is_disabled = |
| base::Contains(disabled_system_apps, id_and_type.second); |
| registry_controller_->SetAppIsDisabled(id_and_type.first, is_disabled); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| } // namespace web_app |