[sql] Remove _HAS_EXCEPTIONS=0 from build info.
[chromium-blink-merge.git] / chrome / browser / sync / glue / favicon_cache.cc
blobc2afd60970698799451f9ff81459ace3c87ddb9f
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sync/glue/favicon_cache.h"
7 #include "base/location.h"
8 #include "base/metrics/histogram.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/thread_task_runner_handle.h"
11 #include "chrome/browser/favicon/favicon_service_factory.h"
12 #include "chrome/browser/history/history_service_factory.h"
13 #include "components/favicon/core/favicon_service.h"
14 #include "components/history/core/browser/history_service.h"
15 #include "components/history/core/browser/history_types.h"
16 #include "sync/api/time.h"
17 #include "sync/protocol/favicon_image_specifics.pb.h"
18 #include "sync/protocol/favicon_tracking_specifics.pb.h"
19 #include "sync/protocol/sync.pb.h"
20 #include "ui/gfx/favicon_size.h"
22 namespace browser_sync {
24 // Synced favicon storage and tracking.
25 // Note: we don't use the favicon service for storing these because these
26 // favicons are not necessarily associated with any local navigation, and
27 // hence would not work with the current expiration logic. We have custom
28 // expiration logic based on visit time/bookmark status/etc.
29 // See crbug.com/122890.
30 struct SyncedFaviconInfo {
31 explicit SyncedFaviconInfo(const GURL& favicon_url)
32 : favicon_url(favicon_url),
33 is_bookmarked(false),
34 received_local_update(false) {}
36 // The actual favicon data.
37 // TODO(zea): don't keep around the actual data for locally sourced
38 // favicons (UI can access those directly).
39 favicon_base::FaviconRawBitmapResult bitmap_data[NUM_SIZES];
40 // The URL this favicon was loaded from.
41 const GURL favicon_url;
42 // Is the favicon for a bookmarked page?
43 bool is_bookmarked;
44 // The last time a tab needed this favicon.
45 // Note: Do not modify this directly! It should only be modified via
46 // UpdateFaviconVisitTime(..).
47 base::Time last_visit_time;
48 // Whether we've received a local update for this favicon since starting up.
49 bool received_local_update;
51 private:
52 DISALLOW_COPY_AND_ASSIGN(SyncedFaviconInfo);
55 // Information for handling local favicon updates. Used in
56 // OnFaviconDataAvailable.
57 struct LocalFaviconUpdateInfo {
58 LocalFaviconUpdateInfo()
59 : new_image(false),
60 new_tracking(false),
61 image_needs_rewrite(false),
62 favicon_info(NULL) {}
64 bool new_image;
65 bool new_tracking;
66 bool image_needs_rewrite;
67 SyncedFaviconInfo* favicon_info;
70 namespace {
72 // Maximum number of favicons to keep in memory (0 means no limit).
73 const size_t kMaxFaviconsInMem = 0;
75 // Maximum width/height resolution supported.
76 const int kMaxFaviconResolution = 16;
78 // Returns a mask of the supported favicon types.
79 // TODO(zea): Supporting other favicons types will involve some work in the
80 // favicon service and navigation controller. See crbug.com/181068.
81 int SupportedFaviconTypes() { return favicon_base::FAVICON; }
83 // Returns the appropriate IconSize to use for a given gfx::Size pixel
84 // dimensions.
85 IconSize GetIconSizeBinFromBitmapResult(const gfx::Size& pixel_size) {
86 int max_size =
87 (pixel_size.width() > pixel_size.height() ?
88 pixel_size.width() : pixel_size.height());
89 // TODO(zea): re-enable 64p and 32p resolutions once we support them.
90 if (max_size > 64)
91 return SIZE_INVALID;
92 else if (max_size > 32)
93 return SIZE_INVALID;
94 else if (max_size > 16)
95 return SIZE_INVALID;
96 else
97 return SIZE_16;
100 // Helper for debug statements.
101 std::string IconSizeToString(IconSize icon_size) {
102 switch (icon_size) {
103 case SIZE_16:
104 return "16";
105 case SIZE_32:
106 return "32";
107 case SIZE_64:
108 return "64";
109 default:
110 return "INVALID";
114 // Extract the favicon url from either of the favicon types.
115 GURL GetFaviconURLFromSpecifics(const sync_pb::EntitySpecifics& specifics) {
116 if (specifics.has_favicon_tracking())
117 return GURL(specifics.favicon_tracking().favicon_url());
118 else
119 return GURL(specifics.favicon_image().favicon_url());
122 // Convert protobuf image data into a FaviconRawBitmapResult.
123 favicon_base::FaviconRawBitmapResult GetImageDataFromSpecifics(
124 const sync_pb::FaviconData& favicon_data) {
125 base::RefCountedString* temp_string =
126 new base::RefCountedString();
127 temp_string->data() = favicon_data.favicon();
128 favicon_base::FaviconRawBitmapResult bitmap_result;
129 bitmap_result.bitmap_data = temp_string;
130 bitmap_result.pixel_size.set_height(favicon_data.height());
131 bitmap_result.pixel_size.set_width(favicon_data.width());
132 return bitmap_result;
135 // Convert a FaviconRawBitmapResult into protobuf image data.
136 void FillSpecificsWithImageData(
137 const favicon_base::FaviconRawBitmapResult& bitmap_result,
138 sync_pb::FaviconData* favicon_data) {
139 if (!bitmap_result.bitmap_data.get())
140 return;
141 favicon_data->set_height(bitmap_result.pixel_size.height());
142 favicon_data->set_width(bitmap_result.pixel_size.width());
143 favicon_data->set_favicon(bitmap_result.bitmap_data->front(),
144 bitmap_result.bitmap_data->size());
147 // Build a FaviconImageSpecifics from a SyncedFaviconInfo.
148 void BuildImageSpecifics(
149 const SyncedFaviconInfo* favicon_info,
150 sync_pb::FaviconImageSpecifics* image_specifics) {
151 image_specifics->set_favicon_url(favicon_info->favicon_url.spec());
152 FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_16],
153 image_specifics->mutable_favicon_web());
154 // TODO(zea): bring this back if we can handle the load.
155 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_32],
156 // image_specifics->mutable_favicon_web_32());
157 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_64],
158 // image_specifics->mutable_favicon_touch_64());
161 // Build a FaviconTrackingSpecifics from a SyncedFaviconInfo.
162 void BuildTrackingSpecifics(
163 const SyncedFaviconInfo* favicon_info,
164 sync_pb::FaviconTrackingSpecifics* tracking_specifics) {
165 tracking_specifics->set_favicon_url(favicon_info->favicon_url.spec());
166 tracking_specifics->set_last_visit_time_ms(
167 syncer::TimeToProtoTime(favicon_info->last_visit_time));
168 tracking_specifics->set_is_bookmarked(favicon_info->is_bookmarked);
171 // Updates |favicon_info| with the image data in |bitmap_result|.
172 bool UpdateFaviconFromBitmapResult(
173 const favicon_base::FaviconRawBitmapResult& bitmap_result,
174 SyncedFaviconInfo* favicon_info) {
175 DCHECK_EQ(favicon_info->favicon_url, bitmap_result.icon_url);
176 if (!bitmap_result.is_valid()) {
177 DVLOG(1) << "Received invalid favicon at " << bitmap_result.icon_url.spec();
178 return false;
181 IconSize icon_size = GetIconSizeBinFromBitmapResult(
182 bitmap_result.pixel_size);
183 if (icon_size == SIZE_INVALID) {
184 DVLOG(1) << "Ignoring unsupported resolution "
185 << bitmap_result.pixel_size.height() << "x"
186 << bitmap_result.pixel_size.width();
187 return false;
188 } else if (!favicon_info->bitmap_data[icon_size].bitmap_data.get() ||
189 !favicon_info->received_local_update) {
190 DVLOG(1) << "Storing " << IconSizeToString(icon_size) << "p"
191 << " favicon for " << favicon_info->favicon_url.spec()
192 << " with size " << bitmap_result.bitmap_data->size()
193 << " bytes.";
194 favicon_info->bitmap_data[icon_size] = bitmap_result;
195 favicon_info->received_local_update = true;
196 return true;
197 } else {
198 // We only allow updating the image data once per restart.
199 DVLOG(2) << "Ignoring local update for " << bitmap_result.icon_url.spec();
200 return false;
204 bool FaviconInfoHasImages(const SyncedFaviconInfo& favicon_info) {
205 return favicon_info.bitmap_data[SIZE_16].bitmap_data.get() ||
206 favicon_info.bitmap_data[SIZE_32].bitmap_data.get() ||
207 favicon_info.bitmap_data[SIZE_64].bitmap_data.get();
210 bool FaviconInfoHasTracking(const SyncedFaviconInfo& favicon_info) {
211 return !favicon_info.last_visit_time.is_null();
214 bool FaviconInfoHasValidTypeData(const SyncedFaviconInfo& favicon_info,
215 syncer::ModelType type) {
216 if (type == syncer::FAVICON_IMAGES)
217 return FaviconInfoHasImages(favicon_info);
218 else if (type == syncer::FAVICON_TRACKING)
219 return FaviconInfoHasTracking(favicon_info);
220 NOTREACHED();
221 return false;
224 } // namespace
226 FaviconCache::FaviconCache(Profile* profile, int max_sync_favicon_limit)
227 : profile_(profile),
228 max_sync_favicon_limit_(max_sync_favicon_limit),
229 history_service_observer_(this),
230 weak_ptr_factory_(this) {
231 history::HistoryService* hs = NULL;
232 if (profile_) {
233 hs = HistoryServiceFactory::GetForProfile(
234 profile_, ServiceAccessType::EXPLICIT_ACCESS);
236 if (hs)
237 history_service_observer_.Add(hs);
238 DVLOG(1) << "Setting favicon limit to " << max_sync_favicon_limit;
241 FaviconCache::~FaviconCache() {}
243 syncer::SyncMergeResult FaviconCache::MergeDataAndStartSyncing(
244 syncer::ModelType type,
245 const syncer::SyncDataList& initial_sync_data,
246 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
247 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
248 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
249 if (type == syncer::FAVICON_IMAGES)
250 favicon_images_sync_processor_ = sync_processor.Pass();
251 else
252 favicon_tracking_sync_processor_ = sync_processor.Pass();
254 syncer::SyncMergeResult merge_result(type);
255 merge_result.set_num_items_before_association(synced_favicons_.size());
256 std::set<GURL> unsynced_favicon_urls;
257 for (FaviconMap::const_iterator iter = synced_favicons_.begin();
258 iter != synced_favicons_.end(); ++iter) {
259 if (FaviconInfoHasValidTypeData(*(iter->second), type))
260 unsynced_favicon_urls.insert(iter->first);
263 syncer::SyncChangeList local_changes;
264 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
265 iter != initial_sync_data.end(); ++iter) {
266 GURL remote_url = GetFaviconURLFromSpecifics(iter->GetSpecifics());
267 GURL favicon_url = GetLocalFaviconFromSyncedData(*iter);
268 if (favicon_url.is_valid()) {
269 unsynced_favicon_urls.erase(favicon_url);
270 MergeSyncFavicon(*iter, &local_changes);
271 merge_result.set_num_items_modified(
272 merge_result.num_items_modified() + 1);
273 } else {
274 AddLocalFaviconFromSyncedData(*iter);
275 merge_result.set_num_items_added(merge_result.num_items_added() + 1);
279 // Rather than trigger a bunch of deletions when we set up sync, we drop
280 // local favicons. Those pages that are currently open are likely to result in
281 // loading new favicons/refreshing old favicons anyways, at which point
282 // they'll be re-added and the appropriate synced favicons will be evicted.
283 // TODO(zea): implement a smarter ordering of the which favicons to drop.
284 int available_favicons = max_sync_favicon_limit_ - initial_sync_data.size();
285 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconsAvailableAtMerge",
286 available_favicons > 0);
287 for (std::set<GURL>::const_iterator iter = unsynced_favicon_urls.begin();
288 iter != unsynced_favicon_urls.end(); ++iter) {
289 if (available_favicons > 0) {
290 local_changes.push_back(
291 syncer::SyncChange(FROM_HERE,
292 syncer::SyncChange::ACTION_ADD,
293 CreateSyncDataFromLocalFavicon(type, *iter)));
294 available_favicons--;
295 } else {
296 FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter);
297 DVLOG(1) << "Dropping local favicon "
298 << favicon_iter->second->favicon_url.spec();
299 DropPartialFavicon(favicon_iter, type);
300 merge_result.set_num_items_deleted(merge_result.num_items_deleted() + 1);
303 UMA_HISTOGRAM_COUNTS_10000("Sync.FaviconCount", synced_favicons_.size());
304 merge_result.set_num_items_after_association(synced_favicons_.size());
306 if (type == syncer::FAVICON_IMAGES) {
307 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
308 local_changes);
309 } else {
310 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
311 local_changes);
313 return merge_result;
316 void FaviconCache::StopSyncing(syncer::ModelType type) {
317 favicon_images_sync_processor_.reset();
318 favicon_tracking_sync_processor_.reset();
319 cancelable_task_tracker_.TryCancelAll();
320 page_task_map_.clear();
323 syncer::SyncDataList FaviconCache::GetAllSyncData(syncer::ModelType type)
324 const {
325 syncer::SyncDataList data_list;
326 for (FaviconMap::const_iterator iter = synced_favicons_.begin();
327 iter != synced_favicons_.end(); ++iter) {
328 if ((type == syncer::FAVICON_IMAGES &&
329 FaviconInfoHasImages(*iter->second)) ||
330 (type == syncer::FAVICON_TRACKING &&
331 FaviconInfoHasTracking(*iter->second))) {
332 data_list.push_back(CreateSyncDataFromLocalFavicon(type, iter->first));
335 return data_list;
338 syncer::SyncError FaviconCache::ProcessSyncChanges(
339 const tracked_objects::Location& from_here,
340 const syncer::SyncChangeList& change_list) {
341 if (!favicon_images_sync_processor_.get() ||
342 !favicon_tracking_sync_processor_.get()) {
343 return syncer::SyncError(FROM_HERE,
344 syncer::SyncError::DATATYPE_ERROR,
345 "One or both favicon types disabled.",
346 change_list[0].sync_data().GetDataType());
349 syncer::SyncChangeList new_changes;
350 syncer::SyncError error;
351 syncer::ModelType type = syncer::UNSPECIFIED;
352 for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
353 iter != change_list.end(); ++iter) {
354 type = iter->sync_data().GetDataType();
355 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
356 GURL favicon_url =
357 GetFaviconURLFromSpecifics(iter->sync_data().GetSpecifics());
358 if (!favicon_url.is_valid()) {
359 error.Reset(FROM_HERE, "Received invalid favicon url.", type);
360 break;
362 FaviconMap::iterator favicon_iter = synced_favicons_.find(favicon_url);
363 if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
364 if (favicon_iter == synced_favicons_.end()) {
365 // Two clients might wind up deleting different parts of the same
366 // favicon, so ignore this.
367 continue;
368 } else {
369 DVLOG(1) << "Deleting favicon at " << favicon_url.spec();
370 // If we only have partial data for the favicon (which implies orphaned
371 // nodes), delete the local favicon only if the type corresponds to the
372 // partial data we have. If we do have orphaned nodes, we rely on the
373 // expiration logic to remove them eventually.
374 DropPartialFavicon(favicon_iter, type);
376 } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE ||
377 iter->change_type() == syncer::SyncChange::ACTION_ADD) {
378 // Adds and updates are treated the same due to the lack of strong
379 // consistency (it's possible we'll receive an update for a tracking info
380 // before we've received the add for the image, and should handle both
381 // gracefully).
382 if (favicon_iter == synced_favicons_.end()) {
383 DVLOG(1) << "Adding favicon at " << favicon_url.spec();
384 AddLocalFaviconFromSyncedData(iter->sync_data());
385 } else {
386 DVLOG(1) << "Updating favicon at " << favicon_url.spec();
387 MergeSyncFavicon(iter->sync_data(), &new_changes);
389 } else {
390 error.Reset(FROM_HERE, "Invalid action received.", type);
391 break;
395 // Note: we deliberately do not expire favicons here. If we received new
396 // favicons and are now over the limit, the next local favicon change will
397 // trigger the necessary expiration.
398 if (!error.IsSet() && !new_changes.empty()) {
399 if (type == syncer::FAVICON_IMAGES) {
400 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
401 new_changes);
402 } else {
403 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
404 new_changes);
408 return error;
411 void FaviconCache::OnPageFaviconUpdated(const GURL& page_url) {
412 DCHECK(page_url.is_valid());
414 // If a favicon load is already happening for this url, let it finish.
415 if (page_task_map_.find(page_url) != page_task_map_.end())
416 return;
418 PageFaviconMap::const_iterator url_iter = page_favicon_map_.find(page_url);
419 if (url_iter != page_favicon_map_.end()) {
420 FaviconMap::const_iterator icon_iter =
421 synced_favicons_.find(url_iter->second);
422 // TODO(zea): consider what to do when only a subset of supported
423 // resolutions are available.
424 if (icon_iter != synced_favicons_.end() &&
425 icon_iter->second->bitmap_data[SIZE_16].bitmap_data.get()) {
426 DVLOG(2) << "Using cached favicon url for " << page_url.spec()
427 << ": " << icon_iter->second->favicon_url.spec();
428 UpdateFaviconVisitTime(icon_iter->second->favicon_url, base::Time::Now());
429 UpdateSyncState(icon_iter->second->favicon_url,
430 syncer::SyncChange::ACTION_INVALID,
431 syncer::SyncChange::ACTION_UPDATE);
432 return;
436 DVLOG(1) << "Triggering favicon load for url " << page_url.spec();
438 if (!profile_) {
439 page_task_map_[page_url] = 0; // For testing only.
440 return;
442 favicon::FaviconService* favicon_service =
443 FaviconServiceFactory::GetForProfile(profile_,
444 ServiceAccessType::EXPLICIT_ACCESS);
445 if (!favicon_service)
446 return;
447 // TODO(zea): This appears to only fetch one favicon (best match based on
448 // desired_size_in_dip). Figure out a way to fetch all favicons we support.
449 // See crbug.com/181068.
450 base::CancelableTaskTracker::TaskId id =
451 favicon_service->GetFaviconForPageURL(
452 page_url,
453 SupportedFaviconTypes(),
454 kMaxFaviconResolution,
455 base::Bind(&FaviconCache::OnFaviconDataAvailable,
456 weak_ptr_factory_.GetWeakPtr(),
457 page_url),
458 &cancelable_task_tracker_);
459 page_task_map_[page_url] = id;
462 void FaviconCache::OnFaviconVisited(const GURL& page_url,
463 const GURL& favicon_url) {
464 DCHECK(page_url.is_valid());
465 if (!favicon_url.is_valid() ||
466 synced_favicons_.find(favicon_url) == synced_favicons_.end()) {
467 // TODO(zea): consider triggering a favicon load if we have some but not
468 // all desired resolutions?
469 OnPageFaviconUpdated(page_url);
470 return;
473 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at "
474 << favicon_url.spec() << " and marking visited.";
475 page_favicon_map_[page_url] = favicon_url;
476 bool had_tracking = FaviconInfoHasTracking(
477 *synced_favicons_.find(favicon_url)->second);
478 UpdateFaviconVisitTime(favicon_url, base::Time::Now());
480 UpdateSyncState(favicon_url,
481 syncer::SyncChange::ACTION_INVALID,
482 (had_tracking ?
483 syncer::SyncChange::ACTION_UPDATE :
484 syncer::SyncChange::ACTION_ADD));
487 bool FaviconCache::GetSyncedFaviconForFaviconURL(
488 const GURL& favicon_url,
489 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
490 if (!favicon_url.is_valid())
491 return false;
492 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
494 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded",
495 iter != synced_favicons_.end());
496 if (iter == synced_favicons_.end())
497 return false;
499 // TODO(zea): support getting other resolutions.
500 if (!iter->second->bitmap_data[SIZE_16].bitmap_data.get())
501 return false;
503 *favicon_png = iter->second->bitmap_data[SIZE_16].bitmap_data;
504 return true;
507 bool FaviconCache::GetSyncedFaviconForPageURL(
508 const GURL& page_url,
509 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
510 if (!page_url.is_valid())
511 return false;
512 PageFaviconMap::const_iterator iter = page_favicon_map_.find(page_url);
514 if (iter == page_favicon_map_.end())
515 return false;
517 return GetSyncedFaviconForFaviconURL(iter->second, favicon_png);
520 void FaviconCache::OnReceivedSyncFavicon(const GURL& page_url,
521 const GURL& icon_url,
522 const std::string& icon_bytes,
523 int64 visit_time_ms) {
524 if (!icon_url.is_valid() || !page_url.is_valid() || icon_url.SchemeIs("data"))
525 return;
526 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at "
527 << icon_url.spec();
528 page_favicon_map_[page_url] = icon_url;
530 // If there is no actual image, it means there either is no synced
531 // favicon, or it's on its way (race condition).
532 // TODO(zea): potentially trigger a favicon web download here (delayed?).
533 if (icon_bytes.size() == 0)
534 return;
536 // Post a task to do the actual association because this method may have been
537 // called while in a transaction.
538 base::ThreadTaskRunnerHandle::Get()->PostTask(
539 FROM_HERE, base::Bind(&FaviconCache::OnReceivedSyncFaviconImpl,
540 weak_ptr_factory_.GetWeakPtr(), icon_url,
541 icon_bytes, visit_time_ms));
544 void FaviconCache::OnReceivedSyncFaviconImpl(
545 const GURL& icon_url,
546 const std::string& icon_bytes,
547 int64 visit_time_ms) {
548 // If this favicon is already synced, do nothing else.
549 if (synced_favicons_.find(icon_url) != synced_favicons_.end())
550 return;
552 // Don't add any more favicons once we hit our in memory limit.
553 // TODO(zea): UMA this.
554 if (kMaxFaviconsInMem != 0 && synced_favicons_.size() > kMaxFaviconsInMem)
555 return;
557 SyncedFaviconInfo* favicon_info = GetFaviconInfo(icon_url);
558 if (!favicon_info)
559 return; // We reached the in-memory limit.
560 base::RefCountedString* temp_string = new base::RefCountedString();
561 temp_string->data() = icon_bytes;
562 favicon_info->bitmap_data[SIZE_16].bitmap_data = temp_string;
563 // We assume legacy favicons are 16x16.
564 favicon_info->bitmap_data[SIZE_16].pixel_size.set_width(16);
565 favicon_info->bitmap_data[SIZE_16].pixel_size.set_height(16);
566 bool added_tracking = !FaviconInfoHasTracking(*favicon_info);
567 UpdateFaviconVisitTime(icon_url,
568 syncer::ProtoTimeToTime(visit_time_ms));
570 UpdateSyncState(icon_url,
571 syncer::SyncChange::ACTION_ADD,
572 (added_tracking ?
573 syncer::SyncChange::ACTION_ADD :
574 syncer::SyncChange::ACTION_UPDATE));
577 bool FaviconCache::FaviconRecencyFunctor::operator()(
578 const linked_ptr<SyncedFaviconInfo>& lhs,
579 const linked_ptr<SyncedFaviconInfo>& rhs) const {
580 // TODO(zea): incorporate bookmarked status here once we care about it.
581 if (lhs->last_visit_time < rhs->last_visit_time)
582 return true;
583 else if (lhs->last_visit_time == rhs->last_visit_time)
584 return lhs->favicon_url.spec() < rhs->favicon_url.spec();
585 return false;
588 void FaviconCache::OnFaviconDataAvailable(
589 const GURL& page_url,
590 const std::vector<favicon_base::FaviconRawBitmapResult>& bitmap_results) {
591 PageTaskMap::iterator page_iter = page_task_map_.find(page_url);
592 if (page_iter == page_task_map_.end())
593 return;
594 page_task_map_.erase(page_iter);
596 if (bitmap_results.size() == 0) {
597 // Either the favicon isn't loaded yet or there is no valid favicon.
598 // We already cleared the task id, so just return.
599 DVLOG(1) << "Favicon load failed for page " << page_url.spec();
600 return;
603 base::Time now = base::Time::Now();
604 std::map<GURL, LocalFaviconUpdateInfo> favicon_updates;
605 for (size_t i = 0; i < bitmap_results.size(); ++i) {
606 const favicon_base::FaviconRawBitmapResult& bitmap_result =
607 bitmap_results[i];
608 GURL favicon_url = bitmap_result.icon_url;
609 if (!favicon_url.is_valid() || favicon_url.SchemeIs("data"))
610 continue; // Can happen if the page is still loading.
612 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
613 if (!favicon_info)
614 return; // We reached the in-memory limit.
616 favicon_updates[favicon_url].new_image |=
617 !FaviconInfoHasImages(*favicon_info);
618 favicon_updates[favicon_url].new_tracking |=
619 !FaviconInfoHasTracking(*favicon_info);
620 favicon_updates[favicon_url].image_needs_rewrite |=
621 UpdateFaviconFromBitmapResult(bitmap_result, favicon_info);
622 favicon_updates[favicon_url].favicon_info = favicon_info;
625 for (std::map<GURL, LocalFaviconUpdateInfo>::const_iterator
626 iter = favicon_updates.begin(); iter != favicon_updates.end();
627 ++iter) {
628 SyncedFaviconInfo* favicon_info = iter->second.favicon_info;
629 const GURL& favicon_url = favicon_info->favicon_url;
631 // TODO(zea): support multiple favicon urls per page.
632 page_favicon_map_[page_url] = favicon_url;
634 if (!favicon_info->last_visit_time.is_null()) {
635 UMA_HISTOGRAM_COUNTS_10000(
636 "Sync.FaviconVisitPeriod",
637 (now - favicon_info->last_visit_time).InHours());
639 favicon_info->received_local_update = true;
640 UpdateFaviconVisitTime(favicon_url, now);
642 syncer::SyncChange::SyncChangeType image_change =
643 syncer::SyncChange::ACTION_INVALID;
644 if (iter->second.new_image)
645 image_change = syncer::SyncChange::ACTION_ADD;
646 else if (iter->second.image_needs_rewrite)
647 image_change = syncer::SyncChange::ACTION_UPDATE;
648 syncer::SyncChange::SyncChangeType tracking_change =
649 syncer::SyncChange::ACTION_UPDATE;
650 if (iter->second.new_tracking)
651 tracking_change = syncer::SyncChange::ACTION_ADD;
652 UpdateSyncState(favicon_url, image_change, tracking_change);
656 void FaviconCache::UpdateSyncState(
657 const GURL& icon_url,
658 syncer::SyncChange::SyncChangeType image_change_type,
659 syncer::SyncChange::SyncChangeType tracking_change_type) {
660 DCHECK(icon_url.is_valid());
661 // It's possible that we'll receive a favicon update before both types
662 // have finished setting up. In that case ignore the update.
663 // TODO(zea): consider tracking these skipped updates somehow?
664 if (!favicon_images_sync_processor_.get() ||
665 !favicon_tracking_sync_processor_.get()) {
666 return;
669 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
670 DCHECK(iter != synced_favicons_.end());
671 const SyncedFaviconInfo* favicon_info = iter->second.get();
673 syncer::SyncChangeList image_changes;
674 syncer::SyncChangeList tracking_changes;
675 if (image_change_type != syncer::SyncChange::ACTION_INVALID) {
676 sync_pb::EntitySpecifics new_specifics;
677 sync_pb::FaviconImageSpecifics* image_specifics =
678 new_specifics.mutable_favicon_image();
679 BuildImageSpecifics(favicon_info, image_specifics);
681 image_changes.push_back(
682 syncer::SyncChange(FROM_HERE,
683 image_change_type,
684 syncer::SyncData::CreateLocalData(
685 icon_url.spec(),
686 icon_url.spec(),
687 new_specifics)));
689 if (tracking_change_type != syncer::SyncChange::ACTION_INVALID) {
690 sync_pb::EntitySpecifics new_specifics;
691 sync_pb::FaviconTrackingSpecifics* tracking_specifics =
692 new_specifics.mutable_favicon_tracking();
693 BuildTrackingSpecifics(favicon_info, tracking_specifics);
695 tracking_changes.push_back(
696 syncer::SyncChange(FROM_HERE,
697 tracking_change_type,
698 syncer::SyncData::CreateLocalData(
699 icon_url.spec(),
700 icon_url.spec(),
701 new_specifics)));
703 ExpireFaviconsIfNecessary(&image_changes, &tracking_changes);
704 if (!image_changes.empty()) {
705 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
706 image_changes);
708 if (!tracking_changes.empty()) {
709 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
710 tracking_changes);
714 SyncedFaviconInfo* FaviconCache::GetFaviconInfo(
715 const GURL& icon_url) {
716 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
717 if (synced_favicons_.count(icon_url) != 0)
718 return synced_favicons_[icon_url].get();
720 // TODO(zea): implement in-memory eviction.
721 DVLOG(1) << "Adding favicon info for " << icon_url.spec();
722 SyncedFaviconInfo* favicon_info = new SyncedFaviconInfo(icon_url);
723 synced_favicons_[icon_url] = make_linked_ptr(favicon_info);
724 recent_favicons_.insert(synced_favicons_[icon_url]);
725 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
726 return favicon_info;
729 void FaviconCache::UpdateFaviconVisitTime(const GURL& icon_url,
730 base::Time time) {
731 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
732 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
733 DCHECK(iter != synced_favicons_.end());
734 if (iter->second->last_visit_time >= time)
735 return;
736 // Erase, update the time, then re-insert to maintain ordering.
737 recent_favicons_.erase(iter->second);
738 DVLOG(1) << "Updating " << icon_url.spec() << " visit time to "
739 << syncer::GetTimeDebugString(time);
740 iter->second->last_visit_time = time;
741 recent_favicons_.insert(iter->second);
743 if (VLOG_IS_ON(2)) {
744 for (RecencySet::const_iterator iter = recent_favicons_.begin();
745 iter != recent_favicons_.end(); ++iter) {
746 DVLOG(2) << "Favicon " << iter->get()->favicon_url.spec() << ": "
747 << syncer::GetTimeDebugString(iter->get()->last_visit_time);
750 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
753 void FaviconCache::ExpireFaviconsIfNecessary(
754 syncer::SyncChangeList* image_changes,
755 syncer::SyncChangeList* tracking_changes) {
756 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
757 // TODO(zea): once we have in-memory eviction, we'll need to track sync
758 // favicon count separately from the synced_favicons_/recent_favicons_.
760 // Iterate until we've removed the necessary amount. |recent_favicons_| is
761 // already in recency order, so just start from the beginning.
762 // TODO(zea): to reduce thrashing, consider removing more than the minimum.
763 while (recent_favicons_.size() > max_sync_favicon_limit_) {
764 linked_ptr<SyncedFaviconInfo> candidate = *recent_favicons_.begin();
765 DVLOG(1) << "Expiring favicon " << candidate->favicon_url.spec();
766 DeleteSyncedFavicon(synced_favicons_.find(candidate->favicon_url),
767 image_changes,
768 tracking_changes);
770 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
773 GURL FaviconCache::GetLocalFaviconFromSyncedData(
774 const syncer::SyncData& sync_favicon) const {
775 syncer::ModelType type = sync_favicon.GetDataType();
776 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
777 GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
778 return (synced_favicons_.count(favicon_url) > 0 ? favicon_url : GURL());
781 void FaviconCache::MergeSyncFavicon(const syncer::SyncData& sync_favicon,
782 syncer::SyncChangeList* sync_changes) {
783 syncer::ModelType type = sync_favicon.GetDataType();
784 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
785 sync_pb::EntitySpecifics new_specifics;
786 GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
787 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
788 DCHECK(iter != synced_favicons_.end());
789 SyncedFaviconInfo* favicon_info = iter->second.get();
790 if (type == syncer::FAVICON_IMAGES) {
791 sync_pb::FaviconImageSpecifics image_specifics =
792 sync_favicon.GetSpecifics().favicon_image();
794 // Remote image data always clobbers local image data.
795 bool needs_update = false;
796 if (image_specifics.has_favicon_web()) {
797 favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
798 image_specifics.favicon_web());
799 } else if (favicon_info->bitmap_data[SIZE_16].bitmap_data.get()) {
800 needs_update = true;
802 if (image_specifics.has_favicon_web_32()) {
803 favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
804 image_specifics.favicon_web_32());
805 } else if (favicon_info->bitmap_data[SIZE_32].bitmap_data.get()) {
806 needs_update = true;
808 if (image_specifics.has_favicon_touch_64()) {
809 favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
810 image_specifics.favicon_touch_64());
811 } else if (favicon_info->bitmap_data[SIZE_64].bitmap_data.get()) {
812 needs_update = true;
815 if (needs_update)
816 BuildImageSpecifics(favicon_info, new_specifics.mutable_favicon_image());
817 } else {
818 sync_pb::FaviconTrackingSpecifics tracking_specifics =
819 sync_favicon.GetSpecifics().favicon_tracking();
821 // Tracking data is merged, such that bookmark data is the logical OR
822 // of the two, and last visit time is the most recent.
824 base::Time last_visit = syncer::ProtoTimeToTime(
825 tracking_specifics.last_visit_time_ms());
826 // Due to crbug.com/258196, there are tracking nodes out there with
827 // null visit times. If this is one of those, artificially make it a valid
828 // visit time, so we know the node exists and update it properly on the next
829 // real visit.
830 if (last_visit.is_null())
831 last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
832 UpdateFaviconVisitTime(favicon_url, last_visit);
833 favicon_info->is_bookmarked = (favicon_info->is_bookmarked ||
834 tracking_specifics.is_bookmarked());
836 if (syncer::TimeToProtoTime(favicon_info->last_visit_time) !=
837 tracking_specifics.last_visit_time_ms() ||
838 favicon_info->is_bookmarked != tracking_specifics.is_bookmarked()) {
839 BuildTrackingSpecifics(favicon_info,
840 new_specifics.mutable_favicon_tracking());
842 DCHECK(!favicon_info->last_visit_time.is_null());
845 if (new_specifics.has_favicon_image() ||
846 new_specifics.has_favicon_tracking()) {
847 sync_changes->push_back(syncer::SyncChange(
848 FROM_HERE,
849 syncer::SyncChange::ACTION_UPDATE,
850 syncer::SyncData::CreateLocalData(favicon_url.spec(),
851 favicon_url.spec(),
852 new_specifics)));
856 void FaviconCache::AddLocalFaviconFromSyncedData(
857 const syncer::SyncData& sync_favicon) {
858 syncer::ModelType type = sync_favicon.GetDataType();
859 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
860 if (type == syncer::FAVICON_IMAGES) {
861 sync_pb::FaviconImageSpecifics image_specifics =
862 sync_favicon.GetSpecifics().favicon_image();
863 GURL favicon_url = GURL(image_specifics.favicon_url());
864 DCHECK(favicon_url.is_valid());
865 DCHECK(!synced_favicons_.count(favicon_url));
867 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
868 if (!favicon_info)
869 return; // We reached the in-memory limit.
870 if (image_specifics.has_favicon_web()) {
871 favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
872 image_specifics.favicon_web());
874 if (image_specifics.has_favicon_web_32()) {
875 favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
876 image_specifics.favicon_web_32());
878 if (image_specifics.has_favicon_touch_64()) {
879 favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
880 image_specifics.favicon_touch_64());
882 } else {
883 sync_pb::FaviconTrackingSpecifics tracking_specifics =
884 sync_favicon.GetSpecifics().favicon_tracking();
885 GURL favicon_url = GURL(tracking_specifics.favicon_url());
886 DCHECK(favicon_url.is_valid());
887 DCHECK(!synced_favicons_.count(favicon_url));
889 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
890 if (!favicon_info)
891 return; // We reached the in-memory limit.
892 base::Time last_visit = syncer::ProtoTimeToTime(
893 tracking_specifics.last_visit_time_ms());
894 // Due to crbug.com/258196, there are tracking nodes out there with
895 // null visit times. If this is one of those, artificially make it a valid
896 // visit time, so we know the node exists and update it properly on the next
897 // real visit.
898 if (last_visit.is_null())
899 last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
900 UpdateFaviconVisitTime(favicon_url, last_visit);
901 favicon_info->is_bookmarked = tracking_specifics.is_bookmarked();
902 DCHECK(!favicon_info->last_visit_time.is_null());
906 syncer::SyncData FaviconCache::CreateSyncDataFromLocalFavicon(
907 syncer::ModelType type,
908 const GURL& favicon_url) const {
909 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
910 DCHECK(favicon_url.is_valid());
911 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
912 DCHECK(iter != synced_favicons_.end());
913 SyncedFaviconInfo* favicon_info = iter->second.get();
915 syncer::SyncData data;
916 sync_pb::EntitySpecifics specifics;
917 if (type == syncer::FAVICON_IMAGES) {
918 sync_pb::FaviconImageSpecifics* image_specifics =
919 specifics.mutable_favicon_image();
920 BuildImageSpecifics(favicon_info, image_specifics);
921 } else {
922 sync_pb::FaviconTrackingSpecifics* tracking_specifics =
923 specifics.mutable_favicon_tracking();
924 BuildTrackingSpecifics(favicon_info, tracking_specifics);
926 data = syncer::SyncData::CreateLocalData(favicon_url.spec(),
927 favicon_url.spec(),
928 specifics);
929 return data;
932 void FaviconCache::DeleteSyncedFavicons(const std::set<GURL>& favicon_urls) {
933 syncer::SyncChangeList image_deletions, tracking_deletions;
934 for (std::set<GURL>::const_iterator iter = favicon_urls.begin();
935 iter != favicon_urls.end(); ++iter) {
936 FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter);
937 if (favicon_iter == synced_favicons_.end())
938 continue;
939 DeleteSyncedFavicon(favicon_iter,
940 &image_deletions,
941 &tracking_deletions);
943 DVLOG(1) << "Deleting " << image_deletions.size() << " synced favicons.";
944 if (favicon_images_sync_processor_.get()) {
945 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
946 image_deletions);
948 if (favicon_tracking_sync_processor_.get()) {
949 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
950 tracking_deletions);
954 void FaviconCache::DeleteSyncedFavicon(
955 FaviconMap::iterator favicon_iter,
956 syncer::SyncChangeList* image_changes,
957 syncer::SyncChangeList* tracking_changes) {
958 linked_ptr<SyncedFaviconInfo> favicon_info = favicon_iter->second;
959 if (FaviconInfoHasImages(*(favicon_iter->second))) {
960 DVLOG(1) << "Deleting image for "
961 << favicon_iter->second.get()->favicon_url;
962 image_changes->push_back(
963 syncer::SyncChange(FROM_HERE,
964 syncer::SyncChange::ACTION_DELETE,
965 syncer::SyncData::CreateLocalDelete(
966 favicon_info->favicon_url.spec(),
967 syncer::FAVICON_IMAGES)));
969 if (FaviconInfoHasTracking(*(favicon_iter->second))) {
970 DVLOG(1) << "Deleting tracking for "
971 << favicon_iter->second.get()->favicon_url;
972 tracking_changes->push_back(
973 syncer::SyncChange(FROM_HERE,
974 syncer::SyncChange::ACTION_DELETE,
975 syncer::SyncData::CreateLocalDelete(
976 favicon_info->favicon_url.spec(),
977 syncer::FAVICON_TRACKING)));
979 DropSyncedFavicon(favicon_iter);
982 void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter) {
983 DVLOG(1) << "Dropping favicon " << favicon_iter->second.get()->favicon_url;
984 recent_favicons_.erase(favicon_iter->second);
985 synced_favicons_.erase(favicon_iter);
988 void FaviconCache::DropPartialFavicon(FaviconMap::iterator favicon_iter,
989 syncer::ModelType type) {
990 // If the type being dropped has no valid data, do nothing.
991 if ((type == syncer::FAVICON_TRACKING &&
992 !FaviconInfoHasTracking(*favicon_iter->second)) ||
993 (type == syncer::FAVICON_IMAGES &&
994 !FaviconInfoHasImages(*favicon_iter->second))) {
995 return;
998 // If the type being dropped is the only type with valid data, just delete
999 // the favicon altogether.
1000 if ((type == syncer::FAVICON_TRACKING &&
1001 !FaviconInfoHasImages(*favicon_iter->second)) ||
1002 (type == syncer::FAVICON_IMAGES &&
1003 !FaviconInfoHasTracking(*favicon_iter->second))) {
1004 DropSyncedFavicon(favicon_iter);
1005 return;
1008 if (type == syncer::FAVICON_IMAGES) {
1009 DVLOG(1) << "Dropping favicon image "
1010 << favicon_iter->second.get()->favicon_url;
1011 for (int i = 0; i < NUM_SIZES; ++i) {
1012 favicon_iter->second->bitmap_data[i] =
1013 favicon_base::FaviconRawBitmapResult();
1015 DCHECK(!FaviconInfoHasImages(*favicon_iter->second));
1016 } else {
1017 DCHECK_EQ(type, syncer::FAVICON_TRACKING);
1018 DVLOG(1) << "Dropping favicon tracking "
1019 << favicon_iter->second.get()->favicon_url;
1020 recent_favicons_.erase(favicon_iter->second);
1021 favicon_iter->second->last_visit_time = base::Time();
1022 favicon_iter->second->is_bookmarked = false;
1023 recent_favicons_.insert(favicon_iter->second);
1024 DCHECK(!FaviconInfoHasTracking(*favicon_iter->second));
1028 size_t FaviconCache::NumFaviconsForTest() const {
1029 return synced_favicons_.size();
1032 size_t FaviconCache::NumTasksForTest() const {
1033 return page_task_map_.size();
1036 void FaviconCache::OnURLsDeleted(history::HistoryService* history_service,
1037 bool all_history,
1038 bool expired,
1039 const history::URLRows& deleted_rows,
1040 const std::set<GURL>& favicon_urls) {
1041 // We only care about actual user (or sync) deletions.
1042 if (expired)
1043 return;
1045 if (!all_history) {
1046 DeleteSyncedFavicons(favicon_urls);
1047 return;
1050 // All history was cleared: just delete all favicons.
1051 DVLOG(1) << "History clear detected, deleting all synced favicons.";
1052 syncer::SyncChangeList image_deletions, tracking_deletions;
1053 while (!synced_favicons_.empty()) {
1054 DeleteSyncedFavicon(synced_favicons_.begin(), &image_deletions,
1055 &tracking_deletions);
1058 if (favicon_images_sync_processor_.get()) {
1059 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
1060 image_deletions);
1062 if (favicon_tracking_sync_processor_.get()) {
1063 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
1064 tracking_deletions);
1068 } // namespace browser_sync