Home | History | Annotate | Download | only in glue
      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       weak_ptr_factory_(this),
    231       max_sync_favicon_limit_(max_sync_favicon_limit) {
    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     if (!favicon_info->last_visit_time.is_null()) {
    683       UMA_HISTOGRAM_COUNTS_10000(
    684           "Sync.FaviconVisitPeriod",
    685           (now - favicon_info->last_visit_time).InHours());
    686     }
    687     favicon_info->received_local_update = true;
    688     UpdateFaviconVisitTime(favicon_url, now);
    689 
    690     syncer::SyncChange::SyncChangeType image_change =
    691         syncer::SyncChange::ACTION_INVALID;
    692     if (iter->second.new_image)
    693       image_change = syncer::SyncChange::ACTION_ADD;
    694     else if (iter->second.image_needs_rewrite)
    695       image_change = syncer::SyncChange::ACTION_UPDATE;
    696     syncer::SyncChange::SyncChangeType tracking_change =
    697         syncer::SyncChange::ACTION_UPDATE;
    698     if (iter->second.new_tracking)
    699       tracking_change = syncer::SyncChange::ACTION_ADD;
    700     UpdateSyncState(favicon_url, image_change, tracking_change);
    701 
    702     // TODO(zea): support multiple favicon urls per page.
    703     page_favicon_map_[page_url] = favicon_url;
    704   }
    705 }
    706 
    707 void FaviconCache::UpdateSyncState(
    708     const GURL& icon_url,
    709     syncer::SyncChange::SyncChangeType image_change_type,
    710     syncer::SyncChange::SyncChangeType tracking_change_type) {
    711   DCHECK(icon_url.is_valid());
    712   // It's possible that we'll receive a favicon update before both types
    713   // have finished setting up. In that case ignore the update.
    714   // TODO(zea): consider tracking these skipped updates somehow?
    715   if (!favicon_images_sync_processor_.get() ||
    716       !favicon_tracking_sync_processor_.get()) {
    717     return;
    718   }
    719 
    720   FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
    721   DCHECK(iter != synced_favicons_.end());
    722   const SyncedFaviconInfo* favicon_info = iter->second.get();
    723 
    724   syncer::SyncChangeList image_changes;
    725   syncer::SyncChangeList tracking_changes;
    726   if (image_change_type != syncer::SyncChange::ACTION_INVALID) {
    727     sync_pb::EntitySpecifics new_specifics;
    728     sync_pb::FaviconImageSpecifics* image_specifics =
    729         new_specifics.mutable_favicon_image();
    730     BuildImageSpecifics(favicon_info, image_specifics);
    731 
    732     image_changes.push_back(
    733         syncer::SyncChange(FROM_HERE,
    734                            image_change_type,
    735                            syncer::SyncData::CreateLocalData(
    736                                icon_url.spec(),
    737                                icon_url.spec(),
    738                                new_specifics)));
    739   }
    740   if (tracking_change_type != syncer::SyncChange::ACTION_INVALID) {
    741     sync_pb::EntitySpecifics new_specifics;
    742     sync_pb::FaviconTrackingSpecifics* tracking_specifics =
    743         new_specifics.mutable_favicon_tracking();
    744     BuildTrackingSpecifics(favicon_info, tracking_specifics);
    745 
    746     tracking_changes.push_back(
    747         syncer::SyncChange(FROM_HERE,
    748                            tracking_change_type,
    749                            syncer::SyncData::CreateLocalData(
    750                                icon_url.spec(),
    751                                icon_url.spec(),
    752                                new_specifics)));
    753   }
    754   ExpireFaviconsIfNecessary(&image_changes, &tracking_changes);
    755   if (!image_changes.empty()) {
    756     favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
    757                                                        image_changes);
    758   }
    759   if (!tracking_changes.empty()) {
    760     favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
    761                                                          tracking_changes);
    762   }
    763 }
    764 
    765 SyncedFaviconInfo* FaviconCache::GetFaviconInfo(
    766     const GURL& icon_url) {
    767   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
    768   if (synced_favicons_.count(icon_url) != 0)
    769     return synced_favicons_[icon_url].get();
    770 
    771   // TODO(zea): implement in-memory eviction.
    772   DVLOG(1) << "Adding favicon info for " << icon_url.spec();
    773   SyncedFaviconInfo* favicon_info = new SyncedFaviconInfo(icon_url);
    774   synced_favicons_[icon_url] = make_linked_ptr(favicon_info);
    775   recent_favicons_.insert(synced_favicons_[icon_url]);
    776   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
    777   return favicon_info;
    778 }
    779 
    780 void FaviconCache::UpdateFaviconVisitTime(const GURL& icon_url,
    781                                           base::Time time) {
    782   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
    783   FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
    784   DCHECK(iter != synced_favicons_.end());
    785   if (iter->second->last_visit_time >= time)
    786     return;
    787   // Erase, update the time, then re-insert to maintain ordering.
    788   recent_favicons_.erase(iter->second);
    789   DVLOG(1) << "Updating " << icon_url.spec() << " visit time to "
    790            << syncer::GetTimeDebugString(time);
    791   iter->second->last_visit_time = time;
    792   recent_favicons_.insert(iter->second);
    793 
    794   if (VLOG_IS_ON(2)) {
    795     for (RecencySet::const_iterator iter = recent_favicons_.begin();
    796          iter != recent_favicons_.end(); ++iter) {
    797       DVLOG(2) << "Favicon " << iter->get()->favicon_url.spec() << ": "
    798                << syncer::GetTimeDebugString(iter->get()->last_visit_time);
    799     }
    800   }
    801   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
    802 }
    803 
    804 void FaviconCache::ExpireFaviconsIfNecessary(
    805     syncer::SyncChangeList* image_changes,
    806     syncer::SyncChangeList* tracking_changes) {
    807   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
    808   // TODO(zea): once we have in-memory eviction, we'll need to track sync
    809   // favicon count separately from the synced_favicons_/recent_favicons_.
    810 
    811   // Iterate until we've removed the necessary amount. |recent_favicons_| is
    812   // already in recency order, so just start from the beginning.
    813   // TODO(zea): to reduce thrashing, consider removing more than the minimum.
    814   while (recent_favicons_.size() > max_sync_favicon_limit_) {
    815     linked_ptr<SyncedFaviconInfo> candidate = *recent_favicons_.begin();
    816     DVLOG(1) << "Expiring favicon " << candidate->favicon_url.spec();
    817     DeleteSyncedFavicon(synced_favicons_.find(candidate->favicon_url),
    818                         image_changes,
    819                         tracking_changes);
    820   }
    821   DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size());
    822 }
    823 
    824 GURL FaviconCache::GetLocalFaviconFromSyncedData(
    825     const syncer::SyncData& sync_favicon) const {
    826   syncer::ModelType type = sync_favicon.GetDataType();
    827   DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
    828   GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
    829   return (synced_favicons_.count(favicon_url) > 0 ? favicon_url : GURL());
    830 }
    831 
    832 void FaviconCache::MergeSyncFavicon(const syncer::SyncData& sync_favicon,
    833                                     syncer::SyncChangeList* sync_changes) {
    834   syncer::ModelType type = sync_favicon.GetDataType();
    835   DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
    836   sync_pb::EntitySpecifics new_specifics;
    837   GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics());
    838   FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
    839   DCHECK(iter != synced_favicons_.end());
    840   SyncedFaviconInfo* favicon_info = iter->second.get();
    841   if (type == syncer::FAVICON_IMAGES) {
    842     sync_pb::FaviconImageSpecifics image_specifics =
    843         sync_favicon.GetSpecifics().favicon_image();
    844 
    845     // Remote image data always clobbers local image data.
    846     bool needs_update = false;
    847     if (image_specifics.has_favicon_web()) {
    848       favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
    849           image_specifics.favicon_web());
    850     } else if (favicon_info->bitmap_data[SIZE_16].bitmap_data.get()) {
    851       needs_update = true;
    852     }
    853     if (image_specifics.has_favicon_web_32()) {
    854       favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
    855           image_specifics.favicon_web_32());
    856     } else if (favicon_info->bitmap_data[SIZE_32].bitmap_data.get()) {
    857       needs_update = true;
    858     }
    859     if (image_specifics.has_favicon_touch_64()) {
    860       favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
    861           image_specifics.favicon_touch_64());
    862     } else if (favicon_info->bitmap_data[SIZE_64].bitmap_data.get()) {
    863       needs_update = true;
    864     }
    865 
    866     if (needs_update)
    867       BuildImageSpecifics(favicon_info, new_specifics.mutable_favicon_image());
    868   } else {
    869     sync_pb::FaviconTrackingSpecifics tracking_specifics =
    870         sync_favicon.GetSpecifics().favicon_tracking();
    871 
    872     // Tracking data is merged, such that bookmark data is the logical OR
    873     // of the two, and last visit time is the most recent.
    874 
    875     base::Time last_visit =  syncer::ProtoTimeToTime(
    876         tracking_specifics.last_visit_time_ms());
    877     // Due to crbug.com/258196, there are tracking nodes out there with
    878     // null visit times. If this is one of those, artificially make it a valid
    879     // visit time, so we know the node exists and update it properly on the next
    880     // real visit.
    881     if (last_visit.is_null())
    882       last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
    883     UpdateFaviconVisitTime(favicon_url, last_visit);
    884     favicon_info->is_bookmarked = (favicon_info->is_bookmarked ||
    885                                    tracking_specifics.is_bookmarked());
    886 
    887     if (syncer::TimeToProtoTime(favicon_info->last_visit_time) !=
    888             tracking_specifics.last_visit_time_ms() ||
    889         favicon_info->is_bookmarked != tracking_specifics.is_bookmarked()) {
    890       BuildTrackingSpecifics(favicon_info,
    891                              new_specifics.mutable_favicon_tracking());
    892     }
    893     DCHECK(!favicon_info->last_visit_time.is_null());
    894   }
    895 
    896   if (new_specifics.has_favicon_image() ||
    897       new_specifics.has_favicon_tracking()) {
    898     sync_changes->push_back(syncer::SyncChange(
    899         FROM_HERE,
    900         syncer::SyncChange::ACTION_UPDATE,
    901         syncer::SyncData::CreateLocalData(favicon_url.spec(),
    902                                           favicon_url.spec(),
    903                                           new_specifics)));
    904   }
    905 }
    906 
    907 void FaviconCache::AddLocalFaviconFromSyncedData(
    908     const syncer::SyncData& sync_favicon) {
    909   syncer::ModelType type = sync_favicon.GetDataType();
    910   DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
    911   if (type == syncer::FAVICON_IMAGES) {
    912     sync_pb::FaviconImageSpecifics image_specifics =
    913         sync_favicon.GetSpecifics().favicon_image();
    914     GURL favicon_url = GURL(image_specifics.favicon_url());
    915     DCHECK(favicon_url.is_valid());
    916     DCHECK(!synced_favicons_.count(favicon_url));
    917 
    918     SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
    919     if (!favicon_info)
    920       return;  // We reached the in-memory limit.
    921     if (image_specifics.has_favicon_web()) {
    922       favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics(
    923           image_specifics.favicon_web());
    924     }
    925     if (image_specifics.has_favicon_web_32()) {
    926       favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics(
    927           image_specifics.favicon_web_32());
    928     }
    929     if (image_specifics.has_favicon_touch_64()) {
    930       favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics(
    931           image_specifics.favicon_touch_64());
    932     }
    933   } else {
    934     sync_pb::FaviconTrackingSpecifics tracking_specifics =
    935         sync_favicon.GetSpecifics().favicon_tracking();
    936     GURL favicon_url = GURL(tracking_specifics.favicon_url());
    937     DCHECK(favicon_url.is_valid());
    938     DCHECK(!synced_favicons_.count(favicon_url));
    939 
    940     SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
    941     if (!favicon_info)
    942       return;  // We reached the in-memory limit.
    943     base::Time last_visit =  syncer::ProtoTimeToTime(
    944         tracking_specifics.last_visit_time_ms());
    945     // Due to crbug.com/258196, there are tracking nodes out there with
    946     // null visit times. If this is one of those, artificially make it a valid
    947     // visit time, so we know the node exists and update it properly on the next
    948     // real visit.
    949     if (last_visit.is_null())
    950       last_visit = last_visit + base::TimeDelta::FromMilliseconds(1);
    951     UpdateFaviconVisitTime(favicon_url, last_visit);
    952     favicon_info->is_bookmarked = tracking_specifics.is_bookmarked();
    953     DCHECK(!favicon_info->last_visit_time.is_null());
    954   }
    955 }
    956 
    957 syncer::SyncData FaviconCache::CreateSyncDataFromLocalFavicon(
    958     syncer::ModelType type,
    959     const GURL& favicon_url) const {
    960   DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING);
    961   DCHECK(favicon_url.is_valid());
    962   FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
    963   DCHECK(iter != synced_favicons_.end());
    964   SyncedFaviconInfo* favicon_info = iter->second.get();
    965 
    966   syncer::SyncData data;
    967   sync_pb::EntitySpecifics specifics;
    968   if (type == syncer::FAVICON_IMAGES) {
    969     sync_pb::FaviconImageSpecifics* image_specifics =
    970         specifics.mutable_favicon_image();
    971     BuildImageSpecifics(favicon_info, image_specifics);
    972   } else {
    973     sync_pb::FaviconTrackingSpecifics* tracking_specifics =
    974         specifics.mutable_favicon_tracking();
    975     BuildTrackingSpecifics(favicon_info, tracking_specifics);
    976   }
    977   data = syncer::SyncData::CreateLocalData(favicon_url.spec(),
    978                                            favicon_url.spec(),
    979                                            specifics);
    980   return data;
    981 }
    982 
    983 void FaviconCache::DeleteSyncedFavicons(const std::set<GURL>& favicon_urls) {
    984   syncer::SyncChangeList image_deletions, tracking_deletions;
    985   for (std::set<GURL>::const_iterator iter = favicon_urls.begin();
    986        iter != favicon_urls.end(); ++iter) {
    987     FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter);
    988     if (favicon_iter == synced_favicons_.end())
    989       continue;
    990     DeleteSyncedFavicon(favicon_iter,
    991                         &image_deletions,
    992                         &tracking_deletions);
    993   }
    994   DVLOG(1) << "Deleting " << image_deletions.size() << " synced favicons.";
    995   if (favicon_images_sync_processor_.get()) {
    996     favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
    997                                                        image_deletions);
    998   }
    999   if (favicon_tracking_sync_processor_.get()) {
   1000     favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE,
   1001                                                          tracking_deletions);
   1002   }
   1003 }
   1004 
   1005 void FaviconCache::DeleteSyncedFavicon(
   1006     FaviconMap::iterator favicon_iter,
   1007     syncer::SyncChangeList* image_changes,
   1008     syncer::SyncChangeList* tracking_changes) {
   1009   linked_ptr<SyncedFaviconInfo> favicon_info = favicon_iter->second;
   1010   if (FaviconInfoHasImages(*(favicon_iter->second))) {
   1011     image_changes->push_back(
   1012         syncer::SyncChange(FROM_HERE,
   1013                            syncer::SyncChange::ACTION_DELETE,
   1014                            syncer::SyncData::CreateLocalDelete(
   1015                                favicon_info->favicon_url.spec(),
   1016                                syncer::FAVICON_IMAGES)));
   1017   }
   1018   if (FaviconInfoHasTracking(*(favicon_iter->second))) {
   1019     tracking_changes->push_back(
   1020         syncer::SyncChange(FROM_HERE,
   1021                            syncer::SyncChange::ACTION_DELETE,
   1022                            syncer::SyncData::CreateLocalDelete(
   1023                                favicon_info->favicon_url.spec(),
   1024                                syncer::FAVICON_TRACKING)));
   1025   }
   1026   DropSyncedFavicon(favicon_iter);
   1027 }
   1028 
   1029 void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter) {
   1030   recent_favicons_.erase(favicon_iter->second);
   1031   synced_favicons_.erase(favicon_iter);
   1032 }
   1033 
   1034 size_t FaviconCache::NumFaviconsForTest() const {
   1035   return synced_favicons_.size();
   1036 }
   1037 
   1038 size_t FaviconCache::NumTasksForTest() const {
   1039   return page_task_map_.size();
   1040 }
   1041 
   1042 }  // namespace browser_sync
   1043