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