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       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(page_url, SupportedFaviconTypes(),
    472                                           kMaxFaviconResolution),
    473       base::Bind(&FaviconCache::OnFaviconDataAvailable,
    474                  weak_ptr_factory_.GetWeakPtr(), page_url),
    475       &cancelable_task_tracker_);
    476   page_task_map_[page_url] = id;
    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
   1044