1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/web_applications/system_web_app_manager.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <tuple>
10 #include <utility>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/command_line.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/run_loop.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/version.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/web_applications/components/app_registrar.h"
23 #include "chrome/browser/web_applications/components/app_registry_controller.h"
24 #include "chrome/browser/web_applications/components/external_install_options.h"
25 #include "chrome/browser/web_applications/components/os_integration_manager.h"
26 #include "chrome/browser/web_applications/components/web_app_constants.h"
27 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
28 #include "chrome/browser/web_applications/components/web_app_ui_manager.h"
29 #include "chrome/browser/web_applications/components/web_app_utils.h"
30 #include "chrome/browser/web_applications/components/web_application_info.h"
31 #include "chrome/common/chrome_features.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/common/webui_url_constants.h"
34 #include "chrome/grit/generated_resources.h"
35 #include "components/pref_registry/pref_registry_syncable.h"
36 #include "components/prefs/pref_service.h"
37 #include "components/user_manager/user_manager.h"
38 #include "components/version_info/version_info.h"
39 #include "content/public/browser/navigation_handle.h"
40 #include "content/public/browser/url_data_source.h"
41 #include "content/public/common/content_switches.h"
42 #include "content/public/common/url_constants.h"
43 #include "ui/base/l10n/l10n_util.h"
44
45 #if defined(OS_CHROMEOS)
46 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
47 #include "base/values.h"
48 #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h"
49 #include "chrome/browser/chromeos/web_applications/camera_system_web_app_info.h"
50 #include "chrome/browser/chromeos/web_applications/connectivity_diagnostics_system_web_app_info.h"
51 #include "chrome/browser/chromeos/web_applications/default_web_app_ids.h"
52 #include "chrome/browser/chromeos/web_applications/diagnostics_system_web_app_info.h"
53 #include "chrome/browser/chromeos/web_applications/help_app_web_app_info.h"
54 #include "chrome/browser/chromeos/web_applications/media_web_app_info.h"
55 #include "chrome/browser/chromeos/web_applications/os_settings_web_app_info.h"
56 #include "chrome/browser/chromeos/web_applications/print_management_web_app_info.h"
57 #include "chrome/browser/chromeos/web_applications/scanning_system_web_app_info.h"
58 #include "chrome/browser/chromeos/web_applications/terminal_source.h"
59 #include "chrome/browser/chromeos/web_applications/terminal_system_web_app_info.h"
60 #include "chromeos/components/camera_app_ui/url_constants.h"
61 #include "chromeos/components/connectivity_diagnostics/url_constants.h"
62 #include "chromeos/components/help_app_ui/url_constants.h"
63 #include "chromeos/components/media_app_ui/url_constants.h"
64 #include "chromeos/constants/chromeos_features.h"
65 #include "chromeos/constants/chromeos_pref_names.h"
66 #include "chromeos/strings/grit/chromeos_strings.h"
67 #include "components/policy/core/common/policy_pref_names.h"
68 #include "extensions/common/constants.h"
69
70 #if !defined(OFFICIAL_BUILD)
71 #include "chrome/browser/chromeos/web_applications/file_manager_web_app_info.h"
72 #include "chrome/browser/chromeos/web_applications/sample_system_web_app_info.h"
73 #include "chrome/browser/chromeos/web_applications/telemetry_extension_web_app_info.h"
74 #endif // !defined(OFFICIAL_BUILD)
75
76 #endif // defined(OS_CHROMEOS)
77
78 namespace web_app {
79
80 namespace {
81
82 // Copy the origin trial name from runtime_enabled_features.json5, to avoid
83 // complex dependencies.
84 const char kFileHandlingOriginTrial[] = "FileHandling";
85
86 // Number of attempts to install a given version & locale of the SWAs before
87 // bailing out.
88 const int kInstallFailureAttempts = 3;
89
90 // Use #if defined to avoid compiler error on unused function.
91 #if defined(OS_CHROMEOS)
92
93 // A convenience method to create OriginTrialsMap. Note, we only support simple
94 // cases for chrome:// and chrome-untrusted:// URLs. We don't support complex
95 // cases such as about:blank (which inherits origins from the embedding frame).
GetOrigin(const char * url)96 url::Origin GetOrigin(const char* url) {
97 GURL gurl = GURL(url);
98 DCHECK(gurl.is_valid());
99
100 url::Origin origin = url::Origin::Create(gurl);
101 DCHECK(!origin.opaque());
102
103 return origin;
104 }
105 #endif // OS_CHROMEOS
106
CreateSystemWebApps(Profile * profile)107 base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps(
108 Profile* profile) {
109 base::flat_map<SystemAppType, SystemAppInfo> infos;
110 // TODO(calamity): Split this into per-platform functions.
111 #if defined(OS_CHROMEOS)
112 // SystemAppInfo's |name| field should be defined. These names are persisted
113 // to logs and should not be renamed.
114 // If new names are added, update tool/metrics/histograms/histograms.xml:
115 // "SystemWebAppName"
116 if (SystemWebAppManager::IsAppEnabled(SystemAppType::CAMERA)) {
117 infos.emplace(
118 SystemAppType::CAMERA,
119 SystemAppInfo(
120 "Camera", GURL("chrome://camera-app/views/main.html"),
121 base::BindRepeating(&CreateWebAppInfoForCameraSystemWebApp)));
122 if (!profile->GetPrefs()->GetBoolean(
123 chromeos::prefs::kHasCameraAppMigratedToSWA)) {
124 infos.at(SystemAppType::CAMERA).uninstall_and_replace = {
125 extension_misc::kCameraAppId};
126 }
127 // We need "FileHandling" to use File Handling API to set launch directory.
128 // And we need "NativeFileSystem2" to use Native File System API.
129 infos.at(SystemAppType::CAMERA).enabled_origin_trials =
130 OriginTrialsMap({{GetOrigin("chrome://camera-app"),
131 {"FileHandling", "NativeFileSystem2"}}});
132 infos.at(SystemAppType::CAMERA).capture_navigations = true;
133 }
134
135 if (SystemWebAppManager::IsAppEnabled(SystemAppType::DIAGNOSTICS)) {
136 infos.emplace(
137 SystemAppType::DIAGNOSTICS,
138 SystemAppInfo(
139 "Diagnostics", GURL("chrome://diagnostics"),
140 base::BindRepeating(&CreateWebAppInfoForDiagnosticsSystemWebApp)));
141 }
142
143 infos.emplace(SystemAppType::SETTINGS,
144 SystemAppInfo("OSSettings", GURL(chrome::kChromeUISettingsURL),
145 base::BindRepeating(
146 &CreateWebAppInfoForOSSettingsSystemWebApp)));
147 infos.at(SystemAppType::SETTINGS).uninstall_and_replace = {
148 chromeos::default_web_apps::kSettingsAppId, ash::kInternalAppIdSettings};
149 // Large enough to see the heading text "Settings" in the top-left.
150 infos.at(SystemAppType::SETTINGS).minimum_window_size = {300, 100};
151
152 if (SystemWebAppManager::IsAppEnabled(SystemAppType::TERMINAL)) {
153 infos.emplace(
154 SystemAppType::TERMINAL,
155 SystemAppInfo(
156 "Terminal", GURL(chrome::kChromeUIUntrustedTerminalURL),
157 base::BindRepeating(&CreateWebAppInfoForTerminalSystemWebApp)));
158 infos.at(SystemAppType::TERMINAL).single_window = false;
159 }
160
161 if (SystemWebAppManager::IsAppEnabled(SystemAppType::HELP)) {
162 infos.emplace(
163 SystemAppType::HELP,
164 SystemAppInfo("Help", GURL("chrome://help-app/pwa.html"),
165 base::BindRepeating(&CreateWebAppInfoForHelpWebApp)));
166 infos.at(SystemAppType::HELP).additional_search_terms = {
167 IDS_GENIUS_APP_NAME, IDS_HELP_APP_PERKS, IDS_HELP_APP_OFFERS};
168 infos.at(SystemAppType::HELP).minimum_window_size = {600, 320};
169 infos.at(SystemAppType::HELP).capture_navigations = true;
170 }
171
172 if (SystemWebAppManager::IsAppEnabled(SystemAppType::MEDIA)) {
173 infos.emplace(
174 SystemAppType::MEDIA,
175 SystemAppInfo("Media", GURL("chrome://media-app/pwa.html"),
176 base::BindRepeating(&CreateWebAppInfoForMediaWebApp)));
177 infos.at(SystemAppType::MEDIA).include_launch_directory = true;
178 infos.at(SystemAppType::MEDIA).show_in_launcher = false;
179 infos.at(SystemAppType::MEDIA).show_in_search = false;
180 infos.at(SystemAppType::MEDIA).enabled_origin_trials =
181 OriginTrialsMap({{GetOrigin("chrome://media-app"),
182 {"FileHandling", "NativeFileSystem2"}}});
183 }
184
185 if (SystemWebAppManager::IsAppEnabled(SystemAppType::PRINT_MANAGEMENT)) {
186 infos.emplace(
187 SystemAppType::PRINT_MANAGEMENT,
188 SystemAppInfo(
189 "PrintManagement", GURL("chrome://print-management/pwa.html"),
190 base::BindRepeating(&CreateWebAppInfoForPrintManagementApp)));
191 infos.at(SystemAppType::PRINT_MANAGEMENT).show_in_launcher = false;
192 infos.at(SystemAppType::PRINT_MANAGEMENT).minimum_window_size = {600, 320};
193 }
194
195 if (SystemWebAppManager::IsAppEnabled(SystemAppType::SCANNING)) {
196 infos.emplace(SystemAppType::SCANNING,
197 SystemAppInfo("Scanning", GURL("chrome://scanning"),
198 base::BindRepeating(
199 &CreateWebAppInfoForScanningSystemWebApp)));
200 }
201
202 if (SystemWebAppManager::IsAppEnabled(
203 SystemAppType::CONNECTIVITY_DIAGNOSTICS)) {
204 infos.emplace(
205 SystemAppType::CONNECTIVITY_DIAGNOSTICS,
206 SystemAppInfo(
207 "ConnectivityDiagnostics",
208 GURL(chromeos::kChromeUIConnectivityDiagnosticsUrl),
209 base::BindRepeating(
210 &CreateWebAppInfoForConnectivityDiagnosticsSystemWebApp)));
211 }
212
213 #if !defined(OFFICIAL_BUILD)
214 if (SystemWebAppManager::IsAppEnabled(SystemAppType::TELEMETRY)) {
215 infos.emplace(
216 SystemAppType::TELEMETRY,
217 SystemAppInfo(
218 "Telemetry", GURL("chrome://telemetry-extension"),
219 base::BindRepeating(&CreateWebAppInfoForTelemetryExtension)));
220 }
221
222 if (SystemWebAppManager::IsAppEnabled(SystemAppType::FILE_MANAGER)) {
223 infos.emplace(
224 SystemAppType::FILE_MANAGER,
225 SystemAppInfo("File Manager", GURL("chrome://file-manager"),
226 base::BindRepeating(&CreateWebAppInfoForFileManager)));
227 infos.at(SystemAppType::FILE_MANAGER).capture_navigations = true;
228 infos.at(SystemAppType::FILE_MANAGER).single_window = false;
229 }
230
231 infos.emplace(
232 SystemAppType::SAMPLE,
233 SystemAppInfo(
234 "Sample", GURL("chrome://sample-system-web-app/pwa.html"),
235 base::BindRepeating(&CreateWebAppInfoForSampleSystemWebApp)));
236 // Frobulate is the name for Sample Origin Trial API, and has no impact on the
237 // Web App's functionality. Here we use it to demonstrate how to enable origin
238 // trials for a System Web App.
239 infos.at(SystemAppType::SAMPLE).enabled_origin_trials = OriginTrialsMap(
240 {{GetOrigin("chrome://sample-system-web-app"), {"Frobulate"}},
241 {GetOrigin("chrome-untrusted://sample-system-web-app"), {"Frobulate"}}});
242 infos.at(SystemAppType::SAMPLE).capture_navigations = true;
243 #endif // !defined(OFFICIAL_BUILD)
244
245 #endif // OS_CHROMEOS
246
247 return infos;
248 }
249
HasSystemWebAppScheme(const GURL & url)250 bool HasSystemWebAppScheme(const GURL& url) {
251 return url.SchemeIs(content::kChromeUIScheme) ||
252 url.SchemeIs(content::kChromeUIUntrustedScheme);
253 }
254
CreateInstallOptionsForSystemApp(const SystemAppInfo & info,bool force_update,bool is_disabled)255 ExternalInstallOptions CreateInstallOptionsForSystemApp(
256 const SystemAppInfo& info,
257 bool force_update,
258 bool is_disabled) {
259 DCHECK(info.install_url.scheme() == content::kChromeUIScheme ||
260 info.install_url.scheme() == content::kChromeUIUntrustedScheme);
261
262 ExternalInstallOptions install_options(
263 info.install_url, DisplayMode::kStandalone,
264 ExternalInstallSource::kSystemInstalled);
265 install_options.only_use_app_info_factory = !!info.app_info_factory;
266 install_options.app_info_factory = info.app_info_factory;
267 install_options.add_to_applications_menu = info.show_in_launcher;
268 install_options.add_to_desktop = false;
269 install_options.add_to_quick_launch_bar = false;
270 install_options.add_to_search = info.show_in_search;
271 install_options.add_to_management = false;
272 install_options.is_disabled = is_disabled;
273 install_options.bypass_service_worker_check = true;
274 install_options.force_reinstall = force_update;
275 install_options.uninstall_and_replace = info.uninstall_and_replace;
276
277 const auto& search_terms = info.additional_search_terms;
278 std::transform(search_terms.begin(), search_terms.end(),
279 std::back_inserter(install_options.additional_search_terms),
280 [](int term) { return l10n_util::GetStringUTF8(term); });
281 return install_options;
282 }
283
GetDisabledSystemWebApps()284 std::set<SystemAppType> GetDisabledSystemWebApps() {
285 std::set<SystemAppType> disabled_system_apps;
286
287 #if defined(OS_CHROMEOS)
288 PrefService* const local_state = g_browser_process->local_state();
289 if (!local_state) // Sometimes it's not available in tests.
290 return disabled_system_apps;
291
292 const base::ListValue* disabled_system_features_pref =
293 local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList);
294 if (!disabled_system_features_pref)
295 return disabled_system_apps;
296
297 for (const auto& entry : *disabled_system_features_pref) {
298 switch (entry.GetInt()) {
299 case policy::SystemFeature::CAMERA:
300 disabled_system_apps.insert(SystemAppType::CAMERA);
301 break;
302 case policy::SystemFeature::OS_SETTINGS:
303 disabled_system_apps.insert(SystemAppType::SETTINGS);
304 break;
305 case policy::SystemFeature::SCANNING:
306 disabled_system_apps.insert(SystemAppType::SCANNING);
307 break;
308 }
309 }
310 #endif // defined(OS_CHROMEOS)
311
312 return disabled_system_apps;
313 }
314
315 } // namespace
316
SystemAppInfo(const std::string & internal_name,const GURL & install_url)317 SystemAppInfo::SystemAppInfo(const std::string& internal_name,
318 const GURL& install_url)
319 : internal_name(internal_name), install_url(install_url) {}
320
SystemAppInfo(const std::string & internal_name,const GURL & install_url,const WebApplicationInfoFactory & app_info_factory)321 SystemAppInfo::SystemAppInfo(const std::string& internal_name,
322 const GURL& install_url,
323 const WebApplicationInfoFactory& app_info_factory)
324 : internal_name(internal_name),
325 install_url(install_url),
326 app_info_factory(app_info_factory) {}
327
328 SystemAppInfo::SystemAppInfo(const SystemAppInfo& other) = default;
329
330 SystemAppInfo::~SystemAppInfo() = default;
331
332 // static
333 const char SystemWebAppManager::kInstallResultHistogramName[];
334 const char SystemWebAppManager::kInstallDurationHistogramName[];
335
336 // static
IsAppEnabled(SystemAppType type)337 bool SystemWebAppManager::IsAppEnabled(SystemAppType type) {
338 if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps))
339 return true;
340
341 #if defined(OS_CHROMEOS)
342 switch (type) {
343 case SystemAppType::SETTINGS:
344 return true;
345 case SystemAppType::CAMERA:
346 return base::FeatureList::IsEnabled(
347 chromeos::features::kCameraSystemWebApp) &&
348 !user_manager::UserManager::Get()->IsLoggedInAsGuest();
349 case SystemAppType::TERMINAL:
350 return true;
351 case SystemAppType::MEDIA:
352 return base::FeatureList::IsEnabled(chromeos::features::kMediaApp);
353 case SystemAppType::HELP:
354 return true;
355 case SystemAppType::PRINT_MANAGEMENT:
356 return base::FeatureList::IsEnabled(
357 chromeos::features::kPrintJobManagementApp);
358 case SystemAppType::SCANNING:
359 return base::FeatureList::IsEnabled(chromeos::features::kScanningUI);
360 case SystemAppType::DIAGNOSTICS:
361 return base::FeatureList::IsEnabled(chromeos::features::kDiagnosticsApp);
362 case SystemAppType::CONNECTIVITY_DIAGNOSTICS:
363 return base::FeatureList::IsEnabled(
364 chromeos::features::kConnectivityDiagnosticsWebUi);
365 #if !defined(OFFICIAL_BUILD)
366 case SystemAppType::TELEMETRY:
367 return base::FeatureList::IsEnabled(
368 chromeos::features::kTelemetryExtension);
369 case SystemAppType::FILE_MANAGER:
370 return base::FeatureList::IsEnabled(chromeos::features::kFilesSWA);
371 case SystemAppType::SAMPLE:
372 NOTREACHED();
373 return false;
374 #endif // !defined(OFFICIAL_BUILD)
375 }
376 #else
377 return false;
378 #endif // OS_CHROMEOS
379 }
380
SystemWebAppManager(Profile * profile)381 SystemWebAppManager::SystemWebAppManager(Profile* profile)
382 : profile_(profile),
383 on_apps_synchronized_(new base::OneShotEvent()),
384 install_result_per_profile_histogram_name_(
385 std::string(kInstallResultHistogramName) + ".Profiles." +
386 GetProfileCategoryForLogging(profile)),
387 pref_service_(profile_->GetPrefs()) {
388 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
389 // Always update in tests.
390 update_policy_ = UpdatePolicy::kAlwaysUpdate;
391
392 // Populate with real system apps if the test asks for it.
393 if (base::FeatureList::IsEnabled(features::kEnableAllSystemWebApps))
394 system_app_infos_ = CreateSystemWebApps(profile_);
395
396 return;
397 }
398
399 #if defined(OFFICIAL_BUILD)
400 // Official builds should trigger updates whenever the version number changes.
401 update_policy_ = UpdatePolicy::kOnVersionChange;
402 #else
403 // Dev builds should update every launch.
404 update_policy_ = UpdatePolicy::kAlwaysUpdate;
405 #endif
406
407 system_app_infos_ = CreateSystemWebApps(profile_);
408 }
409
410 SystemWebAppManager::~SystemWebAppManager() = default;
411
Shutdown()412 void SystemWebAppManager::Shutdown() {
413 shutting_down_ = true;
414 }
415
SetSubsystems(PendingAppManager * pending_app_manager,AppRegistrar * registrar,AppRegistryController * registry_controller,WebAppUiManager * ui_manager,OsIntegrationManager * os_integration_manager)416 void SystemWebAppManager::SetSubsystems(
417 PendingAppManager* pending_app_manager,
418 AppRegistrar* registrar,
419 AppRegistryController* registry_controller,
420 WebAppUiManager* ui_manager,
421 OsIntegrationManager* os_integration_manager) {
422 pending_app_manager_ = pending_app_manager;
423 registrar_ = registrar;
424 registry_controller_ = registry_controller;
425 ui_manager_ = ui_manager;
426 os_integration_manager_ = os_integration_manager;
427 }
428
Start()429 void SystemWebAppManager::Start() {
430 const base::TimeTicks install_start_time = base::TimeTicks::Now();
431
432 #if DCHECK_IS_ON()
433 // Check Origin Trials are defined correctly.
434 for (const auto& type_and_app_info : system_app_infos_) {
435 for (const auto& origin_to_trial_names :
436 type_and_app_info.second.enabled_origin_trials) {
437 // Only allow force enabled origin trials on chrome:// and
438 // chrome-untrusted:// URLs.
439 const auto& scheme = origin_to_trial_names.first.scheme();
440 DCHECK(scheme == content::kChromeUIScheme ||
441 scheme == content::kChromeUIUntrustedScheme);
442 }
443 }
444
445 // TODO(https://crbug.com/1043843): Find some ways to validate supplied origin
446 // trial names. Ideally, construct them from some static const char*.
447 #endif // DCHECK_IS_ON()
448
449 #if defined(OS_CHROMEOS)
450 // Set up terminal data source. Terminal source is needed for install.
451 // TODO(crbug.com/1080384): Move once chrome-untrusted has WebUIControllers.
452 if (SystemWebAppManager::IsAppEnabled(SystemAppType::TERMINAL)) {
453 content::URLDataSource::Add(profile_,
454 TerminalSource::ForTerminal(profile_));
455 }
456 #endif // defined(OS_CHROMEOS)
457
458 std::vector<ExternalInstallOptions> install_options_list;
459 const bool should_force_install_apps = ShouldForceInstallApps();
460 if (should_force_install_apps) {
461 UpdateLastAttemptedInfo();
462 }
463
464 const auto disabled_system_apps = GetDisabledSystemWebApps();
465
466 for (const auto& app : system_app_infos_) {
467 install_options_list.push_back(CreateInstallOptionsForSystemApp(
468 app.second, should_force_install_apps,
469 base::Contains(disabled_system_apps, app.first)));
470 }
471
472 const bool exceeded_retries = CheckAndIncrementRetryAttempts();
473 if (!exceeded_retries) {
474 pending_app_manager_->SynchronizeInstalledApps(
475 std::move(install_options_list),
476 ExternalInstallSource::kSystemInstalled,
477 base::BindOnce(&SystemWebAppManager::OnAppsSynchronized,
478 weak_ptr_factory_.GetWeakPtr(),
479 should_force_install_apps, install_start_time));
480 }
481 #if defined(OS_CHROMEOS)
482 PrefService* const local_state = g_browser_process->local_state();
483 if (local_state) { // Sometimes it's not available in tests.
484 local_state_pref_change_registrar_.Init(local_state);
485
486 // Sometimes this function gets called twice in tests.
487 if (!local_state_pref_change_registrar_.IsObserved(
488 policy::policy_prefs::kSystemFeaturesDisableList)) {
489 local_state_pref_change_registrar_.Add(
490 policy::policy_prefs::kSystemFeaturesDisableList,
491 base::Bind(&SystemWebAppManager::OnAppsPolicyChanged,
492 base::Unretained(this)));
493 }
494 }
495 #endif // defined(OS_CHROMEOS)
496 }
497
InstallSystemAppsForTesting()498 void SystemWebAppManager::InstallSystemAppsForTesting() {
499 on_apps_synchronized_.reset(new base::OneShotEvent());
500 system_app_infos_ = CreateSystemWebApps(profile_);
501 Start();
502
503 // Wait for the System Web Apps to install.
504 base::RunLoop run_loop;
505 on_apps_synchronized().Post(FROM_HERE, run_loop.QuitClosure());
506 run_loop.Run();
507 }
508
509 const base::flat_map<SystemAppType, SystemAppInfo>&
GetRegisteredSystemAppsForTesting() const510 SystemWebAppManager::GetRegisteredSystemAppsForTesting() const {
511 return system_app_infos_;
512 }
513
GetAppIdForSystemApp(SystemAppType id) const514 base::Optional<AppId> SystemWebAppManager::GetAppIdForSystemApp(
515 SystemAppType id) const {
516 auto app_url_it = system_app_infos_.find(id);
517
518 if (app_url_it == system_app_infos_.end())
519 return base::Optional<AppId>();
520
521 return registrar_->LookupExternalAppId(app_url_it->second.install_url);
522 }
523
GetSystemAppTypeForAppId(AppId app_id) const524 base::Optional<SystemAppType> SystemWebAppManager::GetSystemAppTypeForAppId(
525 AppId app_id) const {
526 auto it = app_id_to_app_type_.find(app_id);
527 if (it == app_id_to_app_type_.end())
528 return base::nullopt;
529
530 return it->second;
531 }
532
GetAppIds() const533 std::vector<AppId> SystemWebAppManager::GetAppIds() const {
534 std::vector<AppId> app_ids;
535 for (const auto& app_id_to_app_type : app_id_to_app_type_) {
536 app_ids.push_back(app_id_to_app_type.first);
537 }
538 return app_ids;
539 }
540
IsSystemWebApp(const AppId & app_id) const541 bool SystemWebAppManager::IsSystemWebApp(const AppId& app_id) const {
542 return app_id_to_app_type_.contains(app_id);
543 }
544
IsSingleWindow(SystemAppType type) const545 bool SystemWebAppManager::IsSingleWindow(SystemAppType type) const {
546 auto it = system_app_infos_.find(type);
547 if (it == system_app_infos_.end())
548 return false;
549
550 return it->second.single_window;
551 }
552
AppShouldReceiveLaunchDirectory(SystemAppType type) const553 bool SystemWebAppManager::AppShouldReceiveLaunchDirectory(
554 SystemAppType type) const {
555 auto it = system_app_infos_.find(type);
556 if (it == system_app_infos_.end())
557 return false;
558 return it->second.include_launch_directory;
559 }
560
GetEnabledOriginTrials(const SystemAppType type,const GURL & url)561 const std::vector<std::string>* SystemWebAppManager::GetEnabledOriginTrials(
562 const SystemAppType type,
563 const GURL& url) {
564 const auto& origin_to_origin_trials =
565 system_app_infos_.at(type).enabled_origin_trials;
566 auto iter_trials = origin_to_origin_trials.find(url::Origin::Create(url));
567 if (iter_trials == origin_to_origin_trials.end())
568 return nullptr;
569
570 return &iter_trials->second;
571 }
572
AppHasFileHandlingOriginTrial(SystemAppType type)573 bool SystemWebAppManager::AppHasFileHandlingOriginTrial(SystemAppType type) {
574 const auto& info = system_app_infos_.at(type);
575 const std::vector<std::string>* trials =
576 GetEnabledOriginTrials(type, info.install_url);
577 return trials && base::Contains(*trials, kFileHandlingOriginTrial);
578 }
579
OnReadyToCommitNavigation(const AppId & app_id,content::NavigationHandle * navigation_handle)580 void SystemWebAppManager::OnReadyToCommitNavigation(
581 const AppId& app_id,
582 content::NavigationHandle* navigation_handle) {
583 // No need to setup origin trials for intra-document navigation.
584 if (navigation_handle->IsSameDocument())
585 return;
586
587 const base::Optional<SystemAppType> type = GetSystemAppTypeForAppId(app_id);
588 // This function should only be called when an navigation happens inside a
589 // System App. So the |app_id| should always have a valid associated System
590 // App type.
591 DCHECK(type.has_value());
592
593 const std::vector<std::string>* trials =
594 GetEnabledOriginTrials(type.value(), navigation_handle->GetURL());
595 if (trials)
596 navigation_handle->ForceEnableOriginTrials(*trials);
597 }
598
GetAdditionalSearchTerms(SystemAppType type) const599 std::vector<std::string> SystemWebAppManager::GetAdditionalSearchTerms(
600 SystemAppType type) const {
601 auto it = system_app_infos_.find(type);
602 if (it == system_app_infos_.end())
603 return {};
604
605 const auto& search_terms = it->second.additional_search_terms;
606
607 std::vector<std::string> search_terms_strings;
608 std::transform(search_terms.begin(), search_terms.end(),
609 std::back_inserter(search_terms_strings),
610 [](int term) { return l10n_util::GetStringUTF8(term); });
611 return search_terms_strings;
612 }
613
ShouldShowInLauncher(SystemAppType type) const614 bool SystemWebAppManager::ShouldShowInLauncher(SystemAppType type) const {
615 auto it = system_app_infos_.find(type);
616 if (it == system_app_infos_.end())
617 return false;
618 return it->second.show_in_launcher;
619 }
620
ShouldShowInSearch(SystemAppType type) const621 bool SystemWebAppManager::ShouldShowInSearch(SystemAppType type) const {
622 auto it = system_app_infos_.find(type);
623 if (it == system_app_infos_.end())
624 return false;
625 return it->second.show_in_search;
626 }
627
GetCapturingSystemAppForURL(const GURL & url) const628 base::Optional<SystemAppType> SystemWebAppManager::GetCapturingSystemAppForURL(
629 const GURL& url) const {
630 if (!HasSystemWebAppScheme(url))
631 return base::nullopt;
632
633 base::Optional<AppId> app_id = registrar_->FindAppWithUrlInScope(url);
634 if (!app_id.has_value())
635 return base::nullopt;
636
637 base::Optional<SystemAppType> type = GetSystemAppTypeForAppId(app_id.value());
638 if (!type.has_value())
639 return base::nullopt;
640
641 const auto it = system_app_infos_.find(type);
642 if (it == system_app_infos_.end())
643 return base::nullopt;
644
645 if (!it->second.capture_navigations)
646 return base::nullopt;
647
648 #if defined(OS_CHROMEOS)
649 if (type == SystemAppType::CAMERA &&
650 url.spec() != chromeos::kChromeUICameraAppMainURL)
651 return base::nullopt;
652 #endif // defined(OS_CHROMEOS)
653
654 return type;
655 }
656
GetMinimumWindowSize(const AppId & app_id) const657 gfx::Size SystemWebAppManager::GetMinimumWindowSize(const AppId& app_id) const {
658 auto app_type_it = app_id_to_app_type_.find(app_id);
659 if (app_type_it == app_id_to_app_type_.end())
660 return gfx::Size();
661 const SystemAppType& app_type = app_type_it->second;
662 auto app_info_it = system_app_infos_.find(app_type);
663 if (app_info_it == system_app_infos_.end())
664 return gfx::Size();
665 return app_info_it->second.minimum_window_size;
666 }
667
SetSystemAppsForTesting(base::flat_map<SystemAppType,SystemAppInfo> system_apps)668 void SystemWebAppManager::SetSystemAppsForTesting(
669 base::flat_map<SystemAppType, SystemAppInfo> system_apps) {
670 system_app_infos_ = std::move(system_apps);
671 }
672
SetUpdatePolicyForTesting(UpdatePolicy policy)673 void SystemWebAppManager::SetUpdatePolicyForTesting(UpdatePolicy policy) {
674 update_policy_ = policy;
675 }
676
ResetOnAppsSynchronizedForTesting()677 void SystemWebAppManager::ResetOnAppsSynchronizedForTesting() {
678 on_apps_synchronized_ = std::make_unique<base::OneShotEvent>();
679 }
680
681 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)682 void SystemWebAppManager::RegisterProfilePrefs(
683 user_prefs::PrefRegistrySyncable* registry) {
684 registry->RegisterStringPref(prefs::kSystemWebAppLastUpdateVersion, "");
685 registry->RegisterStringPref(prefs::kSystemWebAppLastInstalledLocale, "");
686 registry->RegisterStringPref(prefs::kSystemWebAppLastAttemptedVersion, "");
687 registry->RegisterStringPref(prefs::kSystemWebAppLastAttemptedLocale, "");
688 registry->RegisterIntegerPref(prefs::kSystemWebAppInstallFailureCount, 0);
689 }
690
CurrentVersion() const691 const base::Version& SystemWebAppManager::CurrentVersion() const {
692 return version_info::GetVersion();
693 }
694
CurrentLocale() const695 const std::string& SystemWebAppManager::CurrentLocale() const {
696 return g_browser_process->GetApplicationLocale();
697 }
698
RecordSystemWebAppInstallDuration(const base::TimeDelta & install_duration) const699 void SystemWebAppManager::RecordSystemWebAppInstallDuration(
700 const base::TimeDelta& install_duration) const {
701 // Install duration should be non-negative. A low resolution clock could
702 // result in a |install_duration| of 0.
703 DCHECK_GE(install_duration.InMilliseconds(), 0);
704
705 if (!shutting_down_) {
706 base::UmaHistogramMediumTimes(kInstallDurationHistogramName,
707 install_duration);
708 }
709 }
710
RecordSystemWebAppInstallResults(const std::map<GURL,InstallResultCode> & install_results) const711 void SystemWebAppManager::RecordSystemWebAppInstallResults(
712 const std::map<GURL, InstallResultCode>& install_results) const {
713 // Report install result codes. Exclude kSuccessAlreadyInstalled from metrics.
714 // This result means the installation pipeline is a no-op (which happens every
715 // time user logs in, and if there hasn't been a version upgrade). This skews
716 // the install success rate.
717 std::map<GURL, InstallResultCode> results_to_report;
718 std::copy_if(install_results.begin(), install_results.end(),
719 std::inserter(results_to_report, results_to_report.end()),
720 [](const auto& url_and_result) {
721 return url_and_result.second !=
722 InstallResultCode::kSuccessAlreadyInstalled;
723 });
724
725 for (const auto& url_and_result : results_to_report) {
726 // Record aggregate result.
727 base::UmaHistogramEnumeration(
728 kInstallResultHistogramName,
729 shutting_down_
730 ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown
731 : url_and_result.second);
732
733 // Record per-profile result.
734 base::UmaHistogramEnumeration(
735 install_result_per_profile_histogram_name_,
736 shutting_down_
737 ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown
738 : url_and_result.second);
739 }
740
741 // Record per-app result.
742 for (const auto& type_and_app_info : system_app_infos_) {
743 const GURL& install_url = type_and_app_info.second.install_url;
744 const auto url_and_result = results_to_report.find(install_url);
745 if (url_and_result != results_to_report.cend()) {
746 const std::string app_histogram_name =
747 std::string(kInstallResultHistogramName) + ".Apps." +
748 type_and_app_info.second.internal_name;
749 base::UmaHistogramEnumeration(
750 app_histogram_name,
751 shutting_down_
752 ? InstallResultCode::kCancelledOnWebAppProviderShuttingDown
753 : url_and_result->second);
754 }
755 }
756 }
757
OnAppsSynchronized(bool did_force_install_apps,const base::TimeTicks & install_start_time,std::map<GURL,InstallResultCode> install_results,std::map<GURL,bool> uninstall_results)758 void SystemWebAppManager::OnAppsSynchronized(
759 bool did_force_install_apps,
760 const base::TimeTicks& install_start_time,
761 std::map<GURL, InstallResultCode> install_results,
762 std::map<GURL, bool> uninstall_results) {
763 // TODO(crbug.com/1053371): Clean up File Handler install. We install SWA file
764 // handlers here, because the code that registers file handlers for regular
765 // Web Apps, does not run when for apps installed in the background.
766 for (const auto& it : system_app_infos_) {
767 const SystemAppType& type = it.first;
768 base::Optional<AppId> app_id = GetAppIdForSystemApp(type);
769 if (!app_id)
770 continue;
771
772 if (AppHasFileHandlingOriginTrial(type)) {
773 os_integration_manager_->ForceEnableFileHandlingOriginTrial(
774 app_id.value());
775 } else {
776 os_integration_manager_->DisableForceEnabledFileHandlingOriginTrial(
777 app_id.value());
778 }
779 }
780
781 const base::TimeDelta install_duration =
782 base::TimeTicks::Now() - install_start_time;
783
784 // TODO(qjw): Figure out where install_results come from, decide if
785 // installation failures need to be handled
786 pref_service_->SetString(prefs::kSystemWebAppLastUpdateVersion,
787 CurrentVersion().GetString());
788 pref_service_->SetString(prefs::kSystemWebAppLastInstalledLocale,
789 CurrentLocale());
790 pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount, 0);
791
792 // Report install duration only if the install pipeline actually installs
793 // all the apps (e.g. on version upgrade).
794 if (did_force_install_apps)
795 RecordSystemWebAppInstallDuration(install_duration);
796
797 RecordSystemWebAppInstallResults(install_results);
798
799 // Build the map from installed app id to app type.
800 for (const auto& it : system_app_infos_) {
801 const SystemAppType& app_type = it.first;
802 base::Optional<AppId> app_id =
803 registrar_->LookupExternalAppId(it.second.install_url);
804 if (app_id.has_value())
805 app_id_to_app_type_[app_id.value()] = app_type;
806 }
807
808 // May be called more than once in tests.
809 if (!on_apps_synchronized_->is_signaled()) {
810 on_apps_synchronized_->Signal();
811 OnAppsPolicyChanged();
812 }
813
814 #if BUILDFLAG(IS_CHROMEOS_ASH)
815 bool is_camera_app_installed =
816 system_app_infos_.find(SystemAppType::CAMERA) != system_app_infos_.end();
817 profile_->GetPrefs()->SetBoolean(chromeos::prefs::kHasCameraAppMigratedToSWA,
818 is_camera_app_installed);
819 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
820 }
821
ShouldForceInstallApps() const822 bool SystemWebAppManager::ShouldForceInstallApps() const {
823 if (base::FeatureList::IsEnabled(features::kAlwaysReinstallSystemWebApps))
824 return true;
825
826 if (update_policy_ == UpdatePolicy::kAlwaysUpdate)
827 return true;
828
829 base::Version current_installed_version(
830 pref_service_->GetString(prefs::kSystemWebAppLastUpdateVersion));
831
832 const std::string& current_installed_locale(
833 pref_service_->GetString(prefs::kSystemWebAppLastInstalledLocale));
834
835 // If Chrome version rolls back for some reason, ensure System Web Apps are
836 // always in sync with Chrome version.
837 const bool versionIsDifferent = !current_installed_version.IsValid() ||
838 current_installed_version != CurrentVersion();
839
840 // If system language changes, ensure System Web Apps launcher localization
841 // are in sync with current language.
842 const bool localeIsDifferent = current_installed_locale != CurrentLocale();
843
844 return versionIsDifferent || localeIsDifferent;
845 }
846
UpdateLastAttemptedInfo()847 void SystemWebAppManager::UpdateLastAttemptedInfo() {
848 base::Version last_attempted_version(
849 pref_service_->GetString(prefs::kSystemWebAppLastAttemptedVersion));
850
851 const std::string& last_attempted_locale(
852 pref_service_->GetString(prefs::kSystemWebAppLastAttemptedLocale));
853
854 const bool is_retry = last_attempted_version.IsValid() &&
855 last_attempted_version == CurrentVersion() &&
856 last_attempted_locale == CurrentLocale();
857 if (!is_retry) {
858 pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount, 0);
859 }
860
861 pref_service_->SetString(prefs::kSystemWebAppLastAttemptedVersion,
862 CurrentVersion().GetString());
863 pref_service_->SetString(prefs::kSystemWebAppLastAttemptedLocale,
864 CurrentLocale());
865 pref_service_->CommitPendingWrite();
866 }
867
CheckAndIncrementRetryAttempts()868 bool SystemWebAppManager::CheckAndIncrementRetryAttempts() {
869 int installation_failures =
870 pref_service_->GetInteger(prefs::kSystemWebAppInstallFailureCount);
871 bool reached_retry_limit = installation_failures > kInstallFailureAttempts;
872
873 if (!reached_retry_limit) {
874 pref_service_->SetInteger(prefs::kSystemWebAppInstallFailureCount,
875 installation_failures + 1);
876 pref_service_->CommitPendingWrite();
877 return false;
878 }
879 return true;
880 }
881
OnAppsPolicyChanged()882 void SystemWebAppManager::OnAppsPolicyChanged() {
883 #if defined(OS_CHROMEOS)
884 if (!on_apps_synchronized_->is_signaled())
885 return;
886
887 auto disabled_system_apps = GetDisabledSystemWebApps();
888
889 for (const auto& id_and_type : app_id_to_app_type_) {
890 const bool is_disabled =
891 base::Contains(disabled_system_apps, id_and_type.second);
892 registry_controller_->SetAppIsDisabled(id_and_type.first, is_disabled);
893 }
894 #endif // defined(OS_CHROMEOS)
895 }
896
897 } // namespace web_app
898