1 // Copyright 2013 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/web_applications/components/web_app_icon_downloader.h"
6 
7 #include "base/bind.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/threading/thread_task_runner_handle.h"
10 #include "chrome/browser/web_applications/components/web_app_helpers.h"
11 #include "content/public/browser/navigation_handle.h"
12 #include "content/public/browser/web_contents.h"
13 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
14 #include "third_party/skia/include/core/SkBitmap.h"
15 #include "ui/gfx/geometry/size.h"
16 
17 namespace web_app {
18 
WebAppIconDownloader(content::WebContents * web_contents,const std::vector<GURL> & extra_favicon_urls,Histogram histogram,WebAppIconDownloaderCallback callback)19 WebAppIconDownloader::WebAppIconDownloader(
20     content::WebContents* web_contents,
21     const std::vector<GURL>& extra_favicon_urls,
22     Histogram histogram,
23     WebAppIconDownloaderCallback callback)
24     : content::WebContentsObserver(web_contents),
25       need_favicon_urls_(true),
26       fail_all_if_any_fail_(false),
27       extra_favicon_urls_(extra_favicon_urls),
28       callback_(std::move(callback)),
29       histogram_(histogram) {}
30 
~WebAppIconDownloader()31 WebAppIconDownloader::~WebAppIconDownloader() {}
32 
SkipPageFavicons()33 void WebAppIconDownloader::SkipPageFavicons() {
34   need_favicon_urls_ = false;
35 }
36 
FailAllIfAnyFail()37 void WebAppIconDownloader::FailAllIfAnyFail() {
38   fail_all_if_any_fail_ = true;
39 }
40 
Start()41 void WebAppIconDownloader::Start() {
42   // Favicons are not supported in extension WebContents.
43   if (IsValidExtensionUrl(web_contents()->GetLastCommittedURL()))
44     SkipPageFavicons();
45 
46   // If the candidates aren't loaded, icons will be fetched when
47   // DidUpdateFaviconURL() is called.
48   FetchIcons(extra_favicon_urls_);
49 
50   if (need_favicon_urls_) {
51     // The call to `GetFaviconURLsFromWebContents()` is to allow this method to
52     // be mocked by unit tests.
53     const auto& favicon_urls = GetFaviconURLsFromWebContents();
54     if (!favicon_urls.empty()) {
55       need_favicon_urls_ = false;
56       FetchIcons(favicon_urls);
57     }
58   }
59 }
60 
DownloadImage(const GURL & url)61 int WebAppIconDownloader::DownloadImage(const GURL& url) {
62   return web_contents()->DownloadImage(
63       url,
64       true,   // is_favicon
65       0,      // no preferred size
66       0,      // no max size
67       false,  // normal cache policy
68       base::BindOnce(&WebAppIconDownloader::DidDownloadFavicon,
69                      weak_ptr_factory_.GetWeakPtr()));
70 }
71 
72 const std::vector<blink::mojom::FaviconURLPtr>&
GetFaviconURLsFromWebContents()73 WebAppIconDownloader::GetFaviconURLsFromWebContents() {
74   return web_contents()->GetFaviconURLs();
75 }
76 
FetchIcons(const std::vector<blink::mojom::FaviconURLPtr> & favicon_urls)77 void WebAppIconDownloader::FetchIcons(
78     const std::vector<blink::mojom::FaviconURLPtr>& favicon_urls) {
79   std::vector<GURL> urls;
80   for (const auto& favicon_url : favicon_urls) {
81     if (favicon_url->icon_type != blink::mojom::FaviconIconType::kInvalid)
82       urls.push_back(favicon_url->icon_url);
83   }
84   FetchIcons(urls);
85 }
86 
FetchIcons(const std::vector<GURL> & urls)87 void WebAppIconDownloader::FetchIcons(const std::vector<GURL>& urls) {
88   // Download icons; put their download ids into |in_progress_requests_| and
89   // their urls into |processed_urls_|.
90   for (auto it = urls.begin(); it != urls.end(); ++it) {
91     // Only start the download if the url hasn't been processed before.
92     if (processed_urls_.insert(*it).second)
93       in_progress_requests_.insert(DownloadImage(*it));
94   }
95 
96   // If no downloads were initiated, we can proceed directly to running the
97   // callback.
98   if (in_progress_requests_.empty() && !need_favicon_urls_) {
99     base::ThreadTaskRunnerHandle::Get()->PostTask(
100         FROM_HERE, base::BindOnce(std::move(callback_), true, icons_map_));
101   }
102 }
103 
DidDownloadFavicon(int id,int http_status_code,const GURL & image_url,const std::vector<SkBitmap> & bitmaps,const std::vector<gfx::Size> & original_bitmap_sizes)104 void WebAppIconDownloader::DidDownloadFavicon(
105     int id,
106     int http_status_code,
107     const GURL& image_url,
108     const std::vector<SkBitmap>& bitmaps,
109     const std::vector<gfx::Size>& original_bitmap_sizes) {
110   // Request may have been canceled by DidFinishNavigation().
111   if (in_progress_requests_.erase(id) == 0)
112     return;
113 
114   if (http_status_code != 0) {
115     const char* histogram_name = nullptr;
116     switch (histogram_) {
117       case Histogram::kForCreate:
118         histogram_name = "WebApp.Icon.HttpStatusCodeClassOnCreate";
119         break;
120       case Histogram::kForSync:
121         histogram_name = "WebApp.Icon.HttpStatusCodeClassOnSync";
122         break;
123       case Histogram::kForUpdate:
124         histogram_name = "WebApp.Icon.HttpStatusCodeClassOnUpdate";
125         break;
126     }
127     DCHECK_LE(100, http_status_code);
128     DCHECK_GT(600, http_status_code);
129     base::UmaHistogramExactLinear(histogram_name, http_status_code / 100, 5);
130   }
131 
132   if (fail_all_if_any_fail_ && bitmaps.empty()) {
133     CancelDownloads();
134     return;
135   }
136 
137   icons_map_[image_url] = bitmaps;
138 
139   // Once all requests have been resolved, perform post-download tasks.
140   if (in_progress_requests_.empty() && !need_favicon_urls_) {
141     std::move(callback_).Run(/*success=*/true, std::move(icons_map_));
142   }
143 }
144 
145 // content::WebContentsObserver overrides:
DidFinishNavigation(content::NavigationHandle * navigation_handle)146 void WebAppIconDownloader::DidFinishNavigation(
147     content::NavigationHandle* navigation_handle) {
148   if (!navigation_handle->IsInMainFrame() ||
149       !navigation_handle->HasCommitted() || navigation_handle->IsSameDocument())
150     return;
151 
152   CancelDownloads();
153 }
154 
DidUpdateFaviconURL(content::RenderFrameHost * rfh,const std::vector<blink::mojom::FaviconURLPtr> & candidates)155 void WebAppIconDownloader::DidUpdateFaviconURL(
156     content::RenderFrameHost* rfh,
157     const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
158   // Only consider the first candidates we are given. This prevents pages that
159   // change their favicon from spamming us.
160   if (!need_favicon_urls_)
161     return;
162 
163   need_favicon_urls_ = false;
164   FetchIcons(candidates);
165 }
166 
CancelDownloads()167 void WebAppIconDownloader::CancelDownloads() {
168   in_progress_requests_.clear();
169   icons_map_.clear();
170   if (callback_)
171     std::move(callback_).Run(/*success=*/false, icons_map_);
172 }
173 
174 }  // namespace web_app
175