1 // Copyright 2015 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/banners/app_banner_manager_desktop.h"
6
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/feature_list.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/optional.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/banners/app_banner_metrics.h"
16 #include "chrome/browser/banners/app_banner_settings_helper.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
19 #include "chrome/browser/web_applications/components/web_app_constants.h"
20 #include "chrome/browser/web_applications/components/web_app_helpers.h"
21 #include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
22 #include "chrome/browser/web_applications/web_app_provider.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/common/constants.h"
25 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
26
27 #if defined(OS_CHROMEOS)
28 #include "chrome/browser/chromeos/arc/arc_util.h"
29 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
30 #endif // defined(OS_CHROMEOS)
31
32 namespace {
33
34 // Platform values defined in:
35 // https://github.com/w3c/manifest/wiki/Platforms
36 const char kPlatformChromeWebStore[] = "chrome_web_store";
37
38 #if defined(OS_CHROMEOS)
39 const char kPlatformPlay[] = "play";
40 #endif // defined(OS_CHROMEOS)
41
42 bool gDisableTriggeringForTesting = false;
43
44 } // namespace
45
46 namespace banners {
47
48 AppBannerManagerDesktop::CreateAppBannerManagerForTesting
49 AppBannerManagerDesktop::override_app_banner_manager_desktop_for_testing_ =
50 nullptr;
51
52 // static
CreateForWebContents(content::WebContents * web_contents)53 void AppBannerManagerDesktop::CreateForWebContents(
54 content::WebContents* web_contents) {
55 if (FromWebContents(web_contents))
56 return;
57
58 if (override_app_banner_manager_desktop_for_testing_) {
59 web_contents->SetUserData(
60 UserDataKey(),
61 override_app_banner_manager_desktop_for_testing_(web_contents));
62 return;
63 }
64 web_contents->SetUserData(
65 UserDataKey(),
66 base::WrapUnique(new AppBannerManagerDesktop(web_contents)));
67 }
68
69 // static
FromWebContents(content::WebContents * web_contents)70 AppBannerManager* AppBannerManager::FromWebContents(
71 content::WebContents* web_contents) {
72 return AppBannerManagerDesktop::FromWebContents(web_contents);
73 }
74
DisableTriggeringForTesting()75 void AppBannerManagerDesktop::DisableTriggeringForTesting() {
76 gDisableTriggeringForTesting = true;
77 }
78
79 TestAppBannerManagerDesktop*
AsTestAppBannerManagerDesktopForTesting()80 AppBannerManagerDesktop::AsTestAppBannerManagerDesktopForTesting() {
81 return nullptr;
82 }
83
AppBannerManagerDesktop(content::WebContents * web_contents)84 AppBannerManagerDesktop::AppBannerManagerDesktop(
85 content::WebContents* web_contents)
86 : AppBannerManager(web_contents) {
87 Profile* profile =
88 Profile::FromBrowserContext(web_contents->GetBrowserContext());
89 extension_registry_ = extensions::ExtensionRegistry::Get(profile);
90 auto* provider = web_app::WebAppProviderBase::GetProviderBase(profile);
91 // May be null in unit tests e.g. TabDesktopMediaListTest.*.
92 if (provider)
93 registrar_observer_.Add(&provider->registrar());
94 }
95
~AppBannerManagerDesktop()96 AppBannerManagerDesktop::~AppBannerManagerDesktop() { }
97
GetWeakPtr()98 base::WeakPtr<AppBannerManager> AppBannerManagerDesktop::GetWeakPtr() {
99 return weak_factory_.GetWeakPtr();
100 }
101
InvalidateWeakPtrs()102 void AppBannerManagerDesktop::InvalidateWeakPtrs() {
103 weak_factory_.InvalidateWeakPtrs();
104 }
105
IsSupportedNonWebAppPlatform(const base::string16 & platform) const106 bool AppBannerManagerDesktop::IsSupportedNonWebAppPlatform(
107 const base::string16& platform) const {
108 if (base::EqualsASCII(platform, kPlatformChromeWebStore))
109 return true;
110
111 #if defined(OS_CHROMEOS)
112 if (base::EqualsASCII(platform, kPlatformPlay) &&
113 arc::IsArcAllowedForProfile(
114 Profile::FromBrowserContext(web_contents()->GetBrowserContext()))) {
115 return true;
116 }
117 #endif // defined(OS_CHROMEOS)
118
119 return false;
120 }
121
IsRelatedNonWebAppInstalled(const blink::Manifest::RelatedApplication & related_app) const122 bool AppBannerManagerDesktop::IsRelatedNonWebAppInstalled(
123 const blink::Manifest::RelatedApplication& related_app) const {
124 if (!related_app.id || related_app.id->empty() || !related_app.platform ||
125 related_app.platform->empty()) {
126 return false;
127 }
128
129 const std::string id = base::UTF16ToUTF8(*related_app.id);
130 const base::string16& platform = *related_app.platform;
131
132 if (base::EqualsASCII(platform, kPlatformChromeWebStore)) {
133 return extension_registry_->GetExtensionById(
134 id, extensions::ExtensionRegistry::ENABLED) != nullptr;
135 }
136
137 #if defined(OS_CHROMEOS)
138 if (base::EqualsASCII(platform, kPlatformPlay)) {
139 ArcAppListPrefs* arc_app_list_prefs =
140 ArcAppListPrefs::Get(web_contents()->GetBrowserContext());
141 return arc_app_list_prefs && arc_app_list_prefs->GetPackage(id) != nullptr;
142 }
143 #endif // defined(OS_CHROMEOS)
144
145 return false;
146 }
147
registrar()148 web_app::AppRegistrar& AppBannerManagerDesktop::registrar() {
149 auto* provider = web_app::WebAppProviderBase::GetProviderBase(
150 Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
151 DCHECK(provider);
152 return provider->registrar();
153 }
154
155 // TODO(https://crbug.com/930612): Move out into a more general purpose
156 // installability check class.
IsExternallyInstalledWebApp()157 bool AppBannerManagerDesktop::IsExternallyInstalledWebApp() {
158 // Public method, so ensure processing is finished before using manifest.
159 if (manifest_.start_url.is_valid()) {
160 // Use manifest as source of truth if available.
161 web_app::AppId manifest_app_id =
162 web_app::GenerateAppIdFromURL(manifest_.start_url);
163 // TODO(crbug.com/1090182): Make HasExternalApp imply IsLocallyInstalled.
164 return registrar().IsLocallyInstalled(manifest_app_id) &&
165 registrar().HasExternalApp(manifest_app_id);
166 }
167
168 // Check URL wouldn't collide with an external app's install URL.
169 const GURL& url = web_contents()->GetLastCommittedURL();
170 base::Optional<web_app::AppId> external_app_id =
171 registrar().LookupExternalAppId(url);
172 // TODO(crbug.com/1090182): Make LookupExternalAppId imply IsLocallyInstalled.
173 if (external_app_id && registrar().IsLocallyInstalled(*external_app_id))
174 return true;
175
176 // Check an app created for this page wouldn't collide with any external app.
177 web_app::AppId possible_app_id = web_app::GenerateAppIdFromURL(url);
178 // TODO(crbug.com/1090182): Make HasExternalApp imply IsLocallyInstalled.
179 return registrar().IsLocallyInstalled(possible_app_id) &&
180 registrar().HasExternalApp(possible_app_id);
181 }
182
ShouldAllowWebAppReplacementInstall()183 bool AppBannerManagerDesktop::ShouldAllowWebAppReplacementInstall() {
184 // Only allow replacement install if this specific app is already installed.
185 web_app::AppId app_id = web_app::GenerateAppIdFromURL(manifest_.start_url);
186 if (!registrar().IsLocallyInstalled(app_id))
187 return false;
188
189 if (IsExternallyInstalledWebApp())
190 return false;
191 auto display_mode = registrar().GetAppUserDisplayMode(app_id);
192 return display_mode == blink::mojom::DisplayMode::kBrowser;
193 }
194
ShowBannerUi(WebappInstallSource install_source)195 void AppBannerManagerDesktop::ShowBannerUi(WebappInstallSource install_source) {
196 RecordDidShowBanner();
197 TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_CREATED);
198 ReportStatus(SHOWING_APP_INSTALLATION_DIALOG);
199 CreateWebApp(install_source);
200 }
201
DidFinishLoad(content::RenderFrameHost * render_frame_host,const GURL & validated_url)202 void AppBannerManagerDesktop::DidFinishLoad(
203 content::RenderFrameHost* render_frame_host,
204 const GURL& validated_url) {
205 if (gDisableTriggeringForTesting)
206 return;
207
208 AppBannerManager::DidFinishLoad(render_frame_host, validated_url);
209 }
210
OnEngagementEvent(content::WebContents * web_contents,const GURL & url,double score,SiteEngagementService::EngagementType type)211 void AppBannerManagerDesktop::OnEngagementEvent(
212 content::WebContents* web_contents,
213 const GURL& url,
214 double score,
215 SiteEngagementService::EngagementType type) {
216 if (gDisableTriggeringForTesting)
217 return;
218
219 AppBannerManager::OnEngagementEvent(web_contents, url, score, type);
220 }
221
OnWebAppInstalled(const web_app::AppId & installed_app_id)222 void AppBannerManagerDesktop::OnWebAppInstalled(
223 const web_app::AppId& installed_app_id) {
224 base::Optional<web_app::AppId> app_id =
225 registrar().FindAppWithUrlInScope(validated_url_);
226 if (app_id.has_value() && *app_id == installed_app_id &&
227 registrar().GetAppUserDisplayMode(*app_id) ==
228 blink::mojom::DisplayMode::kStandalone) {
229 OnInstall(registrar().GetEffectiveDisplayModeFromManifest(*app_id));
230 SetInstallableWebAppCheckResult(InstallableWebAppCheckResult::kNo);
231 }
232 }
233
OnAppRegistrarDestroyed()234 void AppBannerManagerDesktop::OnAppRegistrarDestroyed() {
235 registrar_observer_.RemoveAll();
236 }
237
CreateWebApp(WebappInstallSource install_source)238 void AppBannerManagerDesktop::CreateWebApp(WebappInstallSource install_source) {
239 content::WebContents* contents = web_contents();
240 DCHECK(contents);
241
242 // TODO(loyso): Take appropriate action if WebApps disabled for profile.
243 web_app::CreateWebAppFromManifest(
244 contents, /*bypass_service_worker_check=*/false, install_source,
245 base::BindOnce(&AppBannerManagerDesktop::DidFinishCreatingWebApp,
246 weak_factory_.GetWeakPtr()));
247 }
248
DidFinishCreatingWebApp(const web_app::AppId & app_id,web_app::InstallResultCode code)249 void AppBannerManagerDesktop::DidFinishCreatingWebApp(
250 const web_app::AppId& app_id,
251 web_app::InstallResultCode code) {
252 content::WebContents* contents = web_contents();
253 if (!contents)
254 return;
255
256 // Catch only kSuccessNewInstall and kUserInstallDeclined. Report nothing on
257 // all other errors.
258 if (code == web_app::InstallResultCode::kSuccessNewInstall) {
259 SendBannerAccepted();
260 TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED);
261 AppBannerSettingsHelper::RecordBannerInstallEvent(contents,
262 GetAppIdentifier());
263 } else if (code == web_app::InstallResultCode::kUserInstallDeclined) {
264 SendBannerDismissed();
265 TrackUserResponse(USER_RESPONSE_WEB_APP_DISMISSED);
266 AppBannerSettingsHelper::RecordBannerDismissEvent(contents,
267 GetAppIdentifier());
268 }
269 }
270
271 WEB_CONTENTS_USER_DATA_KEY_IMPL(AppBannerManagerDesktop)
272
273 } // namespace banners
274