- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / sync / glue / favicon_cache.cc
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.
4
5 #include "chrome/browser/sync/glue/favicon_cache.h"
6
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"
21
22 namespace browser_sync {
23
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) {}
35
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;
50
51  private:
52   DISALLOW_COPY_AND_ASSIGN(SyncedFaviconInfo);
53 };
54
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) {}
63
64   bool new_image;
65   bool new_tracking;
66   bool image_needs_rewrite;
67   SyncedFaviconInfo* favicon_info;
68 };
69
70 namespace {
71
72 // Maximum number of favicons to keep in memory (0 means no limit).
73 const size_t kMaxFaviconsInMem = 0;
74
75 // Maximum width/height resolution supported.
76 const int kMaxFaviconResolution = 16;
77
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;
83 }
84
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;
100 }
101
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";
113   }
114 }
115
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());
122 }
123
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;
135 }
136
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());
147 }
148
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());
161 }
162
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);
171 }
172
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;
181   }
182
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;
203   }
204 }
205
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();
210 }
211
212 bool FaviconInfoHasTracking(const SyncedFaviconInfo& favicon_info) {
213   return !favicon_info.last_visit_time.is_null();
214 }
215
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;
224 }
225
226 }  // namespace
227
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;
236 }
237
238 FaviconCache::~FaviconCache() {}
239
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();
250
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);
258   }
259
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);
273     }
274   }
275
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);
298     }
299   }
300   UMA_HISTOGRAM_COUNTS_10000("Sync.FaviconCount", synced_favicons_.size());
301   merge_result.set_num_items_after_association(synced_favicons_.size());
302
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));
311   }
312   return merge_result;
313 }
314
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();
320 }
321
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));
328   }
329   return data_list;
330 }
331
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());
341   }
342
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;
355     }
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();
389             }
390             DCHECK(FaviconInfoHasTracking(*(favicon_iter->second)));
391             DCHECK(!FaviconInfoHasImages(*(favicon_iter->second)));
392           }
393         }
394       }
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);
407       }
408     } else {
409       error.Reset(FROM_HERE, "Invalid action received.", type);
410       break;
411     }
412   }
413
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);
426     }
427   }
428
429   return error;
430 }
431
432 void FaviconCache::OnPageFaviconUpdated(const GURL& page_url) {
433   DCHECK(page_url.is_valid());
434
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;
438
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;
454     }
455   }
456
457   DVLOG(1) << "Triggering favicon load for url " << page_url.spec();
458
459   if (!profile_) {
460     page_task_map_[page_url] = 0;  // For testing only.
461     return;
462   }
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(
472           profile_, page_url, SupportedFaviconTypes(), kMaxFaviconResolution),
473       base::Bind(&FaviconCache::OnFaviconDataAvailable,
474                  weak_ptr_factory_.GetWeakPtr(), page_url),
475       &cancelable_task_tracker_);
476   page_task_map_[page_url] = id;
477 }
478
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;
488   }
489
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));
500 }
501
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);
508
509   UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded",
510                         iter != synced_favicons_.end());
511   if (iter == synced_favicons_.end())
512     return false;
513
514   // TODO(zea): support getting other resolutions.
515   if (!iter->second->bitmap_data[SIZE_16].bitmap_data.get())
516     return false;
517
518   *favicon_png = iter->second->bitmap_data[SIZE_16].bitmap_data;
519   return true;
520 }
521
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);
528
529   if (iter == page_favicon_map_.end())
530     return false;
531
532   return GetSyncedFaviconForFaviconURL(iter->second, favicon_png);
533 }
534
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;
544
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;
550
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));
560 }
561
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;
569
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;
574
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));
587
588   UpdateSyncState(icon_url,
589                   syncer::SyncChange::ACTION_ADD,
590                   (added_tracking ?
591                    syncer::SyncChange::ACTION_ADD :
592                    syncer::SyncChange::ACTION_UPDATE));
593 }
594
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);
599
600   content::Details<history::URLsDeletedDetails> deleted_details(details);
601
602   // We only care about actual user (or sync) deletions.
603   if (deleted_details->archived)
604     return;
605
606   if (!deleted_details->all_history) {
607     DeleteSyncedFavicons(deleted_details->favicon_urls);
608     return;
609   }
610
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);
618   }
619
620   if (favicon_images_sync_processor_.get()) {
621     favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
622                                                        image_deletions);
623   }
624   if (favicon_tracking_sync_processor_.get()) {
625     favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
626                                                          tracking_deletions);
627   }
628 }
629
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;
639 }
640
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);
648
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;
654   }
655
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.
663
664     SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
665     if (!favicon_info)
666       return;  // We reached the in-memory limit.
667
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;
675   }
676
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;
682
683     // TODO(zea): support multiple favicon urls per page.
684     page_favicon_map_[page_url] = favicon_url;
685
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());
690     }
691     favicon_info->received_local_update = true;
692     UpdateFaviconVisitTime(favicon_url, now);
693
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);
705   }
706 }
707
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;
719   }
720
721   FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
722   DCHECK(iter != synced_favicons_.end());
723   const SyncedFaviconInfo* favicon_info = iter->second.get();
724
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);
732
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)));
740   }
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);
746
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)));
754   }
755   ExpireFaviconsIfNecessary(&image_changes, &tracking_changes);
756   if (!image_changes.empty()) {
757     favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
758                                                        image_changes);
759   }
760   if (!tracking_changes.empty()) {
761     favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
762                                                          tracking_changes);
763   }
764 }
765
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();
771
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;
779 }
780
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);
794
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);
800     }
801   }
802   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
803 }
804
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_.
811
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);
821   }
822   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
823 }
824
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());
831 }
832
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();
845
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;
853     }
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;
859     }
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;
865     }
866
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();
872
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.
875
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());
887
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());
893     }
894     DCHECK(!favicon_info->last_visit_time.is_null());
895   }
896
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)));
905   }
906 }
907
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));
918
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());
925     }
926     if (image_specifics.has_favicon_web_32()) {
927       favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
928           image_specifics.favicon_web_32());
929     }
930     if (image_specifics.has_favicon_touch_64()) {
931       favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
932           image_specifics.favicon_touch_64());
933     }
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));
940
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());
955   }
956 }
957
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();
966
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);
977   }
978   data = syncer::SyncData::CreateLocalData(favicon_url.spec(),
979                                            favicon_url.spec(),
980                                            specifics);
981   return data;
982 }
983
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);
994   }
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);
999   }
1000   if (favicon_tracking_sync_processor_.get()) {
1001     favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
1002                                                          tracking_deletions);
1003   }
1004 }
1005
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)));
1018   }
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)));
1026   }
1027   DropSyncedFavicon(favicon_iter);
1028 }
1029
1030 void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter) {
1031   recent_favicons_.erase(favicon_iter->second);
1032   synced_favicons_.erase(favicon_iter);
1033 }
1034
1035 size_t FaviconCache::NumFaviconsForTest() const {
1036   return synced_favicons_.size();
1037 }
1038
1039 size_t FaviconCache::NumTasksForTest() const {
1040   return page_task_map_.size();
1041 }
1042
1043 }  // namespace browser_sync