blob: 51c30a9d619445f744ae37369cba5912634250f8 [file] [log] [blame]
// Copyright 2012 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/favicon/core/favicon_backend.h"
#include <algorithm>
#include <vector>
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/favicon/core/favicon_backend_delegate.h"
#include "components/favicon/core/favicon_database.h"
#include "components/favicon_base/favicon_util.h"
#include "components/favicon_base/select_favicon_frames.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/origin.h"
namespace favicon {
using RedirectList = std::vector<GURL>;
namespace {
// The amount of time before we re-fetch the favicon.
constexpr base::TimeDelta kFaviconRefetchDelta = base::Days(7);
bool IsFaviconBitmapExpired(base::Time last_updated) {
return (base::Time::Now() - last_updated) > kFaviconRefetchDelta;
}
bool AreIconTypesEquivalent(favicon_base::IconType type_a,
favicon_base::IconType type_b) {
if (type_a == type_b)
return true;
// Two icon types are considered 'equivalent' if both types are one of
// kTouchIcon, kTouchPrecomposedIcon or kWebManifestIcon.
const favicon_base::IconTypeSet equivalent_types = {
favicon_base::IconType::kTouchIcon,
favicon_base::IconType::kTouchPrecomposedIcon,
favicon_base::IconType::kWebManifestIcon};
if (equivalent_types.count(type_a) != 0 &&
equivalent_types.count(type_b) != 0) {
return true;
}
return false;
}
} // namespace
// static
std::unique_ptr<FaviconBackend> FaviconBackend::Create(
const base::FilePath& path,
FaviconBackendDelegate* delegate) {
std::unique_ptr<FaviconDatabase> db =
std::make_unique<favicon::FaviconDatabase>();
if (db->Init(path) != sql::INIT_OK) {
LOG(WARNING) << "Could not initialize the favicon database.";
return nullptr;
}
// Computing metrics is costly, only do it every so often.
if (base::RandInt(1, 100) == 50)
db->ComputeDatabaseMetrics();
// WrapUnique() as constructor is private.
return base::WrapUnique(new FaviconBackend(std::move(db), delegate));
}
FaviconBackend::~FaviconBackend() {
db_->CommitTransaction();
}
void FaviconBackend::Commit() {
db_->CommitTransaction();
DCHECK_EQ(db_->transaction_nesting(), 0)
<< "Somebody left a transaction open";
db_->BeginTransaction();
}
void FaviconBackend::TrimMemory() {
db_->TrimMemory();
}
favicon_base::FaviconRawBitmapResult FaviconBackend::GetLargestFaviconForUrl(
const GURL& page_url,
const std::vector<favicon_base::IconTypeSet>& icon_types_list,
int minimum_size_in_pixels) {
base::TimeTicks beginning_time = base::TimeTicks::Now();
std::vector<IconMapping> icon_mappings;
if (!db_->GetIconMappingsForPageURL(page_url, &icon_mappings) ||
icon_mappings.empty())
return {};
favicon_base::IconTypeSet required_icon_types;
for (const favicon_base::IconTypeSet& icon_types : icon_types_list)
required_icon_types.insert(icon_types.begin(), icon_types.end());
// Find the largest bitmap for each IconType placing in
// `largest_favicon_bitmaps`.
std::map<favicon_base::IconType, FaviconBitmap> largest_favicon_bitmaps;
for (std::vector<IconMapping>::const_iterator i = icon_mappings.begin();
i != icon_mappings.end(); ++i) {
if (required_icon_types.count(i->icon_type) == 0)
continue;
std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
db_->GetFaviconBitmapIDSizes(i->icon_id, &bitmap_id_sizes);
FaviconBitmap& largest = largest_favicon_bitmaps[i->icon_type];
for (std::vector<FaviconBitmapIDSize>::const_iterator j =
bitmap_id_sizes.begin();
j != bitmap_id_sizes.end(); ++j) {
if (largest.bitmap_id == 0 ||
(largest.pixel_size.width() < j->pixel_size.width() &&
largest.pixel_size.height() < j->pixel_size.height())) {
largest.icon_id = i->icon_id;
largest.bitmap_id = j->bitmap_id;
largest.pixel_size = j->pixel_size;
}
}
}
if (largest_favicon_bitmaps.empty())
return {};
// Find an icon which is larger than minimum_size_in_pixels in the order of
// icon_types.
FaviconBitmap largest_icon;
for (const favicon_base::IconTypeSet& icon_types : icon_types_list) {
for (std::map<favicon_base::IconType, FaviconBitmap>::const_iterator f =
largest_favicon_bitmaps.begin();
f != largest_favicon_bitmaps.end(); ++f) {
if (icon_types.count(f->first) != 0 &&
(largest_icon.bitmap_id == 0 ||
(largest_icon.pixel_size.height() < f->second.pixel_size.height() &&
largest_icon.pixel_size.width() < f->second.pixel_size.width()))) {
largest_icon = f->second;
}
}
if (largest_icon.pixel_size.width() > minimum_size_in_pixels &&
largest_icon.pixel_size.height() > minimum_size_in_pixels)
break;
}
GURL icon_url;
favicon_base::IconType icon_type;
if (!db_->GetFaviconHeader(largest_icon.icon_id, &icon_url, &icon_type)) {
return {};
}
base::Time last_updated;
base::Time last_requested;
favicon_base::FaviconRawBitmapResult bitmap_result;
bitmap_result.icon_url = std::move(icon_url);
bitmap_result.icon_type = icon_type;
if (!db_->GetFaviconBitmap(largest_icon.bitmap_id, &last_updated,
&last_requested, &bitmap_result.bitmap_data,
&bitmap_result.pixel_size)) {
return {};
}
bitmap_result.expired = IsFaviconBitmapExpired(last_updated);
bitmap_result.fetched_because_of_page_visit = last_requested.is_null();
LOCAL_HISTOGRAM_TIMES("FaviconBackend.GetLargestFaviconForURL",
base::TimeTicks::Now() - beginning_time);
if (!bitmap_result.is_valid())
return {};
return bitmap_result;
}
std::vector<favicon_base::FaviconRawBitmapResult>
FaviconBackend::GetFaviconsForUrl(const GURL& page_url,
const favicon_base::IconTypeSet& icon_types,
const std::vector<int>& desired_sizes,
bool fallback_to_host) {
TRACE_EVENT0("browser", "FaviconBackend::GetFaviconsForURL");
std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results =
GetFaviconsFromDB(page_url, icon_types, desired_sizes, fallback_to_host);
if (desired_sizes.size() == 1 && !bitmap_results.empty()) {
bitmap_results.assign(1, favicon_base::ResizeFaviconBitmapResult(
desired_sizes[0], bitmap_results));
}
for (auto size : desired_sizes) {
// Only record histograms for sizes that are on the |icon_sizes| allowlist.
if (std::find(icon_sizes.begin(), icon_sizes.end(), size) ==
icon_sizes.end()) {
continue;
}
bool size_found = false;
for (auto result : bitmap_results) {
if (result.pixel_size.width() == size &&
result.pixel_size.height() == size) {
size_found = true;
break;
}
}
base::UmaHistogramBoolean(
"Favicons.IconSuccess." + base::NumberToString(size) + "px",
size_found);
}
return bitmap_results;
}
std::vector<favicon_base::FaviconRawBitmapResult>
FaviconBackend::GetFaviconForId(favicon_base::FaviconID favicon_id,
int desired_size) {
TRACE_EVENT0("browser", "FaviconBackend::GetFaviconForID");
std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results =
GetFaviconBitmapResultsForBestMatch({favicon_id}, {desired_size});
if (!bitmap_results.empty()) {
bitmap_results.assign(1, favicon_base::ResizeFaviconBitmapResult(
desired_size, bitmap_results));
}
return bitmap_results;
}
UpdateFaviconMappingsResult FaviconBackend::UpdateFaviconMappingsAndFetch(
const base::flat_set<GURL>& page_urls,
const GURL& icon_url,
favicon_base::IconType icon_type,
const std::vector<int>& desired_sizes) {
UpdateFaviconMappingsResult result;
const auto favicon_id = db_->GetFaviconIDForFaviconURL(icon_url, icon_type);
if (!favicon_id)
return result;
bool per_origin_favicon_id_found = false;
for (const GURL& page_url : page_urls) {
// We check per-origin so that we don't cross-origin load from the cache.
// See crbug.com/1300214 for more context.
const auto per_origin_favicon_id = db_->GetFaviconIDForFaviconURL(
icon_url, icon_type, url::Origin::Create(page_url));
if (!per_origin_favicon_id)
continue;
per_origin_favicon_id_found = true;
bool mappings_updated = SetFaviconMappingsForPageAndRedirects(
page_url, icon_type, per_origin_favicon_id);
if (mappings_updated)
result.updated_page_urls.insert(page_url);
}
// We add the favicon if at least one origin saw it *or* if this was loaded
// without linking the favicon to any page url (used by history service).
if (per_origin_favicon_id_found || page_urls.empty()) {
result.bitmap_results =
GetFaviconBitmapResultsForBestMatch({favicon_id}, desired_sizes);
}
return result;
}
base::flat_set<GURL> FaviconBackend::DeleteFaviconMappings(
const base::flat_set<GURL>& page_urls,
favicon_base::IconType icon_type) {
TRACE_EVENT0("browser", "FaviconBackend::DeleteFaviconMappings");
base::flat_set<GURL> changed;
for (const GURL& page_url : page_urls) {
bool mapping_changed = SetFaviconMappingsForPageAndRedirects(
page_url, icon_type, /*icon_id=*/0);
if (mapping_changed)
changed.insert(page_url);
}
return changed;
}
MergeFaviconResult FaviconBackend::MergeFavicon(
const GURL& page_url,
const GURL& icon_url,
favicon_base::IconType icon_type,
scoped_refptr<base::RefCountedMemory> bitmap_data,
const gfx::Size& pixel_size) {
TRACE_EVENT0("browser", "FaviconBackend::MergeFavicon");
favicon_base::FaviconID favicon_id =
db_->GetFaviconIDForFaviconURL(icon_url, icon_type);
if (!favicon_id) {
// There is no favicon at `icon_url`, create it.
favicon_id = db_->AddFavicon(icon_url, icon_type);
}
std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
db_->GetFaviconBitmapIDSizes(favicon_id, &bitmap_id_sizes);
// If there is already a favicon bitmap of `pixel_size` at `icon_url`,
// replace it.
bool bitmap_identical = false;
bool replaced_bitmap = false;
for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) {
if (bitmap_id_sizes[i].pixel_size == pixel_size) {
if (IsFaviconBitmapDataEqual(bitmap_id_sizes[i].bitmap_id, bitmap_data)) {
// Sync calls MergeFavicon() for all of the favicons that it manages at
// startup. Do not update the "last updated" time if the favicon bitmap
// data matches that in the database.
// TODO(pkotwicz): Pass in boolean to MergeFavicon() if any users of
// MergeFavicon() want the last_updated time to be updated when the new
// bitmap data is identical to the old.
bitmap_identical = true;
} else {
// Expire the favicon bitmap because sync can provide incorrect
// `bitmap_data`. See crbug.com/474421 for more details. Expiring the
// favicon bitmap causes it to be redownloaded the next time that the
// user visits any page which uses `icon_url`. It also allows storing an
// on-demand icon along with the icon from sync.
db_->SetFaviconBitmap(bitmap_id_sizes[i].bitmap_id, bitmap_data,
base::Time());
replaced_bitmap = true;
}
break;
}
}
// Create a vector of the pixel sizes of the favicon bitmaps currently at
// `icon_url`.
std::vector<gfx::Size> favicon_sizes;
for (size_t i = 0; i < bitmap_id_sizes.size(); ++i)
favicon_sizes.push_back(bitmap_id_sizes[i].pixel_size);
if (!replaced_bitmap && !bitmap_identical) {
// Set the preexisting favicon bitmaps as expired as the preexisting favicon
// bitmaps are not consistent with the merged in data.
db_->SetFaviconOutOfDate(favicon_id);
// Delete an arbitrary favicon bitmap to avoid going over the limit of
// `kMaxFaviconBitmapsPerIconURL`.
if (bitmap_id_sizes.size() >= kMaxFaviconBitmapsPerIconURL) {
db_->DeleteFaviconBitmap(bitmap_id_sizes[0].bitmap_id);
favicon_sizes.erase(favicon_sizes.begin());
}
// Set the new bitmap as expired because the bitmaps from sync/profile
// import/etc. are not authoritative. Expiring the favicon bitmap causes the
// bitmaps to be redownloaded the next time that the user visits any page
// which uses `icon_url`. It also allows storing an on-demand icon along
// with the icon from sync.
db_->AddFaviconBitmap(favicon_id, bitmap_data, FaviconBitmapType::ON_VISIT,
base::Time(), pixel_size);
favicon_sizes.push_back(pixel_size);
}
// A site may have changed the favicons that it uses for `page_url`.
// Example Scenario:
// page_url = news.google.com
// Initial State: www.google.com/favicon.ico 16x16, 32x32
// MergeFavicon(news.google.com, news.google.com/news_specific.ico, ...,
// ..., 16x16)
//
// Difficulties:
// 1. Sync requires that a call to GetFaviconsForURL() returns the
// `bitmap_data` passed into MergeFavicon().
// - It is invalid for the 16x16 bitmap for www.google.com/favicon.ico to
// stay mapped to news.google.com because it would be unclear which 16x16
// bitmap should be returned via GetFaviconsForURL().
//
// 2. www.google.com/favicon.ico may be mapped to more than just
// news.google.com (eg www.google.com).
// - The 16x16 bitmap cannot be deleted from www.google.com/favicon.ico
//
// To resolve these problems, we copy all of the favicon bitmaps previously
// mapped to news.google.com (`page_url`) and add them to the favicon at
// news.google.com/news_specific.ico (`icon_url`). The favicon sizes for
// `icon_url` are set to default to indicate that `icon_url` has incomplete
// / incorrect data.
// Difficulty 1: All but news.google.com/news_specific.ico are unmapped from
// news.google.com
// Difficulty 2: The favicon bitmaps for www.google.com/favicon.ico are not
// modified.
std::vector<IconMapping> icon_mappings;
db_->GetIconMappingsForPageURL(page_url, {icon_type}, &icon_mappings);
// Copy the favicon bitmaps mapped to `page_url` to the favicon at `icon_url`
// till the limit of `kMaxFaviconBitmapsPerIconURL` is reached.
bool favicon_bitmaps_copied = false;
for (size_t i = 0; i < icon_mappings.size(); ++i) {
if (favicon_sizes.size() >= kMaxFaviconBitmapsPerIconURL)
break;
if (icon_mappings[i].icon_url == icon_url)
continue;
std::vector<FaviconBitmap> bitmaps_to_copy;
db_->GetFaviconBitmaps(icon_mappings[i].icon_id, &bitmaps_to_copy);
for (size_t j = 0; j < bitmaps_to_copy.size(); ++j) {
// Do not add a favicon bitmap at a pixel size for which there is already
// a favicon bitmap mapped to `icon_url`. The one there is more correct
// and having multiple equally sized favicon bitmaps for `page_url` is
// ambiguous in terms of GetFaviconsForURL().
if (base::Contains(favicon_sizes, bitmaps_to_copy[j].pixel_size))
continue;
// Add the favicon bitmap as expired as it is not consistent with the
// merged in data.
db_->AddFaviconBitmap(favicon_id, bitmaps_to_copy[j].bitmap_data,
FaviconBitmapType::ON_VISIT, base::Time(),
bitmaps_to_copy[j].pixel_size);
favicon_sizes.push_back(bitmaps_to_copy[j].pixel_size);
favicon_bitmaps_copied = true;
if (favicon_sizes.size() >= kMaxFaviconBitmapsPerIconURL)
break;
}
}
MergeFaviconResult result;
// Update the favicon mappings such that only `icon_url` is mapped to
// `page_url`.
if (icon_mappings.size() != 1 || icon_mappings[0].icon_url != icon_url) {
SetFaviconMappingsForPageAndRedirects(page_url, icon_type, favicon_id);
result.did_page_to_icon_mapping_change = true;
}
result.did_icon_change = !bitmap_identical || favicon_bitmaps_copied;
return result;
}
std::set<GURL> FaviconBackend::CloneFaviconMappingsForPages(
const GURL& page_url_to_read,
const favicon_base::IconTypeSet& icon_types,
const base::flat_set<GURL>& page_urls_to_write) {
TRACE_EVENT0("browser", "FaviconBackend::CloneFaviconMappingsForPages");
// Update mappings including redirects for each entry in `page_urls_to_write`.
base::flat_set<GURL> all_page_urls_to_update_mappings;
for (const GURL& update_mappings_for_page : page_urls_to_write) {
RedirectList redirects =
delegate_->GetCachedRecentRedirectsForPage(update_mappings_for_page);
// The delegate must always supply at least the supplied page.
DCHECK(!redirects.empty());
all_page_urls_to_update_mappings.insert(redirects.begin(), redirects.end());
}
base::flat_set<GURL> regular_page_urls_to_update_mappings =
page_urls_to_write;
base::flat_set<GURL> redirect_page_urls_to_update_mappings;
std::set_difference(
all_page_urls_to_update_mappings.begin(),
all_page_urls_to_update_mappings.end(),
regular_page_urls_to_update_mappings.begin(),
regular_page_urls_to_update_mappings.end(),
std::inserter(redirect_page_urls_to_update_mappings,
redirect_page_urls_to_update_mappings.begin()));
// No need to update mapping for `page_url_to_read`, because this is where
// we're getting the mappings from.
regular_page_urls_to_update_mappings.erase(page_url_to_read);
redirect_page_urls_to_update_mappings.erase(page_url_to_read);
if (regular_page_urls_to_update_mappings.empty() &&
redirect_page_urls_to_update_mappings.empty()) {
return {};
}
// Get FaviconIDs for `page_url_to_read` and one of `icon_types`.
std::vector<IconMapping> icon_mappings;
db_->GetIconMappingsForPageURL(page_url_to_read, icon_types, &icon_mappings);
if (icon_mappings.empty())
return {};
std::set<GURL> changed_page_urls;
for (const IconMapping& icon_mapping : icon_mappings) {
std::vector<GURL> v = SetFaviconMappingsForPagesByPageUrlType(
regular_page_urls_to_update_mappings,
redirect_page_urls_to_update_mappings, icon_mapping.icon_type,
icon_mapping.icon_id);
changed_page_urls.insert(std::make_move_iterator(v.begin()),
std::make_move_iterator(v.end()));
}
return changed_page_urls;
}
std::vector<GURL> FaviconBackend::GetFaviconUrlsForUrl(const GURL& page_url) {
std::vector<IconMapping> icon_mappings;
db_->GetIconMappingsForPageURL(page_url, &icon_mappings);
std::vector<GURL> urls;
for (const IconMapping& icon_mapping : icon_mappings) {
urls.push_back(icon_mapping.icon_url);
}
return urls;
}
bool FaviconBackend::CanSetOnDemandFavicons(const GURL& page_url,
favicon_base::IconType icon_type) {
// We allow writing an on demand favicon of type `icon_type` only if there is
// no icon of such type in the DB (so that we never overwrite anything) and if
// all other icons are expired. This in particular allows writing an on-demand
// icon if there is only an icon from sync (icons from sync are immediately
// set as expired).
std::vector<IconMapping> mapping_data;
db_->GetIconMappingsForPageURL(page_url, &mapping_data);
for (const IconMapping& mapping : mapping_data) {
if (AreIconTypesEquivalent(mapping.icon_type, icon_type))
return false;
base::Time last_updated;
if (db_->GetFaviconLastUpdatedTime(mapping.icon_id, &last_updated) &&
!IsFaviconBitmapExpired(last_updated)) {
return false;
}
}
return true;
}
SetFaviconsResult FaviconBackend::SetOnDemandFavicons(
const GURL& page_url,
favicon_base::IconType icon_type,
const GURL& icon_url,
const std::vector<SkBitmap>& bitmaps) {
if (!CanSetOnDemandFavicons(page_url, icon_type))
return {};
return SetFavicons({page_url}, icon_type, icon_url, bitmaps,
FaviconBitmapType::ON_DEMAND);
}
bool FaviconBackend::SetFaviconsOutOfDateForPage(const GURL& page_url) {
TRACE_EVENT0("browser", "FaviconBackend::SetFaviconsOutOfDateForPage");
std::vector<IconMapping> icon_mappings;
if (!db_->GetIconMappingsForPageURL(page_url, &icon_mappings))
return false;
for (auto m = icon_mappings.begin(); m != icon_mappings.end(); ++m)
db_->SetFaviconOutOfDate(m->icon_id);
return true;
}
bool FaviconBackend::SetFaviconsOutOfDateBetween(base::Time begin,
base::Time end) {
TRACE_EVENT0("browser", "FaviconBackend::SetFaviconsOutOfDateForPage");
return db_->SetFaviconsOutOfDateBetween(begin, end);
}
void FaviconBackend::TouchOnDemandFavicon(const GURL& icon_url) {
TRACE_EVENT0("browser", "FaviconBackend::TouchOnDemandFavicon");
db_->TouchOnDemandFavicon(icon_url, base::Time::Now());
}
SetFaviconsResult FaviconBackend::SetFavicons(
const base::flat_set<GURL>& page_urls,
favicon_base::IconType icon_type,
const GURL& icon_url,
const std::vector<SkBitmap>& bitmaps,
FaviconBitmapType bitmap_type) {
TRACE_EVENT0("browser", "FaviconBackend::SetFavicons");
DCHECK(!page_urls.empty());
DCHECK_GE(kMaxFaviconBitmapsPerIconURL, bitmaps.size());
favicon_base::FaviconID icon_id =
db_->GetFaviconIDForFaviconURL(icon_url, icon_type);
bool favicon_created = false;
if (!icon_id) {
icon_id = db_->AddFavicon(icon_url, icon_type);
if (!icon_id) {
// The database write failed. Abort operation.
return SetFaviconsResult();
}
favicon_created = true;
}
SetFaviconsResult result;
if (favicon_created || bitmap_type == FaviconBitmapType::ON_VISIT)
result.did_update_bitmap = SetFaviconBitmaps(icon_id, bitmaps, bitmap_type);
for (const GURL& page_url : page_urls) {
bool mapping_changed =
SetFaviconMappingsForPageAndRedirects(page_url, icon_type, icon_id);
if (mapping_changed)
result.updated_page_urls.insert(page_url);
}
return result;
}
FaviconBackend::FaviconBackend(std::unique_ptr<FaviconDatabase> db,
FaviconBackendDelegate* delegate)
: db_(std::move(db)), delegate_(delegate) {
DCHECK(delegate);
DCHECK(db_);
db_->BeginTransaction();
}
bool FaviconBackend::SetFaviconBitmaps(favicon_base::FaviconID icon_id,
const std::vector<SkBitmap>& bitmaps,
FaviconBitmapType type) {
CHECK(icon_id);
std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
db_->GetFaviconBitmapIDSizes(icon_id, &bitmap_id_sizes);
using PNGEncodedBitmap =
std::pair<scoped_refptr<base::RefCountedBytes>, gfx::Size>;
std::vector<PNGEncodedBitmap> to_add;
for (const auto& bitmap : bitmaps) {
std::optional<std::vector<uint8_t>> encoded =
gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false);
if (!encoded) {
continue;
}
auto bitmap_data =
base::MakeRefCounted<base::RefCountedBytes>(std::move(encoded.value()));
to_add.emplace_back(bitmap_data,
gfx::Size(bitmap.width(), bitmap.height()));
}
bool favicon_bitmaps_changed = false;
for (size_t i = 0; i < bitmap_id_sizes.size(); ++i) {
const gfx::Size& pixel_size = bitmap_id_sizes[i].pixel_size;
auto match_it = to_add.end();
for (auto it = to_add.begin(); it != to_add.end(); ++it) {
if (it->second == pixel_size) {
match_it = it;
break;
}
}
FaviconBitmapID bitmap_id = bitmap_id_sizes[i].bitmap_id;
if (match_it == to_add.end()) {
db_->DeleteFaviconBitmap(bitmap_id);
favicon_bitmaps_changed = true;
} else {
if (!favicon_bitmaps_changed &&
IsFaviconBitmapDataEqual(bitmap_id, match_it->first)) {
db_->SetFaviconBitmapLastUpdateTime(
bitmap_id, base::Time::Now() /* new last updated time */);
} else {
db_->SetFaviconBitmap(bitmap_id, match_it->first,
base::Time::Now() /* new last updated time */);
favicon_bitmaps_changed = true;
}
to_add.erase(match_it);
}
}
for (size_t i = 0; i < to_add.size(); ++i) {
db_->AddFaviconBitmap(
icon_id, to_add[i].first, type,
base::Time::Now() /* new last updated / last requested time */,
to_add[i].second);
favicon_bitmaps_changed = true;
}
return favicon_bitmaps_changed;
}
bool FaviconBackend::IsFaviconBitmapDataEqual(
FaviconBitmapID bitmap_id,
const scoped_refptr<base::RefCountedMemory>& new_bitmap_data) {
if (!new_bitmap_data)
return false;
scoped_refptr<base::RefCountedMemory> original_bitmap_data;
db_->GetFaviconBitmap(bitmap_id, nullptr, nullptr, &original_bitmap_data,
nullptr);
return new_bitmap_data->Equals(original_bitmap_data);
}
std::vector<favicon_base::FaviconRawBitmapResult>
FaviconBackend::GetFaviconsFromDB(const GURL& page_url,
const favicon_base::IconTypeSet& icon_types,
const std::vector<int>& desired_sizes,
bool fallback_to_host) {
// Get FaviconIDs for `page_url` and one of `icon_types`.
std::vector<IconMapping> icon_mappings;
db_->GetIconMappingsForPageURL(page_url, icon_types, &icon_mappings);
bool fallback_to_host_attempted = false;
if (icon_mappings.empty() && fallback_to_host &&
page_url.SchemeIsHTTPOrHTTPS()) {
fallback_to_host_attempted = true;
// We didn't find any matches, and the caller requested falling back to the
// host of `page_url` for fuzzy matching. Query the database for a page_url
// that is known to exist and matches the host of `page_url`. Do this only
// if we have a HTTP/HTTPS url.
std::optional<GURL> fallback_page_url =
db_->FindBestPageURLForHost(page_url, icon_types);
if (fallback_page_url) {
db_->GetIconMappingsForPageURL(fallback_page_url.value(), icon_types,
&icon_mappings);
}
}
std::vector<favicon_base::FaviconID> favicon_ids;
for (size_t i = 0; i < icon_mappings.size(); ++i)
favicon_ids.push_back(icon_mappings[i].icon_id);
std::vector<favicon_base::FaviconRawBitmapResult> results =
GetFaviconBitmapResultsForBestMatch(favicon_ids, desired_sizes);
if (fallback_to_host_attempted) {
base::UmaHistogramBoolean("Favicons.FallbackToHostSuccess",
results.size() > 0);
}
return results;
}
std::vector<favicon_base::FaviconRawBitmapResult>
FaviconBackend::GetFaviconBitmapResultsForBestMatch(
const std::vector<favicon_base::FaviconID>& candidate_favicon_ids,
const std::vector<int>& desired_sizes) {
if (candidate_favicon_ids.empty())
return {};
// Find the FaviconID and the FaviconBitmapIDs which best match
// `desired_size_in_dip` and `desired_scale_factors`.
// TODO(pkotwicz): Select bitmap results from multiple favicons once
// content::FaviconStatus supports multiple icon URLs.
favicon_base::FaviconID best_favicon_id = 0;
std::vector<FaviconBitmapID> best_bitmap_ids;
float highest_score = kSelectFaviconFramesInvalidScore;
for (size_t i = 0; i < candidate_favicon_ids.size(); ++i) {
std::vector<FaviconBitmapIDSize> bitmap_id_sizes;
db_->GetFaviconBitmapIDSizes(candidate_favicon_ids[i], &bitmap_id_sizes);
// Build vector of gfx::Size from `bitmap_id_sizes`.
std::vector<gfx::Size> sizes;
for (size_t j = 0; j < bitmap_id_sizes.size(); ++j)
sizes.push_back(bitmap_id_sizes[j].pixel_size);
std::vector<size_t> candidate_bitmap_indices;
float score = 0;
SelectFaviconFrameIndices(sizes, desired_sizes, &candidate_bitmap_indices,
&score);
if (score > highest_score) {
highest_score = score;
best_favicon_id = candidate_favicon_ids[i];
best_bitmap_ids.clear();
for (size_t j = 0; j < candidate_bitmap_indices.size(); ++j) {
size_t candidate_index = candidate_bitmap_indices[j];
best_bitmap_ids.push_back(bitmap_id_sizes[candidate_index].bitmap_id);
}
}
}
// Construct FaviconRawBitmapResults from `best_favicon_id` and
// `best_bitmap_ids`.
GURL icon_url;
favicon_base::IconType icon_type;
if (!db_->GetFaviconHeader(best_favicon_id, &icon_url, &icon_type))
return {};
std::vector<favicon_base::FaviconRawBitmapResult> results;
for (size_t i = 0; i < best_bitmap_ids.size(); ++i) {
base::Time last_updated;
base::Time last_requested;
favicon_base::FaviconRawBitmapResult bitmap_result;
bitmap_result.icon_url = icon_url;
bitmap_result.icon_type = icon_type;
if (!db_->GetFaviconBitmap(best_bitmap_ids[i], &last_updated,
&last_requested, &bitmap_result.bitmap_data,
&bitmap_result.pixel_size)) {
return results;
}
bitmap_result.expired = IsFaviconBitmapExpired(last_updated);
bitmap_result.fetched_because_of_page_visit = last_requested.is_null();
if (bitmap_result.is_valid())
results.push_back(bitmap_result);
}
return results;
}
bool FaviconBackend::SetFaviconMappingsForPageAndRedirects(
const GURL& page_url,
favicon_base::IconType icon_type,
favicon_base::FaviconID icon_id) {
// Find all the pages whose favicons we should set, we want to set it for
// all the pages in the redirect chain if it redirected.
RedirectList redirects_list =
delegate_->GetCachedRecentRedirectsForPage(page_url);
base::flat_set<GURL> redirects_set(redirects_list);
redirects_set.erase(page_url);
bool mappings_changed =
!SetFaviconMappingsForPagesByPageUrlType(
base::flat_set<GURL>({page_url}), redirects_set, icon_type, icon_id)
.empty();
return mappings_changed;
}
std::vector<GURL> FaviconBackend::SetFaviconMappingsForPagesByPageUrlType(
const base::flat_set<GURL>& regular_page_urls,
const base::flat_set<GURL>& redirect_page_urls,
favicon_base::IconType icon_type,
favicon_base::FaviconID icon_id) {
std::vector<GURL> changed_page_urls;
SetFaviconMappingsForPages(regular_page_urls, icon_type, icon_id,
PageUrlType::kRegular, changed_page_urls);
SetFaviconMappingsForPages(redirect_page_urls, icon_type, icon_id,
PageUrlType::kRedirect, changed_page_urls);
return changed_page_urls;
}
void FaviconBackend::SetFaviconMappingsForPages(
const base::flat_set<GURL>& page_urls,
favicon_base::IconType icon_type,
favicon_base::FaviconID icon_id,
PageUrlType page_url_type,
std::vector<GURL>& changed_page_urls) {
for (const GURL& page_url : page_urls) {
if (SetFaviconMappingsForPage(page_url, icon_type, icon_id,
page_url_type)) {
changed_page_urls.push_back(page_url);
}
}
}
bool FaviconBackend::SetFaviconMappingsForPage(const GURL& page_url,
favicon_base::IconType icon_type,
favicon_base::FaviconID icon_id,
PageUrlType page_url_type) {
bool mappings_changed = false;
// Sets the icon mappings from `page_url` for `icon_type` to the favicon
// with `icon_id`. Mappings for `page_url` to favicons of type `icon_type`
// with FaviconID other than `icon_id` are removed. The mapping is deleted and
// re-added if `page_url_type` is different between the previous mapping and
// the new mapping. All icon mappings for `page_url` to favicons of a type
// equivalent to `icon_type` are removed. Removes any favicons which are
// orphaned as a result of the removal of the icon mappings.
favicon_base::FaviconID unmapped_icon_id = icon_id;
std::vector<IconMapping> icon_mappings;
db_->GetIconMappingsForPageURL(page_url, &icon_mappings);
for (auto m = icon_mappings.begin(); m != icon_mappings.end(); ++m) {
if (unmapped_icon_id == m->icon_id) {
if (page_url_type == m->page_url_type &&
AreIconTypesEquivalent(icon_type, m->icon_type)) {
// The mapping is already correct. Do nothing.
unmapped_icon_id = 0;
} else {
// Delete the mapping to be able to recreate it with the correct
// `page_url_type` and icon type. Don't delete the favicon since it
// is still required.
db_->DeleteIconMapping(m->mapping_id);
}
continue;
}
if (AreIconTypesEquivalent(icon_type, m->icon_type)) {
db_->DeleteIconMapping(m->mapping_id);
// Removing the icon mapping may have orphaned the associated favicon so
// we must recheck it. This is not super fast, but this case will get
// triggered rarely, since normally a page will always map to the same
// favicon IDs. It will mostly happen for favicons we import.
if (!db_->HasMappingFor(m->icon_id))
db_->DeleteFavicon(m->icon_id);
mappings_changed = true;
}
}
if (unmapped_icon_id) {
db_->AddIconMapping(page_url, unmapped_icon_id, page_url_type);
mappings_changed = true;
}
return mappings_changed;
}
bool FaviconBackend::ClearAllExcept(const std::vector<GURL>& kept_page_urls) {
// Isolate from any long-running transaction.
db_->CommitTransaction();
db_->BeginTransaction();
if (!db_->RetainDataForPageUrls(kept_page_urls)) {
db_->RollbackTransaction();
db_->BeginTransaction();
return false;
}
// Vacuum to remove all the pages associated with the dropped tables. There
// must be no transaction open on the table when we do this. We assume that
// our long-running transaction is open, so we complete it and start it again.
DCHECK_EQ(db_->transaction_nesting(), 1);
db_->CommitTransaction();
db_->Vacuum();
db_->BeginTransaction();
return true;
}
} // namespace favicon