1 // Copyright 2016 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 "components/ntp_tiles/icon_cacher_impl.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/metrics/field_trial_params.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "components/favicon/core/favicon_service.h"
13 #include "components/favicon/core/favicon_util.h"
14 #include "components/favicon/core/large_icon_service.h"
15 #include "components/favicon_base/fallback_icon_style.h"
16 #include "components/favicon_base/favicon_types.h"
17 #include "components/favicon_base/favicon_util.h"
18 #include "components/image_fetcher/core/image_decoder.h"
19 #include "components/image_fetcher/core/image_fetcher.h"
20 #include "components/ntp_tiles/features.h"
21 #include "net/traffic_annotation/network_traffic_annotation.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/geometry/size.h"
24 #include "ui/gfx/image/image.h"
25 #include "url/gurl.h"
26
27 namespace ntp_tiles {
28
29 namespace {
30
31 constexpr int kDesiredFrameSize = 128;
32
33 // TODO(jkrcal): Make the size in dip and the scale factor be passed as
34 // arguments from the UI so that we desire for the right size on a given device.
35 // See crbug.com/696563.
36 constexpr int kDefaultTileIconMinSizePx = 1;
37
38 const char kImageFetcherUmaClient[] = "IconCacher";
39
40 constexpr char kTileIconMinSizePxFieldParam[] = "min_size";
41
IconType(const PopularSites::Site & site)42 favicon_base::IconType IconType(const PopularSites::Site& site) {
43 return site.large_icon_url.is_valid() ? favicon_base::IconType::kTouchIcon
44 : favicon_base::IconType::kFavicon;
45 }
46
IconURL(const PopularSites::Site & site)47 const GURL& IconURL(const PopularSites::Site& site) {
48 return site.large_icon_url.is_valid() ? site.large_icon_url
49 : site.favicon_url;
50 }
51
HasResultDefaultBackgroundColor(const favicon_base::LargeIconResult & result)52 bool HasResultDefaultBackgroundColor(
53 const favicon_base::LargeIconResult& result) {
54 if (!result.fallback_icon_style) {
55 return false;
56 }
57 return result.fallback_icon_style->is_default_background_color;
58 }
59
GetMinimumFetchingSizeForChromeSuggestionsFaviconsFromServer()60 int GetMinimumFetchingSizeForChromeSuggestionsFaviconsFromServer() {
61 return base::GetFieldTrialParamByFeatureAsInt(
62 kNtpMostLikelyFaviconsFromServerFeature, kTileIconMinSizePxFieldParam,
63 kDefaultTileIconMinSizePx);
64 }
65
66 } // namespace
67
IconCacherImpl(favicon::FaviconService * favicon_service,favicon::LargeIconService * large_icon_service,std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher)68 IconCacherImpl::IconCacherImpl(
69 favicon::FaviconService* favicon_service,
70 favicon::LargeIconService* large_icon_service,
71 std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher)
72 : favicon_service_(favicon_service),
73 large_icon_service_(large_icon_service),
74 image_fetcher_(std::move(image_fetcher)) {}
75
76 IconCacherImpl::~IconCacherImpl() = default;
77
StartFetchPopularSites(PopularSites::Site site,base::OnceClosure icon_available,base::OnceClosure preliminary_icon_available)78 void IconCacherImpl::StartFetchPopularSites(
79 PopularSites::Site site,
80 base::OnceClosure icon_available,
81 base::OnceClosure preliminary_icon_available) {
82 // Copy values from |site| before it is moved.
83 GURL site_url = site.url;
84 if (!StartRequest(site_url, std::move(icon_available))) {
85 return;
86 }
87
88 favicon_base::IconType icon_type = IconType(site);
89 favicon::GetFaviconImageForPageURL(
90 favicon_service_, site_url, icon_type,
91 base::BindOnce(&IconCacherImpl::OnGetFaviconImageForPageURLFinished,
92 base::Unretained(this), std::move(site),
93 std::move(preliminary_icon_available)),
94 &tracker_);
95 }
96
OnGetFaviconImageForPageURLFinished(PopularSites::Site site,base::OnceClosure preliminary_icon_available,const favicon_base::FaviconImageResult & result)97 void IconCacherImpl::OnGetFaviconImageForPageURLFinished(
98 PopularSites::Site site,
99 base::OnceClosure preliminary_icon_available,
100 const favicon_base::FaviconImageResult& result) {
101 if (!result.image.IsEmpty()) {
102 FinishRequestAndNotifyIconAvailable(site.url, /*newly_available=*/false);
103 return;
104 }
105
106 std::unique_ptr<CancelableImageCallback> preliminary_callback =
107 MaybeProvideDefaultIcon(site, std::move(preliminary_icon_available));
108
109 net::NetworkTrafficAnnotationTag traffic_annotation =
110 net::DefineNetworkTrafficAnnotation("icon_cacher", R"(
111 semantics {
112 sender: "Popular Sites New Tab Fetch"
113 description:
114 "Chrome may display a list of regionally-popular web sites on the "
115 "New Tab Page. This service fetches icons from those sites."
116 trigger:
117 "Whenever a popular site would be displayed, but its icon is not "
118 "yet cached in the browser."
119 data: "The URL for which to retrieve an icon."
120 destination: WEBSITE
121 }
122 policy {
123 cookies_allowed: NO
124 setting: "This feature cannot be disabled in settings."
125 policy_exception_justification: "Not implemented."
126 })");
127 image_fetcher::ImageFetcherParams params(traffic_annotation,
128 kImageFetcherUmaClient);
129 // For images with multiple frames, prefer one of size 128x128px.
130 params.set_frame_size(gfx::Size(kDesiredFrameSize, kDesiredFrameSize));
131 image_fetcher_->FetchImage(
132 IconURL(site),
133 base::BindOnce(&IconCacherImpl::OnPopularSitesFaviconDownloaded,
134 base::Unretained(this), site,
135 std::move(preliminary_callback)),
136 std::move(params));
137 }
138
OnPopularSitesFaviconDownloaded(PopularSites::Site site,std::unique_ptr<CancelableImageCallback> preliminary_callback,const gfx::Image & fetched_image,const image_fetcher::RequestMetadata & metadata)139 void IconCacherImpl::OnPopularSitesFaviconDownloaded(
140 PopularSites::Site site,
141 std::unique_ptr<CancelableImageCallback> preliminary_callback,
142 const gfx::Image& fetched_image,
143 const image_fetcher::RequestMetadata& metadata) {
144 if (fetched_image.IsEmpty()) {
145 FinishRequestAndNotifyIconAvailable(site.url, /*newly_available=*/false);
146 return;
147 }
148
149 // Avoid invoking callback about preliminary icon to be triggered. The best
150 // possible icon has already been downloaded.
151 if (preliminary_callback) {
152 preliminary_callback->Cancel();
153 }
154 SaveIconForSite(site, fetched_image);
155 FinishRequestAndNotifyIconAvailable(site.url, /*newly_available=*/true);
156 }
157
SaveAndNotifyDefaultIconForSite(const PopularSites::Site & site,base::OnceClosure preliminary_icon_available,const gfx::Image & image)158 void IconCacherImpl::SaveAndNotifyDefaultIconForSite(
159 const PopularSites::Site& site,
160 base::OnceClosure preliminary_icon_available,
161 const gfx::Image& image) {
162 SaveIconForSite(site, image);
163 if (preliminary_icon_available) {
164 std::move(preliminary_icon_available).Run();
165 }
166 }
167
SaveIconForSite(const PopularSites::Site & site,const gfx::Image & image)168 void IconCacherImpl::SaveIconForSite(const PopularSites::Site& site,
169 const gfx::Image& image) {
170 // Although |SetFaviconColorSpace| affects OSX only, copies of gfx::Images are
171 // just copies of the reference to the image and therefore cheap.
172 gfx::Image img(image);
173 favicon_base::SetFaviconColorSpace(&img);
174
175 favicon_service_->SetFavicons({site.url}, IconURL(site), IconType(site),
176 std::move(img));
177 }
178
179 std::unique_ptr<IconCacherImpl::CancelableImageCallback>
MaybeProvideDefaultIcon(const PopularSites::Site & site,base::OnceClosure preliminary_icon_available)180 IconCacherImpl::MaybeProvideDefaultIcon(
181 const PopularSites::Site& site,
182 base::OnceClosure preliminary_icon_available) {
183 if (site.default_icon_resource < 0) {
184 return std::unique_ptr<CancelableImageCallback>();
185 }
186 std::unique_ptr<CancelableImageCallback> preliminary_callback(
187 new CancelableImageCallback(
188 base::BindOnce(&IconCacherImpl::SaveAndNotifyDefaultIconForSite,
189 weak_ptr_factory_.GetWeakPtr(), site,
190 std::move(preliminary_icon_available))));
191 image_fetcher_->GetImageDecoder()->DecodeImage(
192 ui::ResourceBundle::GetSharedInstance()
193 .GetRawDataResource(site.default_icon_resource)
194 .as_string(),
195 gfx::Size(kDesiredFrameSize, kDesiredFrameSize),
196 preliminary_callback->callback());
197 return preliminary_callback;
198 }
199
StartFetchMostLikely(const GURL & page_url,base::OnceClosure icon_available)200 void IconCacherImpl::StartFetchMostLikely(const GURL& page_url,
201 base::OnceClosure icon_available) {
202 if (!StartRequest(page_url, std::move(icon_available))) {
203 return;
204 }
205
206 // Desired size 0 means that we do not want the service to resize the image
207 // (as we will not use it anyway).
208 large_icon_service_->GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
209 page_url, GetMinimumFetchingSizeForChromeSuggestionsFaviconsFromServer(),
210 /*desired_size_in_pixel=*/0,
211 base::BindOnce(&IconCacherImpl::OnGetLargeIconOrFallbackStyleFinished,
212 weak_ptr_factory_.GetWeakPtr(), page_url),
213 &tracker_);
214 }
215
OnGetLargeIconOrFallbackStyleFinished(const GURL & page_url,const favicon_base::LargeIconResult & result)216 void IconCacherImpl::OnGetLargeIconOrFallbackStyleFinished(
217 const GURL& page_url,
218 const favicon_base::LargeIconResult& result) {
219 if (!HasResultDefaultBackgroundColor(result)) {
220 // There is already an icon, there is nothing to do. (We should only fetch
221 // for default "gray" tiles so that we never overwrite any favicon of any
222 // size.)
223 FinishRequestAndNotifyIconAvailable(page_url, /*newly_available=*/false);
224 // Update the time when the icon was last requested - postpone thus the
225 // automatic eviction of the favicon from the favicon database.
226 large_icon_service_->TouchIconFromGoogleServer(result.bitmap.icon_url);
227 return;
228 }
229
230 net::NetworkTrafficAnnotationTag traffic_annotation =
231 net::DefineNetworkTrafficAnnotation("icon_catcher_get_large_icon", R"(
232 semantics {
233 sender: "Favicon Component"
234 description:
235 "Sends a request to a Google server to retrieve the favicon bitmap "
236 "for a server-suggested most visited tile on the new tab page."
237 trigger:
238 "A request can be sent if Chrome does not have a favicon for a "
239 "particular page and history sync is enabled."
240 data: "Page URL and desired icon size."
241 destination: GOOGLE_OWNED_SERVICE
242 }
243 policy {
244 cookies_allowed: NO
245 setting:
246 "Users can disable this feature via 'History' setting under "
247 "'Advanced sync settings'."
248 chrome_policy {
249 SyncDisabled {
250 policy_options {mode: MANDATORY}
251 SyncDisabled: true
252 }
253 }
254 })");
255 large_icon_service_
256 ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
257 page_url,
258 /*may_page_url_be_private=*/true, /*should_trim_page_url_path=*/false,
259 traffic_annotation,
260 base::BindOnce(&IconCacherImpl::OnMostLikelyFaviconDownloaded,
261 weak_ptr_factory_.GetWeakPtr(), page_url));
262 }
263
OnMostLikelyFaviconDownloaded(const GURL & request_url,favicon_base::GoogleFaviconServerRequestStatus status)264 void IconCacherImpl::OnMostLikelyFaviconDownloaded(
265 const GURL& request_url,
266 favicon_base::GoogleFaviconServerRequestStatus status) {
267 FinishRequestAndNotifyIconAvailable(
268 request_url,
269 status == favicon_base::GoogleFaviconServerRequestStatus::SUCCESS);
270 }
271
StartRequest(const GURL & request_url,base::OnceClosure icon_available)272 bool IconCacherImpl::StartRequest(const GURL& request_url,
273 base::OnceClosure icon_available) {
274 bool in_flight = in_flight_requests_.count(request_url) > 0;
275 in_flight_requests_[request_url].push_back(std::move(icon_available));
276 return !in_flight;
277 }
278
FinishRequestAndNotifyIconAvailable(const GURL & request_url,bool newly_available)279 void IconCacherImpl::FinishRequestAndNotifyIconAvailable(
280 const GURL& request_url,
281 bool newly_available) {
282 std::vector<base::OnceClosure> callbacks =
283 std::move(in_flight_requests_[request_url]);
284 in_flight_requests_.erase(request_url);
285 if (!newly_available) {
286 return;
287 }
288 for (base::OnceClosure& callback : callbacks) {
289 if (callback) {
290 std::move(callback).Run();
291 }
292 }
293 }
294
295 } // namespace ntp_tiles
296