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"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/banners/app_banner_metrics.h"
10 #include "chrome/browser/banners/app_banner_settings_helper.h"
11 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/infobars/infobar_service.h"
14 #include "chrome/browser/manifest/manifest_icon_selector.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/common/render_messages.h"
17 #include "components/infobars/core/infobar.h"
18 #include "components/rappor/rappor_utils.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/navigation_details.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/service_worker_context.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "net/base/load_flags.h"
26 #include "third_party/WebKit/public/platform/modules/app_banner/WebAppBannerPromptReply.h"
27 #include "ui/gfx/screen.h"
31 base::TimeDelta gTimeDeltaForTesting
;
32 int gCurrentRequestID
= -1;
34 // The requirement for now is an image/png that is at least 144x144.
35 const int kIconMinimumSize
= 144;
36 bool DoesManifestContainRequiredIcon(const content::Manifest
& manifest
) {
37 for (const auto& icon
: manifest
.icons
) {
38 if (!EqualsASCII(icon
.type
.string(), "image/png"))
41 for (const auto& size
: icon
.sizes
) {
42 if (size
.IsEmpty()) // "any"
44 if (size
.width() >= kIconMinimumSize
&& size
.height() >= kIconMinimumSize
)
52 } // anonymous namespace
57 base::Time
AppBannerDataFetcher::GetCurrentTime() {
58 return base::Time::Now() + gTimeDeltaForTesting
;
62 void AppBannerDataFetcher::SetTimeDeltaForTesting(int days
) {
63 gTimeDeltaForTesting
= base::TimeDelta::FromDays(days
);
66 AppBannerDataFetcher::AppBannerDataFetcher(
67 content::WebContents
* web_contents
,
68 base::WeakPtr
<Delegate
> delegate
,
70 : WebContentsObserver(web_contents
),
71 ideal_icon_size_(ideal_icon_size
),
72 weak_delegate_(delegate
),
76 void AppBannerDataFetcher::Start(const GURL
& validated_url
) {
77 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
79 content::WebContents
* web_contents
= GetWebContents();
83 validated_url_
= validated_url
;
84 web_contents
->GetManifest(
85 base::Bind(&AppBannerDataFetcher::OnDidGetManifest
, this));
88 void AppBannerDataFetcher::Cancel() {
89 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
91 FOR_EACH_OBSERVER(Observer
, observer_list_
,
92 OnDecidedWhetherToShow(this, false));
97 void AppBannerDataFetcher::ReplaceWebContents(
98 content::WebContents
* web_contents
) {
99 Observe(web_contents
);
102 void AppBannerDataFetcher::AddObserverForTesting(Observer
* observer
) {
103 observer_list_
.AddObserver(observer
);
106 void AppBannerDataFetcher::RemoveObserverForTesting(Observer
* observer
) {
107 observer_list_
.RemoveObserver(observer
);
110 void AppBannerDataFetcher::WebContentsDestroyed() {
115 void AppBannerDataFetcher::DidNavigateMainFrame(
116 const content::LoadCommittedDetails
& details
,
117 const content::FrameNavigateParams
& params
) {
118 if (!details
.is_in_page
)
122 bool AppBannerDataFetcher::OnMessageReceived(
123 const IPC::Message
& message
, content::RenderFrameHost
* render_frame_host
) {
126 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(AppBannerDataFetcher
, message
,
128 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AppBannerPromptReply
,
130 IPC_MESSAGE_UNHANDLED(handled
= false)
131 IPC_END_MESSAGE_MAP()
136 void AppBannerDataFetcher::OnBannerPromptReply(
137 content::RenderFrameHost
* render_frame_host
,
139 blink::WebAppBannerPromptReply reply
) {
140 content::WebContents
* web_contents
= GetWebContents();
141 if (!is_active_
|| !web_contents
|| request_id
!= gCurrentRequestID
) {
146 // The renderer might have requested the prompt to be canceled.
147 if (reply
== blink::WebAppBannerPromptReply::Cancel
) {
148 // TODO(mlamouri,benwells): we should probably record that to behave
149 // differently with regard to showing the banner.
154 // Definitely going to show the banner now.
155 FOR_EACH_OBSERVER(Observer
, observer_list_
,
156 OnDecidedWhetherToShow(this, true));
158 infobars::InfoBar
* infobar
= CreateBanner(app_icon_
.get(), app_title_
);
160 InfoBarService::FromWebContents(web_contents
)->AddInfoBar(
161 make_scoped_ptr(infobar
));
166 AppBannerDataFetcher::~AppBannerDataFetcher() {
167 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnFetcherDestroyed(this));
170 std::string
AppBannerDataFetcher::GetBannerType() {
174 content::WebContents
* AppBannerDataFetcher::GetWebContents() {
175 if (!web_contents() || web_contents()->IsBeingDestroyed())
177 return web_contents();
180 std::string
AppBannerDataFetcher::GetAppIdentifier() {
181 DCHECK(!web_app_data_
.IsEmpty());
182 return web_app_data_
.start_url
.spec();
185 bool AppBannerDataFetcher::FetchIcon(const GURL
& image_url
) {
186 content::WebContents
* web_contents
= GetWebContents();
187 DCHECK(web_contents
);
189 // Begin asynchronously fetching the app icon. AddRef() is done before the
190 // fetch to ensure that the AppBannerDataFetcher isn't deleted before the
191 // BitmapFetcher has called OnFetchComplete() (where the references are
195 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
196 bitmap_fetcher_
.reset(new chrome::BitmapFetcher(image_url
, this));
197 bitmap_fetcher_
->Start(
198 profile
->GetRequestContext(),
200 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE
,
205 infobars::InfoBar
* AppBannerDataFetcher::CreateBanner(
206 const SkBitmap
* icon
,
207 const base::string16
& title
) {
208 content::WebContents
* web_contents
= GetWebContents();
209 DCHECK(web_contents
&& !web_app_data_
.IsEmpty());
211 // TODO(dfalcantara): Desktop doesn't display app banners, yet. Just pretend
212 // that a banner was shown for testing purposes.
213 RecordDidShowBanner("AppBanner.WebApp.Shown");
217 void AppBannerDataFetcher::RecordDidShowBanner(const std::string
& event_name
) {
218 content::WebContents
* web_contents
= GetWebContents();
219 DCHECK(web_contents
);
221 AppBannerSettingsHelper::RecordBannerEvent(
222 web_contents
, validated_url_
, GetAppIdentifier(),
223 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW
,
225 rappor::SampleDomainAndRegistryFromGURL(g_browser_process
->rappor_service(),
227 web_contents
->GetURL());
228 banners::TrackDisplayEvent(DISPLAY_EVENT_CREATED
);
231 void AppBannerDataFetcher::OnDidGetManifest(
232 const content::Manifest
& manifest
) {
233 content::WebContents
* web_contents
= GetWebContents();
234 if (!is_active_
|| !web_contents
) {
239 if (!IsManifestValid(manifest
)) {
240 if (!weak_delegate_
.get()->OnInvalidManifest(this))
245 banners::TrackDisplayEvent(DISPLAY_EVENT_BANNER_REQUESTED
);
247 web_app_data_
= manifest
;
248 app_title_
= web_app_data_
.name
.string();
250 // Check to see if there is a single service worker controlling this page
251 // and the manifest's start url.
253 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
254 content::StoragePartition
* storage_partition
=
255 content::BrowserContext::GetStoragePartition(
256 profile
, web_contents
->GetSiteInstance());
257 DCHECK(storage_partition
);
259 storage_partition
->GetServiceWorkerContext()->CheckHasServiceWorker(
260 validated_url_
, manifest
.start_url
,
261 base::Bind(&AppBannerDataFetcher::OnDidCheckHasServiceWorker
,
265 void AppBannerDataFetcher::OnDidCheckHasServiceWorker(
266 bool has_service_worker
) {
267 content::WebContents
* web_contents
= GetWebContents();
268 if (!is_active_
|| !web_contents
) {
273 if (has_service_worker
) {
274 // Create an infobar to promote the manifest's app.
276 ManifestIconSelector::FindBestMatchingIcon(
279 gfx::Screen::GetScreenFor(web_contents
->GetNativeView()));
280 if (!icon_url
.is_empty()) {
285 TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER
);
291 void AppBannerDataFetcher::OnFetchComplete(const GURL
& url
,
292 const SkBitmap
* icon
) {
299 void AppBannerDataFetcher::ShowBanner(const SkBitmap
* icon
) {
300 content::WebContents
* web_contents
= GetWebContents();
301 if (!is_active_
|| !web_contents
|| !icon
) {
306 RecordCouldShowBanner();
307 if (!CheckIfShouldShowBanner()) {
312 app_icon_
.reset(new SkBitmap(*icon
));
313 web_contents
->GetMainFrame()->Send(
314 new ChromeViewMsg_AppBannerPromptRequest(
315 web_contents
->GetMainFrame()->GetRoutingID(),
320 void AppBannerDataFetcher::RecordCouldShowBanner() {
321 content::WebContents
* web_contents
= GetWebContents();
322 DCHECK(web_contents
);
324 AppBannerSettingsHelper::RecordBannerEvent(
325 web_contents
, validated_url_
, GetAppIdentifier(),
326 AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW
,
330 bool AppBannerDataFetcher::CheckIfShouldShowBanner() {
331 content::WebContents
* web_contents
= GetWebContents();
332 DCHECK(web_contents
);
334 return AppBannerSettingsHelper::ShouldShowBanner(
335 web_contents
, validated_url_
, GetAppIdentifier(), GetCurrentTime());
339 bool AppBannerDataFetcher::IsManifestValid(
340 const content::Manifest
& manifest
) {
341 if (manifest
.IsEmpty())
343 if (!manifest
.start_url
.is_valid())
345 if (manifest
.name
.is_null() && manifest
.short_name
.is_null())
347 if (!DoesManifestContainRequiredIcon(manifest
))
352 } // namespace banners