diff options
author | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-28 22:19:14 +0000 |
---|---|---|
committer | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-28 22:19:14 +0000 |
commit | ee2ed42c72935932a756dedfe797d96bd0205e31 (patch) | |
tree | aee48705aeabd69a6b7c1b08510c3309484b1561 /chrome/browser/favicon | |
parent | ad9a072a78fea6d59e142716ca4104687490bfc6 (diff) | |
download | chromium_src-ee2ed42c72935932a756dedfe797d96bd0205e31.zip chromium_src-ee2ed42c72935932a756dedfe797d96bd0205e31.tar.gz chromium_src-ee2ed42c72935932a756dedfe797d96bd0205e31.tar.bz2 |
Favicon file shuffling.
Moves them to their own directory, renames the Helper to TabHelper in prep for moving it to TabContentsWrapper.
BUG=71097
TEST=none
Review URL: http://codereview.chromium.org/6902125
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83425 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/favicon')
-rw-r--r-- | chrome/browser/favicon/favicon_handler.cc | 448 | ||||
-rw-r--r-- | chrome/browser/favicon/favicon_handler.h | 261 | ||||
-rw-r--r-- | chrome/browser/favicon/favicon_handler_unittest.cc | 768 | ||||
-rw-r--r-- | chrome/browser/favicon/favicon_service.cc | 97 | ||||
-rw-r--r-- | chrome/browser/favicon/favicon_service.h | 106 | ||||
-rw-r--r-- | chrome/browser/favicon/favicon_tab_helper.cc | 67 | ||||
-rw-r--r-- | chrome/browser/favicon/favicon_tab_helper.h | 76 |
7 files changed, 1823 insertions, 0 deletions
diff --git a/chrome/browser/favicon/favicon_handler.cc b/chrome/browser/favicon/favicon_handler.cc new file mode 100644 index 0000000..66d2b47 --- /dev/null +++ b/chrome/browser/favicon/favicon_handler.cc @@ -0,0 +1,448 @@ +// 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/favicon_handler.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 + +FaviconHandler::DownloadRequest::DownloadRequest() + : callback(NULL), + icon_type(history::INVALID_ICON) { +} + +FaviconHandler::DownloadRequest::DownloadRequest( + const GURL& url, + const GURL& image_url, + FaviconTabHelper::ImageDownloadCallback* callback, + history::IconType icon_type) + : url(url), + image_url(image_url), + callback(callback), + icon_type(icon_type) { +} + +FaviconHandler::FaviconHandler(TabContents* tab_contents, Type icon_type) + : 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), + tab_contents_(tab_contents) { +} + +FaviconHandler::~FaviconHandler() { + 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 FaviconHandler::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, &FaviconHandler::OnFaviconDataForInitialURL)); + } +} + +int FaviconHandler::DownloadImage( + const GURL& image_url, + int image_size, + history::IconType icon_type, + FaviconTabHelper::ImageDownloadCallback* callback) { + DCHECK(callback); // Must provide a callback. + return ScheduleDownload(GURL(), image_url, image_size, icon_type, callback); +} + +FaviconService* FaviconHandler::GetFaviconService() { + return tab_contents()->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); +} + +void FaviconHandler::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 FaviconHandler::UpdateFavicon(NavigationEntry* entry, + scoped_refptr<RefCountedMemory> data) { + SkBitmap image; + gfx::PNGCodec::Decode(data->front(), data->size(), &image); + UpdateFavicon(entry, image); +} + +void FaviconHandler::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 FaviconHandler::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)); +} + +void FaviconHandler::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 FaviconHandler, + // so the next candidate can be processed. + std::vector<FaviconURL> new_candidates(++urls_.begin(), urls_.end()); + OnUpdateFaviconURL(0, new_candidates); + } + } + download_requests_.erase(i); +} + +NavigationEntry* FaviconHandler::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 FaviconHandler::DownloadFavicon(const GURL& image_url, int image_size) { + if (!image_url.is_valid()) { + NOTREACHED(); + return 0; + } + static int next_id = 1; + int id = next_id++; + RenderViewHost* host = tab_contents()->render_view_host(); + host->Send(new IconMsg_DownloadFavicon( + host->routing_id(), id, image_url, image_size)); + return id; +} + +void FaviconHandler::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 FaviconHandler::GetFavicon( + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback) { + GetFaviconService()->GetFavicon(icon_url, icon_type, consumer, callback); +} + +void FaviconHandler::GetFaviconForURL( + const GURL& page_url, + int icon_types, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback) { + GetFaviconService()->GetFaviconForURL(page_url, icon_types, consumer, + callback); +} + +void FaviconHandler::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 FaviconHandler::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); +} + +void FaviconHandler::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 FaviconHandler::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, &FaviconHandler::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, &FaviconHandler::OnFaviconData)); + } + } +} + +void FaviconHandler::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 FaviconHandler::ScheduleDownload( + const GURL& url, + const GURL& image_url, + int image_size, + history::IconType icon_type, + FaviconTabHelper::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 FaviconHandler::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; +} diff --git a/chrome/browser/favicon/favicon_handler.h b/chrome/browser/favicon/favicon_handler.h new file mode 100644 index 0000000..8cc26be --- /dev/null +++ b/chrome/browser/favicon/favicon_handler.h @@ -0,0 +1,261 @@ +// 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. + +#ifndef CHROME_BROWSER_FAVICON_FAVICON_HANDLER_H_ +#define CHROME_BROWSER_FAVICON_FAVICON_HANDLER_H_ +#pragma once + +#include <map> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "chrome/browser/favicon/favicon_service.h" +#include "chrome/browser/favicon/favicon_tab_helper.h" +#include "chrome/common/favicon_url.h" +#include "chrome/common/ref_counted_util.h" +#include "content/browser/cancelable_request.h" +#include "googleurl/src/gurl.h" +#include "ui/gfx/favicon_size.h" + +class NavigationEntry; +class Profile; +class RefCountedMemory; +class SkBitmap; +class TabContents; + +// FaviconHandler works with FaviconTabHelper to fetch the specific type of +// favicon. +// +// FetchFavicon requests the favicon from the favicon service which in turn +// requests the favicon from the history database. At this point +// we only know the URL of the page, and not necessarily the url of the +// favicon. To ensure we handle reloading stale favicons as well as +// reloading a favicon on page reload we always request the favicon from +// history regardless of whether the NavigationEntry has a favicon. +// +// After the navigation two types of events are delivered (which is +// first depends upon who is faster): notification from the history +// db on our request for the favicon (OnFaviconDataForInitialURL), +// or a message from the renderer giving us the URL of the favicon for +// the page (SetFaviconURL). +// . If the history db has a valid up to date favicon for the page, we update +// the NavigationEntry and use the favicon. +// . When we receive the favicon url if it matches that of the NavigationEntry +// and the NavigationEntry's favicon is set, we do nothing (everything is +// ok). +// . On the other hand if the database does not know the favicon for url, or +// the favicon is out date, or the URL from the renderer does not match that +// NavigationEntry we proceed to DownloadFaviconOrAskHistory. Before we +// invoke DownloadFaviconOrAskHistory we wait until we've received both +// the favicon url and the callback from history. We wait to ensure we +// truly know both the favicon url and the state of the database. +// +// DownloadFaviconOrAskHistory does the following: +// . If we have a valid favicon, but it is expired we ask the renderer to +// download the favicon. +// . Otherwise we ask the history database to update the mapping from +// page url to favicon url and call us back with the favicon. Remember, it is +// possible for the db to already have the favicon, just not the mapping +// between page to favicon url. The callback for this is OnFaviconData. +// +// OnFaviconData either updates the favicon of the NavigationEntry (if the +// db knew about the favicon), or requests the renderer to download the +// favicon. +// +// When the renderer downloads the favicon SetFaviconImageData is invoked, +// at which point we update the favicon of the NavigationEntry and notify +// the database to save the favicon. + +class FaviconHandler { + public: + enum Type { + FAVICON, + TOUCH, + }; + + FaviconHandler(TabContents* tab_contents, Type icon_type); + virtual ~FaviconHandler(); + + // Initiates loading the favicon for the specified url. + void FetchFavicon(const GURL& url); + + // Initiates loading an image from given |image_url|. Returns a download id + // for caller to track the request. When download completes, |callback| is + // called with the three params: the download_id, a boolean flag to indicate + // whether the download succeeds and a SkBitmap as the downloaded image. + // Note that |image_size| is a hint for images with multiple sizes. The + // downloaded image is not resized to the given image_size. If 0 is passed, + // the first frame of the image is returned. + int DownloadImage(const GURL& image_url, + int image_size, + history::IconType icon_type, + FaviconTabHelper::ImageDownloadCallback* callback); + + // Message Handler. Must be public, because also called from + // PrerenderContents. + void OnUpdateFaviconURL(int32 page_id, + const std::vector<FaviconURL>& candidates); + + void OnDidDownloadFavicon(int id, + const GURL& image_url, + bool errored, + const SkBitmap& image); + + protected: + // These virtual methods make FaviconHandler testable and are overridden by + // TestFaviconHandler. + + // Return the NavigationEntry for the active entry, or NULL if the active + // entries URL does not match that of the URL last passed to FetchFavicon. + virtual NavigationEntry* GetEntry(); + + // Asks the render to download favicon, returns the request id. + virtual int DownloadFavicon(const GURL& image_url, int image_size); + + // Ask the favicon from history + virtual void UpdateFaviconMappingAndFetch( + const GURL& page_url, + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback); + + virtual void GetFavicon( + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback); + + virtual void GetFaviconForURL( + const GURL& page_url, + int icon_types, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback); + + virtual void SetHistoryFavicon(const GURL& page_url, + const GURL& icon_url, + const std::vector<unsigned char>& image_data, + history::IconType icon_type); + + virtual FaviconService* GetFaviconService(); + + // Returns true if the favicon should be saved. + virtual bool ShouldSaveFavicon(const GURL& url); + + private: + friend class TestFaviconHandler; // For testing + + struct DownloadRequest { + DownloadRequest(); + + DownloadRequest(const GURL& url, + const GURL& image_url, + FaviconTabHelper::ImageDownloadCallback* callback, + history::IconType icon_type); + + GURL url; + GURL image_url; + FaviconTabHelper::ImageDownloadCallback* callback; + history::IconType icon_type; + }; + + // See description above class for details. + void OnFaviconDataForInitialURL(FaviconService::Handle handle, + history::FaviconData favicon); + + // If the favicon has expired, asks the renderer to download the favicon. + // Otherwise asks history to update the mapping between page url and icon + // url with a callback to OnFaviconData when done. + void DownloadFaviconOrAskHistory(const GURL& page_url, + const GURL& icon_url, + history::IconType icon_type); + + // See description above class for details. + void OnFaviconData(FaviconService::Handle handle, + history::FaviconData favicon); + + // Schedules a download for the specified entry. This adds the request to + // download_requests_. + int ScheduleDownload(const GURL& url, + const GURL& image_url, + int image_size, + history::IconType icon_type, + FaviconTabHelper::ImageDownloadCallback* callback); + + // Sets the image data for the favicon. This is invoked asynchronously after + // we request the TabContents to download the favicon. + void SetFavicon(const GURL& url, + const GURL& icon_url, + const SkBitmap& image, + history::IconType icon_type); + + // Converts the FAVICON's image data to an SkBitmap and sets it on the + // NavigationEntry. + // If the TabContents has a delegate, it is notified of the new favicon + // (INVALIDATE_FAVICON). + void UpdateFavicon(NavigationEntry* entry, + scoped_refptr<RefCountedMemory> data); + void UpdateFavicon(NavigationEntry* entry, const SkBitmap& image); + + // Scales the image such that either the width and/or height is 16 pixels + // wide. Does nothing if the image is empty. + SkBitmap ConvertToFaviconSize(const SkBitmap& image); + + void FetchFaviconInternal(); + + // Return the current candidate if any. + FaviconURL* current_candidate() { + return (urls_.size() > current_url_index_) ? + &urls_[current_url_index_] : NULL; + } + + // Returns the preferred_icon_size according icon_types_, 0 means no + // preference. + int preferred_icon_size() { + return icon_types_ == history::FAVICON ? kFaviconSize : 0; + } + + TabContents* tab_contents() { + return tab_contents_; + } + + // Used for history requests. + CancelableRequestConsumer cancelable_consumer_; + + // URL of the page we're requesting the favicon for. + GURL url_; + + // Whether we got the initial response for the favicon back from the renderer. + // See "Favicon Details" in tab_contents.cc for more details. + bool got_favicon_from_history_; + + // Whether the favicon is out of date. If true, it means history knows about + // the favicon, but we need to download the favicon because the icon has + // expired. + // See "Favicon Details" in tab_contents.cc for more details. + bool favicon_expired_; + + // Requests to the renderer to download favicons. + typedef std::map<int, DownloadRequest> DownloadRequests; + DownloadRequests download_requests_; + + // The combination of the supported icon types. + const int icon_types_; + + // The prioritized favicon candidates from the page back from the renderer. + std::vector<FaviconURL> urls_; + + // The current candidate's index in urls_. + size_t current_url_index_; + + // The FaviconData from history. + history::FaviconData history_icon_; + + TabContents* tab_contents_; + + DISALLOW_COPY_AND_ASSIGN(FaviconHandler); +}; + +#endif // CHROME_BROWSER_FAVICON_FAVICON_HANDLER_H_ diff --git a/chrome/browser/favicon/favicon_handler_unittest.cc b/chrome/browser/favicon/favicon_handler_unittest.cc new file mode 100644 index 0000000..dc26ef5 --- /dev/null +++ b/chrome/browser/favicon/favicon_handler_unittest.cc @@ -0,0 +1,768 @@ +// 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/favicon_handler.h" +#include "content/browser/renderer_host/test_render_view_host.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/test_tab_contents.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/favicon_size.h" + +class TestFaviconHandler; + +namespace { + +// Fill the given bmp with valid png data. +void FillDataToBitmap(int w, int h, SkBitmap* bmp) { + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + unsigned char* src_data = + reinterpret_cast<unsigned char*>(bmp->getAddr32(0, 0)); + for (int i = 0; i < w * h; i++) { + src_data[i * 4 + 0] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 1] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 2] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 3] = static_cast<unsigned char>(i % 255); + } +} + +// Fill the given data buffer with valid png data. +void FillBitmap(int w, int h, std::vector<unsigned char>* output) { + SkBitmap bitmap; + FillDataToBitmap(w, h, &bitmap); + gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, output); +} + +// This class is used to save the download request for verifying with test case. +// It also will be used to invoke the onDidDownload callback. +class DownloadHandler { + public: + DownloadHandler(int download_id, + const GURL& image_url, + int image_size, + TestFaviconHandler* favicon_helper) + : image_url_(image_url), + image_size_(image_size), + failed_(false), + download_id_(download_id), + favicon_helper_(favicon_helper) { + FillDataToBitmap(16, 16, &bitmap_); + } + + virtual ~DownloadHandler() { + } + + static void UpdateFaviconURL(FaviconHandler* helper, + const std::vector<FaviconURL> urls); + + void InvokeCallback(); + + void UpdateFaviconURL(const std::vector<FaviconURL> urls); + + const GURL image_url_; + const int image_size_; + + // Simulates download failed or not. + bool failed_; + + private: + // Identified the specific download, will also be passed in + // OnDidDownloadFavicon callback. + int download_id_; + TestFaviconHandler* favicon_helper_; + SkBitmap bitmap_; + + DISALLOW_COPY_AND_ASSIGN(DownloadHandler); +}; + +// This class is used to save the history request for verifying with test case. +// It also will be used to simulate the history response. +class HistoryRequestHandler { + public: + HistoryRequestHandler(const GURL& page_url, + const GURL& icon_url, + int icon_type, + FaviconService::FaviconDataCallback* callback) + : page_url_(page_url), + icon_url_(icon_url), + icon_type_(icon_type), + callback_(callback) { + } + + HistoryRequestHandler(const GURL& page_url, + const GURL& icon_url, + int icon_type, + const std::vector<unsigned char>& image_data, + FaviconService::FaviconDataCallback* callback) + : page_url_(page_url), + icon_url_(icon_url), + icon_type_(icon_type), + image_data_(image_data), + callback_(callback) { + } + + virtual ~HistoryRequestHandler() { + delete callback_; + } + void InvokeCallback(); + + const GURL page_url_; + const GURL icon_url_; + const int icon_type_; + const std::vector<unsigned char> image_data_; + history::FaviconData favicon_data_; + FaviconService::FaviconDataCallback* callback_; + + private: + DISALLOW_COPY_AND_ASSIGN(HistoryRequestHandler); +}; + +} // namespace + +// This class is used to catch the FaviconHandler's download and history +// request, and also provide the methods to access the FaviconHandler internal. +class TestFaviconHandler : public FaviconHandler { + public: + TestFaviconHandler(const GURL& page_url, + TabContents* tab_contents, + Type type) + : FaviconHandler(tab_contents, type), + download_image_size_(0), + download_id_(0), + tab_contents_(tab_contents){ + entry_.set_url(page_url); + } + + virtual ~TestFaviconHandler() { + } + + HistoryRequestHandler* history_handler() { + return history_handler_.get(); + } + + // This method will take the ownership of the given handler. + void set_history_handler(HistoryRequestHandler* handler) { + history_handler_.reset(handler); + } + + DownloadHandler* download_handler() { + return download_handler_.get(); + } + + // This method will take the ownership of the given download_handler. + void set_download_handler(DownloadHandler* download_handler) { + download_handler_.reset(download_handler); + } + + virtual NavigationEntry* GetEntry() { + return &entry_; + } + + const std::vector<FaviconURL>& urls() { + return urls_; + } + + void FetchFavicon(const GURL& url) { + FaviconHandler::FetchFavicon(url); + } + + // The methods to access favicon internal. + FaviconURL* current_candidate() { + return FaviconHandler::current_candidate(); + } + + void OnDidDownloadFavicon(int id, + const GURL& image_url, + bool errored, + const SkBitmap& image) { + FaviconHandler::OnDidDownloadFavicon(id, image_url, errored, image); + } + + protected: + virtual void UpdateFaviconMappingAndFetch( + const GURL& page_url, + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback) OVERRIDE { + history_handler_.reset(new HistoryRequestHandler(page_url, icon_url, + icon_type, callback)); + } + + virtual void GetFavicon( + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback) OVERRIDE { + history_handler_.reset(new HistoryRequestHandler(GURL(), icon_url, + icon_type, callback)); + } + + virtual void GetFaviconForURL( + const GURL& page_url, + int icon_types, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback) OVERRIDE { + history_handler_.reset(new HistoryRequestHandler(page_url, GURL(), + icon_types, callback)); + } + + virtual int DownloadFavicon(const GURL& image_url, int image_size) OVERRIDE { + download_id_++; + download_handler_.reset(new DownloadHandler(download_id_, image_url, + image_size, this)); + return download_id_; + } + + virtual void SetHistoryFavicon(const GURL& page_url, + const GURL& icon_url, + const std::vector<unsigned char>& image_data, + history::IconType icon_type) OVERRIDE { + history_handler_.reset(new HistoryRequestHandler(page_url, icon_url, + icon_type, image_data, NULL)); + } + + virtual FaviconService* GetFaviconService() OVERRIDE { + // Just give none NULL value, so overridden methods can be hit. + return (FaviconService*)(1); + } + + virtual bool ShouldSaveFavicon(const GURL& url) OVERRIDE { + return true; + } + + GURL page_url_; + + GURL download_image_url_; + int download_image_size_; + + private: + NavigationEntry entry_; + + // The unique id of a download request. It will be returned to a + // FaviconHandler. + int download_id_; + + TabContents* tab_contents_; + scoped_ptr<DownloadHandler> download_handler_; + scoped_ptr<HistoryRequestHandler> history_handler_; + + DISALLOW_COPY_AND_ASSIGN(TestFaviconHandler); +}; + +namespace { + +void DownloadHandler::UpdateFaviconURL(FaviconHandler* helper, + const std::vector<FaviconURL> urls) { + helper->OnUpdateFaviconURL(0, urls); +} + +void DownloadHandler::UpdateFaviconURL(const std::vector<FaviconURL> urls) { + UpdateFaviconURL(favicon_helper_, urls); +} + +void DownloadHandler::InvokeCallback() { + favicon_helper_->OnDidDownloadFavicon(download_id_, image_url_, failed_, + bitmap_); +} + +void HistoryRequestHandler::InvokeCallback() { + callback_->Run(0, favicon_data_); +} + +class FaviconHandlerTest : public RenderViewHostTestHarness { +}; + +TEST_F(FaviconHandlerTest, GetFaviconFromHistory) { + const GURL page_url("http://www.google.com"); + const GURL icon_url("http://www.google.com/favicon"); + + TestFaviconHandler helper(page_url, contents(), FaviconHandler::FAVICON); + + helper.FetchFavicon(page_url); + HistoryRequestHandler* history_handler = helper.history_handler(); + // Ensure the data given to history is correct. + ASSERT_TRUE(history_handler); + EXPECT_EQ(page_url, history_handler->page_url_); + EXPECT_EQ(GURL(), history_handler->icon_url_); + EXPECT_EQ(history::FAVICON, history_handler->icon_type_); + + // Set valid icon data. + history_handler->favicon_data_.known_icon = true; + history_handler->favicon_data_.icon_type = history::FAVICON; + history_handler->favicon_data_.expired = false; + history_handler->favicon_data_.icon_url = icon_url; + scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); + FillBitmap(kFaviconSize, kFaviconSize, &data->data); + history_handler->favicon_data_.image_data = data; + + // Send history response. + history_handler->InvokeCallback(); + // Verify FaviconHandler status + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_EQ(icon_url, helper.GetEntry()->favicon().url()); + + // Simulates update favicon url. + std::vector<FaviconURL> urls; + urls.push_back(FaviconURL(icon_url, FaviconURL::FAVICON)); + DownloadHandler::UpdateFaviconURL(&helper, urls); + + // Verify FaviconHandler status + EXPECT_EQ(1U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + ASSERT_EQ(icon_url, helper.current_candidate()->icon_url); + ASSERT_EQ(FaviconURL::FAVICON, helper.current_candidate()->icon_type); + + // Favicon shouldn't request to download icon. + DownloadHandler* download_handler = helper.download_handler(); + ASSERT_FALSE(download_handler); +} + +TEST_F(FaviconHandlerTest, DownloadFavicon) { + const GURL page_url("http://www.google.com"); + const GURL icon_url("http://www.google.com/favicon"); + + TestFaviconHandler helper(page_url, contents(), FaviconHandler::FAVICON); + + helper.FetchFavicon(page_url); + HistoryRequestHandler* history_handler = helper.history_handler(); + // Ensure the data given to history is correct. + ASSERT_TRUE(history_handler); + EXPECT_EQ(page_url, history_handler->page_url_); + EXPECT_EQ(GURL(), history_handler->icon_url_); + EXPECT_EQ(history::FAVICON, history_handler->icon_type_); + + // Set icon data expired + history_handler->favicon_data_.known_icon = true; + history_handler->favicon_data_.icon_type = history::FAVICON; + history_handler->favicon_data_.expired = true; + history_handler->favicon_data_.icon_url = icon_url; + // Send history response. + history_handler->InvokeCallback(); + // Verify FaviconHandler status + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_EQ(icon_url, helper.GetEntry()->favicon().url()); + + // Simulates update favicon url. + std::vector<FaviconURL> urls; + urls.push_back(FaviconURL(icon_url, FaviconURL::FAVICON)); + DownloadHandler::UpdateFaviconURL(&helper, urls); + + // Verify FaviconHandler status + EXPECT_EQ(1U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + ASSERT_EQ(icon_url, helper.current_candidate()->icon_url); + ASSERT_EQ(FaviconURL::FAVICON, helper.current_candidate()->icon_type); + + // Favicon should request to download icon now. + DownloadHandler* download_handler = helper.download_handler(); + ASSERT_TRUE(download_handler); + // Verify the download request. + EXPECT_EQ(icon_url, download_handler->image_url_); + EXPECT_EQ(kFaviconSize, download_handler->image_size_); + + // Reset the history_handler to verify whether favicon is set. + helper.set_history_handler(NULL); + + // Smulates download done. + download_handler->InvokeCallback(); + + // New icon should be saved to history backend and navigation entry. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::FAVICON, history_handler->icon_type_); + EXPECT_LT(0U, history_handler->image_data_.size()); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Verify NavigationEntry. + EXPECT_EQ(icon_url, helper.GetEntry()->favicon().url()); + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_FALSE(helper.GetEntry()->favicon().bitmap().empty()); +} + +TEST_F(FaviconHandlerTest, UpdateAndDownloadFavicon) { + const GURL page_url("http://www.google.com"); + const GURL icon_url("http://www.google.com/favicon"); + const GURL new_icon_url("http://www.google.com/new_favicon"); + + TestFaviconHandler helper(page_url, contents(), FaviconHandler::FAVICON); + + helper.FetchFavicon(page_url); + HistoryRequestHandler* history_handler = helper.history_handler(); + // Ensure the data given to history is correct. + ASSERT_TRUE(history_handler); + EXPECT_EQ(page_url, history_handler->page_url_); + EXPECT_EQ(GURL(), history_handler->icon_url_); + EXPECT_EQ(history::FAVICON, history_handler->icon_type_); + + // Set valid icon data. + history_handler->favicon_data_.known_icon = true; + history_handler->favicon_data_.icon_type = history::FAVICON; + history_handler->favicon_data_.expired = false; + history_handler->favicon_data_.icon_url = icon_url; + scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); + FillBitmap(kFaviconSize, kFaviconSize, &data->data); + history_handler->favicon_data_.image_data = data; + + // Send history response. + history_handler->InvokeCallback(); + // Verify FaviconHandler status. + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_EQ(icon_url, helper.GetEntry()->favicon().url()); + + // Reset the history_handler to verify whether new icon is requested from + // history. + helper.set_history_handler(NULL); + + // Simulates update with the different favicon url. + std::vector<FaviconURL> urls; + urls.push_back(FaviconURL(new_icon_url, FaviconURL::FAVICON)); + DownloadHandler::UpdateFaviconURL(&helper, urls); + + // Verify FaviconHandler status. + EXPECT_EQ(1U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + ASSERT_EQ(new_icon_url, helper.current_candidate()->icon_url); + ASSERT_EQ(FaviconURL::FAVICON, helper.current_candidate()->icon_type); + // The favicon status's url should be updated. + ASSERT_EQ(new_icon_url, helper.GetEntry()->favicon().url()); + + // Favicon should be requested from history. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(new_icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::FAVICON, history_handler->icon_type_); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Simulate not find icon. + history_handler->favicon_data_.known_icon = false; + history_handler->InvokeCallback(); + + // Favicon should request to download icon now. + DownloadHandler* download_handler = helper.download_handler(); + ASSERT_TRUE(download_handler); + // Verify the download request. + EXPECT_EQ(new_icon_url, download_handler->image_url_); + EXPECT_EQ(kFaviconSize, download_handler->image_size_); + + // Reset the history_handler to verify whether favicon is set. + helper.set_history_handler(NULL); + + // Smulates download done. + download_handler->InvokeCallback(); + + // New icon should be saved to history backend and navigation entry. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(new_icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::FAVICON, history_handler->icon_type_); + EXPECT_LT(0U, history_handler->image_data_.size()); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Verify NavigationEntry. + EXPECT_EQ(new_icon_url, helper.GetEntry()->favicon().url()); + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_FALSE(helper.GetEntry()->favicon().bitmap().empty()); +} + +TEST_F(FaviconHandlerTest, UpdateFavicon) { + const GURL page_url("http://www.google.com"); + const GURL icon_url("http://www.google.com/favicon"); + const GURL new_icon_url("http://www.google.com/new_favicon"); + + TestFaviconHandler helper(page_url, contents(), FaviconHandler::FAVICON); + + helper.FetchFavicon(page_url); + HistoryRequestHandler* history_handler = helper.history_handler(); + // Ensure the data given to history is correct. + ASSERT_TRUE(history_handler); + EXPECT_EQ(page_url, history_handler->page_url_); + EXPECT_EQ(GURL(), history_handler->icon_url_); + EXPECT_EQ(history::FAVICON, history_handler->icon_type_); + + // Set valid icon data. + history_handler->favicon_data_.known_icon = true; + history_handler->favicon_data_.icon_type = history::FAVICON; + history_handler->favicon_data_.expired = false; + history_handler->favicon_data_.icon_url = icon_url; + scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); + FillBitmap(kFaviconSize, kFaviconSize, &data->data); + history_handler->favicon_data_.image_data = data; + + // Send history response. + history_handler->InvokeCallback(); + // Verify FaviconHandler status. + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_EQ(icon_url, helper.GetEntry()->favicon().url()); + + // Reset the history_handler to verify whether new icon is requested from + // history. + helper.set_history_handler(NULL); + + // Simulates update with the different favicon url. + std::vector<FaviconURL> urls; + urls.push_back(FaviconURL(new_icon_url, FaviconURL::FAVICON)); + DownloadHandler::UpdateFaviconURL(&helper, urls); + + // Verify FaviconHandler status. + EXPECT_EQ(1U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + ASSERT_EQ(new_icon_url, helper.current_candidate()->icon_url); + ASSERT_EQ(FaviconURL::FAVICON, helper.current_candidate()->icon_type); + // The favicon status's url should be updated. + ASSERT_EQ(new_icon_url, helper.GetEntry()->favicon().url()); + + // Favicon should be requested from history. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(new_icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::FAVICON, history_handler->icon_type_); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Simulate find icon. + history_handler->favicon_data_.known_icon = true; + history_handler->favicon_data_.icon_type = history::FAVICON; + history_handler->favicon_data_.expired = false; + history_handler->favicon_data_.icon_url = new_icon_url; + history_handler->favicon_data_.image_data = data; + history_handler->InvokeCallback(); + + // Shouldn't request download favicon + EXPECT_FALSE(helper.download_handler()); + + // Verify the favicon status. + EXPECT_EQ(new_icon_url, helper.GetEntry()->favicon().url()); + EXPECT_TRUE(helper.GetEntry()->favicon().is_valid()); + EXPECT_FALSE(helper.GetEntry()->favicon().bitmap().empty()); +} + +TEST_F(FaviconHandlerTest, Download2ndFaviconURLCandidate) { + const GURL page_url("http://www.google.com"); + const GURL icon_url("http://www.google.com/favicon"); + const GURL new_icon_url("http://www.google.com/new_favicon"); + + TestFaviconHandler helper(page_url, contents(), FaviconHandler::TOUCH); + + helper.FetchFavicon(page_url); + HistoryRequestHandler* history_handler = helper.history_handler(); + // Ensure the data given to history is correct. + ASSERT_TRUE(history_handler); + EXPECT_EQ(page_url, history_handler->page_url_); + EXPECT_EQ(GURL(), history_handler->icon_url_); + EXPECT_EQ(history::TOUCH_PRECOMPOSED_ICON | history::TOUCH_ICON, + history_handler->icon_type_); + + // Icon not found. + history_handler->favicon_data_.known_icon = false; + // Send history response. + history_handler->InvokeCallback(); + // Verify FaviconHandler status. + EXPECT_FALSE(helper.GetEntry()->favicon().is_valid()); + EXPECT_EQ(GURL(), helper.GetEntry()->favicon().url()); + + // Reset the history_handler to verify whether new icon is requested from + // history. + helper.set_history_handler(NULL); + + // Simulates update with the different favicon url. + std::vector<FaviconURL> urls; + urls.push_back(FaviconURL(icon_url, FaviconURL::TOUCH_PRECOMPOSED_ICON)); + urls.push_back(FaviconURL(new_icon_url, FaviconURL::TOUCH_ICON)); + urls.push_back(FaviconURL(new_icon_url, FaviconURL::FAVICON)); + + DownloadHandler::UpdateFaviconURL(&helper, urls); + + // Verify FaviconHandler status. + EXPECT_EQ(2U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + ASSERT_EQ(icon_url, helper.current_candidate()->icon_url); + ASSERT_EQ(FaviconURL::TOUCH_PRECOMPOSED_ICON, + helper.current_candidate()->icon_type); + + // Favicon should be requested from history. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::TOUCH_PRECOMPOSED_ICON, history_handler->icon_type_); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Simulate not find icon. + history_handler->favicon_data_.known_icon = false; + history_handler->InvokeCallback(); + + // Should request download favicon. + DownloadHandler* download_handler = helper.download_handler(); + EXPECT_TRUE(download_handler); + // Verify the download request. + EXPECT_EQ(icon_url, download_handler->image_url_); + EXPECT_EQ(0, download_handler->image_size_); + + // Reset the history_handler to verify whether favicon is request from + // history. + helper.set_history_handler(NULL); + // Smulates download failed. + download_handler->failed_ = true; + download_handler->InvokeCallback(); + + // Left 1 url. + EXPECT_EQ(1U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + EXPECT_EQ(new_icon_url, helper.current_candidate()->icon_url); + EXPECT_EQ(FaviconURL::TOUCH_ICON, helper.current_candidate()->icon_type); + + // Favicon should be requested from history. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(new_icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::TOUCH_ICON, history_handler->icon_type_); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Reset download handler + helper.set_download_handler(NULL); + + // Smulates getting a expired icon from history. + history_handler->favicon_data_.known_icon = true; + history_handler->favicon_data_.icon_type = history::TOUCH_ICON; + history_handler->favicon_data_.expired = true; + history_handler->favicon_data_.icon_url = new_icon_url; + scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); + FillBitmap(kFaviconSize, kFaviconSize, &data->data); + history_handler->favicon_data_.image_data = data; + history_handler->InvokeCallback(); + + // Verify the download request. + download_handler = helper.download_handler(); + EXPECT_TRUE(download_handler); + EXPECT_EQ(new_icon_url, download_handler->image_url_); + EXPECT_EQ(0, download_handler->image_size_); + + helper.set_history_handler(NULL); + + // Simulates icon being downloaded. + download_handler->InvokeCallback(); + + // New icon should be saved to history backend. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(new_icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::TOUCH_ICON, history_handler->icon_type_); + EXPECT_LT(0U, history_handler->image_data_.size()); + EXPECT_EQ(page_url, history_handler->page_url_); +} + +TEST_F(FaviconHandlerTest, UpdateDuringDownloading) { + const GURL page_url("http://www.google.com"); + const GURL icon_url("http://www.google.com/favicon"); + const GURL new_icon_url("http://www.google.com/new_favicon"); + + TestFaviconHandler helper(page_url, contents(), FaviconHandler::TOUCH); + + helper.FetchFavicon(page_url); + HistoryRequestHandler* history_handler = helper.history_handler(); + // Ensure the data given to history is correct. + ASSERT_TRUE(history_handler); + EXPECT_EQ(page_url, history_handler->page_url_); + EXPECT_EQ(GURL(), history_handler->icon_url_); + EXPECT_EQ(history::TOUCH_PRECOMPOSED_ICON | history::TOUCH_ICON, + history_handler->icon_type_); + + // Icon not found. + history_handler->favicon_data_.known_icon = false; + // Send history response. + history_handler->InvokeCallback(); + // Verify FaviconHandler status. + EXPECT_FALSE(helper.GetEntry()->favicon().is_valid()); + EXPECT_EQ(GURL(), helper.GetEntry()->favicon().url()); + + // Reset the history_handler to verify whether new icon is requested from + // history. + helper.set_history_handler(NULL); + + // Simulates update with the different favicon url. + std::vector<FaviconURL> urls; + urls.push_back(FaviconURL(icon_url, FaviconURL::TOUCH_PRECOMPOSED_ICON)); + urls.push_back(FaviconURL(new_icon_url, FaviconURL::TOUCH_ICON)); + urls.push_back(FaviconURL(new_icon_url, FaviconURL::FAVICON)); + + DownloadHandler::UpdateFaviconURL(&helper, urls); + + // Verify FaviconHandler status. + EXPECT_EQ(2U, helper.urls().size()); + ASSERT_TRUE(helper.current_candidate()); + ASSERT_EQ(icon_url, helper.current_candidate()->icon_url); + ASSERT_EQ(FaviconURL::TOUCH_PRECOMPOSED_ICON, + helper.current_candidate()->icon_type); + + // Favicon should be requested from history. + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::TOUCH_PRECOMPOSED_ICON, history_handler->icon_type_); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Simulate not find icon. + history_handler->favicon_data_.known_icon = false; + history_handler->InvokeCallback(); + + // Should request download favicon. + DownloadHandler* download_handler = helper.download_handler(); + EXPECT_TRUE(download_handler); + // Verify the download request. + EXPECT_EQ(icon_url, download_handler->image_url_); + EXPECT_EQ(0, download_handler->image_size_); + + // Reset the history_handler to verify whether favicon is request from + // history. + helper.set_history_handler(NULL); + const GURL latest_icon_url("http://www.google.com/latest_favicon"); + std::vector<FaviconURL> latest_urls; + latest_urls.push_back(FaviconURL(latest_icon_url, FaviconURL::TOUCH_ICON)); + DownloadHandler::UpdateFaviconURL(&helper, latest_urls); + EXPECT_EQ(1U, helper.urls().size()); + EXPECT_EQ(latest_icon_url, helper.current_candidate()->icon_url); + EXPECT_EQ(FaviconURL::TOUCH_ICON, helper.current_candidate()->icon_type); + + // Whether new icon is requested from history + history_handler = helper.history_handler(); + ASSERT_TRUE(history_handler); + EXPECT_EQ(latest_icon_url, history_handler->icon_url_); + EXPECT_EQ(FaviconURL::TOUCH_ICON, history_handler->icon_type_); + EXPECT_EQ(page_url, history_handler->page_url_); + + // Reset the history_handler to verify whether favicon is request from + // history. + // Save the callback for late use. + FaviconService::FaviconDataCallback* callback = history_handler->callback_; + // Prevent the callback from being released. + history_handler->callback_ = NULL; + helper.set_history_handler(NULL); + + // Smulates download succeed. + download_handler->InvokeCallback(); + // The downloaded icon should be thrown away as there is faviocn update. + EXPECT_FALSE(helper.history_handler()); + + helper.set_download_handler(NULL); + + // Smulates getting the icon from history. + scoped_ptr<HistoryRequestHandler> handler; + handler.reset(new HistoryRequestHandler(page_url, latest_icon_url, + history::TOUCH_ICON, callback)); + handler->favicon_data_.known_icon = true; + handler->favicon_data_.expired = false; + handler->favicon_data_.icon_type = history::TOUCH_ICON; + handler->favicon_data_.icon_url = latest_icon_url; + scoped_refptr<RefCountedBytes> data = new RefCountedBytes(); + FillBitmap(kFaviconSize, kFaviconSize, &data->data); + handler->favicon_data_.image_data = data; + + handler->InvokeCallback(); + + // No download request. + EXPECT_FALSE(helper.download_handler()); +} + +} // namespace. diff --git a/chrome/browser/favicon/favicon_service.cc b/chrome/browser/favicon/favicon_service.cc new file mode 100644 index 0000000..a276c7c --- /dev/null +++ b/chrome/browser/favicon/favicon_service.cc @@ -0,0 +1,97 @@ +// 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/favicon_service.h" + +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_backend.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/chrome_web_ui_factory.h" +#include "chrome/common/url_constants.h" + +FaviconService::FaviconService(Profile* profile) : profile_(profile) { +} + +FaviconService::Handle FaviconService::GetFavicon( + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconDataCallback* callback) { + GetFaviconRequest* request = new GetFaviconRequest(callback); + AddRequest(request, consumer); + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->GetFavicon(request, icon_url, icon_type); + else + ForwardEmptyResultAsync(request); + return request->handle(); +} + +FaviconService::Handle FaviconService::UpdateFaviconMappingAndFetch( + const GURL& page_url, + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconService::FaviconDataCallback* callback) { + GetFaviconRequest* request = new GetFaviconRequest(callback); + AddRequest(request, consumer); + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->UpdateFaviconMappingAndFetch(request, page_url, icon_url, icon_type); + else + ForwardEmptyResultAsync(request); + return request->handle(); +} + +FaviconService::Handle FaviconService::GetFaviconForURL( + const GURL& page_url, + int icon_types, + CancelableRequestConsumerBase* consumer, + FaviconDataCallback* callback) { + GetFaviconRequest* request = new GetFaviconRequest(callback); + AddRequest(request, consumer); + FaviconService::Handle handle = request->handle(); + if (page_url.SchemeIs(chrome::kChromeUIScheme) || + page_url.SchemeIs(chrome::kExtensionScheme)) { + ChromeWebUIFactory::GetInstance()->GetFaviconForURL( + profile_, request, page_url); + } else { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->GetFaviconForURL(request, page_url, icon_types); + else + ForwardEmptyResultAsync(request); + } + return handle; +} + +void FaviconService::SetFaviconOutOfDateForPage(const GURL& page_url) { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->SetFaviconOutOfDateForPage(page_url); +} + +void FaviconService::SetImportedFavicons( + const std::vector<history::ImportedFaviconUsage>& favicon_usage) { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->SetImportedFavicons(favicon_usage); +} + +void FaviconService::SetFavicon(const GURL& page_url, + const GURL& icon_url, + const std::vector<unsigned char>& image_data, + history::IconType icon_type) { + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + if (hs) + hs->SetFavicon(page_url, icon_url, image_data, icon_type); +} + +FaviconService::~FaviconService() { +} + +void FaviconService::ForwardEmptyResultAsync(GetFaviconRequest* request) { + request->ForwardResultAsync(FaviconDataCallback::TupleType( + request->handle(), history::FaviconData())); +} diff --git a/chrome/browser/favicon/favicon_service.h b/chrome/browser/favicon/favicon_service.h new file mode 100644 index 0000000..48d81e8 --- /dev/null +++ b/chrome/browser/favicon/favicon_service.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef CHROME_BROWSER_FAVICON_FAVICON_SERVICE_H_ +#define CHROME_BROWSER_FAVICON_FAVICON_SERVICE_H_ +#pragma once + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_memory.h" +#include "base/task.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/common/ref_counted_util.h" +#include "content/browser/cancelable_request.h" + +class GURL; +class Profile; + +// The favicon service provides methods to access favicons. It calls the history +// backend behind the scenes. +// +// This service is thread safe. Each request callback is invoked in the +// thread that made the request. +class FaviconService : public CancelableRequestProvider, + public base::RefCountedThreadSafe<FaviconService> { + public: + explicit FaviconService(Profile* profile); + + // Callback for GetFavicon. If we have previously inquired about the favicon + // for this URL, |know_favicon| will be true, and the rest of the fields will + // be valid (otherwise they will be ignored). + // + // On |know_favicon| == true, |data| will either contain the PNG encoded + // favicon data, or it will be NULL to indicate that the site does not have + // a favicon (in other words, we know the site doesn't have a favicon, as + // opposed to not knowing anything). |expired| will be set to true if we + // refreshed the favicon "too long" ago and should be updated if the page + // is visited again. + typedef Callback2<Handle, // handle + history::FaviconData>::Type // the type of favicon + FaviconDataCallback; + + typedef CancelableRequest<FaviconDataCallback> GetFaviconRequest; + + // Requests the |icon_type| of favicon. |consumer| is notified when the bits + // have been fetched. + Handle GetFavicon(const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconDataCallback* callback); + + // Fetches the |icon_type| of favicon at |icon_url|, sending the results to + // the given |callback|. If the favicon has previously been set via + // SetFavicon(), then the favicon URL for |page_url| and all redirects is set + // to |icon_url|. If the favicon has not been set, the database is not + // updated. + Handle UpdateFaviconMappingAndFetch(const GURL& page_url, + const GURL& icon_url, + history::IconType icon_type, + CancelableRequestConsumerBase* consumer, + FaviconDataCallback* callback); + + // Requests any |icon_types| of favicon for a web page URL. |consumer| is + // notified when the bits have been fetched. |icon_types| can be any + // combination of IconType value, but only one icon will be returned in the + // priority of TOUCH_PRECOMPOSED_ICON, TOUCH_ICON and FAVICON. + // + // Note: this version is intended to be used to retrieve the favicon of a + // page that has been browsed in the past. |expired| in the callback is + // always false. + Handle GetFaviconForURL(const GURL& page_url, + int icon_types, + CancelableRequestConsumerBase* consumer, + FaviconDataCallback* callback); + + // Marks all types of favicon for the page as being out of date. + void SetFaviconOutOfDateForPage(const GURL& page_url); + + // Allows the importer to set many favicons for many pages at once. The pages + // must exist, any favicon sets for unknown pages will be discarded. Existing + // favicons will not be overwritten. + void SetImportedFavicons( + const std::vector<history::ImportedFaviconUsage>& favicon_usage); + + // Sets the favicon for a page. + void SetFavicon(const GURL& page_url, + const GURL& icon_url, + const std::vector<unsigned char>& image_data, + history::IconType icon_type); + + private: + friend class base::RefCountedThreadSafe<FaviconService>; + + ~FaviconService(); + + Profile* profile_; + + // Helper to forward an empty result if we cannot get the history service. + void ForwardEmptyResultAsync(GetFaviconRequest* request); + + DISALLOW_COPY_AND_ASSIGN(FaviconService); +}; + +#endif // CHROME_BROWSER_FAVICON_FAVICON_SERVICE_H_ diff --git a/chrome/browser/favicon/favicon_tab_helper.cc b/chrome/browser/favicon/favicon_tab_helper.cc new file mode 100644 index 0000000..f6908fb --- /dev/null +++ b/chrome/browser/favicon/favicon_tab_helper.cc @@ -0,0 +1,67 @@ +// 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/favicon_tab_helper.h" + +#include "chrome/browser/defaults.h" +#include "chrome/browser/favicon/favicon_handler.h" +#include "chrome/common/icon_messages.h" + +FaviconTabHelper::FaviconTabHelper(TabContents* tab_contents) + : TabContentsObserver(tab_contents) { + favicon_handler_.reset(new FaviconHandler(tab_contents, + FaviconHandler::FAVICON)); + if (browser_defaults::kEnableTouchIcon) + touch_icon_handler_.reset(new FaviconHandler(tab_contents, + FaviconHandler::TOUCH)); +} + +FaviconTabHelper::~FaviconTabHelper() { +} + +void FaviconTabHelper::FetchFavicon(const GURL& url) { + favicon_handler_->FetchFavicon(url); + if (touch_icon_handler_.get()) + touch_icon_handler_->FetchFavicon(url); +} + +int FaviconTabHelper::DownloadImage(const GURL& image_url, + int image_size, + history::IconType icon_type, + ImageDownloadCallback* callback) { + if (icon_type == history::FAVICON) + return favicon_handler_->DownloadImage(image_url, image_size, icon_type, + callback); + else if (touch_icon_handler_.get()) + return touch_icon_handler_->DownloadImage(image_url, image_size, icon_type, + callback); + return 0; +} + +bool FaviconTabHelper::OnMessageReceived(const IPC::Message& message) { + bool message_handled = true; + IPC_BEGIN_MESSAGE_MAP(FaviconTabHelper, message) + IPC_MESSAGE_HANDLER(IconHostMsg_DidDownloadFavicon, OnDidDownloadFavicon) + IPC_MESSAGE_HANDLER(IconHostMsg_UpdateFaviconURL, OnUpdateFaviconURL) + IPC_MESSAGE_UNHANDLED(message_handled = false) + IPC_END_MESSAGE_MAP() + return message_handled; +} + +void FaviconTabHelper::OnDidDownloadFavicon(int id, + const GURL& image_url, + bool errored, + const SkBitmap& image) { + favicon_handler_->OnDidDownloadFavicon(id, image_url, errored, image); + if (touch_icon_handler_.get()) + touch_icon_handler_->OnDidDownloadFavicon(id, image_url, errored, image); +} + +void FaviconTabHelper::OnUpdateFaviconURL( + int32 page_id, + const std::vector<FaviconURL>& candidates) { + favicon_handler_->OnUpdateFaviconURL(page_id, candidates); + if (touch_icon_handler_.get()) + touch_icon_handler_->OnUpdateFaviconURL(page_id, candidates); +} diff --git a/chrome/browser/favicon/favicon_tab_helper.h b/chrome/browser/favicon/favicon_tab_helper.h new file mode 100644 index 0000000..64064f4 --- /dev/null +++ b/chrome/browser/favicon/favicon_tab_helper.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef CHROME_BROWSER_FAVICON_FAVICON_TAB_HELPER_H_ +#define CHROME_BROWSER_FAVICON_FAVICON_TAB_HELPER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/callback.h" +#include "chrome/browser/favicon/favicon_service.h" +#include "chrome/common/favicon_url.h" +#include "content/browser/tab_contents/tab_contents_observer.h" +#include "googleurl/src/gurl.h" + +class FaviconHandler; +class NavigationEntry; +class Profile; +class RefCountedMemory; +class SkBitmap; +class TabContents; + +// FaviconTabHelper works with FaviconHandlers to fetch the favicons. +// +// FetchFavicon fetches the given page's icons. It requests the icons from the +// history backend. If the icon is not available or expired, the icon will be +// downloaded and saved in the history backend. +// +// DownloadImage downloads the specified icon and returns it through the given +// callback. +// +class FaviconTabHelper : public TabContentsObserver { + public: + explicit FaviconTabHelper(TabContents* tab_contents); + virtual ~FaviconTabHelper(); + + // Initiates loading the favicon for the specified url. + void FetchFavicon(const GURL& url); + + // Initiates loading an image from given |image_url|. Returns a download id + // for caller to track the request. When download completes, |callback| is + // called with the three params: the download_id, a boolean flag to indicate + // whether the download succeeds and a SkBitmap as the downloaded image. + // Note that |image_size| is a hint for images with multiple sizes. The + // downloaded image is not resized to the given image_size. If 0 is passed, + // the first frame of the image is returned. + typedef Callback3<int, bool, const SkBitmap&>::Type ImageDownloadCallback; + int DownloadImage(const GURL& image_url, + int image_size, + history::IconType icon_type, + ImageDownloadCallback* callback); + + // Message Handler. Must be public, because also called from + // PrerenderContents. + void OnUpdateFaviconURL(int32 page_id, + const std::vector<FaviconURL>& candidates); + + private: + // TabContentsObserver overrides. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + void OnDidDownloadFavicon(int id, + const GURL& image_url, + bool errored, + const SkBitmap& image); + + scoped_ptr<FaviconHandler> favicon_handler_; + + // Handles downloading touchicons. It is NULL if + // browser_defaults::kEnableTouchIcon is false. + scoped_ptr<FaviconHandler> touch_icon_handler_; + + DISALLOW_COPY_AND_ASSIGN(FaviconTabHelper); +}; + +#endif // CHROME_BROWSER_FAVICON_FAVICON_TAB_HELPER_H_ |