Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(125)

Side by Side Diff: chrome/browser/banners/app_banner_data_fetcher.cc

Issue 2156113002: Replace AppBannerDataFetcher with InstallableManager. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@banner-refactor
Patch Set: Naming, includes Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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_data_fetcher.h"
6
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/lazy_instance.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/banners/app_banner_debug_log.h"
14 #include "chrome/browser/banners/app_banner_metrics.h"
15 #include "chrome/browser/banners/app_banner_settings_helper.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/manifest/manifest_icon_downloader.h"
18 #include "chrome/browser/manifest/manifest_icon_selector.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/render_messages.h"
22 #include "components/rappor/rappor_utils.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/navigation_details.h"
26 #include "content/public/browser/render_frame_host.h"
27 #include "content/public/browser/service_worker_context.h"
28 #include "content/public/browser/storage_partition.h"
29 #include "net/base/load_flags.h"
30 #include "third_party/WebKit/public/platform/WebDisplayMode.h"
31 #include "third_party/WebKit/public/platform/modules/app_banner/WebAppBannerProm ptReply.h"
32
33 namespace {
34
35 base::LazyInstance<base::TimeDelta> gTimeDeltaForTesting =
36 LAZY_INSTANCE_INITIALIZER;
37 int gCurrentRequestID = -1;
38 const char kPngExtension[] = ".png";
39
40 // The requirement for now is an image/png that is at least 144x144.
41 const int kIconMinimumSize = 144;
42 bool DoesManifestContainRequiredIcon(const content::Manifest& manifest) {
43 for (const auto& icon : manifest.icons) {
44 // The type field is optional. If it isn't present, fall back on checking
45 // the src extension, and allow the icon if the extension ends with png.
46 if (!base::EqualsASCII(icon.type.string(), "image/png") &&
47 !(icon.type.is_null() &&
48 base::EndsWith(icon.src.ExtractFileName(), kPngExtension,
49 base::CompareCase::INSENSITIVE_ASCII)))
50 continue;
51
52 for (const auto& size : icon.sizes) {
53 if (size.IsEmpty()) // "any"
54 return true;
55 if (size.width() >= kIconMinimumSize && size.height() >= kIconMinimumSize)
56 return true;
57 }
58 }
59
60 return false;
61 }
62
63 } // anonymous namespace
64
65 namespace banners {
66
67 // static
68 base::Time AppBannerDataFetcher::GetCurrentTime() {
69 return base::Time::Now() + gTimeDeltaForTesting.Get();
70 }
71
72 // static
73 void AppBannerDataFetcher::SetTimeDeltaForTesting(int days) {
74 gTimeDeltaForTesting.Get() = base::TimeDelta::FromDays(days);
75 }
76
77 AppBannerDataFetcher::AppBannerDataFetcher(content::WebContents* web_contents,
78 base::WeakPtr<Delegate> delegate,
79 int ideal_icon_size_in_dp,
80 int minimum_icon_size_in_dp,
81 bool is_debug_mode)
82 : WebContentsObserver(web_contents),
83 weak_delegate_(delegate),
84 ideal_icon_size_in_dp_(ideal_icon_size_in_dp),
85 minimum_icon_size_in_dp_(minimum_icon_size_in_dp),
86 is_active_(false),
87 was_canceled_by_page_(false),
88 page_requested_prompt_(false),
89 is_debug_mode_(is_debug_mode ||
90 base::CommandLine::ForCurrentProcess()->HasSwitch(
91 switches::kBypassAppBannerEngagementChecks)),
92 event_request_id_(-1) {
93 DCHECK(minimum_icon_size_in_dp <= ideal_icon_size_in_dp);
94 }
95
96 void AppBannerDataFetcher::Start(const GURL& validated_url,
97 ui::PageTransition transition_type) {
98 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
99
100 content::WebContents* web_contents = GetWebContents();
101 DCHECK(web_contents);
102
103 is_active_ = true;
104 was_canceled_by_page_ = false;
105 page_requested_prompt_ = false;
106 transition_type_ = transition_type;
107 validated_url_ = validated_url;
108 referrer_.erase();
109 web_contents->GetManifest(
110 base::Bind(&AppBannerDataFetcher::OnDidGetManifest, this));
111 }
112
113 void AppBannerDataFetcher::Cancel() {
114 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
115 if (is_active_) {
116 FOR_EACH_OBSERVER(Observer, observer_list_,
117 OnDecidedWhetherToShow(this, false));
118 if (was_canceled_by_page_ && !page_requested_prompt_) {
119 TrackBeforeInstallEvent(
120 BEFORE_INSTALL_EVENT_PROMPT_NOT_CALLED_AFTER_PREVENT_DEFAULT);
121 }
122
123 is_active_ = false;
124 was_canceled_by_page_ = false;
125 page_requested_prompt_ = false;
126 referrer_.erase();
127 }
128 }
129
130 void AppBannerDataFetcher::ReplaceWebContents(
131 content::WebContents* web_contents) {
132 Observe(web_contents);
133 }
134
135 void AppBannerDataFetcher::AddObserverForTesting(Observer* observer) {
136 observer_list_.AddObserver(observer);
137 }
138
139 void AppBannerDataFetcher::RemoveObserverForTesting(Observer* observer) {
140 observer_list_.RemoveObserver(observer);
141 }
142
143 void AppBannerDataFetcher::WebContentsDestroyed() {
144 Cancel();
145 Observe(nullptr);
146 }
147
148 void AppBannerDataFetcher::DidNavigateMainFrame(
149 const content::LoadCommittedDetails& details,
150 const content::FrameNavigateParams& params) {
151 if (!details.is_in_page)
152 Cancel();
153 }
154
155 bool AppBannerDataFetcher::OnMessageReceived(
156 const IPC::Message& message, content::RenderFrameHost* render_frame_host) {
157 bool handled = true;
158
159 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(AppBannerDataFetcher, message,
160 render_frame_host)
161 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AppBannerPromptReply,
162 OnBannerPromptReply)
163 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RequestShowAppBanner,
164 OnRequestShowAppBanner)
165 IPC_MESSAGE_UNHANDLED(handled = false)
166 IPC_END_MESSAGE_MAP()
167
168 return handled;
169 }
170
171 void AppBannerDataFetcher::OnBannerPromptReply(
172 content::RenderFrameHost* render_frame_host,
173 int request_id,
174 blink::WebAppBannerPromptReply reply,
175 std::string referrer) {
176 content::WebContents* web_contents = GetWebContents();
177 if (!CheckFetcherIsStillAlive(web_contents) ||
178 request_id != event_request_id_) {
179 Cancel();
180 return;
181 }
182
183 // The renderer might have requested the prompt to be canceled.
184 // They may request that it is redisplayed later, so don't Cancel() here.
185 // However, log that the cancelation was requested, so Cancel() can be
186 // called if a redisplay isn't asked for.
187 //
188 // The redisplay request may be received before the Cancel prompt reply
189 // *after* if it is made before the beforeinstallprompt event handler
190 // concludes (e.g. in the event handler itself), so allow the pipeline
191 // to continue in this case.
192 //
193 // Stash the referrer for the case where the banner is redisplayed.
194 if (reply == blink::WebAppBannerPromptReply::Cancel &&
195 !page_requested_prompt_) {
196 TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_PREVENT_DEFAULT_CALLED);
197 was_canceled_by_page_ = true;
198 referrer_ = referrer;
199 OutputDeveloperNotShownMessage(web_contents, kRendererRequestCancel,
200 is_debug_mode_);
201 return;
202 }
203
204 // If we haven't yet returned, but either of |was_canceled_by_page_| or
205 // |page_requested_prompt_| is true, the page has requested a delayed showing
206 // of the prompt. Otherwise, the prompt was never canceled by the page.
207 if (was_canceled_by_page_ || page_requested_prompt_) {
208 TrackBeforeInstallEvent(
209 BEFORE_INSTALL_EVENT_PROMPT_CALLED_AFTER_PREVENT_DEFAULT);
210 was_canceled_by_page_ = false;
211 } else {
212 TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_NO_ACTION);
213 }
214
215 AppBannerSettingsHelper::RecordMinutesFromFirstVisitToShow(
216 web_contents, validated_url_, GetAppIdentifier(), GetCurrentTime());
217
218 // Definitely going to show the banner now.
219 FOR_EACH_OBSERVER(Observer, observer_list_,
220 OnDecidedWhetherToShow(this, true));
221
222 TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_COMPLETE);
223 ShowBanner(app_icon_url_, app_icon_.get(), app_title_, referrer);
224 is_active_ = false;
225 }
226
227 void AppBannerDataFetcher::OnRequestShowAppBanner(
228 content::RenderFrameHost* render_frame_host,
229 int request_id) {
230 if (was_canceled_by_page_) {
231 // Simulate an "OK" from the website to restart the banner display pipeline.
232 // Don't reset |was_canceled_by_page_| yet for metrics purposes.
233 OnBannerPromptReply(render_frame_host, request_id,
234 blink::WebAppBannerPromptReply::None, referrer_);
235 } else {
236 // Log that the prompt request was made for when we get the prompt reply.
237 page_requested_prompt_ = true;
238 }
239 }
240
241 AppBannerDataFetcher::~AppBannerDataFetcher() {
242 FOR_EACH_OBSERVER(Observer, observer_list_, OnFetcherDestroyed(this));
243 }
244
245 std::string AppBannerDataFetcher::GetBannerType() {
246 return "web";
247 }
248
249 content::WebContents* AppBannerDataFetcher::GetWebContents() {
250 if (!web_contents() || web_contents()->IsBeingDestroyed())
251 return nullptr;
252 return web_contents();
253 }
254
255 std::string AppBannerDataFetcher::GetAppIdentifier() {
256 DCHECK(!manifest_.IsEmpty());
257 return manifest_.start_url.spec();
258 }
259
260 void AppBannerDataFetcher::RecordDidShowBanner(const std::string& event_name) {
261 content::WebContents* web_contents = GetWebContents();
262 DCHECK(web_contents);
263
264 AppBannerSettingsHelper::RecordBannerEvent(
265 web_contents, validated_url_, GetAppIdentifier(),
266 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW,
267 GetCurrentTime());
268 rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
269 event_name,
270 web_contents->GetURL());
271 }
272
273 void AppBannerDataFetcher::OnDidGetManifest(
274 const GURL& manifest_url,
275 const content::Manifest& manifest) {
276 content::WebContents* web_contents = GetWebContents();
277 if (!CheckFetcherIsStillAlive(web_contents)) {
278 Cancel();
279 return;
280 }
281 if (manifest_url.is_empty()) {
282 OutputDeveloperNotShownMessage(web_contents, kNoManifest, is_debug_mode_);
283 Cancel();
284 return;
285 }
286 if (manifest.IsEmpty()) {
287 OutputDeveloperNotShownMessage(web_contents, kManifestEmpty,
288 is_debug_mode_);
289 Cancel();
290 return;
291 }
292
293 if (manifest.prefer_related_applications &&
294 manifest.related_applications.size()) {
295 for (const auto& application : manifest.related_applications) {
296 std::string platform = base::UTF16ToUTF8(application.platform.string());
297 std::string id = base::UTF16ToUTF8(application.id.string());
298 if (weak_delegate_->HandleNonWebApp(platform, application.url, id,
299 is_debug_mode_))
300 return;
301 }
302 }
303
304 if (!IsManifestValidForWebApp(manifest, web_contents, is_debug_mode_)) {
305 Cancel();
306 return;
307 }
308
309 // Since the manifest is valid, one of short name or name must be non-null.
310 // Prefer name if it isn't null.
311 manifest_url_ = manifest_url;
312 manifest_ = manifest;
313 app_title_ = (manifest_.name.is_null()) ? manifest_.short_name.string()
314 : manifest_.name.string();
315
316 if (IsWebAppInstalled(web_contents->GetBrowserContext(),
317 manifest.start_url) &&
318 !is_debug_mode_) {
319 Cancel();
320 return;
321 }
322
323 banners::TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_REQUESTED);
324
325 // Check to see if there is a single service worker controlling this page
326 // and the manifest's start url.
327 Profile* profile =
328 Profile::FromBrowserContext(web_contents->GetBrowserContext());
329 content::StoragePartition* storage_partition =
330 content::BrowserContext::GetStoragePartition(
331 profile, web_contents->GetSiteInstance());
332 DCHECK(storage_partition);
333
334 storage_partition->GetServiceWorkerContext()->CheckHasServiceWorker(
335 validated_url_, manifest.start_url,
336 base::Bind(&AppBannerDataFetcher::OnDidCheckHasServiceWorker,
337 this));
338 }
339
340 void AppBannerDataFetcher::OnDidCheckHasServiceWorker(
341 bool has_service_worker) {
342 content::WebContents* web_contents = GetWebContents();
343 if (!CheckFetcherIsStillAlive(web_contents)) {
344 Cancel();
345 return;
346 }
347
348 if (!has_service_worker) {
349 TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER);
350 OutputDeveloperNotShownMessage(web_contents, kNoMatchingServiceWorker,
351 is_debug_mode_);
352 Cancel();
353 return;
354 }
355
356 OnHasServiceWorker(web_contents);
357 }
358
359 void AppBannerDataFetcher::OnHasServiceWorker(
360 content::WebContents* web_contents) {
361 GURL icon_url = ManifestIconSelector::FindBestMatchingIcon(
362 manifest_.icons, ideal_icon_size_in_dp_, minimum_icon_size_in_dp_);
363
364 if (icon_url.is_empty()) {
365 OutputDeveloperNotShownMessage(
366 web_contents,
367 kNoIconMatchingRequirements,
368 base::IntToString(ManifestIconSelector::ConvertIconSizeFromDpToPx(
369 minimum_icon_size_in_dp_)),
370 is_debug_mode_);
371 Cancel();
372 } else if (!FetchAppIcon(web_contents, icon_url)) {
373 OutputDeveloperNotShownMessage(web_contents, kCannotDownloadIcon,
374 is_debug_mode_);
375 Cancel();
376 }
377 }
378
379 bool AppBannerDataFetcher::FetchAppIcon(content::WebContents* web_contents,
380 const GURL& icon_url) {
381 app_icon_url_ = icon_url;
382 return ManifestIconDownloader::Download(
383 web_contents, icon_url, ideal_icon_size_in_dp_, minimum_icon_size_in_dp_,
384 base::Bind(&AppBannerDataFetcher::OnAppIconFetched, this));
385 }
386
387 void AppBannerDataFetcher::OnAppIconFetched(const SkBitmap& bitmap) {
388 if (!is_active_) return;
389
390 content::WebContents* web_contents = GetWebContents();
391 if (!CheckFetcherIsStillAlive(web_contents)) {
392 Cancel();
393 return;
394 }
395 if (bitmap.drawsNothing()) {
396 OutputDeveloperNotShownMessage(web_contents, kNoIconAvailable,
397 is_debug_mode_);
398 Cancel();
399 return;
400 }
401
402 RecordCouldShowBanner();
403 if (!is_debug_mode_ && !CheckIfShouldShowBanner()) {
404 // At this point, the only possible case is that the banner has been added
405 // to the homescreen, given all of the other checks that have been made.
406 Cancel();
407 return;
408 }
409
410 app_icon_.reset(new SkBitmap(bitmap));
411 event_request_id_ = ++gCurrentRequestID;
412
413 TrackBeforeInstallEvent(BEFORE_INSTALL_EVENT_CREATED);
414 web_contents->GetMainFrame()->Send(
415 new ChromeViewMsg_AppBannerPromptRequest(
416 web_contents->GetMainFrame()->GetRoutingID(),
417 event_request_id_,
418 GetBannerType()));
419 }
420
421 bool AppBannerDataFetcher::IsWebAppInstalled(
422 content::BrowserContext* browser_context,
423 const GURL& start_url) {
424 return false;
425 }
426
427 void AppBannerDataFetcher::RecordCouldShowBanner() {
428 content::WebContents* web_contents = GetWebContents();
429 DCHECK(web_contents);
430
431 AppBannerSettingsHelper::RecordBannerCouldShowEvent(
432 web_contents, validated_url_, GetAppIdentifier(),
433 GetCurrentTime(), transition_type_);
434 }
435
436 bool AppBannerDataFetcher::CheckIfShouldShowBanner() {
437 content::WebContents* web_contents = GetWebContents();
438 DCHECK(web_contents);
439
440 return AppBannerSettingsHelper::ShouldShowBanner(
441 web_contents, validated_url_, GetAppIdentifier(), GetCurrentTime());
442 }
443
444 bool AppBannerDataFetcher::CheckFetcherIsStillAlive(
445 content::WebContents* web_contents) {
446 if (!is_active_) {
447 OutputDeveloperNotShownMessage(
448 web_contents, kUserNavigatedBeforeBannerShown, is_debug_mode_);
449 return false;
450 }
451 if (!web_contents) {
452 return false; // We cannot show a message if |web_contents| is null
453 }
454 return true;
455 }
456
457 // static
458 bool AppBannerDataFetcher::IsManifestValidForWebApp(
459 const content::Manifest& manifest,
460 content::WebContents* web_contents,
461 bool is_debug_mode) {
462 if (manifest.IsEmpty()) {
463 OutputDeveloperNotShownMessage(web_contents, kManifestEmpty, is_debug_mode);
464 return false;
465 }
466 if (!manifest.start_url.is_valid()) {
467 OutputDeveloperNotShownMessage(web_contents, kStartURLNotValid,
468 is_debug_mode);
469 return false;
470 }
471 if ((manifest.name.is_null() || manifest.name.string().empty()) &&
472 (manifest.short_name.is_null() || manifest.short_name.string().empty())) {
473 OutputDeveloperNotShownMessage(
474 web_contents, kManifestMissingNameOrShortName, is_debug_mode);
475 return false;
476 }
477
478 // TODO(dominickn,mlamouri): when Chrome supports "minimal-ui", it should be
479 // accepted. If we accept it today, it would fallback to "browser" and make
480 // this check moot. See https://crbug.com/604390
481 if (manifest.display != blink::WebDisplayModeStandalone &&
482 manifest.display != blink::WebDisplayModeFullscreen) {
483 OutputDeveloperNotShownMessage(
484 web_contents, kManifestDisplayStandaloneFullscreen, is_debug_mode);
485 return false;
486 }
487
488 if (!DoesManifestContainRequiredIcon(manifest)) {
489 OutputDeveloperNotShownMessage(web_contents, kManifestMissingSuitableIcon,
490 is_debug_mode);
491 return false;
492 }
493 return true;
494 }
495
496 } // namespace banners
OLDNEW
« no previous file with comments | « chrome/browser/banners/app_banner_data_fetcher.h ('k') | chrome/browser/banners/app_banner_data_fetcher_browsertest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698