// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/favicon_helper.h"
#include "build/build_config.h"
#include <vector>
#include "base/callback.h"
#include "base/memory/ref_counted_memory.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/icon_messages.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents_delegate.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "skia/ext/image_operations.h"
#include "ui/gfx/codec/png_codec.h"
namespace {
// Returns history::IconType the given icon_type corresponds to.
history::IconType ToHistoryIconType(FaviconURL::IconType icon_type) {
switch (icon_type) {
case FaviconURL::FAVICON:
return history::FAVICON;
case FaviconURL::TOUCH_ICON:
return history::TOUCH_ICON;
case FaviconURL::TOUCH_PRECOMPOSED_ICON:
return history::TOUCH_PRECOMPOSED_ICON;
case FaviconURL::INVALID_ICON:
return history::INVALID_ICON;
}
NOTREACHED();
// Shouldn't reach here, just make compiler happy.
return history::INVALID_ICON;
}
bool DoUrlAndIconMatch(const FaviconURL& favicon_url,
const GURL& url,
history::IconType icon_type) {
return favicon_url.icon_url == url &&
favicon_url.icon_type == static_cast<FaviconURL::IconType>(icon_type);
}
} // namespace
FaviconHelper::DownloadRequest::DownloadRequest()
: callback(NULL),
icon_type(history::INVALID_ICON) {
}
FaviconHelper::DownloadRequest::DownloadRequest(const GURL& url,
const GURL& image_url,
ImageDownloadCallback* callback,
history::IconType icon_type)
: url(url),
image_url(image_url),
callback(callback),
icon_type(icon_type) {
}
FaviconHelper::FaviconHelper(TabContents* tab_contents, Type icon_type)
: TabContentsObserver(tab_contents),
got_favicon_from_history_(false),
favicon_expired_(false),
icon_types_(icon_type == FAVICON ? history::FAVICON :
history::TOUCH_ICON | history::TOUCH_PRECOMPOSED_ICON),
current_url_index_(0) {
}
FaviconHelper::~FaviconHelper() {
SkBitmap empty_image;
// Call pending download callbacks with error to allow caller to clean up.
for (DownloadRequests::iterator i = download_requests_.begin();
i != download_requests_.end(); ++i) {
if (i->second.callback) {
i->second.callback->Run(i->first, true, empty_image);
}
}
}
void FaviconHelper::FetchFavicon(const GURL& url) {
cancelable_consumer_.CancelAllRequests();
url_ = url;
favicon_expired_ = got_favicon_from_history_ = false;
current_url_index_ = 0;
urls_.clear();
// Request the favicon from the history service. In parallel to this the
// renderer is going to notify us (well TabContents) when the favicon url is
// available.
if (GetFaviconService()) {
GetFaviconForURL(url_, icon_types_, &cancelable_consumer_,
NewCallback(this, &FaviconHelper::OnFaviconDataForInitialURL));
}
}
int FaviconHelper::DownloadImage(const GURL& image_url,
int image_size,
history::IconType icon_type,
ImageDownloadCallback* callback) {
DCHECK(callback); // Must provide a callback.
return ScheduleDownload(GURL(), image_url, image_size, icon_type, callback);
}
FaviconService* FaviconHelper::GetFaviconService() {
return tab_contents()->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
}
void FaviconHelper::SetFavicon(
const GURL& url,
const GURL& image_url,
const SkBitmap& image,
history::IconType icon_type) {
const SkBitmap& sized_image = (preferred_icon_size() == 0 ||
(preferred_icon_size() == image.width() &&
preferred_icon_size() == image.height())) ?
image : ConvertToFaviconSize(image);
if (GetFaviconService() && ShouldSaveFavicon(url)) {
std::vector<unsigned char> image_data;
gfx::PNGCodec::EncodeBGRASkBitmap(sized_image, false, &image_data);
SetHistoryFavicon(url, image_url, image_data, icon_type);
}
if (url == url_ && icon_type == history::FAVICON) {
NavigationEntry* entry = GetEntry();
if (entry)
UpdateFavicon(entry, sized_image);
}
}
void FaviconHelper::UpdateFavicon(NavigationEntry* entry,
scoped_refptr<RefCountedMemory> data) {
SkBitmap image;
gfx::PNGCodec::Decode(data->front(), data->size(), &image);
UpdateFavicon(entry, image);
}
void FaviconHelper::UpdateFavicon(NavigationEntry* entry,
const SkBitmap& image) {
// No matter what happens, we need to mark the favicon as being set.
entry->favicon().set_is_valid(true);
if (image.empty())
return;
entry->favicon().set_bitmap(image);
tab_contents()->NotifyNavigationStateChanged(TabContents::INVALIDATE_TAB);
}
void FaviconHelper::OnUpdateFaviconURL(
int32 page_id,
const std::vector<FaviconURL>& candidates) {
NavigationEntry* entry = GetEntry();
if (!entry)
return;
bool got_favicon_url_update = false;
for (std::vector<FaviconURL>::const_iterator i = candidates.begin();
i != candidates.end(); ++i) {
if (!i->icon_url.is_empty() && (i->icon_type & icon_types_)) {
if (!got_favicon_url_update) {
got_favicon_url_update = true;
urls_.clear();
current_url_index_ = 0;
}
urls_.push_back(*i);
}
}
// TODO(davemoore) Should clear on empty url. Currently we ignore it.
// This appears to be what FF does as well.
// No URL was added.
if (!got_favicon_url_update)
return;
if (!GetFaviconService())
return;
// For FAVICON.
if (current_candidate()->icon_type == FaviconURL::FAVICON) {
if (!favicon_expired_ && entry->favicon().is_valid() &&
DoUrlAndIconMatch(*current_candidate(), entry->favicon().url(),
history::FAVICON))
return;
entry->favicon().set_url(current_candidate()->icon_url);
} else if (!favicon_expired_ && got_favicon_from_history_ &&
history_icon_.is_valid() &&
DoUrlAndIconMatch(
*current_candidate(),
history_icon_.icon_url, history_icon_.icon_type)) {
return;
}
if (got_favicon_from_history_)
DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
ToHistoryIconType(current_candidate()->icon_type));
}
NavigationEntry* FaviconHelper::GetEntry() {
NavigationEntry* entry = tab_contents()->controller().GetActiveEntry();
if (entry && entry->url() == url_ &&
tab_contents()->IsActiveEntry(entry->page_id())) {
return entry;
}
// If the URL has changed out from under us (as will happen with redirects)
// return NULL.
return NULL;
}
int FaviconHelper::DownloadFavicon(const GURL& image_url, int image_size) {
return tab_contents()->render_view_host()->DownloadFavicon(image_url,
image_size);
}
void FaviconHelper::UpdateFaviconMappingAndFetch(
const GURL& page_url,
const GURL& icon_url,
history::IconType icon_type,
CancelableRequestConsumerBase* consumer,
FaviconService::FaviconDataCallback* callback) {
GetFaviconService()->UpdateFaviconMappingAndFetch(page_url, icon_url,
icon_type, consumer, callback);
}
void FaviconHelper::GetFavicon(
const GURL& icon_url,
history::IconType icon_type,
CancelableRequestConsumerBase* consumer,
FaviconService::FaviconDataCallback* callback) {
GetFaviconService()->GetFavicon(icon_url, icon_type, consumer, callback);
}
void FaviconHelper::GetFaviconForURL(
const GURL& page_url,
int icon_types,
CancelableRequestConsumerBase* consumer,
FaviconService::FaviconDataCallback* callback) {
GetFaviconService()->GetFaviconForURL(page_url, icon_types, consumer,
callback);
}
void FaviconHelper::SetHistoryFavicon(
const GURL& page_url,
const GURL& icon_url,
const std::vector<unsigned char>& image_data,
history::IconType icon_type) {
GetFaviconService()->SetFavicon(page_url, icon_url, image_data, icon_type);
}
bool FaviconHelper::ShouldSaveFavicon(const GURL& url) {
if (!tab_contents()->profile()->IsOffTheRecord())
return true;
// Otherwise store the favicon if the page is bookmarked.
BookmarkModel* bookmark_model = tab_contents()->profile()->GetBookmarkModel();
return bookmark_model && bookmark_model->IsBookmarked(url);
}
bool FaviconHelper::OnMessageReceived(const IPC::Message& message) {
bool message_handled = true;
IPC_BEGIN_MESSAGE_MAP(FaviconHelper, message)
IPC_MESSAGE_HANDLER(IconHostMsg_DidDownloadFavicon, OnDidDownloadFavicon)
IPC_MESSAGE_UNHANDLED(message_handled = false)
IPC_END_MESSAGE_MAP()
return message_handled;
}
void FaviconHelper::OnDidDownloadFavicon(int id,
const GURL& image_url,
bool errored,
const SkBitmap& image) {
DownloadRequests::iterator i = download_requests_.find(id);
if (i == download_requests_.end()) {
// Currently TabContents notifies us of ANY downloads so that it is
// possible to get here.
return;
}
if (i->second.callback) {
i->second.callback->Run(id, errored, image);
} else if (current_candidate() &&
DoUrlAndIconMatch(*current_candidate(), image_url,
i->second.icon_type)) {
// The downloaded icon is still valid when there is no FaviconURL update
// during the downloading.
if (!errored) {
SetFavicon(i->second.url, image_url, image, i->second.icon_type);
} else if (GetEntry() && ++current_url_index_ < urls_.size()) {
// Copies all candidate except first one and notifies the FaviconHelper,
// so the next candidate can be processed.
std::vector<FaviconURL> new_candidates(++urls_.begin(), urls_.end());
OnUpdateFaviconURL(0, new_candidates);
}
}
download_requests_.erase(i);
}
void FaviconHelper::OnFaviconDataForInitialURL(
FaviconService::Handle handle,
history::FaviconData favicon) {
NavigationEntry* entry = GetEntry();
if (!entry)
return;
got_favicon_from_history_ = true;
history_icon_ = favicon;
favicon_expired_ = (favicon.known_icon && favicon.expired);
if (favicon.known_icon && favicon.icon_type == history::FAVICON &&
!entry->favicon().is_valid() &&
(!current_candidate() ||
DoUrlAndIconMatch(
*current_candidate(), favicon.icon_url, favicon.icon_type))) {
// The db knows the favicon (although it may be out of date) and the entry
// doesn't have an icon. Set the favicon now, and if the favicon turns out
// to be expired (or the wrong url) we'll fetch later on. This way the
// user doesn't see a flash of the default favicon.
entry->favicon().set_url(favicon.icon_url);
if (favicon.is_valid())
UpdateFavicon(entry, favicon.image_data);
entry->favicon().set_is_valid(true);
}
if (favicon.known_icon && !favicon.expired) {
if (current_candidate() &&
!DoUrlAndIconMatch(
*current_candidate(), favicon.icon_url, favicon.icon_type)) {
// Mapping in the database is wrong. DownloadFavIconOrAskHistory will
// update the mapping for this url and download the favicon if we don't
// already have it.
DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
static_cast<history::IconType>(current_candidate()->icon_type));
}
} else if (current_candidate()) {
// We know the official url for the favicon, by either don't have the
// favicon or its expired. Continue on to DownloadFaviconOrAskHistory to
// either download or check history again.
DownloadFaviconOrAskHistory(entry->url(), current_candidate()->icon_url,
ToHistoryIconType(current_candidate()->icon_type));
}
// else we haven't got the icon url. When we get it we'll ask the
// renderer to download the icon.
}
void FaviconHelper::DownloadFaviconOrAskHistory(
const GURL& page_url,
const GURL& icon_url,
history::IconType icon_type) {
if (favicon_expired_) {
// We have the mapping, but the favicon is out of date. Download it now.
ScheduleDownload(page_url, icon_url, preferred_icon_size(), icon_type,
NULL);
} else if (GetFaviconService()) {
// We don't know the favicon, but we may have previously downloaded the
// favicon for another page that shares the same favicon. Ask for the
// favicon given the favicon URL.
if (tab_contents()->profile()->IsOffTheRecord()) {
GetFavicon(icon_url, icon_type, &cancelable_consumer_,
NewCallback(this, &FaviconHelper::OnFaviconData));
} else {
// Ask the history service for the icon. This does two things:
// 1. Attempts to fetch the favicon data from the database.
// 2. If the favicon exists in the database, this updates the database to
// include the mapping between the page url and the favicon url.
// This is asynchronous. The history service will call back when done.
// Issue the request and associate the current page ID with it.
UpdateFaviconMappingAndFetch(page_url, icon_url, icon_type,
&cancelable_consumer_,
NewCallback(this, &FaviconHelper::OnFaviconData));
}
}
}
void FaviconHelper::OnFaviconData(FaviconService::Handle handle,
history::FaviconData favicon) {
NavigationEntry* entry = GetEntry();
if (!entry)
return;
// No need to update the favicon url. By the time we get here
// UpdateFaviconURL will have set the favicon url.
if (favicon.icon_type == history::FAVICON) {
if (favicon.is_valid()) {
// There is a favicon, set it now. If expired we'll download the current
// one again, but at least the user will get some icon instead of the
// default and most likely the current one is fine anyway.
UpdateFavicon(entry, favicon.image_data);
}
if (!favicon.known_icon || favicon.expired) {
// We don't know the favicon, or it is out of date. Request the current
// one.
ScheduleDownload(entry->url(), entry->favicon().url(),
preferred_icon_size(),
history::FAVICON, NULL);
}
} else if (current_candidate() && (!favicon.known_icon || favicon.expired ||
!(DoUrlAndIconMatch(
*current_candidate(), favicon.icon_url, favicon.icon_type)))) {
// We don't know the favicon, it is out of date or its type is not same as
// one got from page. Request the current one.
ScheduleDownload(entry->url(), current_candidate()->icon_url,
preferred_icon_size(),
ToHistoryIconType(current_candidate()->icon_type), NULL);
}
history_icon_ = favicon;
}
int FaviconHelper::ScheduleDownload(const GURL& url,
const GURL& image_url,
int image_size,
history::IconType icon_type,
ImageDownloadCallback* callback) {
const int download_id = DownloadFavicon(image_url, image_size);
if (download_id) {
// Download ids should be unique.
DCHECK(download_requests_.find(download_id) == download_requests_.end());
download_requests_[download_id] =
DownloadRequest(url, image_url, callback, icon_type);
}
return download_id;
}
SkBitmap FaviconHelper::ConvertToFaviconSize(const SkBitmap& image) {
int width = image.width();
int height = image.height();
if (width > 0 && height > 0) {
calc_favicon_target_size(&width, &height);
return skia::ImageOperations::Resize(
image, skia::ImageOperations::RESIZE_LANCZOS3,
width, height);
}
return image;
}