blob: 17e008e568d70a23071ba143930abd889b48b6d2 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/favicon_cache.h"
#include <tuple>
#include "base/containers/lru_cache.h"
#include "base/functional/bind.h"
#include "components/favicon/core/favicon_service.h"
#include "components/omnibox/browser/autocomplete_result.h"
namespace {
size_t GetFaviconCacheSize() {
// Set cache size to twice the number of maximum results in either the
// on-focus or prefix-suggest mode to avoid favicon refetches as the user
// types. Favicon fetches are uncached and can hit disk.
return 2 * std::max(AutocompleteResult::GetMaxMatches(),
AutocompleteResult::GetMaxMatches(true));
}
} // namespace
bool FaviconCache::Request::operator<(const Request& rhs) const {
// Compare |type| first, and if equal, compare |url|.
return std::tie(type, url) < std::tie(rhs.type, rhs.url);
}
FaviconCache::FaviconCache(favicon::FaviconService* favicon_service,
history::HistoryService* history_service)
: favicon_service_(favicon_service),
lru_cache_(GetFaviconCacheSize()),
responses_without_favicons_(GetFaviconCacheSize()) {
if (history_service) {
history_observation_.Observe(history_service);
favicons_changed_subscription_ =
history_service->AddFaviconsChangedCallback(base::BindRepeating(
&FaviconCache::OnFaviconsChanged, weak_factory_.GetWeakPtr()));
}
}
FaviconCache::~FaviconCache() = default;
gfx::Image FaviconCache::GetFaviconForPageUrl(
const GURL& page_url,
FaviconFetchedCallback on_favicon_fetched) {
return GetFaviconInternal({RequestType::BY_PAGE_URL, page_url},
std::move(on_favicon_fetched));
}
gfx::Image FaviconCache::GetLargestFaviconForPageUrl(
const GURL& page_url,
FaviconFetchedCallback on_favicon_fetched) {
return GetFaviconInternal({RequestType::RAW_BY_PAGE_URL, page_url},
std::move(on_favicon_fetched));
}
gfx::Image FaviconCache::GetFaviconForIconUrl(
const GURL& icon_url,
FaviconFetchedCallback on_favicon_fetched) {
return GetFaviconInternal({RequestType::BY_ICON_URL, icon_url},
std::move(on_favicon_fetched));
}
gfx::Image FaviconCache::GetFaviconInternal(
const Request& request,
FaviconFetchedCallback on_favicon_fetched) {
if (!favicon_service_)
return gfx::Image();
if (request.url.is_empty() || !request.url.is_valid())
return gfx::Image();
// Early exit if we have a cached favicon ready.
auto cache_iterator = lru_cache_.Get(request);
if (cache_iterator != lru_cache_.end())
return cache_iterator->second;
// Early exit if we've already established that we don't have the favicon.
if (responses_without_favicons_.Peek(request) !=
responses_without_favicons_.end()) {
return gfx::Image();
}
// We have an outstanding request for this page. Add one more waiting callback
// and return an empty gfx::Image.
auto it = pending_requests_.find(request);
if (it != pending_requests_.end()) {
it->second.push_back(std::move(on_favicon_fetched));
return gfx::Image();
}
if (request.type == RequestType::BY_PAGE_URL) {
favicon_service_->GetFaviconImageForPageURL(
request.url,
base::BindRepeating(&FaviconCache::OnFaviconFetched,
weak_factory_.GetWeakPtr(), request),
&task_tracker_);
} else if (request.type == RequestType::RAW_BY_PAGE_URL) {
favicon_service_->GetRawFaviconForPageURL(
request.url, {favicon_base::IconType::kFavicon},
/*icon_size_in_pixels=*/0, /*fallback_to_host=*/false,
base::BindRepeating(&FaviconCache::OnFaviconRawBitmapFetched,
weak_factory_.GetWeakPtr(), request),
&task_tracker_);
} else if (request.type == RequestType::BY_ICON_URL) {
favicon_service_->GetFaviconImage(
request.url,
base::BindRepeating(&FaviconCache::OnFaviconFetched,
weak_factory_.GetWeakPtr(), request),
&task_tracker_);
} else {
NOTREACHED();
}
pending_requests_[request].push_back(std::move(on_favicon_fetched));
return gfx::Image();
}
void FaviconCache::OnFaviconFetched(
const Request& request,
const favicon_base::FaviconImageResult& result) {
if (result.image.IsEmpty()) {
responses_without_favicons_.Put(request, true);
pending_requests_.erase(request);
return;
}
InvokeRequestCallbackWithFavicon(request, result.image);
}
void FaviconCache::OnFaviconRawBitmapFetched(
const Request& request,
const favicon_base::FaviconRawBitmapResult& bitmap_result) {
if (!bitmap_result.is_valid()) {
responses_without_favicons_.Put(request, true);
pending_requests_.erase(request);
return;
}
InvokeRequestCallbackWithFavicon(
request, gfx::Image::CreateFrom1xPNGBytes(bitmap_result.bitmap_data));
}
void FaviconCache::InvokeRequestCallbackWithFavicon(const Request& request,
const gfx::Image& image) {
DCHECK(!image.IsEmpty());
lru_cache_.Put(request, image);
auto it = pending_requests_.find(request);
CHECK(it != pending_requests_.end());
for (auto& callback : it->second) {
std::move(callback).Run(image);
}
pending_requests_.erase(it);
}
void FaviconCache::OnURLVisited(history::HistoryService* history_service,
const history::URLRow& url_row,
const history::VisitRow& new_visit) {
auto it = responses_without_favicons_.Peek(
{RequestType::BY_PAGE_URL, url_row.url()});
if (it != responses_without_favicons_.end())
responses_without_favicons_.Erase(it);
}
void FaviconCache::InvalidateCachedRequests(const Request& request) {
{
auto it = lru_cache_.Peek(request);
if (it != lru_cache_.end())
lru_cache_.Erase(it);
}
{
auto it = responses_without_favicons_.Peek(request);
if (it != responses_without_favicons_.end())
responses_without_favicons_.Erase(it);
}
}
void FaviconCache::OnHistoryDeletions(
history::HistoryService* history_service,
const history::DeletionInfo& deletion_info) {
// We only care about actual user (or sync) deletions.
if (deletion_info.is_from_expiration())
return;
if (deletion_info.IsAllHistory()) {
lru_cache_.Clear();
responses_without_favicons_.Clear();
return;
}
for (const history::URLRow& row : deletion_info.deleted_rows()) {
InvalidateCachedRequests({RequestType::BY_PAGE_URL, row.url()});
}
}
void FaviconCache::OnFaviconsChanged(const std::set<GURL>& page_urls,
const GURL& icon_url) {
for (GURL page_url : page_urls) {
InvalidateCachedRequests({RequestType::BY_PAGE_URL, page_url});
}
InvalidateCachedRequests({RequestType::BY_ICON_URL, icon_url});
}