blob: 744bc420e7cb388295f8147c0cd109c860eacf67 [file] [log] [blame]
// Copyright 2018 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 "components/omnibox/browser/favicon_cache.h"
#include <tuple>
#include "base/containers/mru_cache.h"
#include "components/favicon/core/favicon_service.h"
#include "components/history/core/browser/history_service.h"
#include "components/omnibox/browser/autocomplete_result.h"
namespace {
size_t GetFaviconCacheSize() {
// Set cache size to twice the number of maximum results to avoid favicon
// refetches as the user types. Favicon fetches are uncached and can hit disk.
return 2 * AutocompleteResult::GetMaxMatches();
}
} // 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),
history_observer_(this),
mru_cache_(GetFaviconCacheSize()),
responses_without_favicons_(GetFaviconCacheSize()),
weak_factory_(this) {
if (history_service) {
history_observer_.Add(history_service);
favicons_changed_subscription_ =
history_service->AddFaviconsChangedCallback(base::BindRepeating(
&FaviconCache::OnFaviconsChanged, weak_factory_.GetWeakPtr()));
}
}
FaviconCache::~FaviconCache() {}
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::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 = mru_cache_.Get(request);
if (cache_iterator != mru_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::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;
}
mru_cache_.Put(request, result.image);
auto it = pending_requests_.find(request);
DCHECK(it != pending_requests_.end());
for (auto& callback : it->second) {
std::move(callback).Run(result.image);
}
pending_requests_.erase(it);
}
void FaviconCache::OnURLVisited(history::HistoryService* history_service,
ui::PageTransition transition,
const history::URLRow& row,
const history::RedirectList& redirects,
base::Time visit_time) {
auto it =
responses_without_favicons_.Peek({RequestType::BY_PAGE_URL, row.url()});
if (it != responses_without_favicons_.end())
responses_without_favicons_.Erase(it);
}
void FaviconCache::InvalidateCachedRequests(const Request& request) {
{
auto it = mru_cache_.Peek(request);
if (it != mru_cache_.end())
mru_cache_.Erase(it);
}
{
auto it = responses_without_favicons_.Peek(request);
if (it != responses_without_favicons_.end())
responses_without_favicons_.Erase(it);
}
}
void FaviconCache::OnURLsDeleted(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()) {
mru_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});
}