blob: bae2883eb182bde5c669265b6587eb7f33f1bca1 [file] [log] [blame]
// Copyright 2013 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/sync_sessions/favicon_cache.h"
#include <utility>
#include "base/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "components/favicon/core/favicon_service.h"
#include "components/history/core/browser/history_service.h"
#include "components/sync/model/time.h"
#include "components/sync/protocol/favicon_image_specifics.pb.h"
#include "components/sync/protocol/favicon_tracking_specifics.pb.h"
#include "components/sync/protocol/sync.pb.h"
#include "ui/gfx/favicon_size.h"
namespace sync_sessions {
// Synced favicon storage and tracking.
// Note: we don't use the favicon service for storing these because these
// favicons are not necessarily associated with any local navigation, and
// hence would not work with the current expiration logic. We have custom
// expiration logic based on visit time/bookmark status/etc.
// See crbug.com/122890.
struct SyncedFaviconInfo {
explicit SyncedFaviconInfo(const GURL& favicon_url)
: favicon_url(favicon_url),
is_bookmarked(false),
received_local_update(false) {}
// The actual favicon data.
// TODO(zea): don't keep around the actual data for locally sourced
// favicons (UI can access those directly).
favicon_base::FaviconRawBitmapResult bitmap_data[NUM_SIZES];
// The URL this favicon was loaded from.
const GURL favicon_url;
// Is the favicon for a bookmarked page?
bool is_bookmarked;
// The last time a tab needed this favicon.
// Note: Do not modify this directly! It should only be modified via
// UpdateFaviconVisitTime(..).
base::Time last_visit_time;
// Whether we've received a local update for this favicon since starting up.
bool received_local_update;
private:
DISALLOW_COPY_AND_ASSIGN(SyncedFaviconInfo);
};
// Information for handling local favicon updates. Used in
// OnFaviconDataAvailable.
struct LocalFaviconUpdateInfo {
LocalFaviconUpdateInfo()
: new_image(false),
new_tracking(false),
image_needs_rewrite(false),
favicon_info(nullptr) {}
bool new_image;
bool new_tracking;
bool image_needs_rewrite;
SyncedFaviconInfo* favicon_info;
};
namespace {
// Maximum width/height resolution supported.
const int kMaxFaviconResolution = 16;
// Returns a mask of the supported favicon types.
// TODO(zea): Supporting other favicons types will involve some work in the
// favicon service and navigation controller. See crbug.com/181068.
favicon_base::IconTypeSet SupportedFaviconTypes() {
return {favicon_base::IconType::kFavicon};
}
// Returns the appropriate IconSize to use for a given gfx::Size pixel
// dimensions.
IconSize GetIconSizeBinFromBitmapResult(const gfx::Size& pixel_size) {
int max_size =
(pixel_size.width() > pixel_size.height() ?
pixel_size.width() : pixel_size.height());
// TODO(zea): re-enable 64p and 32p resolutions once we support them.
if (max_size > 64)
return SIZE_INVALID;
else if (max_size > 32)
return SIZE_INVALID;
else if (max_size > 16)
return SIZE_INVALID;
else
return SIZE_16;
}
// Helper for debug statements.
std::string IconSizeToString(IconSize icon_size) {
switch (icon_size) {
case SIZE_16:
return "16";
case SIZE_32:
return "32";
case SIZE_64:
return "64";
default:
return "INVALID";
}
}
// Extract the favicon url from either of the favicon types.
GURL GetFaviconURLFromSpecifics(const sync_pb::EntitySpecifics& specifics) {
if (specifics.has_favicon_tracking())
return GURL(specifics.favicon_tracking().favicon_url());
else
return GURL(specifics.favicon_image().favicon_url());
}
// Convert protobuf image data into a FaviconRawBitmapResult.
favicon_base::FaviconRawBitmapResult GetImageDataFromSpecifics(
const sync_pb::FaviconData& favicon_data) {
base::RefCountedString* temp_string =
new base::RefCountedString();
temp_string->data() = favicon_data.favicon();
favicon_base::FaviconRawBitmapResult bitmap_result;
bitmap_result.bitmap_data = temp_string;
bitmap_result.pixel_size.set_height(favicon_data.height());
bitmap_result.pixel_size.set_width(favicon_data.width());
return bitmap_result;
}
// Convert a FaviconRawBitmapResult into protobuf image data.
void FillSpecificsWithImageData(
const favicon_base::FaviconRawBitmapResult& bitmap_result,
sync_pb::FaviconData* favicon_data) {
if (!bitmap_result.bitmap_data.get())
return;
favicon_data->set_height(bitmap_result.pixel_size.height());
favicon_data->set_width(bitmap_result.pixel_size.width());
favicon_data->set_favicon(bitmap_result.bitmap_data->front(),
bitmap_result.bitmap_data->size());
}
// Build a FaviconImageSpecifics from a SyncedFaviconInfo.
void BuildImageSpecifics(
const SyncedFaviconInfo* favicon_info,
sync_pb::FaviconImageSpecifics* image_specifics) {
image_specifics->set_favicon_url(favicon_info->favicon_url.spec());
FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_16],
image_specifics->mutable_favicon_web());
// TODO(zea): bring this back if we can handle the load.
// FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_32],
// image_specifics->mutable_favicon_web_32());
// FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_64],
// image_specifics->mutable_favicon_touch_64());
}
// Build a FaviconTrackingSpecifics from a SyncedFaviconInfo.
void BuildTrackingSpecifics(
const SyncedFaviconInfo* favicon_info,
sync_pb::FaviconTrackingSpecifics* tracking_specifics) {
tracking_specifics->set_favicon_url(favicon_info->favicon_url.spec());
tracking_specifics->set_last_visit_time_ms(
syncer::TimeToProtoTime(favicon_info->last_visit_time));
tracking_specifics->set_is_bookmarked(favicon_info->is_bookmarked);
}
// Updates |favicon_info| with the image data in |bitmap_result|.
bool UpdateFaviconFromBitmapResult(
const favicon_base::FaviconRawBitmapResult& bitmap_result,
SyncedFaviconInfo* favicon_info) {
DCHECK_EQ(favicon_info->favicon_url, bitmap_result.icon_url);
if (!bitmap_result.is_valid()) {
DVLOG(1) << "Received invalid favicon at " << bitmap_result.icon_url.spec();
return false;
}
IconSize icon_size = GetIconSizeBinFromBitmapResult(
bitmap_result.pixel_size);
if (icon_size == SIZE_INVALID) {
DVLOG(1) << "Ignoring unsupported resolution "
<< bitmap_result.pixel_size.height() << "x"
<< bitmap_result.pixel_size.width();
return false;
} else if (!favicon_info->bitmap_data[icon_size].bitmap_data.get() ||
!favicon_info->received_local_update) {
DVLOG(1) << "Storing " << IconSizeToString(icon_size) << "p"
<< " favicon for " << favicon_info->favicon_url.spec()
<< " with size " << bitmap_result.bitmap_data->size()
<< " bytes.";
favicon_info->bitmap_data[icon_size] = bitmap_result;
favicon_info->received_local_update = true;
return true;
} else {
// We only allow updating the image data once per restart.
DVLOG(2) << "Ignoring local update for " << bitmap_result.icon_url.spec();
return false;
}
}
bool FaviconInfoHasImages(const SyncedFaviconInfo& favicon_info) {
return favicon_info.bitmap_data[SIZE_16].bitmap_data.get() ||
favicon_info.bitmap_data[SIZE_32].bitmap_data.get() ||
favicon_info.bitmap_data[SIZE_64].bitmap_data.get();
}
bool FaviconInfoHasTracking(const SyncedFaviconInfo& favicon_info) {
return !favicon_info.last_visit_time.is_null();
}
bool FaviconInfoHasValidTypeData(const SyncedFaviconInfo& favicon_info,
syncer::ModelType type) {
if (type == syncer::FAVICON_IMAGES)
return FaviconInfoHasImages(favicon_info);
else if (type == syncer::FAVICON_TRACKING)
return FaviconInfoHasTracking(favicon_info);
NOTREACHED();
return false;
}
} // namespace
FaviconCache::FaviconCache(favicon::FaviconService* favicon_service,
history::HistoryService* history_service,
int max_sync_favicon_limit)
: favicon_service_(favicon_service),
max_sync_favicon_limit_(max_sync_favicon_limit),
history_service_observer_(this),
weak_ptr_factory_(this) {
if (history_service)
history_service_observer_.Add(history_service);
DVLOG(1) << "Setting favicon limit to " << max_sync_favicon_limit;
}
FaviconCache::~FaviconCache() {}
syncer::SyncMergeResult FaviconCache::MergeDataAndStartSyncing(
syncer::ModelType type,
const syncer::SyncDataList& initial_sync_data,
std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
std::unique_ptr<syncer::SyncErrorFactory> error_handler) {
DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
if (type == syncer::FAVICON_IMAGES)
favicon_images_sync_processor_ = std::move(sync_processor);
else
favicon_tracking_sync_processor_ = std::move(sync_processor);
syncer::SyncMergeResult merge_result(type);
merge_result.set_num_items_before_association(synced_favicons_.size());
std::set<GURL> unsynced_favicon_urls;
for (const auto& url_icon_pair : synced_favicons_) {
if (FaviconInfoHasValidTypeData(*url_icon_pair.second, type))
unsynced_favicon_urls.insert(url_icon_pair.first);
}
syncer::SyncChangeList local_changes;
for (auto iter = initial_sync_data.begin(); iter != initial_sync_data.end();
++iter) {
GURL remote_url = GetFaviconURLFromSpecifics(iter->GetSpecifics());
GURL favicon_url = GetLocalFaviconFromSyncedData(*iter);
if (favicon_url.is_valid()) {
unsynced_favicon_urls.erase(favicon_url);
MergeSyncFavicon(*iter, &local_changes);
merge_result.set_num_items_modified(
merge_result.num_items_modified() + 1);
} else {
AddLocalFaviconFromSyncedData(*iter);
merge_result.set_num_items_added(merge_result.num_items_added() + 1);
}
}
// Rather than trigger a bunch of deletions when we set up sync, we drop
// local favicons. Those pages that are currently open are likely to result in
// loading new favicons/refreshing old favicons anyways, at which point
// they'll be re-added and the appropriate synced favicons will be evicted.
// TODO(zea): implement a smarter ordering of the which favicons to drop.
int available_favicons = max_sync_favicon_limit_ - initial_sync_data.size();
for (auto iter = unsynced_favicon_urls.begin();
iter != unsynced_favicon_urls.end(); ++iter) {
if (available_favicons > 0) {
local_changes.push_back(
syncer::SyncChange(FROM_HERE,
syncer::SyncChange::ACTION_ADD,
CreateSyncDataFromLocalFavicon(type, *iter)));
available_favicons--;
} else {
auto favicon_iter = synced_favicons_.find(*iter);
DVLOG(1) << "Dropping local favicon "
<< favicon_iter->second->favicon_url.spec();
DropPartialFavicon(favicon_iter, type);
merge_result.set_num_items_deleted(merge_result.num_items_deleted() + 1);
}
}
merge_result.set_num_items_after_association(synced_favicons_.size());
if (type == syncer::FAVICON_IMAGES) {
favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
local_changes);
} else {
favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
local_changes);
}
return merge_result;
}
void FaviconCache::StopSyncing(syncer::ModelType type) {
favicon_images_sync_processor_.reset();
favicon_tracking_sync_processor_.reset();
cancelable_task_tracker_.TryCancelAll();
page_task_map_.clear();
}
syncer::SyncDataList FaviconCache::GetAllSyncData(syncer::ModelType type)
const {
syncer::SyncDataList data_list;
for (auto iter = synced_favicons_.begin(); iter != synced_favicons_.end();
++iter) {
if ((type == syncer::FAVICON_IMAGES &&
FaviconInfoHasImages(*iter->second)) ||
(type == syncer::FAVICON_TRACKING &&
FaviconInfoHasTracking(*iter->second))) {
data_list.push_back(CreateSyncDataFromLocalFavicon(type, iter->first));
}
}
return data_list;
}
syncer::SyncError FaviconCache::ProcessSyncChanges(
const base::Location& from_here,
const syncer::SyncChangeList& change_list) {
if (!favicon_images_sync_processor_.get() ||
!favicon_tracking_sync_processor_.get()) {
return syncer::SyncError(FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
"One or both favicon types disabled.",
change_list[0].sync_data().GetDataType());
}
syncer::SyncChangeList new_changes;
syncer::SyncError error;
syncer::ModelType type = syncer::UNSPECIFIED;
for (auto iter = change_list.begin(); iter != change_list.end(); ++iter) {
type = iter->sync_data().GetDataType();
DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
GURL favicon_url =
GetFaviconURLFromSpecifics(iter->sync_data().GetSpecifics());
if (!favicon_url.is_valid()) {
error.Reset(FROM_HERE, "Received invalid favicon url.", type);
break;
}
auto favicon_iter = synced_favicons_.find(favicon_url);
if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
if (favicon_iter == synced_favicons_.end()) {
// Two clients might wind up deleting different parts of the same
// favicon, so ignore this.
continue;
} else {
DVLOG(1) << "Deleting favicon at " << favicon_url.spec();
// If we only have partial data for the favicon (which implies orphaned
// nodes), delete the local favicon only if the type corresponds to the
// partial data we have. If we do have orphaned nodes, we rely on the
// expiration logic to remove them eventually.
DropPartialFavicon(favicon_iter, type);
}
} else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE ||
iter->change_type() == syncer::SyncChange::ACTION_ADD) {
// Adds and updates are treated the same due to the lack of strong
// consistency (it's possible we'll receive an update for a tracking info
// before we've received the add for the image, and should handle both
// gracefully).
if (favicon_iter == synced_favicons_.end()) {
DVLOG(1) << "Adding favicon at " << favicon_url.spec();
AddLocalFaviconFromSyncedData(iter->sync_data());
} else {
DVLOG(1) << "Updating favicon at " << favicon_url.spec();
MergeSyncFavicon(iter->sync_data(), &new_changes);
}
} else {
error.Reset(FROM_HERE, "Invalid action received.", type);
break;
}
}
// Note: we deliberately do not expire favicons here. If we received new
// favicons and are now over the limit, the next local favicon change will
// trigger the necessary expiration.
if (!error.IsSet() && !new_changes.empty()) {
if (type == syncer::FAVICON_IMAGES) {
favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
new_changes);
} else {
favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
new_changes);
}
}
return error;
}
void FaviconCache::OnPageFaviconUpdated(const GURL& page_url,
base::Time mtime) {
DCHECK(page_url.is_valid());
// If a favicon load is already happening for this url, let it finish.
if (page_task_map_.find(page_url) != page_task_map_.end())
return;
auto url_iter = page_favicon_map_.find(page_url);
if (url_iter != page_favicon_map_.end()) {
auto icon_iter = synced_favicons_.find(url_iter->second);
// TODO(zea): consider what to do when only a subset of supported
// resolutions are available.
if (icon_iter != synced_favicons_.end() &&
icon_iter->second->bitmap_data[SIZE_16].bitmap_data.get()) {
DVLOG(2) << "Using cached favicon url for " << page_url.spec()
<< ": " << icon_iter->second->favicon_url.spec();
UpdateFaviconVisitTime(icon_iter->second->favicon_url, mtime);
UpdateSyncState(icon_iter->second->favicon_url,
syncer::SyncChange::ACTION_INVALID,
syncer::SyncChange::ACTION_UPDATE);
return;
}
}
DVLOG(1) << "Triggering favicon load for url " << page_url.spec();
if (!favicon_service_) {
page_task_map_[page_url] = 0; // For testing only.
return;
}
// TODO(zea): This appears to only fetch one favicon (best match based on
// desired_size_in_dip). Figure out a way to fetch all favicons we support.
// See crbug.com/181068.
base::CancelableTaskTracker::TaskId id =
favicon_service_->GetFaviconForPageURL(
page_url, SupportedFaviconTypes(), kMaxFaviconResolution,
base::Bind(&FaviconCache::OnFaviconDataAvailable,
weak_ptr_factory_.GetWeakPtr(), page_url, mtime),
&cancelable_task_tracker_);
page_task_map_[page_url] = id;
}
void FaviconCache::OnFaviconVisited(const GURL& page_url,
const GURL& favicon_url) {
DCHECK(page_url.is_valid());
if (!favicon_url.is_valid() ||
synced_favicons_.find(favicon_url) == synced_favicons_.end()) {
// TODO(zea): consider triggering a favicon load if we have some but not
// all desired resolutions?
OnPageFaviconUpdated(page_url, base::Time::Now());
return;
}
DVLOG(1) << "Associating " << page_url.spec() << " with favicon at "
<< favicon_url.spec() << " and marking visited.";
page_favicon_map_[page_url] = favicon_url;
bool had_tracking = FaviconInfoHasTracking(
*synced_favicons_.find(favicon_url)->second);
UpdateFaviconVisitTime(favicon_url, base::Time::Now());
UpdateSyncState(favicon_url,
syncer::SyncChange::ACTION_INVALID,
(had_tracking ?
syncer::SyncChange::ACTION_UPDATE :
syncer::SyncChange::ACTION_ADD));
}
bool FaviconCache::GetSyncedFaviconForFaviconURL(
const GURL& favicon_url,
scoped_refptr<base::RefCountedMemory>* favicon_png) const {
if (!favicon_url.is_valid())
return false;
auto iter = synced_favicons_.find(favicon_url);
UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded",
iter != synced_favicons_.end());
if (iter == synced_favicons_.end())
return false;
// TODO(zea): support getting other resolutions.
if (!iter->second->bitmap_data[SIZE_16].bitmap_data.get())
return false;
*favicon_png = iter->second->bitmap_data[SIZE_16].bitmap_data;
return true;
}
bool FaviconCache::GetSyncedFaviconForPageURL(
const GURL& page_url,
scoped_refptr<base::RefCountedMemory>* favicon_png) const {
if (!page_url.is_valid())
return false;
auto iter = page_favicon_map_.find(page_url);
if (iter == page_favicon_map_.end())
return false;
return GetSyncedFaviconForFaviconURL(iter->second, favicon_png);
}
void FaviconCache::UpdateMappingsFromForeignTab(const sync_pb::SessionTab& tab,
base::Time visit_time) {
for (const sync_pb::TabNavigation& navigation : tab.navigation()) {
const GURL page_url(navigation.virtual_url());
const GURL icon_url(navigation.favicon_url());
if (!icon_url.is_valid() || !page_url.is_valid() ||
icon_url.SchemeIs("data")) {
continue;
}
DVLOG(1) << "Associating " << page_url << " with favicon at " << icon_url;
page_favicon_map_[page_url] = icon_url;
if (synced_favicons_.count(icon_url) != 0)
UpdateFaviconVisitTime(icon_url, visit_time);
}
}
base::WeakPtr<FaviconCache> FaviconCache::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
size_t FaviconCache::NumFaviconsForTest() const {
return synced_favicons_.size();
}
size_t FaviconCache::NumTasksForTest() const {
return page_task_map_.size();
}
base::Time FaviconCache::GetLastVisitTimeForTest(
const GURL& favicon_url) const {
auto iter = synced_favicons_.find(favicon_url);
DCHECK(iter != synced_favicons_.end());
return iter->second->last_visit_time;
}
bool FaviconCache::FaviconRecencyFunctor::operator()(
const SyncedFaviconInfo* lhs,
const SyncedFaviconInfo* rhs) const {
// TODO(zea): incorporate bookmarked status here once we care about it.
if (lhs->last_visit_time < rhs->last_visit_time)
return true;
else if (lhs->last_visit_time == rhs->last_visit_time)
return lhs->favicon_url.spec() < rhs->favicon_url.spec();
return false;
}
void FaviconCache::OnFaviconDataAvailable(
const GURL& page_url,
base::Time mtime,
const std::vector<favicon_base::FaviconRawBitmapResult>& bitmap_results) {
auto page_iter = page_task_map_.find(page_url);
if (page_iter == page_task_map_.end())
return;
page_task_map_.erase(page_iter);
if (bitmap_results.size() == 0) {
// Either the favicon isn't loaded yet or there is no valid favicon.
// We already cleared the task id, so just return.
DVLOG(1) << "Favicon load failed for page " << page_url.spec();
return;
}
std::map<GURL, LocalFaviconUpdateInfo> favicon_updates;
for (size_t i = 0; i < bitmap_results.size(); ++i) {
const favicon_base::FaviconRawBitmapResult& bitmap_result =
bitmap_results[i];
GURL favicon_url = bitmap_result.icon_url;
if (!favicon_url.is_valid() || favicon_url.SchemeIs("data"))
continue; // Can happen if the page is still loading.
SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
if (!favicon_info)
return; // We reached the in-memory limit.
favicon_updates[favicon_url].new_image |=
!FaviconInfoHasImages(*favicon_info);
favicon_updates[favicon_url].new_tracking |=
!FaviconInfoHasTracking(*favicon_info);
favicon_updates[favicon_url].image_needs_rewrite |=
UpdateFaviconFromBitmapResult(bitmap_result, favicon_info);
favicon_updates[favicon_url].favicon_info = favicon_info;
}
for (std::map<GURL, LocalFaviconUpdateInfo>::const_iterator
iter = favicon_updates.begin(); iter != favicon_updates.end();
++iter) {
SyncedFaviconInfo* favicon_info = iter->second.favicon_info;
const GURL& favicon_url = favicon_info->favicon_url;
// TODO(zea): support multiple favicon urls per page.
page_favicon_map_[page_url] = favicon_url;
favicon_info->received_local_update = true;
UpdateFaviconVisitTime(favicon_url, mtime);
syncer::SyncChange::SyncChangeType image_change =
syncer::SyncChange::ACTION_INVALID;
if (iter->second.new_image)
image_change = syncer::SyncChange::ACTION_ADD;
else if (iter->second.image_needs_rewrite)
image_change = syncer::SyncChange::ACTION_UPDATE;
syncer::SyncChange::SyncChangeType tracking_change =
syncer::SyncChange::ACTION_UPDATE;
if (iter->second.new_tracking)
tracking_change = syncer::SyncChange::ACTION_ADD;
UpdateSyncState(favicon_url, image_change, tracking_change);
}
}
void FaviconCache::UpdateSyncState(
const GURL& icon_url,
syncer::SyncChange::SyncChangeType image_change_type,
syncer::SyncChange::SyncChangeType tracking_change_type) {
DCHECK(icon_url.is_valid());
// It's possible that we'll receive a favicon update before both types
// have finished setting up. In that case ignore the update.
// TODO(zea): consider tracking these skipped updates somehow?
if (!favicon_images_sync_processor_.get() ||
!favicon_tracking_sync_processor_.get()) {
return;
}
auto iter = synced_favicons_.find(icon_url);
DCHECK(iter != synced_favicons_.end());
const SyncedFaviconInfo* favicon_info = iter->second.get();
syncer::SyncChangeList image_changes;
syncer::SyncChangeList tracking_changes;
if (image_change_type != syncer::SyncChange::ACTION_INVALID) {
sync_pb::EntitySpecifics new_specifics;
sync_pb::FaviconImageSpecifics* image_specifics =
new_specifics.mutable_favicon_image();
BuildImageSpecifics(favicon_info, image_specifics);
image_changes.push_back(
syncer::SyncChange(FROM_HERE,
image_change_type,
syncer::SyncData::CreateLocalData(
icon_url.spec(),
icon_url.spec(),
new_specifics)));
}
if (tracking_change_type != syncer::SyncChange::ACTION_INVALID) {
sync_pb::EntitySpecifics new_specifics;
sync_pb::FaviconTrackingSpecifics* tracking_specifics =
new_specifics.mutable_favicon_tracking();
BuildTrackingSpecifics(favicon_info, tracking_specifics);
tracking_changes.push_back(
syncer::SyncChange(FROM_HERE,
tracking_change_type,
syncer::SyncData::CreateLocalData(
icon_url.spec(),
icon_url.spec(),
new_specifics)));
}
ExpireFaviconsIfNecessary(&image_changes, &tracking_changes);
if (!image_changes.empty()) {
favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
image_changes);
}
if (!tracking_changes.empty()) {
favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
tracking_changes);
}
}
SyncedFaviconInfo* FaviconCache::GetFaviconInfo(
const GURL& icon_url) {
DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
if (synced_favicons_.count(icon_url) != 0)
return synced_favicons_[icon_url].get();
// TODO(zea): implement in-memory eviction.
DVLOG(1) << "Adding favicon info for " << icon_url.spec();
auto favicon_info = std::make_unique<SyncedFaviconInfo>(icon_url);
SyncedFaviconInfo* favicon_info_ptr = favicon_info.get();
synced_favicons_[icon_url] = std::move(favicon_info);
recent_favicons_.insert(favicon_info_ptr);
DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
return favicon_info_ptr;
}
void FaviconCache::UpdateFaviconVisitTime(const GURL& icon_url,
base::Time time) {
DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
auto iter = synced_favicons_.find(icon_url);
DCHECK(iter != synced_favicons_.end());
if (iter->second->last_visit_time >= time)
return;
// Erase, update the time, then re-insert to maintain ordering.
recent_favicons_.erase(iter->second.get());
DVLOG(1) << "Updating " << icon_url.spec() << " visit time to "
<< syncer::GetTimeDebugString(time);
iter->second->last_visit_time = time;
recent_favicons_.insert(iter->second.get());
if (VLOG_IS_ON(2)) {
for (const auto* icon : recent_favicons_) {
DVLOG(2) << "Favicon " << icon->favicon_url.spec() << ": "
<< syncer::GetTimeDebugString(icon->last_visit_time);
}
}
DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
}
void FaviconCache::ExpireFaviconsIfNecessary(
syncer::SyncChangeList* image_changes,
syncer::SyncChangeList* tracking_changes) {
DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
// TODO(zea): once we have in-memory eviction, we'll need to track sync
// favicon count separately from the synced_favicons_/recent_favicons_.
// Iterate until we've removed the necessary amount. |recent_favicons_| is
// already in recency order, so just start from the beginning.
// TODO(zea): to reduce thrashing, consider removing more than the minimum.
while (recent_favicons_.size() > max_sync_favicon_limit_) {
SyncedFaviconInfo* candidate = *recent_favicons_.begin();
DVLOG(1) << "Expiring favicon " << candidate->favicon_url.spec();
DeleteSyncedFavicon(synced_favicons_.find(candidate->favicon_url),
image_changes,
tracking_changes);
}
DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
}
GURL FaviconCache::GetLocalFaviconFromSyncedData(
const syncer::SyncData& sync_favicon) const {
syncer::ModelType type = sync_favicon.GetDataType();
DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
return (synced_favicons_.count(favicon_url) > 0 ? favicon_url : GURL());
}
void FaviconCache::MergeSyncFavicon(const syncer::SyncData& sync_favicon,
syncer::SyncChangeList* sync_changes) {
syncer::ModelType type = sync_favicon.GetDataType();
DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
sync_pb::EntitySpecifics new_specifics;
GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
auto iter = synced_favicons_.find(favicon_url);
DCHECK(iter != synced_favicons_.end());
SyncedFaviconInfo* favicon_info = iter->second.get();
if (type == syncer::FAVICON_IMAGES) {
sync_pb::FaviconImageSpecifics image_specifics =
sync_favicon.GetSpecifics().favicon_image();
// Remote image data always clobbers local image data.
bool needs_update = false;
if (image_specifics.has_favicon_web()) {
favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
image_specifics.favicon_web());
} else if (favicon_info->bitmap_data[SIZE_16].bitmap_data.get()) {
needs_update = true;
}
if (image_specifics.has_favicon_web_32()) {
favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
image_specifics.favicon_web_32());
} else if (favicon_info->bitmap_data[SIZE_32].bitmap_data.get()) {
needs_update = true;
}
if (image_specifics.has_favicon_touch_64()) {
favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
image_specifics.favicon_touch_64());
} else if (favicon_info->bitmap_data[SIZE_64].bitmap_data.get()) {
needs_update = true;
}
if (needs_update)
BuildImageSpecifics(favicon_info, new_specifics.mutable_favicon_image());
} else {
sync_pb::FaviconTrackingSpecifics tracking_specifics =
sync_favicon.GetSpecifics().favicon_tracking();
// Tracking data is merged, such that bookmark data is the logical OR
// of the two, and last visit time is the most recent.
base::Time last_visit = syncer::ProtoTimeToTime(
tracking_specifics.last_visit_time_ms());
// Due to crbug.com/258196, there are tracking nodes out there with
// null visit times. If this is one of those, artificially make it a valid
// visit time, so we know the node exists and update it properly on the next
// real visit.
if (last_visit.is_null())
last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
UpdateFaviconVisitTime(favicon_url, last_visit);
favicon_info->is_bookmarked = (favicon_info->is_bookmarked ||
tracking_specifics.is_bookmarked());
if (syncer::TimeToProtoTime(favicon_info->last_visit_time) !=
tracking_specifics.last_visit_time_ms() ||
favicon_info->is_bookmarked != tracking_specifics.is_bookmarked()) {
BuildTrackingSpecifics(favicon_info,
new_specifics.mutable_favicon_tracking());
}
DCHECK(!favicon_info->last_visit_time.is_null());
}
if (new_specifics.has_favicon_image() ||
new_specifics.has_favicon_tracking()) {
sync_changes->push_back(syncer::SyncChange(
FROM_HERE,
syncer::SyncChange::ACTION_UPDATE,
syncer::SyncData::CreateLocalData(favicon_url.spec(),
favicon_url.spec(),
new_specifics)));
}
}
void FaviconCache::AddLocalFaviconFromSyncedData(
const syncer::SyncData& sync_favicon) {
syncer::ModelType type = sync_favicon.GetDataType();
DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
if (type == syncer::FAVICON_IMAGES) {
sync_pb::FaviconImageSpecifics image_specifics =
sync_favicon.GetSpecifics().favicon_image();
GURL favicon_url = GURL(image_specifics.favicon_url());
DCHECK(favicon_url.is_valid());
DCHECK(!synced_favicons_.count(favicon_url));
SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
if (!favicon_info)
return; // We reached the in-memory limit.
if (image_specifics.has_favicon_web()) {
favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
image_specifics.favicon_web());
}
if (image_specifics.has_favicon_web_32()) {
favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
image_specifics.favicon_web_32());
}
if (image_specifics.has_favicon_touch_64()) {
favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
image_specifics.favicon_touch_64());
}
} else {
sync_pb::FaviconTrackingSpecifics tracking_specifics =
sync_favicon.GetSpecifics().favicon_tracking();
GURL favicon_url = GURL(tracking_specifics.favicon_url());
DCHECK(favicon_url.is_valid());
DCHECK(!synced_favicons_.count(favicon_url));
SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
if (!favicon_info)
return; // We reached the in-memory limit.
base::Time last_visit = syncer::ProtoTimeToTime(
tracking_specifics.last_visit_time_ms());
// Due to crbug.com/258196, there are tracking nodes out there with
// null visit times. If this is one of those, artificially make it a valid
// visit time, so we know the node exists and update it properly on the next
// real visit.
if (last_visit.is_null())
last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
UpdateFaviconVisitTime(favicon_url, last_visit);
favicon_info->is_bookmarked = tracking_specifics.is_bookmarked();
DCHECK(!favicon_info->last_visit_time.is_null());
}
}
syncer::SyncData FaviconCache::CreateSyncDataFromLocalFavicon(
syncer::ModelType type,
const GURL& favicon_url) const {
DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
DCHECK(favicon_url.is_valid());
auto iter = synced_favicons_.find(favicon_url);
DCHECK(iter != synced_favicons_.end());
SyncedFaviconInfo* favicon_info = iter->second.get();
syncer::SyncData data;
sync_pb::EntitySpecifics specifics;
if (type == syncer::FAVICON_IMAGES) {
sync_pb::FaviconImageSpecifics* image_specifics =
specifics.mutable_favicon_image();
BuildImageSpecifics(favicon_info, image_specifics);
} else {
sync_pb::FaviconTrackingSpecifics* tracking_specifics =
specifics.mutable_favicon_tracking();
BuildTrackingSpecifics(favicon_info, tracking_specifics);
}
data = syncer::SyncData::CreateLocalData(favicon_url.spec(),
favicon_url.spec(),
specifics);
return data;
}
void FaviconCache::DeleteSyncedFavicons(const std::set<GURL>& favicon_urls) {
syncer::SyncChangeList image_deletions, tracking_deletions;
for (auto iter = favicon_urls.begin(); iter != favicon_urls.end(); ++iter) {
auto favicon_iter = synced_favicons_.find(*iter);
if (favicon_iter == synced_favicons_.end())
continue;
DeleteSyncedFavicon(favicon_iter,
&image_deletions,
&tracking_deletions);
}
DVLOG(1) << "Deleting " << image_deletions.size() << " synced favicons.";
if (favicon_images_sync_processor_.get()) {
favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
image_deletions);
}
if (favicon_tracking_sync_processor_.get()) {
favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
tracking_deletions);
}
}
void FaviconCache::DeleteSyncedFavicon(
FaviconMap::iterator favicon_iter,
syncer::SyncChangeList* image_changes,
syncer::SyncChangeList* tracking_changes) {
SyncedFaviconInfo* favicon_info = favicon_iter->second.get();
if (FaviconInfoHasImages(*(favicon_iter->second))) {
DVLOG(1) << "Deleting image for "
<< favicon_iter->second.get()->favicon_url;
image_changes->push_back(
syncer::SyncChange(FROM_HERE,
syncer::SyncChange::ACTION_DELETE,
syncer::SyncData::CreateLocalDelete(
favicon_info->favicon_url.spec(),
syncer::FAVICON_IMAGES)));
}
if (FaviconInfoHasTracking(*(favicon_iter->second))) {
DVLOG(1) << "Deleting tracking for "
<< favicon_iter->second.get()->favicon_url;
tracking_changes->push_back(
syncer::SyncChange(FROM_HERE,
syncer::SyncChange::ACTION_DELETE,
syncer::SyncData::CreateLocalDelete(
favicon_info->favicon_url.spec(),
syncer::FAVICON_TRACKING)));
}
DropSyncedFavicon(favicon_iter);
}
void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter) {
DVLOG(1) << "Dropping favicon " << favicon_iter->second.get()->favicon_url;
const GURL& url = favicon_iter->first;
recent_favicons_.erase(favicon_iter->second.get());
base::EraseIf(page_favicon_map_,
[url](const auto& kv) { return kv.second == url; });
synced_favicons_.erase(favicon_iter);
}
void FaviconCache::DropPartialFavicon(FaviconMap::iterator favicon_iter,
syncer::ModelType type) {
// If the type being dropped has no valid data, do nothing.
if ((type == syncer::FAVICON_TRACKING &&
!FaviconInfoHasTracking(*favicon_iter->second)) ||
(type == syncer::FAVICON_IMAGES &&
!FaviconInfoHasImages(*favicon_iter->second))) {
return;
}
// If the type being dropped is the only type with valid data, just delete
// the favicon altogether.
if ((type == syncer::FAVICON_TRACKING &&
!FaviconInfoHasImages(*favicon_iter->second)) ||
(type == syncer::FAVICON_IMAGES &&
!FaviconInfoHasTracking(*favicon_iter->second))) {
DropSyncedFavicon(favicon_iter);
return;
}
if (type == syncer::FAVICON_IMAGES) {
DVLOG(1) << "Dropping favicon image "
<< favicon_iter->second.get()->favicon_url;
for (int i = 0; i < NUM_SIZES; ++i) {
favicon_iter->second->bitmap_data[i] =
favicon_base::FaviconRawBitmapResult();
}
DCHECK(!FaviconInfoHasImages(*favicon_iter->second));
} else {
DCHECK_EQ(type, syncer::FAVICON_TRACKING);
DVLOG(1) << "Dropping favicon tracking "
<< favicon_iter->second.get()->favicon_url;
recent_favicons_.erase(favicon_iter->second.get());
favicon_iter->second->last_visit_time = base::Time();
favicon_iter->second->is_bookmarked = false;
recent_favicons_.insert(favicon_iter->second.get());
DCHECK(!FaviconInfoHasTracking(*favicon_iter->second));
}
}
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()) {
DeleteSyncedFavicons(deletion_info.favicon_urls());
return;
}
// All history was cleared: just delete all favicons.
DVLOG(1) << "History clear detected, deleting all synced favicons.";
syncer::SyncChangeList image_deletions, tracking_deletions;
while (!synced_favicons_.empty()) {
DeleteSyncedFavicon(synced_favicons_.begin(), &image_deletions,
&tracking_deletions);
}
if (favicon_images_sync_processor_.get()) {
favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
image_deletions);
}
if (favicon_tracking_sync_processor_.get()) {
favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
tracking_deletions);
}
}
} // namespace sync_sessions