[Media Router] Add integration tests and e2e tests for media router and presentation...
[chromium-blink-merge.git] / chrome / browser / banners / app_banner_data_fetcher.cc
blobf0530fe6b6880f006e2655ce758d4831c02df27b
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/manifest/manifest_icon_selector.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/render_messages.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"
29 namespace {
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 (!base::EqualsASCII(icon.type.string(), "image/png"))
39 continue;
41 for (const auto& size : icon.sizes) {
42 if (size.IsEmpty()) // "any"
43 return true;
44 if (size.width() >= kIconMinimumSize && size.height() >= kIconMinimumSize)
45 return true;
49 return false;
52 } // anonymous namespace
54 namespace banners {
56 // static
57 base::Time AppBannerDataFetcher::GetCurrentTime() {
58 return base::Time::Now() + gTimeDeltaForTesting;
61 // static
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,
69 int ideal_icon_size)
70 : WebContentsObserver(web_contents),
71 ideal_icon_size_(ideal_icon_size),
72 weak_delegate_(delegate),
73 is_active_(false),
74 event_request_id_(-1) {
77 void AppBannerDataFetcher::Start(const GURL& validated_url) {
78 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
80 content::WebContents* web_contents = GetWebContents();
81 DCHECK(web_contents);
83 is_active_ = true;
84 validated_url_ = validated_url;
85 web_contents->GetManifest(
86 base::Bind(&AppBannerDataFetcher::OnDidGetManifest, this));
89 void AppBannerDataFetcher::Cancel() {
90 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
91 if (is_active_) {
92 FOR_EACH_OBSERVER(Observer, observer_list_,
93 OnDecidedWhetherToShow(this, false));
94 is_active_ = false;
98 void AppBannerDataFetcher::ReplaceWebContents(
99 content::WebContents* web_contents) {
100 Observe(web_contents);
103 void AppBannerDataFetcher::AddObserverForTesting(Observer* observer) {
104 observer_list_.AddObserver(observer);
107 void AppBannerDataFetcher::RemoveObserverForTesting(Observer* observer) {
108 observer_list_.RemoveObserver(observer);
111 void AppBannerDataFetcher::WebContentsDestroyed() {
112 Cancel();
113 Observe(nullptr);
116 void AppBannerDataFetcher::DidNavigateMainFrame(
117 const content::LoadCommittedDetails& details,
118 const content::FrameNavigateParams& params) {
119 if (!details.is_in_page)
120 Cancel();
123 bool AppBannerDataFetcher::OnMessageReceived(
124 const IPC::Message& message, content::RenderFrameHost* render_frame_host) {
125 bool handled = true;
127 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(AppBannerDataFetcher, message,
128 render_frame_host)
129 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_AppBannerPromptReply,
130 OnBannerPromptReply)
131 IPC_MESSAGE_UNHANDLED(handled = false)
132 IPC_END_MESSAGE_MAP()
134 return handled;
137 void AppBannerDataFetcher::OnBannerPromptReply(
138 content::RenderFrameHost* render_frame_host,
139 int request_id,
140 blink::WebAppBannerPromptReply reply) {
141 content::WebContents* web_contents = GetWebContents();
142 if (!CheckFetcherIsStillAlive(web_contents) ||
143 request_id != event_request_id_) {
144 Cancel();
145 return;
148 // The renderer might have requested the prompt to be canceled.
149 if (reply == blink::WebAppBannerPromptReply::Cancel) {
150 OutputDeveloperNotShownMessage(web_contents, kRendererRequestCancel);
151 Cancel();
152 return;
155 // Definitely going to show the banner now.
156 FOR_EACH_OBSERVER(Observer, observer_list_,
157 OnDecidedWhetherToShow(this, true));
159 ShowBanner(app_icon_.get(), app_title_);
160 is_active_ = false;
163 AppBannerDataFetcher::~AppBannerDataFetcher() {
164 FOR_EACH_OBSERVER(Observer, observer_list_, OnFetcherDestroyed(this));
167 std::string AppBannerDataFetcher::GetBannerType() {
168 return "web";
171 content::WebContents* AppBannerDataFetcher::GetWebContents() {
172 if (!web_contents() || web_contents()->IsBeingDestroyed())
173 return nullptr;
174 return web_contents();
177 std::string AppBannerDataFetcher::GetAppIdentifier() {
178 DCHECK(!web_app_data_.IsEmpty());
179 return web_app_data_.start_url.spec();
182 bool AppBannerDataFetcher::FetchIcon(const GURL& image_url) {
183 content::WebContents* web_contents = GetWebContents();
184 DCHECK(web_contents);
186 // Begin asynchronously fetching the app icon. AddRef() is done before the
187 // fetch to ensure that the AppBannerDataFetcher isn't deleted before the
188 // BitmapFetcher has called OnFetchComplete() (where the references are
189 // decremented).
190 AddRef();
191 Profile* profile =
192 Profile::FromBrowserContext(web_contents->GetBrowserContext());
193 bitmap_fetcher_.reset(new chrome::BitmapFetcher(image_url, this));
194 bitmap_fetcher_->Init(
195 profile->GetRequestContext(),
196 std::string(),
197 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
198 net::LOAD_NORMAL);
199 bitmap_fetcher_->Start();
200 return true;
203 void AppBannerDataFetcher::RecordDidShowBanner(const std::string& event_name) {
204 content::WebContents* web_contents = GetWebContents();
205 DCHECK(web_contents);
207 AppBannerSettingsHelper::RecordBannerEvent(
208 web_contents, validated_url_, GetAppIdentifier(),
209 AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW,
210 GetCurrentTime());
211 rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
212 event_name,
213 web_contents->GetURL());
214 banners::TrackDisplayEvent(DISPLAY_EVENT_CREATED);
217 void AppBannerDataFetcher::OnDidGetManifest(
218 const content::Manifest& manifest) {
219 content::WebContents* web_contents = GetWebContents();
220 if (!CheckFetcherIsStillAlive(web_contents)) {
221 Cancel();
222 return;
224 if (manifest.IsEmpty()) {
225 OutputDeveloperNotShownMessage(web_contents, kManifestEmpty);
226 Cancel();
227 return;
230 if (manifest.prefer_related_applications &&
231 manifest.related_applications.size()) {
232 for (const auto& application : manifest.related_applications) {
233 std::string platform = base::UTF16ToUTF8(application.platform.string());
234 std::string id = base::UTF16ToUTF8(application.id.string());
235 if (weak_delegate_->HandleNonWebApp(platform, application.url, id))
236 return;
240 if (!IsManifestValidForWebApp(manifest, web_contents)) {
241 Cancel();
242 return;
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.
252 Profile* profile =
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,
262 this));
265 void AppBannerDataFetcher::OnDidCheckHasServiceWorker(
266 bool has_service_worker) {
267 content::WebContents* web_contents = GetWebContents();
268 if (!CheckFetcherIsStillAlive(web_contents)) {
269 Cancel();
270 return;
273 if (has_service_worker) {
274 // Create an infobar to promote the manifest's app.
275 GURL icon_url =
276 ManifestIconSelector::FindBestMatchingIcon(
277 web_app_data_.icons,
278 ideal_icon_size_,
279 gfx::Screen::GetScreenFor(web_contents->GetNativeView()));
280 if (!icon_url.is_empty()) {
281 FetchIcon(icon_url);
282 return;
284 OutputDeveloperNotShownMessage(web_contents, kCannotDetermineBestIcon);
285 } else {
286 TrackDisplayEvent(DISPLAY_EVENT_LACKS_SERVICE_WORKER);
287 OutputDeveloperNotShownMessage(web_contents, kNoMatchingServiceWorker);
290 Cancel();
293 void AppBannerDataFetcher::OnFetchComplete(const GURL& url,
294 const SkBitmap* icon) {
295 if (is_active_)
296 RequestShowBanner(icon);
298 Release();
301 void AppBannerDataFetcher::RequestShowBanner(const SkBitmap* icon) {
302 content::WebContents* web_contents = GetWebContents();
303 if (!CheckFetcherIsStillAlive(web_contents)) {
304 Cancel();
305 return;
307 if (!icon) {
308 OutputDeveloperNotShownMessage(web_contents, kNoIconAvailable);
309 Cancel();
310 return;
313 RecordCouldShowBanner();
314 if (!CheckIfShouldShowBanner()) {
315 // At this point, the only possible case is that the banner has been added
316 // to the homescreen, given all of the other checks that have been made.
317 OutputDeveloperNotShownMessage(web_contents, kBannerAlreadyAdded);
318 Cancel();
319 return;
322 app_icon_.reset(new SkBitmap(*icon));
323 event_request_id_ = ++gCurrentRequestID;
324 web_contents->GetMainFrame()->Send(
325 new ChromeViewMsg_AppBannerPromptRequest(
326 web_contents->GetMainFrame()->GetRoutingID(),
327 event_request_id_,
328 GetBannerType()));
331 void AppBannerDataFetcher::RecordCouldShowBanner() {
332 content::WebContents* web_contents = GetWebContents();
333 DCHECK(web_contents);
335 AppBannerSettingsHelper::RecordBannerEvent(
336 web_contents, validated_url_, GetAppIdentifier(),
337 AppBannerSettingsHelper::APP_BANNER_EVENT_COULD_SHOW,
338 GetCurrentTime());
341 bool AppBannerDataFetcher::CheckIfShouldShowBanner() {
342 content::WebContents* web_contents = GetWebContents();
343 DCHECK(web_contents);
345 return AppBannerSettingsHelper::ShouldShowBanner(
346 web_contents, validated_url_, GetAppIdentifier(), GetCurrentTime());
349 bool AppBannerDataFetcher::CheckFetcherIsStillAlive(
350 content::WebContents* web_contents) {
351 if (!is_active_) {
352 OutputDeveloperNotShownMessage(web_contents,
353 kUserNavigatedBeforeBannerShown);
354 return false;
356 if (!web_contents) {
357 return false; // We cannot show a message if |web_contents| is null
359 return true;
362 // static
363 bool AppBannerDataFetcher::IsManifestValidForWebApp(
364 const content::Manifest& manifest,
365 content::WebContents* web_contents) {
366 if (manifest.IsEmpty()) {
367 OutputDeveloperNotShownMessage(web_contents, kManifestEmpty);
368 return false;
370 if (!manifest.start_url.is_valid()) {
371 OutputDeveloperNotShownMessage(web_contents, kStartURLNotValid);
372 return false;
374 if (manifest.name.is_null() && manifest.short_name.is_null()) {
375 OutputDeveloperNotShownMessage(web_contents,
376 kManifestMissingNameOrShortName);
377 return false;
379 if (!DoesManifestContainRequiredIcon(manifest)) {
380 OutputDeveloperNotShownMessage(web_contents, kManifestMissingSuitableIcon);
381 return false;
383 return true;
386 } // namespace banners