Home | History | Annotate | Download | only in browser
      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