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