1 // Copyright 2019 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/components/app_registrar.h"
6 
7 #include "base/stl_util.h"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/web_applications/components/app_registrar_observer.h"
11 #include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
12 #include "chrome/browser/web_applications/components/install_bounce_metric.h"
13 #include "chrome/browser/web_applications/components/web_app_helpers.h"
14 #include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
15 #include "chrome/common/chrome_features.h"
16 #include "content/public/common/content_features.h"
17 
18 namespace web_app {
19 
AppRegistrar(Profile * profile)20 AppRegistrar::AppRegistrar(Profile* profile) : profile_(profile) {}
21 
~AppRegistrar()22 AppRegistrar::~AppRegistrar() {
23   for (AppRegistrarObserver& observer : observers_)
24     observer.OnAppRegistrarDestroyed();
25 }
26 
SetSubsystems(OsIntegrationManager * os_integration_manager)27 void AppRegistrar::SetSubsystems(OsIntegrationManager* os_integration_manager) {
28   os_integration_manager_ = os_integration_manager;
29 }
30 
IsLocallyInstalled(const GURL & start_url) const31 bool AppRegistrar::IsLocallyInstalled(const GURL& start_url) const {
32   return IsLocallyInstalled(GenerateAppIdFromURL(start_url));
33 }
34 
IsPlaceholderApp(const AppId & app_id) const35 bool AppRegistrar::IsPlaceholderApp(const AppId& app_id) const {
36   return ExternallyInstalledWebAppPrefs(profile_->GetPrefs())
37       .IsPlaceholderApp(app_id);
38 }
39 
AddObserver(AppRegistrarObserver * observer)40 void AppRegistrar::AddObserver(AppRegistrarObserver* observer) {
41   observers_.AddObserver(observer);
42 }
43 
RemoveObserver(AppRegistrarObserver * observer)44 void AppRegistrar::RemoveObserver(AppRegistrarObserver* observer) {
45   observers_.RemoveObserver(observer);
46 }
47 
NotifyWebAppInstalled(const AppId & app_id)48 void AppRegistrar::NotifyWebAppInstalled(const AppId& app_id) {
49   for (AppRegistrarObserver& observer : observers_)
50     observer.OnWebAppInstalled(app_id);
51   // TODO(alancutter): Call RecordWebAppInstallation here when we get access to
52   // the WebappInstallSource in this event.
53 }
54 
NotifyWebAppManifestUpdated(const AppId & app_id,base::StringPiece old_name)55 void AppRegistrar::NotifyWebAppManifestUpdated(const AppId& app_id,
56                                                base::StringPiece old_name) {
57   for (AppRegistrarObserver& observer : observers_)
58     observer.OnWebAppManifestUpdated(app_id, old_name);
59 }
60 
NotifyWebAppsWillBeUpdatedFromSync(const std::vector<const WebApp * > & new_apps_state)61 void AppRegistrar::NotifyWebAppsWillBeUpdatedFromSync(
62     const std::vector<const WebApp*>& new_apps_state) {
63   for (AppRegistrarObserver& observer : observers_)
64     observer.OnWebAppsWillBeUpdatedFromSync(new_apps_state);
65 }
66 
NotifyWebAppUninstalled(const AppId & app_id)67 void AppRegistrar::NotifyWebAppUninstalled(const AppId& app_id) {
68   for (AppRegistrarObserver& observer : observers_)
69     observer.OnWebAppUninstalled(app_id);
70   RecordWebAppUninstallation(profile()->GetPrefs(), app_id);
71 }
72 
NotifyWebAppLocallyInstalledStateChanged(const AppId & app_id,bool is_locally_installed)73 void AppRegistrar::NotifyWebAppLocallyInstalledStateChanged(
74     const AppId& app_id,
75     bool is_locally_installed) {
76   for (AppRegistrarObserver& observer : observers_)
77     observer.OnWebAppLocallyInstalledStateChanged(app_id, is_locally_installed);
78 }
79 
NotifyWebAppDisabledStateChanged(const AppId & app_id,bool is_disabled)80 void AppRegistrar::NotifyWebAppDisabledStateChanged(const AppId& app_id,
81                                                     bool is_disabled) {
82   for (AppRegistrarObserver& observer : observers_)
83     observer.OnWebAppDisabledStateChanged(app_id, is_disabled);
84 }
85 
NotifyWebAppLastLaunchTimeChanged(const AppId & app_id,const base::Time & time)86 void AppRegistrar::NotifyWebAppLastLaunchTimeChanged(const AppId& app_id,
87                                                      const base::Time& time) {
88   for (AppRegistrarObserver& observer : observers_)
89     observer.OnWebAppLastLaunchTimeChanged(app_id, time);
90 }
91 
NotifyWebAppInstallTimeChanged(const AppId & app_id,const base::Time & time)92 void AppRegistrar::NotifyWebAppInstallTimeChanged(const AppId& app_id,
93                                                   const base::Time& time) {
94   for (AppRegistrarObserver& observer : observers_)
95     observer.OnWebAppInstallTimeChanged(app_id, time);
96 }
97 
NotifyWebAppProfileWillBeDeleted(const AppId & app_id)98 void AppRegistrar::NotifyWebAppProfileWillBeDeleted(const AppId& app_id) {
99   for (AppRegistrarObserver& observer : observers_)
100     observer.OnWebAppProfileWillBeDeleted(app_id);
101 }
102 
NotifyWebAppInstalledWithOsHooks(const AppId & app_id)103 void AppRegistrar::NotifyWebAppInstalledWithOsHooks(const AppId& app_id) {
104   for (AppRegistrarObserver& observer : observers_)
105     observer.OnWebAppInstalledWithOsHooks(app_id);
106 }
107 
NotifyAppRegistrarShutdown()108 void AppRegistrar::NotifyAppRegistrarShutdown() {
109   for (AppRegistrarObserver& observer : observers_)
110     observer.OnAppRegistrarShutdown();
111 }
112 
GetExternallyInstalledApps(ExternalInstallSource install_source) const113 std::map<AppId, GURL> AppRegistrar::GetExternallyInstalledApps(
114     ExternalInstallSource install_source) const {
115   std::map<AppId, GURL> installed_apps =
116       ExternallyInstalledWebAppPrefs::BuildAppIdsMap(profile()->GetPrefs(),
117                                                      install_source);
118   base::EraseIf(installed_apps, [this](const std::pair<AppId, GURL>& app) {
119     return !IsInstalled(app.first);
120   });
121 
122   return installed_apps;
123 }
124 
LookupExternalAppId(const GURL & install_url) const125 base::Optional<AppId> AppRegistrar::LookupExternalAppId(
126     const GURL& install_url) const {
127   return ExternallyInstalledWebAppPrefs(profile()->GetPrefs())
128       .LookupAppId(install_url);
129 }
130 
HasExternalApp(const AppId & app_id) const131 bool AppRegistrar::HasExternalApp(const AppId& app_id) const {
132   return ExternallyInstalledWebAppPrefs::HasAppId(profile()->GetPrefs(),
133                                                   app_id);
134 }
135 
HasExternalAppWithInstallSource(const AppId & app_id,ExternalInstallSource install_source) const136 bool AppRegistrar::HasExternalAppWithInstallSource(
137     const AppId& app_id,
138     ExternalInstallSource install_source) const {
139   return ExternallyInstalledWebAppPrefs::HasAppIdWithInstallSource(
140       profile()->GetPrefs(), app_id, install_source);
141 }
142 
GetAppLaunchUrl(const AppId & app_id) const143 GURL AppRegistrar::GetAppLaunchUrl(const AppId& app_id) const {
144   const GURL& start_url = GetAppStartUrl(app_id);
145   const std::string* launch_query_params = GetAppLaunchQueryParams(app_id);
146   if (!start_url.is_valid() || !launch_query_params)
147     return start_url;
148 
149   GURL::Replacements replacements;
150   if (start_url.query_piece().empty()) {
151     replacements.SetQueryStr(*launch_query_params);
152     return start_url.ReplaceComponents(replacements);
153   }
154 
155   if (start_url.query_piece().find(*launch_query_params) !=
156       base::StringPiece::npos) {
157     return start_url;
158   }
159 
160   std::string query_params = start_url.query() + "&" + *launch_query_params;
161   replacements.SetQueryStr(query_params);
162   return start_url.ReplaceComponents(replacements);
163 }
164 
AsBookmarkAppRegistrar()165 extensions::BookmarkAppRegistrar* AppRegistrar::AsBookmarkAppRegistrar() {
166   return nullptr;
167 }
168 
GetAppScope(const AppId & app_id) const169 GURL AppRegistrar::GetAppScope(const AppId& app_id) const {
170   base::Optional<GURL> scope = GetAppScopeInternal(app_id);
171   if (scope)
172     return *scope;
173   if (base::FeatureList::IsEnabled(
174           features::kDesktopPWAsTabStripLinkCapturing) &&
175       IsInExperimentalTabbedWindowMode(app_id)) {
176     return GetAppStartUrl(app_id).GetOrigin();
177   }
178   return GetAppStartUrl(app_id).GetWithoutFilename();
179 }
180 
FindAppWithUrlInScope(const GURL & url) const181 base::Optional<AppId> AppRegistrar::FindAppWithUrlInScope(
182     const GURL& url) const {
183   const std::string url_path = url.spec();
184 
185   base::Optional<AppId> best_app_id;
186   size_t best_app_path_length = 0U;
187   bool best_app_is_shortcut = true;
188 
189   for (const AppId& app_id : GetAppIds()) {
190     // TODO(crbug.com/910016): Treat shortcuts as PWAs.
191     bool app_is_shortcut = IsShortcutApp(app_id);
192     if (app_is_shortcut && !best_app_is_shortcut)
193       continue;
194 
195     const std::string app_path = GetAppScope(app_id).spec();
196 
197     if ((app_path.size() > best_app_path_length ||
198          (best_app_is_shortcut && !app_is_shortcut)) &&
199         base::StartsWith(url_path, app_path, base::CompareCase::SENSITIVE)) {
200       best_app_id = app_id;
201       best_app_path_length = app_path.size();
202       best_app_is_shortcut = app_is_shortcut;
203     }
204   }
205 
206   return best_app_id;
207 }
208 
DoesScopeContainAnyApp(const GURL & scope) const209 bool AppRegistrar::DoesScopeContainAnyApp(const GURL& scope) const {
210   std::string scope_str = scope.spec();
211 
212   for (const auto& app_id : GetAppIds()) {
213     if (!IsLocallyInstalled(app_id))
214       continue;
215 
216     std::string app_scope = GetAppScope(app_id).spec();
217     DCHECK(!app_scope.empty());
218 
219     if (base::StartsWith(app_scope, scope_str, base::CompareCase::SENSITIVE))
220       return true;
221   }
222 
223   return false;
224 }
225 
FindAppsInScope(const GURL & scope) const226 std::vector<AppId> AppRegistrar::FindAppsInScope(const GURL& scope) const {
227   std::string scope_str = scope.spec();
228 
229   std::vector<AppId> in_scope;
230   for (const auto& app_id : GetAppIds()) {
231     if (!IsLocallyInstalled(app_id))
232       continue;
233 
234     std::string app_scope = GetAppScope(app_id).spec();
235     DCHECK(!app_scope.empty());
236 
237     if (!base::StartsWith(app_scope, scope_str, base::CompareCase::SENSITIVE))
238       continue;
239 
240     in_scope.push_back(app_id);
241   }
242 
243   return in_scope;
244 }
245 
FindInstalledAppWithUrlInScope(const GURL & url,bool window_only) const246 base::Optional<AppId> AppRegistrar::FindInstalledAppWithUrlInScope(
247     const GURL& url,
248     bool window_only) const {
249   const std::string url_path = url.spec();
250 
251   base::Optional<AppId> best_app_id;
252   size_t best_app_path_length = 0U;
253   bool best_app_is_shortcut = true;
254 
255   for (const AppId& app_id : GetAppIds()) {
256     // TODO(crbug.com/910016): Treat shortcuts as PWAs.
257     bool app_is_shortcut = IsShortcutApp(app_id);
258     if (app_is_shortcut && !best_app_is_shortcut)
259       continue;
260 
261     if (!IsLocallyInstalled(app_id))
262       continue;
263 
264     if (window_only &&
265         GetAppEffectiveDisplayMode(app_id) == DisplayMode::kBrowser) {
266       continue;
267     }
268 
269     const std::string app_path = GetAppScope(app_id).spec();
270 
271     if ((app_path.size() > best_app_path_length ||
272          (best_app_is_shortcut && !app_is_shortcut)) &&
273         base::StartsWith(url_path, app_path, base::CompareCase::SENSITIVE)) {
274       best_app_id = app_id;
275       best_app_path_length = app_path.size();
276       best_app_is_shortcut = app_is_shortcut;
277     }
278   }
279 
280   return best_app_id;
281 }
282 
IsShortcutApp(const AppId & app_id) const283 bool AppRegistrar::IsShortcutApp(const AppId& app_id) const {
284   // TODO (crbug/910016): Make app scope always return a value and record this
285   //  distinction in some other way.
286   return !GetAppScopeInternal(app_id).has_value();
287 }
288 
GetAppEffectiveDisplayMode(const AppId & app_id) const289 DisplayMode AppRegistrar::GetAppEffectiveDisplayMode(
290     const AppId& app_id) const {
291   if (!IsLocallyInstalled(app_id))
292     return DisplayMode::kBrowser;
293 
294   auto app_display_mode = GetAppDisplayMode(app_id);
295   auto user_display_mode = GetAppUserDisplayMode(app_id);
296   if (app_display_mode == DisplayMode::kUndefined ||
297       user_display_mode == DisplayMode::kUndefined) {
298     return DisplayMode::kUndefined;
299   }
300 
301   std::vector<DisplayMode> display_mode_overrides;
302   if (base::FeatureList::IsEnabled(features::kWebAppManifestDisplayOverride))
303     display_mode_overrides = GetAppDisplayModeOverride(app_id);
304 
305   return ResolveEffectiveDisplayMode(app_display_mode, display_mode_overrides,
306                                      user_display_mode);
307 }
308 
GetEffectiveDisplayModeFromManifest(const AppId & app_id) const309 DisplayMode AppRegistrar::GetEffectiveDisplayModeFromManifest(
310     const AppId& app_id) const {
311   if (base::FeatureList::IsEnabled(features::kWebAppManifestDisplayOverride)) {
312     std::vector<DisplayMode> display_mode_overrides =
313         GetAppDisplayModeOverride(app_id);
314 
315     if (!display_mode_overrides.empty())
316       return display_mode_overrides[0];
317   }
318 
319   return GetAppDisplayMode(app_id);
320 }
321 
IsInExperimentalTabbedWindowMode(const AppId & app_id) const322 bool AppRegistrar::IsInExperimentalTabbedWindowMode(const AppId& app_id) const {
323   return base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip) &&
324          GetBoolWebAppPref(profile()->GetPrefs(), app_id,
325                            kExperimentalTabbedWindowMode);
326 }
327 
328 }  // namespace web_app
329