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