1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sync/glue/favicon_cache.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/metrics/histogram.h"
9 #include "chrome/browser/favicon/favicon_service_factory.h"
10 #include "chrome/browser/history/history_service_factory.h"
11 #include "components/favicon/core/favicon_service.h"
12 #include "components/history/core/browser/history_service.h"
13 #include "components/history/core/browser/history_types.h"
14 #include "sync/api/time.h"
15 #include "sync/protocol/favicon_image_specifics.pb.h"
16 #include "sync/protocol/favicon_tracking_specifics.pb.h"
17 #include "sync/protocol/sync.pb.h"
18 #include "ui/gfx/favicon_size.h"
20 namespace browser_sync
{
22 // Synced favicon storage and tracking.
23 // Note: we don't use the favicon service for storing these because these
24 // favicons are not necessarily associated with any local navigation, and
25 // hence would not work with the current expiration logic. We have custom
26 // expiration logic based on visit time/bookmark status/etc.
27 // See crbug.com/122890.
28 struct SyncedFaviconInfo
{
29 explicit SyncedFaviconInfo(const GURL
& favicon_url
)
30 : favicon_url(favicon_url
),
32 received_local_update(false) {}
34 // The actual favicon data.
35 // TODO(zea): don't keep around the actual data for locally sourced
36 // favicons (UI can access those directly).
37 favicon_base::FaviconRawBitmapResult bitmap_data
[NUM_SIZES
];
38 // The URL this favicon was loaded from.
39 const GURL favicon_url
;
40 // Is the favicon for a bookmarked page?
42 // The last time a tab needed this favicon.
43 // Note: Do not modify this directly! It should only be modified via
44 // UpdateFaviconVisitTime(..).
45 base::Time last_visit_time
;
46 // Whether we've received a local update for this favicon since starting up.
47 bool received_local_update
;
50 DISALLOW_COPY_AND_ASSIGN(SyncedFaviconInfo
);
53 // Information for handling local favicon updates. Used in
54 // OnFaviconDataAvailable.
55 struct LocalFaviconUpdateInfo
{
56 LocalFaviconUpdateInfo()
59 image_needs_rewrite(false),
64 bool image_needs_rewrite
;
65 SyncedFaviconInfo
* favicon_info
;
70 // Maximum number of favicons to keep in memory (0 means no limit).
71 const size_t kMaxFaviconsInMem
= 0;
73 // Maximum width/height resolution supported.
74 const int kMaxFaviconResolution
= 16;
76 // Returns a mask of the supported favicon types.
77 // TODO(zea): Supporting other favicons types will involve some work in the
78 // favicon service and navigation controller. See crbug.com/181068.
79 int SupportedFaviconTypes() { return favicon_base::FAVICON
; }
81 // Returns the appropriate IconSize to use for a given gfx::Size pixel
83 IconSize
GetIconSizeBinFromBitmapResult(const gfx::Size
& pixel_size
) {
85 (pixel_size
.width() > pixel_size
.height() ?
86 pixel_size
.width() : pixel_size
.height());
87 // TODO(zea): re-enable 64p and 32p resolutions once we support them.
90 else if (max_size
> 32)
92 else if (max_size
> 16)
98 // Helper for debug statements.
99 std::string
IconSizeToString(IconSize icon_size
) {
112 // Extract the favicon url from either of the favicon types.
113 GURL
GetFaviconURLFromSpecifics(const sync_pb::EntitySpecifics
& specifics
) {
114 if (specifics
.has_favicon_tracking())
115 return GURL(specifics
.favicon_tracking().favicon_url());
117 return GURL(specifics
.favicon_image().favicon_url());
120 // Convert protobuf image data into a FaviconRawBitmapResult.
121 favicon_base::FaviconRawBitmapResult
GetImageDataFromSpecifics(
122 const sync_pb::FaviconData
& favicon_data
) {
123 base::RefCountedString
* temp_string
=
124 new base::RefCountedString();
125 temp_string
->data() = favicon_data
.favicon();
126 favicon_base::FaviconRawBitmapResult bitmap_result
;
127 bitmap_result
.bitmap_data
= temp_string
;
128 bitmap_result
.pixel_size
.set_height(favicon_data
.height());
129 bitmap_result
.pixel_size
.set_width(favicon_data
.width());
130 return bitmap_result
;
133 // Convert a FaviconRawBitmapResult into protobuf image data.
134 void FillSpecificsWithImageData(
135 const favicon_base::FaviconRawBitmapResult
& bitmap_result
,
136 sync_pb::FaviconData
* favicon_data
) {
137 if (!bitmap_result
.bitmap_data
.get())
139 favicon_data
->set_height(bitmap_result
.pixel_size
.height());
140 favicon_data
->set_width(bitmap_result
.pixel_size
.width());
141 favicon_data
->set_favicon(bitmap_result
.bitmap_data
->front(),
142 bitmap_result
.bitmap_data
->size());
145 // Build a FaviconImageSpecifics from a SyncedFaviconInfo.
146 void BuildImageSpecifics(
147 const SyncedFaviconInfo
* favicon_info
,
148 sync_pb::FaviconImageSpecifics
* image_specifics
) {
149 image_specifics
->set_favicon_url(favicon_info
->favicon_url
.spec());
150 FillSpecificsWithImageData(favicon_info
->bitmap_data
[SIZE_16
],
151 image_specifics
->mutable_favicon_web());
152 // TODO(zea): bring this back if we can handle the load.
153 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_32],
154 // image_specifics->mutable_favicon_web_32());
155 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_64],
156 // image_specifics->mutable_favicon_touch_64());
159 // Build a FaviconTrackingSpecifics from a SyncedFaviconInfo.
160 void BuildTrackingSpecifics(
161 const SyncedFaviconInfo
* favicon_info
,
162 sync_pb::FaviconTrackingSpecifics
* tracking_specifics
) {
163 tracking_specifics
->set_favicon_url(favicon_info
->favicon_url
.spec());
164 tracking_specifics
->set_last_visit_time_ms(
165 syncer::TimeToProtoTime(favicon_info
->last_visit_time
));
166 tracking_specifics
->set_is_bookmarked(favicon_info
->is_bookmarked
);
169 // Updates |favicon_info| with the image data in |bitmap_result|.
170 bool UpdateFaviconFromBitmapResult(
171 const favicon_base::FaviconRawBitmapResult
& bitmap_result
,
172 SyncedFaviconInfo
* favicon_info
) {
173 DCHECK_EQ(favicon_info
->favicon_url
, bitmap_result
.icon_url
);
174 if (!bitmap_result
.is_valid()) {
175 DVLOG(1) << "Received invalid favicon at " << bitmap_result
.icon_url
.spec();
179 IconSize icon_size
= GetIconSizeBinFromBitmapResult(
180 bitmap_result
.pixel_size
);
181 if (icon_size
== SIZE_INVALID
) {
182 DVLOG(1) << "Ignoring unsupported resolution "
183 << bitmap_result
.pixel_size
.height() << "x"
184 << bitmap_result
.pixel_size
.width();
186 } else if (!favicon_info
->bitmap_data
[icon_size
].bitmap_data
.get() ||
187 !favicon_info
->received_local_update
) {
188 DVLOG(1) << "Storing " << IconSizeToString(icon_size
) << "p"
189 << " favicon for " << favicon_info
->favicon_url
.spec()
190 << " with size " << bitmap_result
.bitmap_data
->size()
192 favicon_info
->bitmap_data
[icon_size
] = bitmap_result
;
193 favicon_info
->received_local_update
= true;
196 // We only allow updating the image data once per restart.
197 DVLOG(2) << "Ignoring local update for " << bitmap_result
.icon_url
.spec();
202 bool FaviconInfoHasImages(const SyncedFaviconInfo
& favicon_info
) {
203 return favicon_info
.bitmap_data
[SIZE_16
].bitmap_data
.get() ||
204 favicon_info
.bitmap_data
[SIZE_32
].bitmap_data
.get() ||
205 favicon_info
.bitmap_data
[SIZE_64
].bitmap_data
.get();
208 bool FaviconInfoHasTracking(const SyncedFaviconInfo
& favicon_info
) {
209 return !favicon_info
.last_visit_time
.is_null();
212 bool FaviconInfoHasValidTypeData(const SyncedFaviconInfo
& favicon_info
,
213 syncer::ModelType type
) {
214 if (type
== syncer::FAVICON_IMAGES
)
215 return FaviconInfoHasImages(favicon_info
);
216 else if (type
== syncer::FAVICON_TRACKING
)
217 return FaviconInfoHasTracking(favicon_info
);
224 FaviconCache::FaviconCache(Profile
* profile
, int max_sync_favicon_limit
)
226 max_sync_favicon_limit_(max_sync_favicon_limit
),
227 history_service_observer_(this),
228 weak_ptr_factory_(this) {
229 history::HistoryService
* hs
= NULL
;
231 hs
= HistoryServiceFactory::GetForProfile(
232 profile_
, ServiceAccessType::EXPLICIT_ACCESS
);
235 history_service_observer_
.Add(hs
);
236 DVLOG(1) << "Setting favicon limit to " << max_sync_favicon_limit
;
239 FaviconCache::~FaviconCache() {}
241 syncer::SyncMergeResult
FaviconCache::MergeDataAndStartSyncing(
242 syncer::ModelType type
,
243 const syncer::SyncDataList
& initial_sync_data
,
244 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
245 scoped_ptr
<syncer::SyncErrorFactory
> error_handler
) {
246 DCHECK(type
== syncer::FAVICON_IMAGES
|| type
== syncer::FAVICON_TRACKING
);
247 if (type
== syncer::FAVICON_IMAGES
)
248 favicon_images_sync_processor_
= sync_processor
.Pass();
250 favicon_tracking_sync_processor_
= sync_processor
.Pass();
252 syncer::SyncMergeResult
merge_result(type
);
253 merge_result
.set_num_items_before_association(synced_favicons_
.size());
254 std::set
<GURL
> unsynced_favicon_urls
;
255 for (FaviconMap::const_iterator iter
= synced_favicons_
.begin();
256 iter
!= synced_favicons_
.end(); ++iter
) {
257 if (FaviconInfoHasValidTypeData(*(iter
->second
), type
))
258 unsynced_favicon_urls
.insert(iter
->first
);
261 syncer::SyncChangeList local_changes
;
262 for (syncer::SyncDataList::const_iterator iter
= initial_sync_data
.begin();
263 iter
!= initial_sync_data
.end(); ++iter
) {
264 GURL remote_url
= GetFaviconURLFromSpecifics(iter
->GetSpecifics());
265 GURL favicon_url
= GetLocalFaviconFromSyncedData(*iter
);
266 if (favicon_url
.is_valid()) {
267 unsynced_favicon_urls
.erase(favicon_url
);
268 MergeSyncFavicon(*iter
, &local_changes
);
269 merge_result
.set_num_items_modified(
270 merge_result
.num_items_modified() + 1);
272 AddLocalFaviconFromSyncedData(*iter
);
273 merge_result
.set_num_items_added(merge_result
.num_items_added() + 1);
277 // Rather than trigger a bunch of deletions when we set up sync, we drop
278 // local favicons. Those pages that are currently open are likely to result in
279 // loading new favicons/refreshing old favicons anyways, at which point
280 // they'll be re-added and the appropriate synced favicons will be evicted.
281 // TODO(zea): implement a smarter ordering of the which favicons to drop.
282 int available_favicons
= max_sync_favicon_limit_
- initial_sync_data
.size();
283 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconsAvailableAtMerge",
284 available_favicons
> 0);
285 for (std::set
<GURL
>::const_iterator iter
= unsynced_favicon_urls
.begin();
286 iter
!= unsynced_favicon_urls
.end(); ++iter
) {
287 if (available_favicons
> 0) {
288 local_changes
.push_back(
289 syncer::SyncChange(FROM_HERE
,
290 syncer::SyncChange::ACTION_ADD
,
291 CreateSyncDataFromLocalFavicon(type
, *iter
)));
292 available_favicons
--;
294 FaviconMap::iterator favicon_iter
= synced_favicons_
.find(*iter
);
295 DVLOG(1) << "Dropping local favicon "
296 << favicon_iter
->second
->favicon_url
.spec();
297 DropPartialFavicon(favicon_iter
, type
);
298 merge_result
.set_num_items_deleted(merge_result
.num_items_deleted() + 1);
301 UMA_HISTOGRAM_COUNTS_10000("Sync.FaviconCount", synced_favicons_
.size());
302 merge_result
.set_num_items_after_association(synced_favicons_
.size());
304 if (type
== syncer::FAVICON_IMAGES
) {
305 favicon_images_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
308 favicon_tracking_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
314 void FaviconCache::StopSyncing(syncer::ModelType type
) {
315 favicon_images_sync_processor_
.reset();
316 favicon_tracking_sync_processor_
.reset();
317 cancelable_task_tracker_
.TryCancelAll();
318 page_task_map_
.clear();
321 syncer::SyncDataList
FaviconCache::GetAllSyncData(syncer::ModelType type
)
323 syncer::SyncDataList data_list
;
324 for (FaviconMap::const_iterator iter
= synced_favicons_
.begin();
325 iter
!= synced_favicons_
.end(); ++iter
) {
326 if ((type
== syncer::FAVICON_IMAGES
&&
327 FaviconInfoHasImages(*iter
->second
)) ||
328 (type
== syncer::FAVICON_TRACKING
&&
329 FaviconInfoHasTracking(*iter
->second
))) {
330 data_list
.push_back(CreateSyncDataFromLocalFavicon(type
, iter
->first
));
336 syncer::SyncError
FaviconCache::ProcessSyncChanges(
337 const tracked_objects::Location
& from_here
,
338 const syncer::SyncChangeList
& change_list
) {
339 if (!favicon_images_sync_processor_
.get() ||
340 !favicon_tracking_sync_processor_
.get()) {
341 return syncer::SyncError(FROM_HERE
,
342 syncer::SyncError::DATATYPE_ERROR
,
343 "One or both favicon types disabled.",
344 change_list
[0].sync_data().GetDataType());
347 syncer::SyncChangeList new_changes
;
348 syncer::SyncError error
;
349 syncer::ModelType type
= syncer::UNSPECIFIED
;
350 for (syncer::SyncChangeList::const_iterator iter
= change_list
.begin();
351 iter
!= change_list
.end(); ++iter
) {
352 type
= iter
->sync_data().GetDataType();
353 DCHECK(type
== syncer::FAVICON_IMAGES
|| type
== syncer::FAVICON_TRACKING
);
355 GetFaviconURLFromSpecifics(iter
->sync_data().GetSpecifics());
356 if (!favicon_url
.is_valid()) {
357 error
.Reset(FROM_HERE
, "Received invalid favicon url.", type
);
360 FaviconMap::iterator favicon_iter
= synced_favicons_
.find(favicon_url
);
361 if (iter
->change_type() == syncer::SyncChange::ACTION_DELETE
) {
362 if (favicon_iter
== synced_favicons_
.end()) {
363 // Two clients might wind up deleting different parts of the same
364 // favicon, so ignore this.
367 DVLOG(1) << "Deleting favicon at " << favicon_url
.spec();
368 // If we only have partial data for the favicon (which implies orphaned
369 // nodes), delete the local favicon only if the type corresponds to the
370 // partial data we have. If we do have orphaned nodes, we rely on the
371 // expiration logic to remove them eventually.
372 DropPartialFavicon(favicon_iter
, type
);
374 } else if (iter
->change_type() == syncer::SyncChange::ACTION_UPDATE
||
375 iter
->change_type() == syncer::SyncChange::ACTION_ADD
) {
376 // Adds and updates are treated the same due to the lack of strong
377 // consistency (it's possible we'll receive an update for a tracking info
378 // before we've received the add for the image, and should handle both
380 if (favicon_iter
== synced_favicons_
.end()) {
381 DVLOG(1) << "Adding favicon at " << favicon_url
.spec();
382 AddLocalFaviconFromSyncedData(iter
->sync_data());
384 DVLOG(1) << "Updating favicon at " << favicon_url
.spec();
385 MergeSyncFavicon(iter
->sync_data(), &new_changes
);
388 error
.Reset(FROM_HERE
, "Invalid action received.", type
);
393 // Note: we deliberately do not expire favicons here. If we received new
394 // favicons and are now over the limit, the next local favicon change will
395 // trigger the necessary expiration.
396 if (!error
.IsSet() && !new_changes
.empty()) {
397 if (type
== syncer::FAVICON_IMAGES
) {
398 favicon_images_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
401 favicon_tracking_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
409 void FaviconCache::OnPageFaviconUpdated(const GURL
& page_url
) {
410 DCHECK(page_url
.is_valid());
412 // If a favicon load is already happening for this url, let it finish.
413 if (page_task_map_
.find(page_url
) != page_task_map_
.end())
416 PageFaviconMap::const_iterator url_iter
= page_favicon_map_
.find(page_url
);
417 if (url_iter
!= page_favicon_map_
.end()) {
418 FaviconMap::const_iterator icon_iter
=
419 synced_favicons_
.find(url_iter
->second
);
420 // TODO(zea): consider what to do when only a subset of supported
421 // resolutions are available.
422 if (icon_iter
!= synced_favicons_
.end() &&
423 icon_iter
->second
->bitmap_data
[SIZE_16
].bitmap_data
.get()) {
424 DVLOG(2) << "Using cached favicon url for " << page_url
.spec()
425 << ": " << icon_iter
->second
->favicon_url
.spec();
426 UpdateFaviconVisitTime(icon_iter
->second
->favicon_url
, base::Time::Now());
427 UpdateSyncState(icon_iter
->second
->favicon_url
,
428 syncer::SyncChange::ACTION_INVALID
,
429 syncer::SyncChange::ACTION_UPDATE
);
434 DVLOG(1) << "Triggering favicon load for url " << page_url
.spec();
437 page_task_map_
[page_url
] = 0; // For testing only.
440 FaviconService
* favicon_service
= FaviconServiceFactory::GetForProfile(
441 profile_
, ServiceAccessType::EXPLICIT_ACCESS
);
442 if (!favicon_service
)
444 // TODO(zea): This appears to only fetch one favicon (best match based on
445 // desired_size_in_dip). Figure out a way to fetch all favicons we support.
446 // See crbug.com/181068.
447 base::CancelableTaskTracker::TaskId id
=
448 favicon_service
->GetFaviconForPageURL(
450 SupportedFaviconTypes(),
451 kMaxFaviconResolution
,
452 base::Bind(&FaviconCache::OnFaviconDataAvailable
,
453 weak_ptr_factory_
.GetWeakPtr(),
455 &cancelable_task_tracker_
);
456 page_task_map_
[page_url
] = id
;
459 void FaviconCache::OnFaviconVisited(const GURL
& page_url
,
460 const GURL
& favicon_url
) {
461 DCHECK(page_url
.is_valid());
462 if (!favicon_url
.is_valid() ||
463 synced_favicons_
.find(favicon_url
) == synced_favicons_
.end()) {
464 // TODO(zea): consider triggering a favicon load if we have some but not
465 // all desired resolutions?
466 OnPageFaviconUpdated(page_url
);
470 DVLOG(1) << "Associating " << page_url
.spec() << " with favicon at "
471 << favicon_url
.spec() << " and marking visited.";
472 page_favicon_map_
[page_url
] = favicon_url
;
473 bool had_tracking
= FaviconInfoHasTracking(
474 *synced_favicons_
.find(favicon_url
)->second
);
475 UpdateFaviconVisitTime(favicon_url
, base::Time::Now());
477 UpdateSyncState(favicon_url
,
478 syncer::SyncChange::ACTION_INVALID
,
480 syncer::SyncChange::ACTION_UPDATE
:
481 syncer::SyncChange::ACTION_ADD
));
484 bool FaviconCache::GetSyncedFaviconForFaviconURL(
485 const GURL
& favicon_url
,
486 scoped_refptr
<base::RefCountedMemory
>* favicon_png
) const {
487 if (!favicon_url
.is_valid())
489 FaviconMap::const_iterator iter
= synced_favicons_
.find(favicon_url
);
491 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded",
492 iter
!= synced_favicons_
.end());
493 if (iter
== synced_favicons_
.end())
496 // TODO(zea): support getting other resolutions.
497 if (!iter
->second
->bitmap_data
[SIZE_16
].bitmap_data
.get())
500 *favicon_png
= iter
->second
->bitmap_data
[SIZE_16
].bitmap_data
;
504 bool FaviconCache::GetSyncedFaviconForPageURL(
505 const GURL
& page_url
,
506 scoped_refptr
<base::RefCountedMemory
>* favicon_png
) const {
507 if (!page_url
.is_valid())
509 PageFaviconMap::const_iterator iter
= page_favicon_map_
.find(page_url
);
511 if (iter
== page_favicon_map_
.end())
514 return GetSyncedFaviconForFaviconURL(iter
->second
, favicon_png
);
517 void FaviconCache::OnReceivedSyncFavicon(const GURL
& page_url
,
518 const GURL
& icon_url
,
519 const std::string
& icon_bytes
,
520 int64 visit_time_ms
) {
521 if (!icon_url
.is_valid() || !page_url
.is_valid() || icon_url
.SchemeIs("data"))
523 DVLOG(1) << "Associating " << page_url
.spec() << " with favicon at "
525 page_favicon_map_
[page_url
] = icon_url
;
527 // If there is no actual image, it means there either is no synced
528 // favicon, or it's on its way (race condition).
529 // TODO(zea): potentially trigger a favicon web download here (delayed?).
530 if (icon_bytes
.size() == 0)
533 // Post a task to do the actual association because this method may have been
534 // called while in a transaction.
535 base::MessageLoop::current()->PostTask(
537 base::Bind(&FaviconCache::OnReceivedSyncFaviconImpl
,
538 weak_ptr_factory_
.GetWeakPtr(),
544 void FaviconCache::OnReceivedSyncFaviconImpl(
545 const GURL
& icon_url
,
546 const std::string
& icon_bytes
,
547 int64 visit_time_ms
) {
548 // If this favicon is already synced, do nothing else.
549 if (synced_favicons_
.find(icon_url
) != synced_favicons_
.end())
552 // Don't add any more favicons once we hit our in memory limit.
553 // TODO(zea): UMA this.
554 if (kMaxFaviconsInMem
!= 0 && synced_favicons_
.size() > kMaxFaviconsInMem
)
557 SyncedFaviconInfo
* favicon_info
= GetFaviconInfo(icon_url
);
559 return; // We reached the in-memory limit.
560 base::RefCountedString
* temp_string
= new base::RefCountedString();
561 temp_string
->data() = icon_bytes
;
562 favicon_info
->bitmap_data
[SIZE_16
].bitmap_data
= temp_string
;
563 // We assume legacy favicons are 16x16.
564 favicon_info
->bitmap_data
[SIZE_16
].pixel_size
.set_width(16);
565 favicon_info
->bitmap_data
[SIZE_16
].pixel_size
.set_height(16);
566 bool added_tracking
= !FaviconInfoHasTracking(*favicon_info
);
567 UpdateFaviconVisitTime(icon_url
,
568 syncer::ProtoTimeToTime(visit_time_ms
));
570 UpdateSyncState(icon_url
,
571 syncer::SyncChange::ACTION_ADD
,
573 syncer::SyncChange::ACTION_ADD
:
574 syncer::SyncChange::ACTION_UPDATE
));
577 bool FaviconCache::FaviconRecencyFunctor::operator()(
578 const linked_ptr
<SyncedFaviconInfo
>& lhs
,
579 const linked_ptr
<SyncedFaviconInfo
>& rhs
) const {
580 // TODO(zea): incorporate bookmarked status here once we care about it.
581 if (lhs
->last_visit_time
< rhs
->last_visit_time
)
583 else if (lhs
->last_visit_time
== rhs
->last_visit_time
)
584 return lhs
->favicon_url
.spec() < rhs
->favicon_url
.spec();
588 void FaviconCache::OnFaviconDataAvailable(
589 const GURL
& page_url
,
590 const std::vector
<favicon_base::FaviconRawBitmapResult
>& bitmap_results
) {
591 PageTaskMap::iterator page_iter
= page_task_map_
.find(page_url
);
592 if (page_iter
== page_task_map_
.end())
594 page_task_map_
.erase(page_iter
);
596 if (bitmap_results
.size() == 0) {
597 // Either the favicon isn't loaded yet or there is no valid favicon.
598 // We already cleared the task id, so just return.
599 DVLOG(1) << "Favicon load failed for page " << page_url
.spec();
603 base::Time now
= base::Time::Now();
604 std::map
<GURL
, LocalFaviconUpdateInfo
> favicon_updates
;
605 for (size_t i
= 0; i
< bitmap_results
.size(); ++i
) {
606 const favicon_base::FaviconRawBitmapResult
& bitmap_result
=
608 GURL favicon_url
= bitmap_result
.icon_url
;
609 if (!favicon_url
.is_valid() || favicon_url
.SchemeIs("data"))
610 continue; // Can happen if the page is still loading.
612 SyncedFaviconInfo
* favicon_info
= GetFaviconInfo(favicon_url
);
614 return; // We reached the in-memory limit.
616 favicon_updates
[favicon_url
].new_image
|=
617 !FaviconInfoHasImages(*favicon_info
);
618 favicon_updates
[favicon_url
].new_tracking
|=
619 !FaviconInfoHasTracking(*favicon_info
);
620 favicon_updates
[favicon_url
].image_needs_rewrite
|=
621 UpdateFaviconFromBitmapResult(bitmap_result
, favicon_info
);
622 favicon_updates
[favicon_url
].favicon_info
= favicon_info
;
625 for (std::map
<GURL
, LocalFaviconUpdateInfo
>::const_iterator
626 iter
= favicon_updates
.begin(); iter
!= favicon_updates
.end();
628 SyncedFaviconInfo
* favicon_info
= iter
->second
.favicon_info
;
629 const GURL
& favicon_url
= favicon_info
->favicon_url
;
631 // TODO(zea): support multiple favicon urls per page.
632 page_favicon_map_
[page_url
] = favicon_url
;
634 if (!favicon_info
->last_visit_time
.is_null()) {
635 UMA_HISTOGRAM_COUNTS_10000(
636 "Sync.FaviconVisitPeriod",
637 (now
- favicon_info
->last_visit_time
).InHours());
639 favicon_info
->received_local_update
= true;
640 UpdateFaviconVisitTime(favicon_url
, now
);
642 syncer::SyncChange::SyncChangeType image_change
=
643 syncer::SyncChange::ACTION_INVALID
;
644 if (iter
->second
.new_image
)
645 image_change
= syncer::SyncChange::ACTION_ADD
;
646 else if (iter
->second
.image_needs_rewrite
)
647 image_change
= syncer::SyncChange::ACTION_UPDATE
;
648 syncer::SyncChange::SyncChangeType tracking_change
=
649 syncer::SyncChange::ACTION_UPDATE
;
650 if (iter
->second
.new_tracking
)
651 tracking_change
= syncer::SyncChange::ACTION_ADD
;
652 UpdateSyncState(favicon_url
, image_change
, tracking_change
);
656 void FaviconCache::UpdateSyncState(
657 const GURL
& icon_url
,
658 syncer::SyncChange::SyncChangeType image_change_type
,
659 syncer::SyncChange::SyncChangeType tracking_change_type
) {
660 DCHECK(icon_url
.is_valid());
661 // It's possible that we'll receive a favicon update before both types
662 // have finished setting up. In that case ignore the update.
663 // TODO(zea): consider tracking these skipped updates somehow?
664 if (!favicon_images_sync_processor_
.get() ||
665 !favicon_tracking_sync_processor_
.get()) {
669 FaviconMap::const_iterator iter
= synced_favicons_
.find(icon_url
);
670 DCHECK(iter
!= synced_favicons_
.end());
671 const SyncedFaviconInfo
* favicon_info
= iter
->second
.get();
673 syncer::SyncChangeList image_changes
;
674 syncer::SyncChangeList tracking_changes
;
675 if (image_change_type
!= syncer::SyncChange::ACTION_INVALID
) {
676 sync_pb::EntitySpecifics new_specifics
;
677 sync_pb::FaviconImageSpecifics
* image_specifics
=
678 new_specifics
.mutable_favicon_image();
679 BuildImageSpecifics(favicon_info
, image_specifics
);
681 image_changes
.push_back(
682 syncer::SyncChange(FROM_HERE
,
684 syncer::SyncData::CreateLocalData(
689 if (tracking_change_type
!= syncer::SyncChange::ACTION_INVALID
) {
690 sync_pb::EntitySpecifics new_specifics
;
691 sync_pb::FaviconTrackingSpecifics
* tracking_specifics
=
692 new_specifics
.mutable_favicon_tracking();
693 BuildTrackingSpecifics(favicon_info
, tracking_specifics
);
695 tracking_changes
.push_back(
696 syncer::SyncChange(FROM_HERE
,
697 tracking_change_type
,
698 syncer::SyncData::CreateLocalData(
703 ExpireFaviconsIfNecessary(&image_changes
, &tracking_changes
);
704 if (!image_changes
.empty()) {
705 favicon_images_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
708 if (!tracking_changes
.empty()) {
709 favicon_tracking_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
714 SyncedFaviconInfo
* FaviconCache::GetFaviconInfo(
715 const GURL
& icon_url
) {
716 DCHECK_EQ(recent_favicons_
.size(), synced_favicons_
.size());
717 if (synced_favicons_
.count(icon_url
) != 0)
718 return synced_favicons_
[icon_url
].get();
720 // TODO(zea): implement in-memory eviction.
721 DVLOG(1) << "Adding favicon info for " << icon_url
.spec();
722 SyncedFaviconInfo
* favicon_info
= new SyncedFaviconInfo(icon_url
);
723 synced_favicons_
[icon_url
] = make_linked_ptr(favicon_info
);
724 recent_favicons_
.insert(synced_favicons_
[icon_url
]);
725 DCHECK_EQ(recent_favicons_
.size(), synced_favicons_
.size());
729 void FaviconCache::UpdateFaviconVisitTime(const GURL
& icon_url
,
731 DCHECK_EQ(recent_favicons_
.size(), synced_favicons_
.size());
732 FaviconMap::const_iterator iter
= synced_favicons_
.find(icon_url
);
733 DCHECK(iter
!= synced_favicons_
.end());
734 if (iter
->second
->last_visit_time
>= time
)
736 // Erase, update the time, then re-insert to maintain ordering.
737 recent_favicons_
.erase(iter
->second
);
738 DVLOG(1) << "Updating " << icon_url
.spec() << " visit time to "
739 << syncer::GetTimeDebugString(time
);
740 iter
->second
->last_visit_time
= time
;
741 recent_favicons_
.insert(iter
->second
);
744 for (RecencySet::const_iterator iter
= recent_favicons_
.begin();
745 iter
!= recent_favicons_
.end(); ++iter
) {
746 DVLOG(2) << "Favicon " << iter
->get()->favicon_url
.spec() << ": "
747 << syncer::GetTimeDebugString(iter
->get()->last_visit_time
);
750 DCHECK_EQ(recent_favicons_
.size(), synced_favicons_
.size());
753 void FaviconCache::ExpireFaviconsIfNecessary(
754 syncer::SyncChangeList
* image_changes
,
755 syncer::SyncChangeList
* tracking_changes
) {
756 DCHECK_EQ(recent_favicons_
.size(), synced_favicons_
.size());
757 // TODO(zea): once we have in-memory eviction, we'll need to track sync
758 // favicon count separately from the synced_favicons_/recent_favicons_.
760 // Iterate until we've removed the necessary amount. |recent_favicons_| is
761 // already in recency order, so just start from the beginning.
762 // TODO(zea): to reduce thrashing, consider removing more than the minimum.
763 while (recent_favicons_
.size() > max_sync_favicon_limit_
) {
764 linked_ptr
<SyncedFaviconInfo
> candidate
= *recent_favicons_
.begin();
765 DVLOG(1) << "Expiring favicon " << candidate
->favicon_url
.spec();
766 DeleteSyncedFavicon(synced_favicons_
.find(candidate
->favicon_url
),
770 DCHECK_EQ(recent_favicons_
.size(), synced_favicons_
.size());
773 GURL
FaviconCache::GetLocalFaviconFromSyncedData(
774 const syncer::SyncData
& sync_favicon
) const {
775 syncer::ModelType type
= sync_favicon
.GetDataType();
776 DCHECK(type
== syncer::FAVICON_IMAGES
|| type
== syncer::FAVICON_TRACKING
);
777 GURL favicon_url
= GetFaviconURLFromSpecifics(sync_favicon
.GetSpecifics());
778 return (synced_favicons_
.count(favicon_url
) > 0 ? favicon_url
: GURL());
781 void FaviconCache::MergeSyncFavicon(const syncer::SyncData
& sync_favicon
,
782 syncer::SyncChangeList
* sync_changes
) {
783 syncer::ModelType type
= sync_favicon
.GetDataType();
784 DCHECK(type
== syncer::FAVICON_IMAGES
|| type
== syncer::FAVICON_TRACKING
);
785 sync_pb::EntitySpecifics new_specifics
;
786 GURL favicon_url
= GetFaviconURLFromSpecifics(sync_favicon
.GetSpecifics());
787 FaviconMap::const_iterator iter
= synced_favicons_
.find(favicon_url
);
788 DCHECK(iter
!= synced_favicons_
.end());
789 SyncedFaviconInfo
* favicon_info
= iter
->second
.get();
790 if (type
== syncer::FAVICON_IMAGES
) {
791 sync_pb::FaviconImageSpecifics image_specifics
=
792 sync_favicon
.GetSpecifics().favicon_image();
794 // Remote image data always clobbers local image data.
795 bool needs_update
= false;
796 if (image_specifics
.has_favicon_web()) {
797 favicon_info
->bitmap_data
[SIZE_16
] = GetImageDataFromSpecifics(
798 image_specifics
.favicon_web());
799 } else if (favicon_info
->bitmap_data
[SIZE_16
].bitmap_data
.get()) {
802 if (image_specifics
.has_favicon_web_32()) {
803 favicon_info
->bitmap_data
[SIZE_32
] = GetImageDataFromSpecifics(
804 image_specifics
.favicon_web_32());
805 } else if (favicon_info
->bitmap_data
[SIZE_32
].bitmap_data
.get()) {
808 if (image_specifics
.has_favicon_touch_64()) {
809 favicon_info
->bitmap_data
[SIZE_64
] = GetImageDataFromSpecifics(
810 image_specifics
.favicon_touch_64());
811 } else if (favicon_info
->bitmap_data
[SIZE_64
].bitmap_data
.get()) {
816 BuildImageSpecifics(favicon_info
, new_specifics
.mutable_favicon_image());
818 sync_pb::FaviconTrackingSpecifics tracking_specifics
=
819 sync_favicon
.GetSpecifics().favicon_tracking();
821 // Tracking data is merged, such that bookmark data is the logical OR
822 // of the two, and last visit time is the most recent.
824 base::Time last_visit
= syncer::ProtoTimeToTime(
825 tracking_specifics
.last_visit_time_ms());
826 // Due to crbug.com/258196, there are tracking nodes out there with
827 // null visit times. If this is one of those, artificially make it a valid
828 // visit time, so we know the node exists and update it properly on the next
830 if (last_visit
.is_null())
831 last_visit
= last_visit
+ base::TimeDelta::FromMilliseconds(1);
832 UpdateFaviconVisitTime(favicon_url
, last_visit
);
833 favicon_info
->is_bookmarked
= (favicon_info
->is_bookmarked
||
834 tracking_specifics
.is_bookmarked());
836 if (syncer::TimeToProtoTime(favicon_info
->last_visit_time
) !=
837 tracking_specifics
.last_visit_time_ms() ||
838 favicon_info
->is_bookmarked
!= tracking_specifics
.is_bookmarked()) {
839 BuildTrackingSpecifics(favicon_info
,
840 new_specifics
.mutable_favicon_tracking());
842 DCHECK(!favicon_info
->last_visit_time
.is_null());
845 if (new_specifics
.has_favicon_image() ||
846 new_specifics
.has_favicon_tracking()) {
847 sync_changes
->push_back(syncer::SyncChange(
849 syncer::SyncChange::ACTION_UPDATE
,
850 syncer::SyncData::CreateLocalData(favicon_url
.spec(),
856 void FaviconCache::AddLocalFaviconFromSyncedData(
857 const syncer::SyncData
& sync_favicon
) {
858 syncer::ModelType type
= sync_favicon
.GetDataType();
859 DCHECK(type
== syncer::FAVICON_IMAGES
|| type
== syncer::FAVICON_TRACKING
);
860 if (type
== syncer::FAVICON_IMAGES
) {
861 sync_pb::FaviconImageSpecifics image_specifics
=
862 sync_favicon
.GetSpecifics().favicon_image();
863 GURL favicon_url
= GURL(image_specifics
.favicon_url());
864 DCHECK(favicon_url
.is_valid());
865 DCHECK(!synced_favicons_
.count(favicon_url
));
867 SyncedFaviconInfo
* favicon_info
= GetFaviconInfo(favicon_url
);
869 return; // We reached the in-memory limit.
870 if (image_specifics
.has_favicon_web()) {
871 favicon_info
->bitmap_data
[SIZE_16
] = GetImageDataFromSpecifics(
872 image_specifics
.favicon_web());
874 if (image_specifics
.has_favicon_web_32()) {
875 favicon_info
->bitmap_data
[SIZE_32
] = GetImageDataFromSpecifics(
876 image_specifics
.favicon_web_32());
878 if (image_specifics
.has_favicon_touch_64()) {
879 favicon_info
->bitmap_data
[SIZE_64
] = GetImageDataFromSpecifics(
880 image_specifics
.favicon_touch_64());
883 sync_pb::FaviconTrackingSpecifics tracking_specifics
=
884 sync_favicon
.GetSpecifics().favicon_tracking();
885 GURL favicon_url
= GURL(tracking_specifics
.favicon_url());
886 DCHECK(favicon_url
.is_valid());
887 DCHECK(!synced_favicons_
.count(favicon_url
));
889 SyncedFaviconInfo
* favicon_info
= GetFaviconInfo(favicon_url
);
891 return; // We reached the in-memory limit.
892 base::Time last_visit
= syncer::ProtoTimeToTime(
893 tracking_specifics
.last_visit_time_ms());
894 // Due to crbug.com/258196, there are tracking nodes out there with
895 // null visit times. If this is one of those, artificially make it a valid
896 // visit time, so we know the node exists and update it properly on the next
898 if (last_visit
.is_null())
899 last_visit
= last_visit
+ base::TimeDelta::FromMilliseconds(1);
900 UpdateFaviconVisitTime(favicon_url
, last_visit
);
901 favicon_info
->is_bookmarked
= tracking_specifics
.is_bookmarked();
902 DCHECK(!favicon_info
->last_visit_time
.is_null());
906 syncer::SyncData
FaviconCache::CreateSyncDataFromLocalFavicon(
907 syncer::ModelType type
,
908 const GURL
& favicon_url
) const {
909 DCHECK(type
== syncer::FAVICON_IMAGES
|| type
== syncer::FAVICON_TRACKING
);
910 DCHECK(favicon_url
.is_valid());
911 FaviconMap::const_iterator iter
= synced_favicons_
.find(favicon_url
);
912 DCHECK(iter
!= synced_favicons_
.end());
913 SyncedFaviconInfo
* favicon_info
= iter
->second
.get();
915 syncer::SyncData data
;
916 sync_pb::EntitySpecifics specifics
;
917 if (type
== syncer::FAVICON_IMAGES
) {
918 sync_pb::FaviconImageSpecifics
* image_specifics
=
919 specifics
.mutable_favicon_image();
920 BuildImageSpecifics(favicon_info
, image_specifics
);
922 sync_pb::FaviconTrackingSpecifics
* tracking_specifics
=
923 specifics
.mutable_favicon_tracking();
924 BuildTrackingSpecifics(favicon_info
, tracking_specifics
);
926 data
= syncer::SyncData::CreateLocalData(favicon_url
.spec(),
932 void FaviconCache::DeleteSyncedFavicons(const std::set
<GURL
>& favicon_urls
) {
933 syncer::SyncChangeList image_deletions
, tracking_deletions
;
934 for (std::set
<GURL
>::const_iterator iter
= favicon_urls
.begin();
935 iter
!= favicon_urls
.end(); ++iter
) {
936 FaviconMap::iterator favicon_iter
= synced_favicons_
.find(*iter
);
937 if (favicon_iter
== synced_favicons_
.end())
939 DeleteSyncedFavicon(favicon_iter
,
941 &tracking_deletions
);
943 DVLOG(1) << "Deleting " << image_deletions
.size() << " synced favicons.";
944 if (favicon_images_sync_processor_
.get()) {
945 favicon_images_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
948 if (favicon_tracking_sync_processor_
.get()) {
949 favicon_tracking_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
954 void FaviconCache::DeleteSyncedFavicon(
955 FaviconMap::iterator favicon_iter
,
956 syncer::SyncChangeList
* image_changes
,
957 syncer::SyncChangeList
* tracking_changes
) {
958 linked_ptr
<SyncedFaviconInfo
> favicon_info
= favicon_iter
->second
;
959 if (FaviconInfoHasImages(*(favicon_iter
->second
))) {
960 DVLOG(1) << "Deleting image for "
961 << favicon_iter
->second
.get()->favicon_url
;
962 image_changes
->push_back(
963 syncer::SyncChange(FROM_HERE
,
964 syncer::SyncChange::ACTION_DELETE
,
965 syncer::SyncData::CreateLocalDelete(
966 favicon_info
->favicon_url
.spec(),
967 syncer::FAVICON_IMAGES
)));
969 if (FaviconInfoHasTracking(*(favicon_iter
->second
))) {
970 DVLOG(1) << "Deleting tracking for "
971 << favicon_iter
->second
.get()->favicon_url
;
972 tracking_changes
->push_back(
973 syncer::SyncChange(FROM_HERE
,
974 syncer::SyncChange::ACTION_DELETE
,
975 syncer::SyncData::CreateLocalDelete(
976 favicon_info
->favicon_url
.spec(),
977 syncer::FAVICON_TRACKING
)));
979 DropSyncedFavicon(favicon_iter
);
982 void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter
) {
983 DVLOG(1) << "Dropping favicon " << favicon_iter
->second
.get()->favicon_url
;
984 recent_favicons_
.erase(favicon_iter
->second
);
985 synced_favicons_
.erase(favicon_iter
);
988 void FaviconCache::DropPartialFavicon(FaviconMap::iterator favicon_iter
,
989 syncer::ModelType type
) {
990 // If the type being dropped has no valid data, do nothing.
991 if ((type
== syncer::FAVICON_TRACKING
&&
992 !FaviconInfoHasTracking(*favicon_iter
->second
)) ||
993 (type
== syncer::FAVICON_IMAGES
&&
994 !FaviconInfoHasImages(*favicon_iter
->second
))) {
998 // If the type being dropped is the only type with valid data, just delete
999 // the favicon altogether.
1000 if ((type
== syncer::FAVICON_TRACKING
&&
1001 !FaviconInfoHasImages(*favicon_iter
->second
)) ||
1002 (type
== syncer::FAVICON_IMAGES
&&
1003 !FaviconInfoHasTracking(*favicon_iter
->second
))) {
1004 DropSyncedFavicon(favicon_iter
);
1008 if (type
== syncer::FAVICON_IMAGES
) {
1009 DVLOG(1) << "Dropping favicon image "
1010 << favicon_iter
->second
.get()->favicon_url
;
1011 for (int i
= 0; i
< NUM_SIZES
; ++i
) {
1012 favicon_iter
->second
->bitmap_data
[i
] =
1013 favicon_base::FaviconRawBitmapResult();
1015 DCHECK(!FaviconInfoHasImages(*favicon_iter
->second
));
1017 DCHECK_EQ(type
, syncer::FAVICON_TRACKING
);
1018 DVLOG(1) << "Dropping favicon tracking "
1019 << favicon_iter
->second
.get()->favicon_url
;
1020 recent_favicons_
.erase(favicon_iter
->second
);
1021 favicon_iter
->second
->last_visit_time
= base::Time();
1022 favicon_iter
->second
->is_bookmarked
= false;
1023 recent_favicons_
.insert(favicon_iter
->second
);
1024 DCHECK(!FaviconInfoHasTracking(*favicon_iter
->second
));
1028 size_t FaviconCache::NumFaviconsForTest() const {
1029 return synced_favicons_
.size();
1032 size_t FaviconCache::NumTasksForTest() const {
1033 return page_task_map_
.size();
1036 void FaviconCache::OnURLsDeleted(history::HistoryService
* history_service
,
1039 const history::URLRows
& deleted_rows
,
1040 const std::set
<GURL
>& favicon_urls
) {
1041 // We only care about actual user (or sync) deletions.
1046 DeleteSyncedFavicons(favicon_urls
);
1050 // All history was cleared: just delete all favicons.
1051 DVLOG(1) << "History clear detected, deleting all synced favicons.";
1052 syncer::SyncChangeList image_deletions
, tracking_deletions
;
1053 while (!synced_favicons_
.empty()) {
1054 DeleteSyncedFavicon(synced_favicons_
.begin(), &image_deletions
,
1055 &tracking_deletions
);
1058 if (favicon_images_sync_processor_
.get()) {
1059 favicon_images_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
1062 if (favicon_tracking_sync_processor_
.get()) {
1063 favicon_tracking_sync_processor_
->ProcessSyncChanges(FROM_HERE
,
1064 tracking_deletions
);
1068 } // namespace browser_sync