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. 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 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 52 FaviconHelper::DownloadRequest::DownloadRequest() 53 : callback(NULL), 54 icon_type(history::INVALID_ICON) { 55 } 56 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 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 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 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 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 114 FaviconService* FaviconHelper::GetFaviconService() { 115 return tab_contents()->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); 116 } 117 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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