[Sync] Rename PSS::IsSyncEnabled to PSS::IsSyncAllowedByFlag.
[chromium-blink-merge.git] / chrome / browser / banners / app_banner_data_fetcher.cc
blob8f973ce45678d382aa8013f8ff63d597bc113674
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.
5 #include "chrome/browser/banners/app_banner_data_fetcher.h"
7 #include "base/bind.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/banners/app_banner_debug_log.h"
11 #include "chrome/browser/banners/app_banner_metrics.h"
12 #include "chrome/browser/banners/app_banner_settings_helper.h"
13 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/infobars/infobar_service.h"
16 #include "chrome/browser/manifest/manifest_icon_selector.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/render_messages.h"
19 #include "components/infobars/core/infobar.h"
20 #include "components/rappor/rappor_utils.h"
21 #include "content/public/browser/browser_context.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/navigation_details.h"
24 #include "content/public/browser/render_frame_host.h"
25 #include "content/public/browser/service_worker_context.h"
26 #include "content/public/browser/storage_partition.h"
27 #include "net/base/load_flags.h"
28 #include "third_party/WebKit/public/platform/modules/app_banner/WebAppBannerPromptReply.h"
29 #include "ui/gfx/screen.h"
31 namespace {
33 base::TimeDelta gTimeDeltaForTesting;
34 int gCurrentRequestID = -1;
36 // The requirement for now is an image/png that is at least 144x144.
37 const int kIconMinimumSize = 144;
38 bool DoesManifestContainRequiredIcon(const content::Manifest& manifest) {
39 for (const auto& icon : manifest.icons) {
40 if (!EqualsASCII(icon.type.string(), "image/png"))
41 continue;
43 for (const auto& size : icon.sizes) {
44 if (size.IsEmpty()) // "any"
45 return true;
46 if (size.width() >= kIconMinimumSize && size.height() >= kIconMinimumSize)
47 return true;
51 return false;
54 } // anonymous namespace
56 namespace banners {
58 // static
59 base::Time AppBannerDataFetcher::GetCurrentTime() {
60 return base::Time::Now() + gTimeDeltaForTesting;
63 // static
64 void AppBannerDataFetcher::SetTimeDeltaForTesting(int days) {
65 gTimeDeltaForTesting = base::TimeDelta::FromDays(days);
68 AppBannerDataFetcher::AppBannerDataFetcher(
69 content::WebContents* web_contents,
70 base::WeakPtr<Delegate> delegate,
71 int ideal_icon_size)
72 : WebContentsObserver(web_contents),
73 ideal_icon_size_(ideal_icon_size),
74 weak_delegate_(delegate),
75 is_active_(false),
76 event_request_id_(-1) {
79 void AppBannerDataFetcher::Start(const GURL& validated_url) {
80 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
82 content::WebContents* web_contents = GetWebContents();
83 DCHECK(web_contents);
85 is_active_ = true;
86 validated_url_ = validated_url;
87 web_contents->GetManifest(
88 base::Bind(&AppBannerDataFetcher::OnDidGetManifest, this));
91 void AppBannerDataFetcher::Cancel() {
92 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
93 if (is_active_) {
94 FOR_EACH_OBSERVER(Observer, observer_list_,
95 OnDecidedWhetherToShow(this, false));
96 is_active_ = false;
100 void AppBannerDataFetcher::ReplaceWebContents(
101 content::WebContents* web_contents) {
102 Observe(web_contents);
105 void AppBannerDataFetcher::AddObserverForTesting(Observer* observer) {
106 observer_list_.AddObserver(observer);
109 void AppBannerDataFetcher::RemoveObserverForTesting(Observer* observer) {
110 observer_list_.RemoveObserver(observer);
113 void AppBannerDataFetcher::WebContentsDestroyed() {
114 Cancel();
115 Observe(nullptr);
118 void AppBannerDataFetcher::DidNavigateMainFrame(
119 const content::LoadCommittedDetails& details,
120 const content::FrameNavigateParams& params) {
121 if (!details.is_in_page)
122 Cancel();
125 bool AppBannerDataFetcher::OnMessageReceived(
126 const IPC::Message& message, content::RenderFrameHost* render_frame_host) {
127 bool handled = true;
129 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(AppBannerDataFetcher, message,
130 render_frame_host)
131 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AppBannerPromptReply,
132 OnBannerPromptReply)
133 IPC_MESSAGE_UNHANDLED(handled = false)
134 IPC_END_MESSAGE_MAP()
136 return handled;
139 void AppBannerDataFetcher::OnBannerPromptReply(
140 content::RenderFrameHost* render_frame_host,
141 int request_id,
142 blink::WebAppBannerPromptReply reply) {
143 content::WebContents* web_contents = GetWebContents();
144 if (!CheckFetcherIsStillAlive(web_contents) ||
145 request_id != event_request_id_) {
146 Cancel();
147 return;
150 // The renderer might have requested the prompt to be canceled.
151 if (reply == blink::WebAppBannerPromptReply::Cancel) {
152 OutputDeveloperNotShownMessage(web_contents, kRendererRequestCancel);
153 Cancel();
154 return;
157 // Definitely going to show the banner now.
158 FOR_EACH_OBSERVER(Observer, observer_list_,
159 OnDecidedWhetherToShow(this, true));
161 infobars::InfoBar* infobar = CreateBanner(app_icon_.get(), app_title_);
162 if (infobar) {
163 InfoBarService::FromWebContents(web_contents)->AddInfoBar(
164 make_scoped_ptr(infobar));
166 is_active_ = false;
169 AppBannerDataFetcher::~AppBannerDataFetcher() {
170 FOR_EACH_OBSERVER(Observer, observer_list_, OnFetcherDestroyed(this));
173 std::string AppBannerDataFetcher::GetBannerType() {
174 return "web";
177 content::WebContents* AppBannerDataFetcher::GetWebContents() {
178 if (!web_contents() || web_contents()->IsBeingDestroyed())
179 return nullptr;
180 return web_contents();
183 std::string AppBannerDataFetcher::GetAppIdentifier() {
184 DCHECK(!web_app_data_.IsEmpty());
185 return web_app_data_.start_url.spec();
188 bool AppBannerDataFetcher::FetchIcon(const GURL& image_url) {
189 content::WebContents* web_contents = GetWebContents();
190 DCHECK(web_contents);
192 // Begin asynchronously fetching the app icon. AddRef() is done before the
193 // fetch to ensure that the AppBannerDataFetcher isn't deleted before the
194 // BitmapFetcher has called OnFetchComplete() (where the references are
195 // decremented).
196 AddRef();
197 Profile* profile =
198 Profile::FromBrowserContext(web_contents->GetBrowserContext());
199 bitmap_fetcher_.reset(new chrome::BitmapFetcher(image_url, this));
200 bitmap_fetcher_->Start(
201 profile->GetRequestContext(),
202 std::string(),
203 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
204 net::LOAD_NORMAL);
205 return true;
208 infobars::InfoBar* AppBannerDataFetcher::CreateBanner(
209 const SkBitmap* icon,
210 const base::string16& title) {
211 content::WebContents* web_contents = GetWebContents();
212 DCHECK(web_contents && !web_app_data_.IsEmpty());
214 // TODO(dfalcantara): Desktop doesn't display app banners, yet. Just pretend
215 // that a banner was shown for testing purposes.
216 RecordDidShowBanner("AppBanner.WebApp.Shown");
217 return nullptr;
220 void AppBannerDataFetcher::RecordDidShowBanner(const std::string& event_name) {
221 content::WebContents* web_contents = GetWebContents();
222 DCHECK(web_contents);
224 AppBannerSettingsHelper::RecordBannerEvent(
225 web_contents, validated_url_, GetAppIdentifier(),
226 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW,
227 GetCurrentTime());
228 rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
229 event_name,
230 web_contents->GetURL());
231 banners::TrackDisplayEvent(DISPLAY_EVENT_CREATED);
234 void AppBannerDataFetcher::OnDidGetManifest(
235 const content::Manifest& manifest) {
236 content::WebContents* web_contents = GetWebContents();
237 if (!CheckFetcherIsStillAlive(web_contents)) {
238 Cancel();
239 return;
241 if (manifest.IsEmpty()) {
242 OutputDeveloperNotShownMessage(web_contents, kManifestEmpty);
243 Cancel();
244 return;
247 if (manifest.prefer_related_applications &&
248 manifest.related_applications.size()) {
249 for (const auto& application : manifest.related_applications) {
250 std::string platform = base::UTF16ToUTF8(application.platform.string());
251 std::string id = base::UTF16ToUTF8(application.id.string());
252 if (weak_delegate_->HandleNonWebApp(platform, application.url, id))
253 return;
257 if (!IsManifestValidForWebApp(manifest, web_contents)) {
258 Cancel();
259 return;
262 banners::TrackDisplayEvent(DISPLAY_EVENT_BANNER_REQUESTED);
264 web_app_data_ = manifest;
265 app_title_ = web_app_data_.name.string();
267 // Check to see if there is a single service worker controlling this page
268 // and the manifest's start url.
269 Profile* profile =
270 Profile::FromBrowserContext(web_contents->GetBrowserContext());
271 content::StoragePartition* storage_partition =
272 content::BrowserContext::GetStoragePartition(
273 profile, web_contents->GetSiteInstance());
274 DCHECK(storage_partition);
276 storage_partition->GetServiceWorkerContext()->CheckHasServiceWorker(
277 validated_url_, manifest.start_url,
278 base::Bind(&AppBannerDataFetcher::OnDidCheckHasServiceWorker,
279 this));
282 void AppBannerDataFetcher::OnDidCheckHasServiceWorker(
283 bool has_service_worker) {
284 content::WebContents* web_contents = GetWebContents();
285 if (!CheckFetcherIsStillAlive(web_contents)) {
286 Cancel();
287 return;
290 if (has_service_worker) {
291 // Create an infobar to promote the manifest's app.
292 GURL icon_url =
293 ManifestIconSelector::FindBestMatchingIcon(
294 web_app_data_.icons,
295 ideal_icon_size_,
296 gfx::Screen::GetScreenFor(web_contents->GetNativeView()));
297 if (!icon_url.is_empty()) {
298 FetchIcon(icon_url);
299 return;
301 OutputDeveloperNotShownMessage(web_contents, kCannotDetermineBestIcon);
302 } else {
303 TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER);
304 OutputDeveloperNotShownMessage(web_contents, kNoMatchingServiceWorker);
307 Cancel();
310 void AppBannerDataFetcher::OnFetchComplete(const GURL& url,
311 const SkBitmap* icon) {
312 if (is_active_)
313 ShowBanner(icon);
315 Release();
318 void AppBannerDataFetcher::ShowBanner(const SkBitmap* icon) {
319 content::WebContents* web_contents = GetWebContents();
320 if (!CheckFetcherIsStillAlive(web_contents)) {
321 Cancel();
322 return;
324 if (!icon) {
325 OutputDeveloperNotShownMessage(web_contents, kNoIconAvailable);
326 Cancel();
327 return;
330 RecordCouldShowBanner();
331 if (!CheckIfShouldShowBanner()) {
332 // At this point, the only possible case is that the banner has been added
333 // to the homescreen, given all of the other checks that have been made.
334 OutputDeveloperNotShownMessage(web_contents, kBannerAlreadyAdded);
335 Cancel();
336 return;
339 app_icon_.reset(new SkBitmap(*icon));
340 event_request_id_ = ++gCurrentRequestID;
341 web_contents->GetMainFrame()->Send(
342 new ChromeViewMsg_AppBannerPromptRequest(
343 web_contents->GetMainFrame()->GetRoutingID(),
344 event_request_id_,
345 GetBannerType()));
348 void AppBannerDataFetcher::RecordCouldShowBanner() {
349 content::WebContents* web_contents = GetWebContents();
350 DCHECK(web_contents);
352 AppBannerSettingsHelper::RecordBannerEvent(
353 web_contents, validated_url_, GetAppIdentifier(),
354 AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW,
355 GetCurrentTime());
358 bool AppBannerDataFetcher::CheckIfShouldShowBanner() {
359 content::WebContents* web_contents = GetWebContents();
360 DCHECK(web_contents);
362 return AppBannerSettingsHelper::ShouldShowBanner(
363 web_contents, validated_url_, GetAppIdentifier(), GetCurrentTime());
366 bool AppBannerDataFetcher::CheckFetcherIsStillAlive(
367 content::WebContents* web_contents) {
368 if (!is_active_) {
369 OutputDeveloperNotShownMessage(web_contents,
370 kUserNavigatedBeforeBannerShown);
371 return false;
373 if (!web_contents) {
374 return false; // We cannot show a message if |web_contents| is null
376 return true;
379 // static
380 bool AppBannerDataFetcher::IsManifestValidForWebApp(
381 const content::Manifest& manifest,
382 content::WebContents* web_contents) {
383 if (manifest.IsEmpty()) {
384 OutputDeveloperNotShownMessage(web_contents, kManifestEmpty);
385 return false;
387 if (!manifest.start_url.is_valid()) {
388 OutputDeveloperNotShownMessage(web_contents, kStartURLNotValid);
389 return false;
391 if (manifest.name.is_null() && manifest.short_name.is_null()) {
392 OutputDeveloperNotShownMessage(web_contents,
393 kManifestMissingNameOrShortName);
394 return false;
396 if (!DoesManifestContainRequiredIcon(manifest)) {
397 OutputDeveloperNotShownMessage(web_contents, kManifestMissingSuitableIcon);
398 return false;
400 return true;
403 } // namespace banners