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