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