Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sync / glue / favicon_cache.cc
blobf250d9b4c5c88f1a95c7e75f84f5a08994912748
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/chrome_notification_types.h"
10 #include "chrome/browser/favicon/favicon_service.h"
11 #include "chrome/browser/favicon/favicon_service_factory.h"
12 #include "chrome/browser/history/history_notifications.h"
13 #include "chrome/browser/history/history_types.h"
14 #include "content/public/browser/notification_details.h"
15 #include "content/public/browser/notification_source.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 chrome::FaviconBitmapResult 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() {
82 return chrome::FAVICON;
85 // Returns the appropriate IconSize to use for a given gfx::Size pixel
86 // dimensions.
87 IconSize GetIconSizeBinFromBitmapResult(const gfx::Size& pixel_size) {
88 int max_size =
89 (pixel_size.width() > pixel_size.height() ?
90 pixel_size.width() : pixel_size.height());
91 // TODO(zea): re-enable 64p and 32p resolutions once we support them.
92 if (max_size > 64)
93 return SIZE_INVALID;
94 else if (max_size > 32)
95 return SIZE_INVALID;
96 else if (max_size > 16)
97 return SIZE_INVALID;
98 else
99 return SIZE_16;
102 // Helper for debug statements.
103 std::string IconSizeToString(IconSize icon_size) {
104 switch (icon_size) {
105 case SIZE_16:
106 return "16";
107 case SIZE_32:
108 return "32";
109 case SIZE_64:
110 return "64";
111 default:
112 return "INVALID";
116 // Extract the favicon url from either of the favicon types.
117 GURL GetFaviconURLFromSpecifics(const sync_pb::EntitySpecifics& specifics) {
118 if (specifics.has_favicon_tracking())
119 return GURL(specifics.favicon_tracking().favicon_url());
120 else
121 return GURL(specifics.favicon_image().favicon_url());
124 // Convert protobuf image data into a FaviconBitmapResult.
125 chrome::FaviconBitmapResult GetImageDataFromSpecifics(
126 const sync_pb::FaviconData& favicon_data) {
127 base::RefCountedString* temp_string =
128 new base::RefCountedString();
129 temp_string->data() = favicon_data.favicon();
130 chrome::FaviconBitmapResult bitmap_result;
131 bitmap_result.bitmap_data = temp_string;
132 bitmap_result.pixel_size.set_height(favicon_data.height());
133 bitmap_result.pixel_size.set_width(favicon_data.width());
134 return bitmap_result;
137 // Convert a FaviconBitmapResult into protobuf image data.
138 void FillSpecificsWithImageData(
139 const chrome::FaviconBitmapResult& bitmap_result,
140 sync_pb::FaviconData* favicon_data) {
141 if (!bitmap_result.bitmap_data.get())
142 return;
143 favicon_data->set_height(bitmap_result.pixel_size.height());
144 favicon_data->set_width(bitmap_result.pixel_size.width());
145 favicon_data->set_favicon(bitmap_result.bitmap_data->front(),
146 bitmap_result.bitmap_data->size());
149 // Build a FaviconImageSpecifics from a SyncedFaviconInfo.
150 void BuildImageSpecifics(
151 const SyncedFaviconInfo* favicon_info,
152 sync_pb::FaviconImageSpecifics* image_specifics) {
153 image_specifics->set_favicon_url(favicon_info->favicon_url.spec());
154 FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_16],
155 image_specifics->mutable_favicon_web());
156 // TODO(zea): bring this back if we can handle the load.
157 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_32],
158 // image_specifics->mutable_favicon_web_32());
159 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_64],
160 // image_specifics->mutable_favicon_touch_64());
163 // Build a FaviconTrackingSpecifics from a SyncedFaviconInfo.
164 void BuildTrackingSpecifics(
165 const SyncedFaviconInfo* favicon_info,
166 sync_pb::FaviconTrackingSpecifics* tracking_specifics) {
167 tracking_specifics->set_favicon_url(favicon_info->favicon_url.spec());
168 tracking_specifics->set_last_visit_time_ms(
169 syncer::TimeToProtoTime(favicon_info->last_visit_time));
170 tracking_specifics->set_is_bookmarked(favicon_info->is_bookmarked);
173 // Updates |favicon_info| with the image data in |bitmap_result|.
174 bool UpdateFaviconFromBitmapResult(
175 const chrome::FaviconBitmapResult& bitmap_result,
176 SyncedFaviconInfo* favicon_info) {
177 DCHECK_EQ(favicon_info->favicon_url, bitmap_result.icon_url);
178 if (!bitmap_result.is_valid()) {
179 DVLOG(1) << "Received invalid favicon at " << bitmap_result.icon_url.spec();
180 return false;
183 IconSize icon_size = GetIconSizeBinFromBitmapResult(
184 bitmap_result.pixel_size);
185 if (icon_size == SIZE_INVALID) {
186 DVLOG(1) << "Ignoring unsupported resolution "
187 << bitmap_result.pixel_size.height() << "x"
188 << bitmap_result.pixel_size.width();
189 return false;
190 } else if (!favicon_info->bitmap_data[icon_size].bitmap_data.get() ||
191 !favicon_info->received_local_update) {
192 DVLOG(1) << "Storing " << IconSizeToString(icon_size) << "p"
193 << " favicon for " << favicon_info->favicon_url.spec()
194 << " with size " << bitmap_result.bitmap_data->size()
195 << " bytes.";
196 favicon_info->bitmap_data[icon_size] = bitmap_result;
197 favicon_info->received_local_update = true;
198 return true;
199 } else {
200 // We only allow updating the image data once per restart.
201 DVLOG(2) << "Ignoring local update for " << bitmap_result.icon_url.spec();
202 return false;
206 bool FaviconInfoHasImages(const SyncedFaviconInfo& favicon_info) {
207 return favicon_info.bitmap_data[SIZE_16].bitmap_data.get() ||
208 favicon_info.bitmap_data[SIZE_32].bitmap_data.get() ||
209 favicon_info.bitmap_data[SIZE_64].bitmap_data.get();
212 bool FaviconInfoHasTracking(const SyncedFaviconInfo& favicon_info) {
213 return !favicon_info.last_visit_time.is_null();
216 bool FaviconInfoHasValidTypeData(const SyncedFaviconInfo& favicon_info,
217 syncer::ModelType type) {
218 if (type == syncer::FAVICON_IMAGES)
219 return FaviconInfoHasImages(favicon_info);
220 else if (type == syncer::FAVICON_TRACKING)
221 return FaviconInfoHasTracking(favicon_info);
222 NOTREACHED();
223 return false;
226 } // namespace
228 FaviconCache::FaviconCache(Profile* profile, int max_sync_favicon_limit)
229 : profile_(profile),
230 max_sync_favicon_limit_(max_sync_favicon_limit),
231 weak_ptr_factory_(this) {
232 notification_registrar_.Add(this,
233 chrome::NOTIFICATION_HISTORY_URLS_DELETED,
234 content::Source<Profile>(profile_));
235 DVLOG(1) << "Setting favicon limit to " << max_sync_favicon_limit;
238 FaviconCache::~FaviconCache() {}
240 syncer::SyncMergeResult FaviconCache::MergeDataAndStartSyncing(
241 syncer::ModelType type,
242 const syncer::SyncDataList& initial_sync_data,
243 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
244 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
245 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
246 if (type == syncer::FAVICON_IMAGES)
247 favicon_images_sync_processor_ = sync_processor.Pass();
248 else
249 favicon_tracking_sync_processor_ = sync_processor.Pass();
251 syncer::SyncMergeResult merge_result(type);
252 merge_result.set_num_items_before_association(synced_favicons_.size());
253 std::set<GURL> unsynced_favicon_urls;
254 for (FaviconMap::const_iterator iter = synced_favicons_.begin();
255 iter != synced_favicons_.end(); ++iter) {
256 if (FaviconInfoHasValidTypeData(*(iter->second), type))
257 unsynced_favicon_urls.insert(iter->first);
260 syncer::SyncChangeList local_changes;
261 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
262 iter != initial_sync_data.end(); ++iter) {
263 GURL remote_url = GetFaviconURLFromSpecifics(iter->GetSpecifics());
264 GURL favicon_url = GetLocalFaviconFromSyncedData(*iter);
265 if (favicon_url.is_valid()) {
266 unsynced_favicon_urls.erase(favicon_url);
267 MergeSyncFavicon(*iter, &local_changes);
268 merge_result.set_num_items_modified(
269 merge_result.num_items_modified() + 1);
270 } else {
271 AddLocalFaviconFromSyncedData(*iter);
272 merge_result.set_num_items_added(merge_result.num_items_added() + 1);
276 // Rather than trigger a bunch of deletions when we set up sync, we drop
277 // local favicons. Those pages that are currently open are likely to result in
278 // loading new favicons/refreshing old favicons anyways, at which point
279 // they'll be re-added and the appropriate synced favicons will be evicted.
280 // TODO(zea): implement a smarter ordering of the which favicons to drop.
281 int available_favicons = max_sync_favicon_limit_ - initial_sync_data.size();
282 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconsAvailableAtMerge",
283 available_favicons > 0);
284 for (std::set<GURL>::const_iterator iter = unsynced_favicon_urls.begin();
285 iter != unsynced_favicon_urls.end(); ++iter) {
286 if (available_favicons > 0) {
287 local_changes.push_back(
288 syncer::SyncChange(FROM_HERE,
289 syncer::SyncChange::ACTION_ADD,
290 CreateSyncDataFromLocalFavicon(type, *iter)));
291 available_favicons--;
292 } else {
293 FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter);
294 DVLOG(1) << "Dropping local favicon "
295 << favicon_iter->second->favicon_url.spec();
296 DropSyncedFavicon(favicon_iter);
297 merge_result.set_num_items_deleted(merge_result.num_items_deleted() + 1);
300 UMA_HISTOGRAM_COUNTS_10000("Sync.FaviconCount", synced_favicons_.size());
301 merge_result.set_num_items_after_association(synced_favicons_.size());
303 if (type == syncer::FAVICON_IMAGES) {
304 merge_result.set_error(
305 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
306 local_changes));
307 } else {
308 merge_result.set_error(
309 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
310 local_changes));
312 return merge_result;
315 void FaviconCache::StopSyncing(syncer::ModelType type) {
316 favicon_images_sync_processor_.reset();
317 favicon_tracking_sync_processor_.reset();
318 cancelable_task_tracker_.TryCancelAll();
319 page_task_map_.clear();
322 syncer::SyncDataList FaviconCache::GetAllSyncData(syncer::ModelType type)
323 const {
324 syncer::SyncDataList data_list;
325 for (FaviconMap::const_iterator iter = synced_favicons_.begin();
326 iter != synced_favicons_.end(); ++iter) {
327 data_list.push_back(CreateSyncDataFromLocalFavicon(type, iter->first));
329 return data_list;
332 syncer::SyncError FaviconCache::ProcessSyncChanges(
333 const tracked_objects::Location& from_here,
334 const syncer::SyncChangeList& change_list) {
335 if (!favicon_images_sync_processor_.get() ||
336 !favicon_tracking_sync_processor_.get()) {
337 return syncer::SyncError(FROM_HERE,
338 syncer::SyncError::DATATYPE_ERROR,
339 "One or both favicon types disabled.",
340 change_list[0].sync_data().GetDataType());
343 syncer::SyncChangeList new_changes;
344 syncer::SyncError error;
345 syncer::ModelType type = syncer::UNSPECIFIED;
346 for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
347 iter != change_list.end(); ++iter) {
348 type = iter->sync_data().GetDataType();
349 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
350 GURL favicon_url =
351 GetFaviconURLFromSpecifics(iter->sync_data().GetSpecifics());
352 if (!favicon_url.is_valid()) {
353 error.Reset(FROM_HERE, "Received invalid favicon url.", type);
354 break;
356 FaviconMap::iterator favicon_iter = synced_favicons_.find(favicon_url);
357 if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
358 if (favicon_iter == synced_favicons_.end()) {
359 // Two clients might wind up deleting different parts of the same
360 // favicon, so ignore this.
361 continue;
362 } else {
363 DVLOG(1) << "Deleting favicon at " << favicon_url.spec();
364 // If we only have partial data for the favicon (which implies orphaned
365 // nodes), delete the local favicon only if the type corresponds to the
366 // partial data we have. If we do have orphaned nodes, we rely on the
367 // expiration logic to remove them eventually.
368 if (type == syncer::FAVICON_IMAGES &&
369 FaviconInfoHasImages(*(favicon_iter->second)) &&
370 !FaviconInfoHasTracking(*(favicon_iter->second))) {
371 DropSyncedFavicon(favicon_iter);
372 } else if (type == syncer::FAVICON_TRACKING &&
373 !FaviconInfoHasImages(*(favicon_iter->second)) &&
374 FaviconInfoHasTracking(*(favicon_iter->second))) {
375 DropSyncedFavicon(favicon_iter);
376 } else {
377 // Only delete the data for the modified type.
378 if (type == syncer::FAVICON_TRACKING) {
379 recent_favicons_.erase(favicon_iter->second);
380 favicon_iter->second->last_visit_time = base::Time();
381 favicon_iter->second->is_bookmarked = false;
382 recent_favicons_.insert(favicon_iter->second);
383 DCHECK(!FaviconInfoHasTracking(*(favicon_iter->second)));
384 DCHECK(FaviconInfoHasImages(*(favicon_iter->second)));
385 } else {
386 for (int i = 0; i < NUM_SIZES; ++i) {
387 favicon_iter->second->bitmap_data[i] =
388 chrome::FaviconBitmapResult();
390 DCHECK(FaviconInfoHasTracking(*(favicon_iter->second)));
391 DCHECK(!FaviconInfoHasImages(*(favicon_iter->second)));
395 } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE ||
396 iter->change_type() == syncer::SyncChange::ACTION_ADD) {
397 // Adds and updates are treated the same due to the lack of strong
398 // consistency (it's possible we'll receive an update for a tracking info
399 // before we've received the add for the image, and should handle both
400 // gracefully).
401 if (favicon_iter == synced_favicons_.end()) {
402 DVLOG(1) << "Adding favicon at " << favicon_url.spec();
403 AddLocalFaviconFromSyncedData(iter->sync_data());
404 } else {
405 DVLOG(1) << "Updating favicon at " << favicon_url.spec();
406 MergeSyncFavicon(iter->sync_data(), &new_changes);
408 } else {
409 error.Reset(FROM_HERE, "Invalid action received.", type);
410 break;
414 // Note: we deliberately do not expire favicons here. If we received new
415 // favicons and are now over the limit, the next local favicon change will
416 // trigger the necessary expiration.
417 if (!error.IsSet() && !new_changes.empty()) {
418 if (type == syncer::FAVICON_IMAGES) {
419 error =
420 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
421 new_changes);
422 } else {
423 error =
424 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
425 new_changes);
429 return error;
432 void FaviconCache::OnPageFaviconUpdated(const GURL& page_url) {
433 DCHECK(page_url.is_valid());
435 // If a favicon load is already happening for this url, let it finish.
436 if (page_task_map_.find(page_url) != page_task_map_.end())
437 return;
439 PageFaviconMap::const_iterator url_iter = page_favicon_map_.find(page_url);
440 if (url_iter != page_favicon_map_.end()) {
441 FaviconMap::const_iterator icon_iter =
442 synced_favicons_.find(url_iter->second);
443 // TODO(zea): consider what to do when only a subset of supported
444 // resolutions are available.
445 if (icon_iter != synced_favicons_.end() &&
446 icon_iter->second->bitmap_data[SIZE_16].bitmap_data.get()) {
447 DVLOG(2) << "Using cached favicon url for " << page_url.spec()
448 << ": " << icon_iter->second->favicon_url.spec();
449 UpdateFaviconVisitTime(icon_iter->second->favicon_url, base::Time::Now());
450 UpdateSyncState(icon_iter->second->favicon_url,
451 syncer::SyncChange::ACTION_INVALID,
452 syncer::SyncChange::ACTION_UPDATE);
453 return;
457 DVLOG(1) << "Triggering favicon load for url " << page_url.spec();
459 if (!profile_) {
460 page_task_map_[page_url] = 0; // For testing only.
461 return;
463 FaviconService* favicon_service =
464 FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
465 if (!favicon_service)
466 return;
467 // TODO(zea): This appears to only fetch one favicon (best match based on
468 // desired_size_in_dip). Figure out a way to fetch all favicons we support.
469 // See crbug.com/181068.
470 CancelableTaskTracker::TaskId id = favicon_service->GetFaviconForURL(
471 FaviconService::FaviconForURLParams(page_url, SupportedFaviconTypes(),
472 kMaxFaviconResolution),
473 base::Bind(&FaviconCache::OnFaviconDataAvailable,
474 weak_ptr_factory_.GetWeakPtr(), page_url),
475 &cancelable_task_tracker_);
476 page_task_map_[page_url] = id;
479 void FaviconCache::OnFaviconVisited(const GURL& page_url,
480 const GURL& favicon_url) {
481 DCHECK(page_url.is_valid());
482 if (!favicon_url.is_valid() ||
483 synced_favicons_.find(favicon_url) == synced_favicons_.end()) {
484 // TODO(zea): consider triggering a favicon load if we have some but not
485 // all desired resolutions?
486 OnPageFaviconUpdated(page_url);
487 return;
490 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at "
491 << favicon_url.spec() << " and marking visited.";
492 page_favicon_map_[page_url] = favicon_url;
493 UpdateFaviconVisitTime(favicon_url, base::Time::Now());
494 UpdateSyncState(favicon_url,
495 syncer::SyncChange::ACTION_INVALID,
496 (FaviconInfoHasTracking(
497 *synced_favicons_.find(favicon_url)->second) ?
498 syncer::SyncChange::ACTION_UPDATE :
499 syncer::SyncChange::ACTION_ADD));
502 bool FaviconCache::GetSyncedFaviconForFaviconURL(
503 const GURL& favicon_url,
504 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
505 if (!favicon_url.is_valid())
506 return false;
507 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
509 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded",
510 iter != synced_favicons_.end());
511 if (iter == synced_favicons_.end())
512 return false;
514 // TODO(zea): support getting other resolutions.
515 if (!iter->second->bitmap_data[SIZE_16].bitmap_data.get())
516 return false;
518 *favicon_png = iter->second->bitmap_data[SIZE_16].bitmap_data;
519 return true;
522 bool FaviconCache::GetSyncedFaviconForPageURL(
523 const GURL& page_url,
524 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
525 if (!page_url.is_valid())
526 return false;
527 PageFaviconMap::const_iterator iter = page_favicon_map_.find(page_url);
529 if (iter == page_favicon_map_.end())
530 return false;
532 return GetSyncedFaviconForFaviconURL(iter->second, favicon_png);
535 void FaviconCache::OnReceivedSyncFavicon(const GURL& page_url,
536 const GURL& icon_url,
537 const std::string& icon_bytes,
538 int64 visit_time_ms) {
539 if (!icon_url.is_valid() || !page_url.is_valid() || icon_url.SchemeIs("data"))
540 return;
541 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at "
542 << icon_url.spec();
543 page_favicon_map_[page_url] = icon_url;
545 // If there is no actual image, it means there either is no synced
546 // favicon, or it's on its way (race condition).
547 // TODO(zea): potentially trigger a favicon web download here (delayed?).
548 if (icon_bytes.size() == 0)
549 return;
551 // Post a task to do the actual association because this method may have been
552 // called while in a transaction.
553 base::MessageLoop::current()->PostTask(
554 FROM_HERE,
555 base::Bind(&FaviconCache::OnReceivedSyncFaviconImpl,
556 weak_ptr_factory_.GetWeakPtr(),
557 icon_url,
558 icon_bytes,
559 visit_time_ms));
562 void FaviconCache::OnReceivedSyncFaviconImpl(
563 const GURL& icon_url,
564 const std::string& icon_bytes,
565 int64 visit_time_ms) {
566 // If this favicon is already synced, do nothing else.
567 if (synced_favicons_.find(icon_url) != synced_favicons_.end())
568 return;
570 // Don't add any more favicons once we hit our in memory limit.
571 // TODO(zea): UMA this.
572 if (kMaxFaviconsInMem != 0 && synced_favicons_.size() > kMaxFaviconsInMem)
573 return;
575 SyncedFaviconInfo* favicon_info = GetFaviconInfo(icon_url);
576 if (!favicon_info)
577 return; // We reached the in-memory limit.
578 base::RefCountedString* temp_string = new base::RefCountedString();
579 temp_string->data() = icon_bytes;
580 favicon_info->bitmap_data[SIZE_16].bitmap_data = temp_string;
581 // We assume legacy favicons are 16x16.
582 favicon_info->bitmap_data[SIZE_16].pixel_size.set_width(16);
583 favicon_info->bitmap_data[SIZE_16].pixel_size.set_height(16);
584 bool added_tracking = !FaviconInfoHasTracking(*favicon_info);
585 UpdateFaviconVisitTime(icon_url,
586 syncer::ProtoTimeToTime(visit_time_ms));
588 UpdateSyncState(icon_url,
589 syncer::SyncChange::ACTION_ADD,
590 (added_tracking ?
591 syncer::SyncChange::ACTION_ADD :
592 syncer::SyncChange::ACTION_UPDATE));
595 void FaviconCache::Observe(int type,
596 const content::NotificationSource& source,
597 const content::NotificationDetails& details) {
598 DCHECK_EQ(type, chrome::NOTIFICATION_HISTORY_URLS_DELETED);
600 content::Details<history::URLsDeletedDetails> deleted_details(details);
602 // We only care about actual user (or sync) deletions.
603 if (deleted_details->archived)
604 return;
606 if (!deleted_details->all_history) {
607 DeleteSyncedFavicons(deleted_details->favicon_urls);
608 return;
611 // All history was cleared: just delete all favicons.
612 DVLOG(1) << "History clear detected, deleting all synced favicons.";
613 syncer::SyncChangeList image_deletions, tracking_deletions;
614 while (!synced_favicons_.empty()) {
615 DeleteSyncedFavicon(synced_favicons_.begin(),
616 &image_deletions,
617 &tracking_deletions);
620 if (favicon_images_sync_processor_.get()) {
621 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
622 image_deletions);
624 if (favicon_tracking_sync_processor_.get()) {
625 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
626 tracking_deletions);
630 bool FaviconCache::FaviconRecencyFunctor::operator()(
631 const linked_ptr<SyncedFaviconInfo>& lhs,
632 const linked_ptr<SyncedFaviconInfo>& rhs) const {
633 // TODO(zea): incorporate bookmarked status here once we care about it.
634 if (lhs->last_visit_time < rhs->last_visit_time)
635 return true;
636 else if (lhs->last_visit_time == rhs->last_visit_time)
637 return lhs->favicon_url.spec() < rhs->favicon_url.spec();
638 return false;
641 void FaviconCache::OnFaviconDataAvailable(
642 const GURL& page_url,
643 const std::vector<chrome::FaviconBitmapResult>& bitmap_results) {
644 PageTaskMap::iterator page_iter = page_task_map_.find(page_url);
645 if (page_iter == page_task_map_.end())
646 return;
647 page_task_map_.erase(page_iter);
649 if (bitmap_results.size() == 0) {
650 // Either the favicon isn't loaded yet or there is no valid favicon.
651 // We already cleared the task id, so just return.
652 DVLOG(1) << "Favicon load failed for page " << page_url.spec();
653 return;
656 base::Time now = base::Time::Now();
657 std::map<GURL, LocalFaviconUpdateInfo> favicon_updates;
658 for (size_t i = 0; i < bitmap_results.size(); ++i) {
659 const chrome::FaviconBitmapResult& bitmap_result = bitmap_results[i];
660 GURL favicon_url = bitmap_result.icon_url;
661 if (!favicon_url.is_valid() || favicon_url.SchemeIs("data"))
662 continue; // Can happen if the page is still loading.
664 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
665 if (!favicon_info)
666 return; // We reached the in-memory limit.
668 favicon_updates[favicon_url].new_image |=
669 !FaviconInfoHasImages(*favicon_info);
670 favicon_updates[favicon_url].new_tracking |=
671 !FaviconInfoHasTracking(*favicon_info);
672 favicon_updates[favicon_url].image_needs_rewrite |=
673 UpdateFaviconFromBitmapResult(bitmap_result, favicon_info);
674 favicon_updates[favicon_url].favicon_info = favicon_info;
677 for (std::map<GURL, LocalFaviconUpdateInfo>::const_iterator
678 iter = favicon_updates.begin(); iter != favicon_updates.end();
679 ++iter) {
680 SyncedFaviconInfo* favicon_info = iter->second.favicon_info;
681 const GURL& favicon_url = favicon_info->favicon_url;
683 // TODO(zea): support multiple favicon urls per page.
684 page_favicon_map_[page_url] = favicon_url;
686 if (!favicon_info->last_visit_time.is_null()) {
687 UMA_HISTOGRAM_COUNTS_10000(
688 "Sync.FaviconVisitPeriod",
689 (now - favicon_info->last_visit_time).InHours());
691 favicon_info->received_local_update = true;
692 UpdateFaviconVisitTime(favicon_url, now);
694 syncer::SyncChange::SyncChangeType image_change =
695 syncer::SyncChange::ACTION_INVALID;
696 if (iter->second.new_image)
697 image_change = syncer::SyncChange::ACTION_ADD;
698 else if (iter->second.image_needs_rewrite)
699 image_change = syncer::SyncChange::ACTION_UPDATE;
700 syncer::SyncChange::SyncChangeType tracking_change =
701 syncer::SyncChange::ACTION_UPDATE;
702 if (iter->second.new_tracking)
703 tracking_change = syncer::SyncChange::ACTION_ADD;
704 UpdateSyncState(favicon_url, image_change, tracking_change);
708 void FaviconCache::UpdateSyncState(
709 const GURL& icon_url,
710 syncer::SyncChange::SyncChangeType image_change_type,
711 syncer::SyncChange::SyncChangeType tracking_change_type) {
712 DCHECK(icon_url.is_valid());
713 // It's possible that we'll receive a favicon update before both types
714 // have finished setting up. In that case ignore the update.
715 // TODO(zea): consider tracking these skipped updates somehow?
716 if (!favicon_images_sync_processor_.get() ||
717 !favicon_tracking_sync_processor_.get()) {
718 return;
721 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
722 DCHECK(iter != synced_favicons_.end());
723 const SyncedFaviconInfo* favicon_info = iter->second.get();
725 syncer::SyncChangeList image_changes;
726 syncer::SyncChangeList tracking_changes;
727 if (image_change_type != syncer::SyncChange::ACTION_INVALID) {
728 sync_pb::EntitySpecifics new_specifics;
729 sync_pb::FaviconImageSpecifics* image_specifics =
730 new_specifics.mutable_favicon_image();
731 BuildImageSpecifics(favicon_info, image_specifics);
733 image_changes.push_back(
734 syncer::SyncChange(FROM_HERE,
735 image_change_type,
736 syncer::SyncData::CreateLocalData(
737 icon_url.spec(),
738 icon_url.spec(),
739 new_specifics)));
741 if (tracking_change_type != syncer::SyncChange::ACTION_INVALID) {
742 sync_pb::EntitySpecifics new_specifics;
743 sync_pb::FaviconTrackingSpecifics* tracking_specifics =
744 new_specifics.mutable_favicon_tracking();
745 BuildTrackingSpecifics(favicon_info, tracking_specifics);
747 tracking_changes.push_back(
748 syncer::SyncChange(FROM_HERE,
749 tracking_change_type,
750 syncer::SyncData::CreateLocalData(
751 icon_url.spec(),
752 icon_url.spec(),
753 new_specifics)));
755 ExpireFaviconsIfNecessary(&image_changes, &tracking_changes);
756 if (!image_changes.empty()) {
757 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
758 image_changes);
760 if (!tracking_changes.empty()) {
761 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
762 tracking_changes);
766 SyncedFaviconInfo* FaviconCache::GetFaviconInfo(
767 const GURL& icon_url) {
768 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
769 if (synced_favicons_.count(icon_url) != 0)
770 return synced_favicons_[icon_url].get();
772 // TODO(zea): implement in-memory eviction.
773 DVLOG(1) << "Adding favicon info for " << icon_url.spec();
774 SyncedFaviconInfo* favicon_info = new SyncedFaviconInfo(icon_url);
775 synced_favicons_[icon_url] = make_linked_ptr(favicon_info);
776 recent_favicons_.insert(synced_favicons_[icon_url]);
777 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
778 return favicon_info;
781 void FaviconCache::UpdateFaviconVisitTime(const GURL& icon_url,
782 base::Time time) {
783 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
784 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
785 DCHECK(iter != synced_favicons_.end());
786 if (iter->second->last_visit_time >= time)
787 return;
788 // Erase, update the time, then re-insert to maintain ordering.
789 recent_favicons_.erase(iter->second);
790 DVLOG(1) << "Updating " << icon_url.spec() << " visit time to "
791 << syncer::GetTimeDebugString(time);
792 iter->second->last_visit_time = time;
793 recent_favicons_.insert(iter->second);
795 if (VLOG_IS_ON(2)) {
796 for (RecencySet::const_iterator iter = recent_favicons_.begin();
797 iter != recent_favicons_.end(); ++iter) {
798 DVLOG(2) << "Favicon " << iter->get()->favicon_url.spec() << ": "
799 << syncer::GetTimeDebugString(iter->get()->last_visit_time);
802 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
805 void FaviconCache::ExpireFaviconsIfNecessary(
806 syncer::SyncChangeList* image_changes,
807 syncer::SyncChangeList* tracking_changes) {
808 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
809 // TODO(zea): once we have in-memory eviction, we'll need to track sync
810 // favicon count separately from the synced_favicons_/recent_favicons_.
812 // Iterate until we've removed the necessary amount. |recent_favicons_| is
813 // already in recency order, so just start from the beginning.
814 // TODO(zea): to reduce thrashing, consider removing more than the minimum.
815 while (recent_favicons_.size() > max_sync_favicon_limit_) {
816 linked_ptr<SyncedFaviconInfo> candidate = *recent_favicons_.begin();
817 DVLOG(1) << "Expiring favicon " << candidate->favicon_url.spec();
818 DeleteSyncedFavicon(synced_favicons_.find(candidate->favicon_url),
819 image_changes,
820 tracking_changes);
822 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
825 GURL FaviconCache::GetLocalFaviconFromSyncedData(
826 const syncer::SyncData& sync_favicon) const {
827 syncer::ModelType type = sync_favicon.GetDataType();
828 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
829 GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
830 return (synced_favicons_.count(favicon_url) > 0 ? favicon_url : GURL());
833 void FaviconCache::MergeSyncFavicon(const syncer::SyncData& sync_favicon,
834 syncer::SyncChangeList* sync_changes) {
835 syncer::ModelType type = sync_favicon.GetDataType();
836 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
837 sync_pb::EntitySpecifics new_specifics;
838 GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
839 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
840 DCHECK(iter != synced_favicons_.end());
841 SyncedFaviconInfo* favicon_info = iter->second.get();
842 if (type == syncer::FAVICON_IMAGES) {
843 sync_pb::FaviconImageSpecifics image_specifics =
844 sync_favicon.GetSpecifics().favicon_image();
846 // Remote image data always clobbers local image data.
847 bool needs_update = false;
848 if (image_specifics.has_favicon_web()) {
849 favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
850 image_specifics.favicon_web());
851 } else if (favicon_info->bitmap_data[SIZE_16].bitmap_data.get()) {
852 needs_update = true;
854 if (image_specifics.has_favicon_web_32()) {
855 favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
856 image_specifics.favicon_web_32());
857 } else if (favicon_info->bitmap_data[SIZE_32].bitmap_data.get()) {
858 needs_update = true;
860 if (image_specifics.has_favicon_touch_64()) {
861 favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
862 image_specifics.favicon_touch_64());
863 } else if (favicon_info->bitmap_data[SIZE_64].bitmap_data.get()) {
864 needs_update = true;
867 if (needs_update)
868 BuildImageSpecifics(favicon_info, new_specifics.mutable_favicon_image());
869 } else {
870 sync_pb::FaviconTrackingSpecifics tracking_specifics =
871 sync_favicon.GetSpecifics().favicon_tracking();
873 // Tracking data is merged, such that bookmark data is the logical OR
874 // of the two, and last visit time is the most recent.
876 base::Time last_visit = syncer::ProtoTimeToTime(
877 tracking_specifics.last_visit_time_ms());
878 // Due to crbug.com/258196, there are tracking nodes out there with
879 // null visit times. If this is one of those, artificially make it a valid
880 // visit time, so we know the node exists and update it properly on the next
881 // real visit.
882 if (last_visit.is_null())
883 last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
884 UpdateFaviconVisitTime(favicon_url, last_visit);
885 favicon_info->is_bookmarked = (favicon_info->is_bookmarked ||
886 tracking_specifics.is_bookmarked());
888 if (syncer::TimeToProtoTime(favicon_info->last_visit_time) !=
889 tracking_specifics.last_visit_time_ms() ||
890 favicon_info->is_bookmarked != tracking_specifics.is_bookmarked()) {
891 BuildTrackingSpecifics(favicon_info,
892 new_specifics.mutable_favicon_tracking());
894 DCHECK(!favicon_info->last_visit_time.is_null());
897 if (new_specifics.has_favicon_image() ||
898 new_specifics.has_favicon_tracking()) {
899 sync_changes->push_back(syncer::SyncChange(
900 FROM_HERE,
901 syncer::SyncChange::ACTION_UPDATE,
902 syncer::SyncData::CreateLocalData(favicon_url.spec(),
903 favicon_url.spec(),
904 new_specifics)));
908 void FaviconCache::AddLocalFaviconFromSyncedData(
909 const syncer::SyncData& sync_favicon) {
910 syncer::ModelType type = sync_favicon.GetDataType();
911 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
912 if (type == syncer::FAVICON_IMAGES) {
913 sync_pb::FaviconImageSpecifics image_specifics =
914 sync_favicon.GetSpecifics().favicon_image();
915 GURL favicon_url = GURL(image_specifics.favicon_url());
916 DCHECK(favicon_url.is_valid());
917 DCHECK(!synced_favicons_.count(favicon_url));
919 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
920 if (!favicon_info)
921 return; // We reached the in-memory limit.
922 if (image_specifics.has_favicon_web()) {
923 favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
924 image_specifics.favicon_web());
926 if (image_specifics.has_favicon_web_32()) {
927 favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
928 image_specifics.favicon_web_32());
930 if (image_specifics.has_favicon_touch_64()) {
931 favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
932 image_specifics.favicon_touch_64());
934 } else {
935 sync_pb::FaviconTrackingSpecifics tracking_specifics =
936 sync_favicon.GetSpecifics().favicon_tracking();
937 GURL favicon_url = GURL(tracking_specifics.favicon_url());
938 DCHECK(favicon_url.is_valid());
939 DCHECK(!synced_favicons_.count(favicon_url));
941 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
942 if (!favicon_info)
943 return; // We reached the in-memory limit.
944 base::Time last_visit = syncer::ProtoTimeToTime(
945 tracking_specifics.last_visit_time_ms());
946 // Due to crbug.com/258196, there are tracking nodes out there with
947 // null visit times. If this is one of those, artificially make it a valid
948 // visit time, so we know the node exists and update it properly on the next
949 // real visit.
950 if (last_visit.is_null())
951 last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
952 UpdateFaviconVisitTime(favicon_url, last_visit);
953 favicon_info->is_bookmarked = tracking_specifics.is_bookmarked();
954 DCHECK(!favicon_info->last_visit_time.is_null());
958 syncer::SyncData FaviconCache::CreateSyncDataFromLocalFavicon(
959 syncer::ModelType type,
960 const GURL& favicon_url) const {
961 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
962 DCHECK(favicon_url.is_valid());
963 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
964 DCHECK(iter != synced_favicons_.end());
965 SyncedFaviconInfo* favicon_info = iter->second.get();
967 syncer::SyncData data;
968 sync_pb::EntitySpecifics specifics;
969 if (type == syncer::FAVICON_IMAGES) {
970 sync_pb::FaviconImageSpecifics* image_specifics =
971 specifics.mutable_favicon_image();
972 BuildImageSpecifics(favicon_info, image_specifics);
973 } else {
974 sync_pb::FaviconTrackingSpecifics* tracking_specifics =
975 specifics.mutable_favicon_tracking();
976 BuildTrackingSpecifics(favicon_info, tracking_specifics);
978 data = syncer::SyncData::CreateLocalData(favicon_url.spec(),
979 favicon_url.spec(),
980 specifics);
981 return data;
984 void FaviconCache::DeleteSyncedFavicons(const std::set<GURL>& favicon_urls) {
985 syncer::SyncChangeList image_deletions, tracking_deletions;
986 for (std::set<GURL>::const_iterator iter = favicon_urls.begin();
987 iter != favicon_urls.end(); ++iter) {
988 FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter);
989 if (favicon_iter == synced_favicons_.end())
990 continue;
991 DeleteSyncedFavicon(favicon_iter,
992 &image_deletions,
993 &tracking_deletions);
995 DVLOG(1) << "Deleting " << image_deletions.size() << " synced favicons.";
996 if (favicon_images_sync_processor_.get()) {
997 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
998 image_deletions);
1000 if (favicon_tracking_sync_processor_.get()) {
1001 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
1002 tracking_deletions);
1006 void FaviconCache::DeleteSyncedFavicon(
1007 FaviconMap::iterator favicon_iter,
1008 syncer::SyncChangeList* image_changes,
1009 syncer::SyncChangeList* tracking_changes) {
1010 linked_ptr<SyncedFaviconInfo> favicon_info = favicon_iter->second;
1011 if (FaviconInfoHasImages(*(favicon_iter->second))) {
1012 image_changes->push_back(
1013 syncer::SyncChange(FROM_HERE,
1014 syncer::SyncChange::ACTION_DELETE,
1015 syncer::SyncData::CreateLocalDelete(
1016 favicon_info->favicon_url.spec(),
1017 syncer::FAVICON_IMAGES)));
1019 if (FaviconInfoHasTracking(*(favicon_iter->second))) {
1020 tracking_changes->push_back(
1021 syncer::SyncChange(FROM_HERE,
1022 syncer::SyncChange::ACTION_DELETE,
1023 syncer::SyncData::CreateLocalDelete(
1024 favicon_info->favicon_url.spec(),
1025 syncer::FAVICON_TRACKING)));
1027 DropSyncedFavicon(favicon_iter);
1030 void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter) {
1031 recent_favicons_.erase(favicon_iter->second);
1032 synced_favicons_.erase(favicon_iter);
1035 size_t FaviconCache::NumFaviconsForTest() const {
1036 return synced_favicons_.size();
1039 size_t FaviconCache::NumTasksForTest() const {
1040 return page_task_map_.size();
1043 } // namespace browser_sync