1 // Copyright (c) 2012 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/favicon/favicon_handler.h"
7 #include "build/build_config.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/memory/ref_counted_memory.h"
14 #include "chrome/browser/bookmarks/bookmark_model.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/common/icon_messages.h"
17 #include "content/public/browser/favicon_status.h"
18 #include "content/public/browser/navigation_entry.h"
19 #include "skia/ext/image_operations.h"
20 #include "ui/gfx/codec/png_codec.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/image/image_util.h"
24 using content::NavigationEntry
;
28 // Returns history::IconType the given icon_type corresponds to.
29 history::IconType
ToHistoryIconType(FaviconURL::IconType icon_type
) {
31 case FaviconURL::FAVICON
:
32 return history::FAVICON
;
33 case FaviconURL::TOUCH_ICON
:
34 return history::TOUCH_ICON
;
35 case FaviconURL::TOUCH_PRECOMPOSED_ICON
:
36 return history::TOUCH_PRECOMPOSED_ICON
;
37 case FaviconURL::INVALID_ICON
:
38 return history::INVALID_ICON
;
41 // Shouldn't reach here, just make compiler happy.
42 return history::INVALID_ICON
;
45 bool DoUrlAndIconMatch(const FaviconURL
& favicon_url
,
47 history::IconType icon_type
) {
48 return favicon_url
.icon_url
== url
&&
49 favicon_url
.icon_type
== static_cast<FaviconURL::IconType
>(icon_type
);
54 FaviconHandler::DownloadRequest::DownloadRequest()
55 : icon_type(history::INVALID_ICON
) {
58 FaviconHandler::DownloadRequest::~DownloadRequest() {
61 FaviconHandler::DownloadRequest::DownloadRequest(
63 const GURL
& image_url
,
64 const FaviconTabHelper::ImageDownloadCallback
& callback
,
65 history::IconType icon_type
)
69 icon_type(icon_type
) {
72 FaviconHandler::FaviconHandler(Profile
* profile
,
73 FaviconHandlerDelegate
* delegate
,
75 : got_favicon_from_history_(false),
76 favicon_expired_(false),
77 icon_types_(icon_type
== FAVICON
? history::FAVICON
:
78 history::TOUCH_ICON
| history::TOUCH_PRECOMPOSED_ICON
),
79 current_url_index_(0),
86 FaviconHandler::~FaviconHandler() {
87 // Call pending download callbacks with error to allow caller to clean up.
88 for (DownloadRequests::iterator i
= download_requests_
.begin();
89 i
!= download_requests_
.end(); ++i
) {
90 if (!i
->second
.callback
.is_null()) {
91 i
->second
.callback
.Run(i
->first
, true, SkBitmap());
96 void FaviconHandler::FetchFavicon(const GURL
& url
) {
97 cancelable_consumer_
.CancelAllRequests();
101 favicon_expired_
= got_favicon_from_history_
= false;
102 current_url_index_
= 0;
105 // Request the favicon from the history service. In parallel to this the
106 // renderer is going to notify us (well TabContents) when the favicon url is
108 if (GetFaviconService()) {
109 GetFaviconForURL(url_
, icon_types_
, &cancelable_consumer_
,
110 base::Bind(&FaviconHandler::OnFaviconDataForInitialURL
,
111 base::Unretained(this)));
115 int FaviconHandler::DownloadImage(
116 const GURL
& image_url
,
118 history::IconType icon_type
,
119 const FaviconTabHelper::ImageDownloadCallback
& callback
) {
120 return ScheduleDownload(GURL(), image_url
, image_size
, icon_type
, callback
);
123 FaviconService
* FaviconHandler::GetFaviconService() {
124 return profile_
->GetFaviconService(Profile::EXPLICIT_ACCESS
);
127 void FaviconHandler::SetFavicon(
129 const GURL
& image_url
,
130 const gfx::Image
& image
,
131 history::IconType icon_type
) {
132 const SkBitmap
& bitmap
= image
;
133 const gfx::Image
& sized_image
= (preferred_icon_size() == 0 ||
134 (preferred_icon_size() == bitmap
.width() &&
135 preferred_icon_size() == bitmap
.height())) ?
136 image
: ResizeFaviconIfNeeded(image
);
138 if (GetFaviconService() && ShouldSaveFavicon(url
)) {
139 std::vector
<unsigned char> image_data
;
140 if (gfx::PNGEncodedDataFromImage(sized_image
, &image_data
))
141 SetHistoryFavicon(url
, image_url
, image_data
, icon_type
);
144 if (url
== url_
&& icon_type
== history::FAVICON
) {
145 NavigationEntry
* entry
= GetEntry();
147 UpdateFavicon(entry
, &sized_image
);
151 void FaviconHandler::UpdateFavicon(NavigationEntry
* entry
,
152 scoped_refptr
<RefCountedMemory
> data
) {
153 scoped_ptr
<gfx::Image
> image(gfx::ImageFromPNGEncodedData(data
->front(),
155 UpdateFavicon(entry
, image
.get());
158 void FaviconHandler::UpdateFavicon(NavigationEntry
* entry
,
159 const gfx::Image
* image
) {
160 // No matter what happens, we need to mark the favicon as being set.
161 entry
->GetFavicon().valid
= true;
166 entry
->GetFavicon().bitmap
= *image
;
167 delegate_
->NotifyFaviconUpdated();
170 void FaviconHandler::OnUpdateFaviconURL(
172 const std::vector
<FaviconURL
>& candidates
) {
173 NavigationEntry
* entry
= GetEntry();
177 bool got_favicon_url_update
= false;
178 for (std::vector
<FaviconURL
>::const_iterator i
= candidates
.begin();
179 i
!= candidates
.end(); ++i
) {
180 if (!i
->icon_url
.is_empty() && (i
->icon_type
& icon_types_
)) {
181 if (!got_favicon_url_update
) {
182 got_favicon_url_update
= true;
184 current_url_index_
= 0;
190 // TODO(davemoore) Should clear on empty url. Currently we ignore it.
191 // This appears to be what FF does as well.
193 if (!got_favicon_url_update
)
196 if (!GetFaviconService())
200 if (current_candidate()->icon_type
== FaviconURL::FAVICON
) {
201 if (!favicon_expired_
&& entry
->GetFavicon().valid
&&
202 DoUrlAndIconMatch(*current_candidate(), entry
->GetFavicon().url
,
206 entry
->GetFavicon().url
= current_candidate()->icon_url
;
207 } else if (!favicon_expired_
&& got_favicon_from_history_
&&
208 history_icon_
.is_valid() &&
210 *current_candidate(),
211 history_icon_
.icon_url
, history_icon_
.icon_type
)) {
215 if (got_favicon_from_history_
)
216 DownloadFaviconOrAskHistory(entry
->GetURL(), current_candidate()->icon_url
,
217 ToHistoryIconType(current_candidate()->icon_type
));
220 void FaviconHandler::OnDidDownloadFavicon(int id
,
221 const GURL
& image_url
,
223 const gfx::Image
& image
) {
224 DownloadRequests::iterator i
= download_requests_
.find(id
);
225 if (i
== download_requests_
.end()) {
226 // Currently TabContents notifies us of ANY downloads so that it is
227 // possible to get here.
231 if (!i
->second
.callback
.is_null()) {
232 i
->second
.callback
.Run(id
, errored
, *(&image
));
233 } else if (current_candidate() &&
234 DoUrlAndIconMatch(*current_candidate(), image_url
,
235 i
->second
.icon_type
)) {
236 // The downloaded icon is still valid when there is no FaviconURL update
237 // during the downloading.
239 SetFavicon(i
->second
.url
, image_url
, image
, i
->second
.icon_type
);
240 } else if (GetEntry() && ++current_url_index_
< urls_
.size()) {
241 // Copies all candidate except first one and notifies the FaviconHandler,
242 // so the next candidate can be processed.
243 std::vector
<FaviconURL
> new_candidates(urls_
.begin() + 1, urls_
.end());
244 OnUpdateFaviconURL(0, new_candidates
);
247 download_requests_
.erase(i
);
250 NavigationEntry
* FaviconHandler::GetEntry() {
251 NavigationEntry
* entry
= delegate_
->GetActiveEntry();
252 if (entry
&& entry
->GetURL() == url_
)
255 // If the URL has changed out from under us (as will happen with redirects)
260 int FaviconHandler::DownloadFavicon(const GURL
& image_url
, int image_size
) {
261 if (!image_url
.is_valid()) {
265 static int next_id
= 1;
267 delegate_
->StartDownload(id
, image_url
, image_size
);
271 void FaviconHandler::UpdateFaviconMappingAndFetch(
272 const GURL
& page_url
,
273 const GURL
& icon_url
,
274 history::IconType icon_type
,
275 CancelableRequestConsumerBase
* consumer
,
276 const FaviconService::FaviconDataCallback
& callback
) {
277 GetFaviconService()->UpdateFaviconMappingAndFetch(page_url
, icon_url
,
278 icon_type
, consumer
, callback
);
281 void FaviconHandler::GetFavicon(
282 const GURL
& icon_url
,
283 history::IconType icon_type
,
284 CancelableRequestConsumerBase
* consumer
,
285 const FaviconService::FaviconDataCallback
& callback
) {
286 GetFaviconService()->GetFavicon(icon_url
, icon_type
, consumer
, callback
);
289 void FaviconHandler::GetFaviconForURL(
290 const GURL
& page_url
,
292 CancelableRequestConsumerBase
* consumer
,
293 const FaviconService::FaviconDataCallback
& callback
) {
294 GetFaviconService()->GetFaviconForURL(page_url
, icon_types
, consumer
,
298 void FaviconHandler::SetHistoryFavicon(
299 const GURL
& page_url
,
300 const GURL
& icon_url
,
301 const std::vector
<unsigned char>& image_data
,
302 history::IconType icon_type
) {
303 GetFaviconService()->SetFavicon(page_url
, icon_url
, image_data
, icon_type
);
306 bool FaviconHandler::ShouldSaveFavicon(const GURL
& url
) {
307 if (!profile_
->IsOffTheRecord())
310 // Otherwise store the favicon if the page is bookmarked.
311 BookmarkModel
* bookmark_model
= profile_
->GetBookmarkModel();
312 return bookmark_model
&& bookmark_model
->IsBookmarked(url
);
315 void FaviconHandler::OnFaviconDataForInitialURL(
316 FaviconService::Handle handle
,
317 history::FaviconData favicon
) {
318 NavigationEntry
* entry
= GetEntry();
322 got_favicon_from_history_
= true;
323 history_icon_
= favicon
;
325 favicon_expired_
= (favicon
.known_icon
&& favicon
.expired
);
327 if (favicon
.known_icon
&& favicon
.icon_type
== history::FAVICON
&&
328 !entry
->GetFavicon().valid
&&
329 (!current_candidate() ||
331 *current_candidate(), favicon
.icon_url
, favicon
.icon_type
))) {
332 // The db knows the favicon (although it may be out of date) and the entry
333 // doesn't have an icon. Set the favicon now, and if the favicon turns out
334 // to be expired (or the wrong url) we'll fetch later on. This way the
335 // user doesn't see a flash of the default favicon.
336 entry
->GetFavicon().url
= favicon
.icon_url
;
337 if (favicon
.is_valid())
338 UpdateFavicon(entry
, favicon
.image_data
);
339 entry
->GetFavicon().valid
= true;
342 if (favicon
.known_icon
&& !favicon
.expired
) {
343 if (current_candidate() &&
345 *current_candidate(), favicon
.icon_url
, favicon
.icon_type
)) {
346 // Mapping in the database is wrong. DownloadFavIconOrAskHistory will
347 // update the mapping for this url and download the favicon if we don't
349 DownloadFaviconOrAskHistory(entry
->GetURL(),
350 current_candidate()->icon_url
,
351 static_cast<history::IconType
>(current_candidate()->icon_type
));
353 } else if (current_candidate()) {
354 // We know the official url for the favicon, by either don't have the
355 // favicon or its expired. Continue on to DownloadFaviconOrAskHistory to
356 // either download or check history again.
357 DownloadFaviconOrAskHistory(entry
->GetURL(), current_candidate()->icon_url
,
358 ToHistoryIconType(current_candidate()->icon_type
));
360 // else we haven't got the icon url. When we get it we'll ask the
361 // renderer to download the icon.
364 void FaviconHandler::DownloadFaviconOrAskHistory(
365 const GURL
& page_url
,
366 const GURL
& icon_url
,
367 history::IconType icon_type
) {
368 if (favicon_expired_
) {
369 // We have the mapping, but the favicon is out of date. Download it now.
370 ScheduleDownload(page_url
, icon_url
, preferred_icon_size(), icon_type
,
371 FaviconTabHelper::ImageDownloadCallback());
372 } else if (GetFaviconService()) {
373 // We don't know the favicon, but we may have previously downloaded the
374 // favicon for another page that shares the same favicon. Ask for the
375 // favicon given the favicon URL.
376 if (profile_
->IsOffTheRecord()) {
377 GetFavicon(icon_url
, icon_type
, &cancelable_consumer_
,
378 base::Bind(&FaviconHandler::OnFaviconData
, base::Unretained(this)));
380 // Ask the history service for the icon. This does two things:
381 // 1. Attempts to fetch the favicon data from the database.
382 // 2. If the favicon exists in the database, this updates the database to
383 // include the mapping between the page url and the favicon url.
384 // This is asynchronous. The history service will call back when done.
385 // Issue the request and associate the current page ID with it.
386 UpdateFaviconMappingAndFetch(page_url
, icon_url
, icon_type
,
387 &cancelable_consumer_
,
388 base::Bind(&FaviconHandler::OnFaviconData
, base::Unretained(this)));
393 void FaviconHandler::OnFaviconData(FaviconService::Handle handle
,
394 history::FaviconData favicon
) {
395 NavigationEntry
* entry
= GetEntry();
399 // No need to update the favicon url. By the time we get here
400 // UpdateFaviconURL will have set the favicon url.
401 if (favicon
.icon_type
== history::FAVICON
) {
402 if (favicon
.is_valid()) {
403 // There is a favicon, set it now. If expired we'll download the current
404 // one again, but at least the user will get some icon instead of the
405 // default and most likely the current one is fine anyway.
406 UpdateFavicon(entry
, favicon
.image_data
);
408 if (!favicon
.known_icon
|| favicon
.expired
) {
409 // We don't know the favicon, or it is out of date. Request the current
411 ScheduleDownload(entry
->GetURL(), entry
->GetFavicon().url
,
412 preferred_icon_size(),
414 FaviconTabHelper::ImageDownloadCallback());
416 } else if (current_candidate() && (!favicon
.known_icon
|| favicon
.expired
||
418 *current_candidate(), favicon
.icon_url
, favicon
.icon_type
)))) {
419 // We don't know the favicon, it is out of date or its type is not same as
420 // one got from page. Request the current one.
421 ScheduleDownload(entry
->GetURL(), current_candidate()->icon_url
,
422 preferred_icon_size(),
423 ToHistoryIconType(current_candidate()->icon_type
),
424 FaviconTabHelper::ImageDownloadCallback());
426 history_icon_
= favicon
;
429 int FaviconHandler::ScheduleDownload(
431 const GURL
& image_url
,
433 history::IconType icon_type
,
434 const FaviconTabHelper::ImageDownloadCallback
& callback
) {
435 const int download_id
= DownloadFavicon(image_url
, image_size
);
437 // Download ids should be unique.
438 DCHECK(download_requests_
.find(download_id
) == download_requests_
.end());
439 download_requests_
[download_id
] =
440 DownloadRequest(url
, image_url
, callback
, icon_type
);
446 gfx::Image
FaviconHandler::ResizeFaviconIfNeeded(const gfx::Image
& image
) {
447 // Get an SkBitmap from the gfx::Image.
448 SkBitmap bitmap
= *image
.ToSkBitmap();
449 int width
= bitmap
.width();
450 int height
= bitmap
.height();
451 if (width
> 0 && height
> 0) {
452 gfx::CalculateFaviconTargetSize(&width
, &height
);
453 return gfx::Image(skia::ImageOperations::Resize(
454 bitmap
, skia::ImageOperations::RESIZE_LANCZOS3
,