1 // Copyright (c) 2011 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/favicon_helper.h"
6
7 #include "build/build_config.h"
8
9 #include <vector>
10
11 #include "base/callback.h"
12 #include "base/memory/ref_counted_memory.h"
13 #include "chrome/browser/bookmarks/bookmark_model.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/icon_messages.h"
16 #include "content/browser/renderer_host/render_view_host.h"
17 #include "content/browser/tab_contents/navigation_controller.h"
18 #include "content/browser/tab_contents/navigation_entry.h"
19 #include "content/browser/tab_contents/tab_contents_delegate.h"
20 #include "content/browser/tab_contents/tab_contents.h"
21 #include "skia/ext/image_operations.h"
22 #include "ui/gfx/codec/png_codec.h"
23
24 namespace {
25
26 // Returns history::IconType the given icon_type corresponds to.
ToHistoryIconType(FaviconURL::IconType icon_type)27 history::IconType ToHistoryIconType(FaviconURL::IconType icon_type) {
28 switch (icon_type) {
29 case FaviconURL::FAVICON:
30 return history::FAVICON;
31 case FaviconURL::TOUCH_ICON:
32 return history::TOUCH_ICON;
33 case FaviconURL::TOUCH_PRECOMPOSED_ICON:
34 return history::TOUCH_PRECOMPOSED_ICON;
35 case FaviconURL::INVALID_ICON:
36 return history::INVALID_ICON;
37 }
38 NOTREACHED();
39 // Shouldn't reach here, just make compiler happy.
40 return history::INVALID_ICON;
41 }
42
DoUrlAndIconMatch(const FaviconURL & favicon_url,const GURL & url,history::IconType icon_type)43 bool DoUrlAndIconMatch(const FaviconURL& favicon_url,
44 const GURL& url,
45 history::IconType icon_type) {
46 return favicon_url.icon_url == url &&
47 favicon_url.icon_type == static_cast<FaviconURL::IconType>(icon_type);
48 }
49
50 } // namespace
51
DownloadRequest()52 FaviconHelper::DownloadRequest::DownloadRequest()
53 : callback(NULL),
54 icon_type(history::INVALID_ICON) {
55 }
56
DownloadRequest(const GURL & url,const GURL & image_url,ImageDownloadCallback * callback,history::IconType icon_type)57 FaviconHelper::DownloadRequest::DownloadRequest(const GURL& url,
58 const GURL& image_url,
59 ImageDownloadCallback* callback,
60 history::IconType icon_type)
61 : url(url),
62 image_url(image_url),
63 callback(callback),
64 icon_type(icon_type) {
65 }
66
FaviconHelper(TabContents * tab_contents,Type icon_type)67 FaviconHelper::FaviconHelper(TabContents* tab_contents, Type icon_type)
68 : TabContentsObserver(tab_contents),
69 got_favicon_from_history_(false),
70 favicon_expired_(false),
71 icon_types_(icon_type == FAVICON ? history::FAVICON :
72 history::TOUCH_ICON | history::TOUCH_PRECOMPOSED_ICON),
73 current_url_index_(0) {
74 }
75
~FaviconHelper()76 FaviconHelper::~FaviconHelper() {
77 SkBitmap empty_image;
78
79 // Call pending download callbacks with error to allow caller to clean up.
80 for (DownloadRequests::iterator i = download_requests_.begin();
81 i != download_requests_.end(); ++i) {
82 if (i->second.callback) {
83 i->second.callback->Run(i->first, true, empty_image);
84 }
85 }
86 }
87
FetchFavicon(const GURL & url)88 void FaviconHelper::FetchFavicon(const GURL& url) {
89 cancelable_consumer_.CancelAllRequests();
90
91 url_ = url;
92
93 favicon_expired_ = got_favicon_from_history_ = false;
94 current_url_index_ = 0;
95 urls_.clear();
96
97 // Request the favicon from the history service. In parallel to this the
98 // renderer is going to notify us (well TabContents) when the favicon url is
99 // available.
100 if (GetFaviconService()) {
101 GetFaviconForURL(url_, icon_types_, &cancelable_consumer_,
102 NewCallback(this, &FaviconHelper::OnFaviconDataForInitialURL));
103 }
104 }
105
DownloadImage(const GURL & image_url,int image_size,history::IconType icon_type,ImageDownloadCallback * callback)106 int FaviconHelper::DownloadImage(const GURL& image_url,
107 int image_size,
108 history::IconType icon_type,
109 ImageDownloadCallback* callback) {
110 DCHECK(callback); // Must provide a callback.
111 return ScheduleDownload(GURL(), image_url, image_size, icon_type, callback);
112 }
113
GetFaviconService()114 FaviconService* FaviconHelper::GetFaviconService() {
115 return tab_contents()->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
116 }
117
SetFavicon(const GURL & url,const GURL & image_url,const SkBitmap & image,history::IconType icon_type)118 void FaviconHelper::SetFavicon(
119 const GURL& url,
120 const GURL& image_url,
121 const SkBitmap& image,
122 history::IconType icon_type) {
123 const SkBitmap& sized_image = (preferred_icon_size() == 0 ||
124 (preferred_icon_size() == image.width() &&
125 preferred_icon_size() == image.height())) ?
126 image : ConvertToFaviconSize(image);
127
128 if (GetFaviconService() && ShouldSaveFavicon(url)) {
129 std::vector<unsigned char> image_data;
130 gfx::PNGCodec::EncodeBGRASkBitmap(sized_image, false, &image_data);
131 SetHistoryFavicon(url, image_url, image_data, icon_type);
132 }
133
134 if (url == url_ && icon_type == history::FAVICON) {
135 NavigationEntry* entry = GetEntry();
136 if (entry)
137 UpdateFavicon(entry, sized_image);
138 }
139 }
140
UpdateFavicon(NavigationEntry * entry,scoped_refptr<RefCountedMemory> data)141 void FaviconHelper::UpdateFavicon(NavigationEntry* entry,
142 scoped_refptr<RefCountedMemory> data) {
143 SkBitmap image;
144 gfx::PNGCodec::Decode(data->front(), data->size(), &image);
145 UpdateFavicon(entry, image);
146 }
147
UpdateFavicon(NavigationEntry * entry,const SkBitmap & image)148 void FaviconHelper::UpdateFavicon(NavigationEntry* entry,
149 const SkBitmap& image) {
150 // No matter what happens, we need to mark the favicon as being set.
151 entry->favicon().set_is_valid(true);
152
153 if (image.empty())
154 return;
155
156 entry->favicon().set_bitmap(image);
157 tab_contents()->NotifyNavigationStateChanged(TabContents::INVALIDATE_TAB);
158 }
159
OnUpdateFaviconURL(int32 page_id,const std::vector<FaviconURL> & candidates)160 void FaviconHelper::OnUpdateFaviconURL(
161 int32 page_id,
162 const std::vector<FaviconURL>& candidates) {
163 NavigationEntry* entry = GetEntry();
164 if (!entry)
165 return;
166
167 bool got_favicon_url_update = false;
168 for (std::vector<FaviconURL>::const_iterator i = candidates.begin();
169 i != candidates.end(); ++i) {
170 if (!i->icon_url.is_empty() && (i->icon_type & icon_types_)) {
171 if (!got_favicon_url_update) {
172 got_favicon_url_update = true;
173 urls_.clear();
174 current_url_index_ = 0;
175 }
176 urls_.push_back(*i);
177 }
178 }
179
180 // TODO(davemoore) Should clear on empty url. Currently we ignore it.
181 // This appears to be what FF does as well.
182 // No URL was added.
183 if (!got_favicon_url_update)
184 return;
185
186 if (!GetFaviconService())
187 return;
188
189 // For FAVICON.
190 if (current_candidate()->icon_type == FaviconURL::FAVICON) {
191 if (!favicon_expired_ && entry->favicon().is_valid() &&
192 DoUrlAndIconMatch(*current_candidate(), entry->favicon().url(),
193 history::FAVICON))
194 return;
195
196 entry->favicon().set_url(current_candidate()->icon_url);
197 } else if (!favicon_expired_ && got_favicon_from_history_ &&
198 history_icon_.is_valid() &&
199 DoUrlAndIconMatch(
200 *current_candidate(),
201 history_icon_.icon_url, history_icon_.icon_type)) {
202 return;
203 }
204
205 if (got_favicon_from_history_)
206 DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
207 ToHistoryIconType(current_candidate()->icon_type));
208 }
209
GetEntry()210 NavigationEntry* FaviconHelper::GetEntry() {
211 NavigationEntry* entry = tab_contents()->controller().GetActiveEntry();
212 if (entry && entry->url() == url_ &&
213 tab_contents()->IsActiveEntry(entry->page_id())) {
214 return entry;
215 }
216 // If the URL has changed out from under us (as will happen with redirects)
217 // return NULL.
218 return NULL;
219 }
220
DownloadFavicon(const GURL & image_url,int image_size)221 int FaviconHelper::DownloadFavicon(const GURL& image_url, int image_size) {
222 return tab_contents()->render_view_host()->DownloadFavicon(image_url,
223 image_size);
224 }
225
UpdateFaviconMappingAndFetch(const GURL & page_url,const GURL & icon_url,history::IconType icon_type,CancelableRequestConsumerBase * consumer,FaviconService::FaviconDataCallback * callback)226 void FaviconHelper::UpdateFaviconMappingAndFetch(
227 const GURL& page_url,
228 const GURL& icon_url,
229 history::IconType icon_type,
230 CancelableRequestConsumerBase* consumer,
231 FaviconService::FaviconDataCallback* callback) {
232 GetFaviconService()->UpdateFaviconMappingAndFetch(page_url, icon_url,
233 icon_type, consumer, callback);
234 }
235
GetFavicon(const GURL & icon_url,history::IconType icon_type,CancelableRequestConsumerBase * consumer,FaviconService::FaviconDataCallback * callback)236 void FaviconHelper::GetFavicon(
237 const GURL& icon_url,
238 history::IconType icon_type,
239 CancelableRequestConsumerBase* consumer,
240 FaviconService::FaviconDataCallback* callback) {
241 GetFaviconService()->GetFavicon(icon_url, icon_type, consumer, callback);
242 }
243
GetFaviconForURL(const GURL & page_url,int icon_types,CancelableRequestConsumerBase * consumer,FaviconService::FaviconDataCallback * callback)244 void FaviconHelper::GetFaviconForURL(
245 const GURL& page_url,
246 int icon_types,
247 CancelableRequestConsumerBase* consumer,
248 FaviconService::FaviconDataCallback* callback) {
249 GetFaviconService()->GetFaviconForURL(page_url, icon_types, consumer,
250 callback);
251 }
252
SetHistoryFavicon(const GURL & page_url,const GURL & icon_url,const std::vector<unsigned char> & image_data,history::IconType icon_type)253 void FaviconHelper::SetHistoryFavicon(
254 const GURL& page_url,
255 const GURL& icon_url,
256 const std::vector<unsigned char>& image_data,
257 history::IconType icon_type) {
258 GetFaviconService()->SetFavicon(page_url, icon_url, image_data, icon_type);
259 }
260
ShouldSaveFavicon(const GURL & url)261 bool FaviconHelper::ShouldSaveFavicon(const GURL& url) {
262 if (!tab_contents()->profile()->IsOffTheRecord())
263 return true;
264
265 // Otherwise store the favicon if the page is bookmarked.
266 BookmarkModel* bookmark_model = tab_contents()->profile()->GetBookmarkModel();
267 return bookmark_model && bookmark_model->IsBookmarked(url);
268 }
269
OnMessageReceived(const IPC::Message & message)270 bool FaviconHelper::OnMessageReceived(const IPC::Message& message) {
271 bool message_handled = true;
272 IPC_BEGIN_MESSAGE_MAP(FaviconHelper, message)
273 IPC_MESSAGE_HANDLER(IconHostMsg_DidDownloadFavicon, OnDidDownloadFavicon)
274 IPC_MESSAGE_UNHANDLED(message_handled = false)
275 IPC_END_MESSAGE_MAP()
276 return message_handled;
277 }
278
OnDidDownloadFavicon(int id,const GURL & image_url,bool errored,const SkBitmap & image)279 void FaviconHelper::OnDidDownloadFavicon(int id,
280 const GURL& image_url,
281 bool errored,
282 const SkBitmap& image) {
283 DownloadRequests::iterator i = download_requests_.find(id);
284 if (i == download_requests_.end()) {
285 // Currently TabContents notifies us of ANY downloads so that it is
286 // possible to get here.
287 return;
288 }
289
290 if (i->second.callback) {
291 i->second.callback->Run(id, errored, image);
292 } else if (current_candidate() &&
293 DoUrlAndIconMatch(*current_candidate(), image_url,
294 i->second.icon_type)) {
295 // The downloaded icon is still valid when there is no FaviconURL update
296 // during the downloading.
297 if (!errored) {
298 SetFavicon(i->second.url, image_url, image, i->second.icon_type);
299 } else if (GetEntry() && ++current_url_index_ < urls_.size()) {
300 // Copies all candidate except first one and notifies the FaviconHelper,
301 // so the next candidate can be processed.
302 std::vector<FaviconURL> new_candidates(++urls_.begin(), urls_.end());
303 OnUpdateFaviconURL(0, new_candidates);
304 }
305 }
306 download_requests_.erase(i);
307 }
308
OnFaviconDataForInitialURL(FaviconService::Handle handle,history::FaviconData favicon)309 void FaviconHelper::OnFaviconDataForInitialURL(
310 FaviconService::Handle handle,
311 history::FaviconData favicon) {
312 NavigationEntry* entry = GetEntry();
313 if (!entry)
314 return;
315
316 got_favicon_from_history_ = true;
317 history_icon_ = favicon;
318
319 favicon_expired_ = (favicon.known_icon && favicon.expired);
320
321 if (favicon.known_icon && favicon.icon_type == history::FAVICON &&
322 !entry->favicon().is_valid() &&
323 (!current_candidate() ||
324 DoUrlAndIconMatch(
325 *current_candidate(), favicon.icon_url, favicon.icon_type))) {
326 // The db knows the favicon (although it may be out of date) and the entry
327 // doesn't have an icon. Set the favicon now, and if the favicon turns out
328 // to be expired (or the wrong url) we'll fetch later on. This way the
329 // user doesn't see a flash of the default favicon.
330 entry->favicon().set_url(favicon.icon_url);
331 if (favicon.is_valid())
332 UpdateFavicon(entry, favicon.image_data);
333 entry->favicon().set_is_valid(true);
334 }
335
336 if (favicon.known_icon && !favicon.expired) {
337 if (current_candidate() &&
338 !DoUrlAndIconMatch(
339 *current_candidate(), favicon.icon_url, favicon.icon_type)) {
340 // Mapping in the database is wrong. DownloadFavIconOrAskHistory will
341 // update the mapping for this url and download the favicon if we don't
342 // already have it.
343 DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
344 static_cast<history::IconType>(current_candidate()->icon_type));
345 }
346 } else if (current_candidate()) {
347 // We know the official url for the favicon, by either don't have the
348 // favicon or its expired. Continue on to DownloadFaviconOrAskHistory to
349 // either download or check history again.
350 DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
351 ToHistoryIconType(current_candidate()->icon_type));
352 }
353 // else we haven't got the icon url. When we get it we'll ask the
354 // renderer to download the icon.
355 }
356
DownloadFaviconOrAskHistory(const GURL & page_url,const GURL & icon_url,history::IconType icon_type)357 void FaviconHelper::DownloadFaviconOrAskHistory(
358 const GURL& page_url,
359 const GURL& icon_url,
360 history::IconType icon_type) {
361 if (favicon_expired_) {
362 // We have the mapping, but the favicon is out of date. Download it now.
363 ScheduleDownload(page_url, icon_url, preferred_icon_size(), icon_type,
364 NULL);
365 } else if (GetFaviconService()) {
366 // We don't know the favicon, but we may have previously downloaded the
367 // favicon for another page that shares the same favicon. Ask for the
368 // favicon given the favicon URL.
369 if (tab_contents()->profile()->IsOffTheRecord()) {
370 GetFavicon(icon_url, icon_type, &cancelable_consumer_,
371 NewCallback(this, &FaviconHelper::OnFaviconData));
372 } else {
373 // Ask the history service for the icon. This does two things:
374 // 1. Attempts to fetch the favicon data from the database.
375 // 2. If the favicon exists in the database, this updates the database to
376 // include the mapping between the page url and the favicon url.
377 // This is asynchronous. The history service will call back when done.
378 // Issue the request and associate the current page ID with it.
379 UpdateFaviconMappingAndFetch(page_url, icon_url, icon_type,
380 &cancelable_consumer_,
381 NewCallback(this, &FaviconHelper::OnFaviconData));
382 }
383 }
384 }
385
OnFaviconData(FaviconService::Handle handle,history::FaviconData favicon)386 void FaviconHelper::OnFaviconData(FaviconService::Handle handle,
387 history::FaviconData favicon) {
388 NavigationEntry* entry = GetEntry();
389 if (!entry)
390 return;
391
392 // No need to update the favicon url. By the time we get here
393 // UpdateFaviconURL will have set the favicon url.
394 if (favicon.icon_type == history::FAVICON) {
395 if (favicon.is_valid()) {
396 // There is a favicon, set it now. If expired we'll download the current
397 // one again, but at least the user will get some icon instead of the
398 // default and most likely the current one is fine anyway.
399 UpdateFavicon(entry, favicon.image_data);
400 }
401 if (!favicon.known_icon || favicon.expired) {
402 // We don't know the favicon, or it is out of date. Request the current
403 // one.
404 ScheduleDownload(entry->url(), entry->favicon().url(),
405 preferred_icon_size(),
406 history::FAVICON, NULL);
407 }
408 } else if (current_candidate() && (!favicon.known_icon || favicon.expired ||
409 !(DoUrlAndIconMatch(
410 *current_candidate(), favicon.icon_url, favicon.icon_type)))) {
411 // We don't know the favicon, it is out of date or its type is not same as
412 // one got from page. Request the current one.
413 ScheduleDownload(entry->url(), current_candidate()->icon_url,
414 preferred_icon_size(),
415 ToHistoryIconType(current_candidate()->icon_type), NULL);
416 }
417 history_icon_ = favicon;
418 }
419
ScheduleDownload(const GURL & url,const GURL & image_url,int image_size,history::IconType icon_type,ImageDownloadCallback * callback)420 int FaviconHelper::ScheduleDownload(const GURL& url,
421 const GURL& image_url,
422 int image_size,
423 history::IconType icon_type,
424 ImageDownloadCallback* callback) {
425 const int download_id = DownloadFavicon(image_url, image_size);
426 if (download_id) {
427 // Download ids should be unique.
428 DCHECK(download_requests_.find(download_id) == download_requests_.end());
429 download_requests_[download_id] =
430 DownloadRequest(url, image_url, callback, icon_type);
431 }
432
433 return download_id;
434 }
435
ConvertToFaviconSize(const SkBitmap & image)436 SkBitmap FaviconHelper::ConvertToFaviconSize(const SkBitmap& image) {
437 int width = image.width();
438 int height = image.height();
439 if (width > 0 && height > 0) {
440 calc_favicon_target_size(&width, &height);
441 return skia::ImageOperations::Resize(
442 image, skia::ImageOperations::RESIZE_LANCZOS3,
443 width, height);
444 }
445 return image;
446 }
447