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