blob: a40ec2a7ffbcdc970dc8ab50721ba0abb2ee66ef [file] [log] [blame]
// Copyright (c) 2010 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/fav_icon_helper.h"
#include "build/build_config.h"
#include <vector>
#include "base/callback.h"
#include "base/ref_counted_memory.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/render_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"
#include "ui/gfx/favicon_size.h"
FavIconHelper::FavIconHelper(TabContents* tab_contents)
: TabContentsObserver(tab_contents),
got_fav_icon_url_(false),
got_fav_icon_from_history_(false),
fav_icon_expired_(false) {
}
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;
fav_icon_expired_ = got_fav_icon_from_history_ = got_fav_icon_url_ = false;
// 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()) {
GetFaviconService()->GetFaviconForURL(url_, &cancelable_consumer_,
NewCallback(this, &FavIconHelper::OnFavIconDataForInitialURL));
}
}
int FavIconHelper::DownloadImage(const GURL& image_url,
int image_size,
ImageDownloadCallback* callback) {
DCHECK(callback); // Must provide a callback.
return ScheduleDownload(GURL(), image_url, image_size, callback);
}
Profile* FavIconHelper::profile() {
return tab_contents()->profile();
}
FaviconService* FavIconHelper::GetFaviconService() {
return profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
}
void FavIconHelper::SetFavIcon(
const GURL& url,
const GURL& image_url,
const SkBitmap& image) {
const SkBitmap& sized_image =
(image.width() == kFavIconSize && image.height() == kFavIconSize)
? image : ConvertToFavIconSize(image);
if (GetFaviconService() && ShouldSaveFavicon(url)) {
std::vector<unsigned char> image_data;
gfx::PNGCodec::EncodeBGRASkBitmap(sized_image, false, &image_data);
GetFaviconService()->SetFavicon(url, image_url, image_data);
}
if (url == url_) {
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 GURL& icon_url) {
// TODO(davemoore) Should clear on empty url. Currently we ignore it.
// This appears to be what FF does as well.
if (icon_url.is_empty())
return;
NavigationEntry* entry = GetEntry();
if (!entry)
return;
got_fav_icon_url_ = true;
if (!GetFaviconService())
return;
if (!fav_icon_expired_ && entry->favicon().is_valid() &&
entry->favicon().url() == icon_url) {
// We already have the icon, no need to proceed.
return;
}
entry->favicon().set_url(icon_url);
if (got_fav_icon_from_history_)
DownloadFavIconOrAskHistory(entry);
}
bool FavIconHelper::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(FavIconHelper, message)
IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateFavIconURL, OnUpdateFavIconURL)
IPC_MESSAGE_HANDLER(ViewHostMsg_DidDownloadFavIcon, OnDidDownloadFavIcon)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return 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 (!errored) {
SetFavIcon(i->second.url, image_url, image);
}
download_requests_.erase(i);
}
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;
}
void FavIconHelper::OnFavIconDataForInitialURL(
FaviconService::Handle handle,
bool know_favicon,
scoped_refptr<RefCountedMemory> data,
bool expired,
GURL icon_url) {
NavigationEntry* entry = GetEntry();
if (!entry)
return;
got_fav_icon_from_history_ = true;
fav_icon_expired_ = (know_favicon && expired);
if (know_favicon && !entry->favicon().is_valid() &&
(!got_fav_icon_url_ || entry->favicon().url() == icon_url)) {
// 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(icon_url);
if (data.get() && data->size())
UpdateFavIcon(entry, data);
entry->favicon().set_is_valid(true);
}
if (know_favicon && !expired) {
if (got_fav_icon_url_ && entry->favicon().url() != icon_url) {
// 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);
}
} else if (got_fav_icon_url_) {
// 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);
}
// else we haven't got the icon url. When we get it we'll ask the
// renderer to download the icon.
}
void FavIconHelper::DownloadFavIconOrAskHistory(NavigationEntry* entry) {
DCHECK(entry); // We should only get here if entry is valid.
if (fav_icon_expired_) {
// We have the mapping, but the favicon is out of date. Download it now.
ScheduleDownload(entry->url(), entry->favicon().url(), kFavIconSize, 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 (profile()->IsOffTheRecord()) {
GetFaviconService()->GetFavicon(
entry->favicon().url(),
&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.
GetFaviconService()->UpdateFaviconMappingAndFetch(
entry->url(),
entry->favicon().url(), &cancelable_consumer_,
NewCallback(this, &FavIconHelper::OnFavIconData));
}
}
}
void FavIconHelper::OnFavIconData(
FaviconService::Handle handle,
bool know_favicon,
scoped_refptr<RefCountedMemory> data,
bool expired,
GURL icon_url) {
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 (know_favicon && data.get() && data->size()) {
// 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, data);
}
if (!know_favicon || expired) {
// We don't know the favicon, or it is out of date. Request the current one.
ScheduleDownload(entry->url(), entry->favicon().url(), kFavIconSize, NULL);
}
}
int FavIconHelper::ScheduleDownload(const GURL& url,
const GURL& image_url,
int image_size,
ImageDownloadCallback* callback) {
const int download_id = tab_contents()->render_view_host()->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);
}
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;
}
bool FavIconHelper::ShouldSaveFavicon(const GURL& url) {
if (!profile()->IsOffTheRecord())
return true;
// Otherwise store the favicon if the page is bookmarked.
BookmarkModel* bookmark_model = profile()->GetBookmarkModel();
return bookmark_model && bookmark_model->IsBookmarked(url);
}