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