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 favicon_base::FaviconRawBitmapResult 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() { return favicon_base::FAVICON; } 82 83 // Returns the appropriate IconSize to use for a given gfx::Size pixel 84 // dimensions. 85 IconSize GetIconSizeBinFromBitmapResult(const gfx::Size& pixel_size) { 86 int max_size = 87 (pixel_size.width() > pixel_size.height() ? 88 pixel_size.width() : pixel_size.height()); 89 // TODO(zea): re-enable 64p and 32p resolutions once we support them. 90 if (max_size > 64) 91 return SIZE_INVALID; 92 else if (max_size > 32) 93 return SIZE_INVALID; 94 else if (max_size > 16) 95 return SIZE_INVALID; 96 else 97 return SIZE_16; 98 } 99 100 // Helper for debug statements. 101 std::string IconSizeToString(IconSize icon_size) { 102 switch (icon_size) { 103 case SIZE_16: 104 return "16"; 105 case SIZE_32: 106 return "32"; 107 case SIZE_64: 108 return "64"; 109 default: 110 return "INVALID"; 111 } 112 } 113 114 // Extract the favicon url from either of the favicon types. 115 GURL GetFaviconURLFromSpecifics(const sync_pb::EntitySpecifics& specifics) { 116 if (specifics.has_favicon_tracking()) 117 return GURL(specifics.favicon_tracking().favicon_url()); 118 else 119 return GURL(specifics.favicon_image().favicon_url()); 120 } 121 122 // Convert protobuf image data into a FaviconRawBitmapResult. 123 favicon_base::FaviconRawBitmapResult GetImageDataFromSpecifics( 124 const sync_pb::FaviconData& favicon_data) { 125 base::RefCountedString* temp_string = 126 new base::RefCountedString(); 127 temp_string->data() = favicon_data.favicon(); 128 favicon_base::FaviconRawBitmapResult bitmap_result; 129 bitmap_result.bitmap_data = temp_string; 130 bitmap_result.pixel_size.set_height(favicon_data.height()); 131 bitmap_result.pixel_size.set_width(favicon_data.width()); 132 return bitmap_result; 133 } 134 135 // Convert a FaviconRawBitmapResult into protobuf image data. 136 void FillSpecificsWithImageData( 137 const favicon_base::FaviconRawBitmapResult& bitmap_result, 138 sync_pb::FaviconData* favicon_data) { 139 if (!bitmap_result.bitmap_data.get()) 140 return; 141 favicon_data->set_height(bitmap_result.pixel_size.height()); 142 favicon_data->set_width(bitmap_result.pixel_size.width()); 143 favicon_data->set_favicon(bitmap_result.bitmap_data->front(), 144 bitmap_result.bitmap_data->size()); 145 } 146 147 // Build a FaviconImageSpecifics from a SyncedFaviconInfo. 148 void BuildImageSpecifics( 149 const SyncedFaviconInfo* favicon_info, 150 sync_pb::FaviconImageSpecifics* image_specifics) { 151 image_specifics->set_favicon_url(favicon_info->favicon_url.spec()); 152 FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_16], 153 image_specifics->mutable_favicon_web()); 154 // TODO(zea): bring this back if we can handle the load. 155 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_32], 156 // image_specifics->mutable_favicon_web_32()); 157 // FillSpecificsWithImageData(favicon_info->bitmap_data[SIZE_64], 158 // image_specifics->mutable_favicon_touch_64()); 159 } 160 161 // Build a FaviconTrackingSpecifics from a SyncedFaviconInfo. 162 void BuildTrackingSpecifics( 163 const SyncedFaviconInfo* favicon_info, 164 sync_pb::FaviconTrackingSpecifics* tracking_specifics) { 165 tracking_specifics->set_favicon_url(favicon_info->favicon_url.spec()); 166 tracking_specifics->set_last_visit_time_ms( 167 syncer::TimeToProtoTime(favicon_info->last_visit_time)); 168 tracking_specifics->set_is_bookmarked(favicon_info->is_bookmarked); 169 } 170 171 // Updates |favicon_info| with the image data in |bitmap_result|. 172 bool UpdateFaviconFromBitmapResult( 173 const favicon_base::FaviconRawBitmapResult& bitmap_result, 174 SyncedFaviconInfo* favicon_info) { 175 DCHECK_EQ(favicon_info->favicon_url, bitmap_result.icon_url); 176 if (!bitmap_result.is_valid()) { 177 DVLOG(1) << "Received invalid favicon at " << bitmap_result.icon_url.spec(); 178 return false; 179 } 180 181 IconSize icon_size = GetIconSizeBinFromBitmapResult( 182 bitmap_result.pixel_size); 183 if (icon_size == SIZE_INVALID) { 184 DVLOG(1) << "Ignoring unsupported resolution " 185 << bitmap_result.pixel_size.height() << "x" 186 << bitmap_result.pixel_size.width(); 187 return false; 188 } else if (!favicon_info->bitmap_data[icon_size].bitmap_data.get() || 189 !favicon_info->received_local_update) { 190 DVLOG(1) << "Storing " << IconSizeToString(icon_size) << "p" 191 << " favicon for " << favicon_info->favicon_url.spec() 192 << " with size " << bitmap_result.bitmap_data->size() 193 << " bytes."; 194 favicon_info->bitmap_data[icon_size] = bitmap_result; 195 favicon_info->received_local_update = true; 196 return true; 197 } else { 198 // We only allow updating the image data once per restart. 199 DVLOG(2) << "Ignoring local update for " << bitmap_result.icon_url.spec(); 200 return false; 201 } 202 } 203 204 bool FaviconInfoHasImages(const SyncedFaviconInfo& favicon_info) { 205 return favicon_info.bitmap_data[SIZE_16].bitmap_data.get() || 206 favicon_info.bitmap_data[SIZE_32].bitmap_data.get() || 207 favicon_info.bitmap_data[SIZE_64].bitmap_data.get(); 208 } 209 210 bool FaviconInfoHasTracking(const SyncedFaviconInfo& favicon_info) { 211 return !favicon_info.last_visit_time.is_null(); 212 } 213 214 bool FaviconInfoHasValidTypeData(const SyncedFaviconInfo& favicon_info, 215 syncer::ModelType type) { 216 if (type == syncer::FAVICON_IMAGES) 217 return FaviconInfoHasImages(favicon_info); 218 else if (type == syncer::FAVICON_TRACKING) 219 return FaviconInfoHasTracking(favicon_info); 220 NOTREACHED(); 221 return false; 222 } 223 224 } // namespace 225 226 FaviconCache::FaviconCache(Profile* profile, int max_sync_favicon_limit) 227 : profile_(profile), 228 max_sync_favicon_limit_(max_sync_favicon_limit), 229 weak_ptr_factory_(this) { 230 notification_registrar_.Add(this, 231 chrome::NOTIFICATION_HISTORY_URLS_DELETED, 232 content::Source<Profile>(profile_)); 233 DVLOG(1) << "Setting favicon limit to " << max_sync_favicon_limit; 234 } 235 236 FaviconCache::~FaviconCache() {} 237 238 syncer::SyncMergeResult FaviconCache::MergeDataAndStartSyncing( 239 syncer::ModelType type, 240 const syncer::SyncDataList& initial_sync_data, 241 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 242 scoped_ptr<syncer::SyncErrorFactory> error_handler) { 243 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING); 244 if (type == syncer::FAVICON_IMAGES) 245 favicon_images_sync_processor_ = sync_processor.Pass(); 246 else 247 favicon_tracking_sync_processor_ = sync_processor.Pass(); 248 249 syncer::SyncMergeResult merge_result(type); 250 merge_result.set_num_items_before_association(synced_favicons_.size()); 251 std::set<GURL> unsynced_favicon_urls; 252 for (FaviconMap::const_iterator iter = synced_favicons_.begin(); 253 iter != synced_favicons_.end(); ++iter) { 254 if (FaviconInfoHasValidTypeData(*(iter->second), type)) 255 unsynced_favicon_urls.insert(iter->first); 256 } 257 258 syncer::SyncChangeList local_changes; 259 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin(); 260 iter != initial_sync_data.end(); ++iter) { 261 GURL remote_url = GetFaviconURLFromSpecifics(iter->GetSpecifics()); 262 GURL favicon_url = GetLocalFaviconFromSyncedData(*iter); 263 if (favicon_url.is_valid()) { 264 unsynced_favicon_urls.erase(favicon_url); 265 MergeSyncFavicon(*iter, &local_changes); 266 merge_result.set_num_items_modified( 267 merge_result.num_items_modified() + 1); 268 } else { 269 AddLocalFaviconFromSyncedData(*iter); 270 merge_result.set_num_items_added(merge_result.num_items_added() + 1); 271 } 272 } 273 274 // Rather than trigger a bunch of deletions when we set up sync, we drop 275 // local favicons. Those pages that are currently open are likely to result in 276 // loading new favicons/refreshing old favicons anyways, at which point 277 // they'll be re-added and the appropriate synced favicons will be evicted. 278 // TODO(zea): implement a smarter ordering of the which favicons to drop. 279 int available_favicons = max_sync_favicon_limit_ - initial_sync_data.size(); 280 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconsAvailableAtMerge", 281 available_favicons > 0); 282 for (std::set<GURL>::const_iterator iter = unsynced_favicon_urls.begin(); 283 iter != unsynced_favicon_urls.end(); ++iter) { 284 if (available_favicons > 0) { 285 local_changes.push_back( 286 syncer::SyncChange(FROM_HERE, 287 syncer::SyncChange::ACTION_ADD, 288 CreateSyncDataFromLocalFavicon(type, *iter))); 289 available_favicons--; 290 } else { 291 FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter); 292 DVLOG(1) << "Dropping local favicon " 293 << favicon_iter->second->favicon_url.spec(); 294 DropPartialFavicon(favicon_iter, type); 295 merge_result.set_num_items_deleted(merge_result.num_items_deleted() + 1); 296 } 297 } 298 UMA_HISTOGRAM_COUNTS_10000("Sync.FaviconCount", synced_favicons_.size()); 299 merge_result.set_num_items_after_association(synced_favicons_.size()); 300 301 if (type == syncer::FAVICON_IMAGES) { 302 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE, 303 local_changes); 304 } else { 305 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE, 306 local_changes); 307 } 308 return merge_result; 309 } 310 311 void FaviconCache::StopSyncing(syncer::ModelType type) { 312 favicon_images_sync_processor_.reset(); 313 favicon_tracking_sync_processor_.reset(); 314 cancelable_task_tracker_.TryCancelAll(); 315 page_task_map_.clear(); 316 } 317 318 syncer::SyncDataList FaviconCache::GetAllSyncData(syncer::ModelType type) 319 const { 320 syncer::SyncDataList data_list; 321 for (FaviconMap::const_iterator iter = synced_favicons_.begin(); 322 iter != synced_favicons_.end(); ++iter) { 323 if ((type == syncer::FAVICON_IMAGES && 324 FaviconInfoHasImages(*iter->second)) || 325 (type == syncer::FAVICON_TRACKING && 326 FaviconInfoHasTracking(*iter->second))) { 327 data_list.push_back(CreateSyncDataFromLocalFavicon(type, iter->first)); 328 } 329 } 330 return data_list; 331 } 332 333 syncer::SyncError FaviconCache::ProcessSyncChanges( 334 const tracked_objects::Location& from_here, 335 const syncer::SyncChangeList& change_list) { 336 if (!favicon_images_sync_processor_.get() || 337 !favicon_tracking_sync_processor_.get()) { 338 return syncer::SyncError(FROM_HERE, 339 syncer::SyncError::DATATYPE_ERROR, 340 "One or both favicon types disabled.", 341 change_list[0].sync_data().GetDataType()); 342 } 343 344 syncer::SyncChangeList new_changes; 345 syncer::SyncError error; 346 syncer::ModelType type = syncer::UNSPECIFIED; 347 for (syncer::SyncChangeList::const_iterator iter = change_list.begin(); 348 iter != change_list.end(); ++iter) { 349 type = iter->sync_data().GetDataType(); 350 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING); 351 GURL favicon_url = 352 GetFaviconURLFromSpecifics(iter->sync_data().GetSpecifics()); 353 if (!favicon_url.is_valid()) { 354 error.Reset(FROM_HERE, "Received invalid favicon url.", type); 355 break; 356 } 357 FaviconMap::iterator favicon_iter = synced_favicons_.find(favicon_url); 358 if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) { 359 if (favicon_iter == synced_favicons_.end()) { 360 // Two clients might wind up deleting different parts of the same 361 // favicon, so ignore this. 362 continue; 363 } else { 364 DVLOG(1) << "Deleting favicon at " << favicon_url.spec(); 365 // If we only have partial data for the favicon (which implies orphaned 366 // nodes), delete the local favicon only if the type corresponds to the 367 // partial data we have. If we do have orphaned nodes, we rely on the 368 // expiration logic to remove them eventually. 369 DropPartialFavicon(favicon_iter, type); 370 } 371 } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE || 372 iter->change_type() == syncer::SyncChange::ACTION_ADD) { 373 // Adds and updates are treated the same due to the lack of strong 374 // consistency (it's possible we'll receive an update for a tracking info 375 // before we've received the add for the image, and should handle both 376 // gracefully). 377 if (favicon_iter == synced_favicons_.end()) { 378 DVLOG(1) << "Adding favicon at " << favicon_url.spec(); 379 AddLocalFaviconFromSyncedData(iter->sync_data()); 380 } else { 381 DVLOG(1) << "Updating favicon at " << favicon_url.spec(); 382 MergeSyncFavicon(iter->sync_data(), &new_changes); 383 } 384 } else { 385 error.Reset(FROM_HERE, "Invalid action received.", type); 386 break; 387 } 388 } 389 390 // Note: we deliberately do not expire favicons here. If we received new 391 // favicons and are now over the limit, the next local favicon change will 392 // trigger the necessary expiration. 393 if (!error.IsSet() && !new_changes.empty()) { 394 if (type == syncer::FAVICON_IMAGES) { 395 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE, 396 new_changes); 397 } else { 398 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE, 399 new_changes); 400 } 401 } 402 403 return error; 404 } 405 406 void FaviconCache::OnPageFaviconUpdated(const GURL& page_url) { 407 DCHECK(page_url.is_valid()); 408 409 // If a favicon load is already happening for this url, let it finish. 410 if (page_task_map_.find(page_url) != page_task_map_.end()) 411 return; 412 413 PageFaviconMap::const_iterator url_iter = page_favicon_map_.find(page_url); 414 if (url_iter != page_favicon_map_.end()) { 415 FaviconMap::const_iterator icon_iter = 416 synced_favicons_.find(url_iter->second); 417 // TODO(zea): consider what to do when only a subset of supported 418 // resolutions are available. 419 if (icon_iter != synced_favicons_.end() && 420 icon_iter->second->bitmap_data[SIZE_16].bitmap_data.get()) { 421 DVLOG(2) << "Using cached favicon url for " << page_url.spec() 422 << ": " << icon_iter->second->favicon_url.spec(); 423 UpdateFaviconVisitTime(icon_iter->second->favicon_url, base::Time::Now()); 424 UpdateSyncState(icon_iter->second->favicon_url, 425 syncer::SyncChange::ACTION_INVALID, 426 syncer::SyncChange::ACTION_UPDATE); 427 return; 428 } 429 } 430 431 DVLOG(1) << "Triggering favicon load for url " << page_url.spec(); 432 433 if (!profile_) { 434 page_task_map_[page_url] = 0; // For testing only. 435 return; 436 } 437 FaviconService* favicon_service = 438 FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS); 439 if (!favicon_service) 440 return; 441 // TODO(zea): This appears to only fetch one favicon (best match based on 442 // desired_size_in_dip). Figure out a way to fetch all favicons we support. 443 // See crbug.com/181068. 444 base::CancelableTaskTracker::TaskId id = 445 favicon_service->GetFaviconForPageURL( 446 FaviconService::FaviconForPageURLParams( 447 page_url, SupportedFaviconTypes(), kMaxFaviconResolution), 448 base::Bind(&FaviconCache::OnFaviconDataAvailable, 449 weak_ptr_factory_.GetWeakPtr(), 450 page_url), 451 &cancelable_task_tracker_); 452 page_task_map_[page_url] = id; 453 } 454 455 void FaviconCache::OnFaviconVisited(const GURL& page_url, 456 const GURL& favicon_url) { 457 DCHECK(page_url.is_valid()); 458 if (!favicon_url.is_valid() || 459 synced_favicons_.find(favicon_url) == synced_favicons_.end()) { 460 // TODO(zea): consider triggering a favicon load if we have some but not 461 // all desired resolutions? 462 OnPageFaviconUpdated(page_url); 463 return; 464 } 465 466 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at " 467 << favicon_url.spec() << " and marking visited."; 468 page_favicon_map_[page_url] = favicon_url; 469 bool had_tracking = FaviconInfoHasTracking( 470 *synced_favicons_.find(favicon_url)->second); 471 UpdateFaviconVisitTime(favicon_url, base::Time::Now()); 472 473 UpdateSyncState(favicon_url, 474 syncer::SyncChange::ACTION_INVALID, 475 (had_tracking ? 476 syncer::SyncChange::ACTION_UPDATE : 477 syncer::SyncChange::ACTION_ADD)); 478 } 479 480 bool FaviconCache::GetSyncedFaviconForFaviconURL( 481 const GURL& favicon_url, 482 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 483 if (!favicon_url.is_valid()) 484 return false; 485 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url); 486 487 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded", 488 iter != synced_favicons_.end()); 489 if (iter == synced_favicons_.end()) 490 return false; 491 492 // TODO(zea): support getting other resolutions. 493 if (!iter->second->bitmap_data[SIZE_16].bitmap_data.get()) 494 return false; 495 496 *favicon_png = iter->second->bitmap_data[SIZE_16].bitmap_data; 497 return true; 498 } 499 500 bool FaviconCache::GetSyncedFaviconForPageURL( 501 const GURL& page_url, 502 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 503 if (!page_url.is_valid()) 504 return false; 505 PageFaviconMap::const_iterator iter = page_favicon_map_.find(page_url); 506 507 if (iter == page_favicon_map_.end()) 508 return false; 509 510 return GetSyncedFaviconForFaviconURL(iter->second, favicon_png); 511 } 512 513 void FaviconCache::OnReceivedSyncFavicon(const GURL& page_url, 514 const GURL& icon_url, 515 const std::string& icon_bytes, 516 int64 visit_time_ms) { 517 if (!icon_url.is_valid() || !page_url.is_valid() || icon_url.SchemeIs("data")) 518 return; 519 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at " 520 << icon_url.spec(); 521 page_favicon_map_[page_url] = icon_url; 522 523 // If there is no actual image, it means there either is no synced 524 // favicon, or it's on its way (race condition). 525 // TODO(zea): potentially trigger a favicon web download here (delayed?). 526 if (icon_bytes.size() == 0) 527 return; 528 529 // Post a task to do the actual association because this method may have been 530 // called while in a transaction. 531 base::MessageLoop::current()->PostTask( 532 FROM_HERE, 533 base::Bind(&FaviconCache::OnReceivedSyncFaviconImpl, 534 weak_ptr_factory_.GetWeakPtr(), 535 icon_url, 536 icon_bytes, 537 visit_time_ms)); 538 } 539 540 void FaviconCache::OnReceivedSyncFaviconImpl( 541 const GURL& icon_url, 542 const std::string& icon_bytes, 543 int64 visit_time_ms) { 544 // If this favicon is already synced, do nothing else. 545 if (synced_favicons_.find(icon_url) != synced_favicons_.end()) 546 return; 547 548 // Don't add any more favicons once we hit our in memory limit. 549 // TODO(zea): UMA this. 550 if (kMaxFaviconsInMem != 0 && synced_favicons_.size() > kMaxFaviconsInMem) 551 return; 552 553 SyncedFaviconInfo* favicon_info = GetFaviconInfo(icon_url); 554 if (!favicon_info) 555 return; // We reached the in-memory limit. 556 base::RefCountedString* temp_string = new base::RefCountedString(); 557 temp_string->data() = icon_bytes; 558 favicon_info->bitmap_data[SIZE_16].bitmap_data = temp_string; 559 // We assume legacy favicons are 16x16. 560 favicon_info->bitmap_data[SIZE_16].pixel_size.set_width(16); 561 favicon_info->bitmap_data[SIZE_16].pixel_size.set_height(16); 562 bool added_tracking = !FaviconInfoHasTracking(*favicon_info); 563 UpdateFaviconVisitTime(icon_url, 564 syncer::ProtoTimeToTime(visit_time_ms)); 565 566 UpdateSyncState(icon_url, 567 syncer::SyncChange::ACTION_ADD, 568 (added_tracking ? 569 syncer::SyncChange::ACTION_ADD : 570 syncer::SyncChange::ACTION_UPDATE)); 571 } 572 573 void FaviconCache::Observe(int type, 574 const content::NotificationSource& source, 575 const content::NotificationDetails& details) { 576 DCHECK_EQ(type, chrome::NOTIFICATION_HISTORY_URLS_DELETED); 577 578 content::Details<history::URLsDeletedDetails> deleted_details(details); 579 580 // We only care about actual user (or sync) deletions. 581 if (deleted_details->expired) 582 return; 583 584 if (!deleted_details->all_history) { 585 DeleteSyncedFavicons(deleted_details->favicon_urls); 586 return; 587 } 588 589 // All history was cleared: just delete all favicons. 590 DVLOG(1) << "History clear detected, deleting all synced favicons."; 591 syncer::SyncChangeList image_deletions, tracking_deletions; 592 while (!synced_favicons_.empty()) { 593 DeleteSyncedFavicon(synced_favicons_.begin(), 594 &image_deletions, 595 &tracking_deletions); 596 } 597 598 if (favicon_images_sync_processor_.get()) { 599 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE, 600 image_deletions); 601 } 602 if (favicon_tracking_sync_processor_.get()) { 603 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE, 604 tracking_deletions); 605 } 606 } 607 608 bool FaviconCache::FaviconRecencyFunctor::operator()( 609 const linked_ptr<SyncedFaviconInfo>& lhs, 610 const linked_ptr<SyncedFaviconInfo>& rhs) const { 611 // TODO(zea): incorporate bookmarked status here once we care about it. 612 if (lhs->last_visit_time < rhs->last_visit_time) 613 return true; 614 else if (lhs->last_visit_time == rhs->last_visit_time) 615 return lhs->favicon_url.spec() < rhs->favicon_url.spec(); 616 return false; 617 } 618 619 void FaviconCache::OnFaviconDataAvailable( 620 const GURL& page_url, 621 const std::vector<favicon_base::FaviconRawBitmapResult>& bitmap_results) { 622 PageTaskMap::iterator page_iter = page_task_map_.find(page_url); 623 if (page_iter == page_task_map_.end()) 624 return; 625 page_task_map_.erase(page_iter); 626 627 if (bitmap_results.size() == 0) { 628 // Either the favicon isn't loaded yet or there is no valid favicon. 629 // We already cleared the task id, so just return. 630 DVLOG(1) << "Favicon load failed for page " << page_url.spec(); 631 return; 632 } 633 634 base::Time now = base::Time::Now(); 635 std::map<GURL, LocalFaviconUpdateInfo> favicon_updates; 636 for (size_t i = 0; i < bitmap_results.size(); ++i) { 637 const favicon_base::FaviconRawBitmapResult& bitmap_result = 638 bitmap_results[i]; 639 GURL favicon_url = bitmap_result.icon_url; 640 if (!favicon_url.is_valid() || favicon_url.SchemeIs("data")) 641 continue; // Can happen if the page is still loading. 642 643 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url); 644 if (!favicon_info) 645 return; // We reached the in-memory limit. 646 647 favicon_updates[favicon_url].new_image |= 648 !FaviconInfoHasImages(*favicon_info); 649 favicon_updates[favicon_url].new_tracking |= 650 !FaviconInfoHasTracking(*favicon_info); 651 favicon_updates[favicon_url].image_needs_rewrite |= 652 UpdateFaviconFromBitmapResult(bitmap_result, favicon_info); 653 favicon_updates[favicon_url].favicon_info = favicon_info; 654 } 655 656 for (std::map<GURL, LocalFaviconUpdateInfo>::const_iterator 657 iter = favicon_updates.begin(); iter != favicon_updates.end(); 658 ++iter) { 659 SyncedFaviconInfo* favicon_info = iter->second.favicon_info; 660 const GURL& favicon_url = favicon_info->favicon_url; 661 662 // TODO(zea): support multiple favicon urls per page. 663 page_favicon_map_[page_url] = favicon_url; 664 665 if (!favicon_info->last_visit_time.is_null()) { 666 UMA_HISTOGRAM_COUNTS_10000( 667 "Sync.FaviconVisitPeriod", 668 (now - favicon_info->last_visit_time).InHours()); 669 } 670 favicon_info->received_local_update = true; 671 UpdateFaviconVisitTime(favicon_url, now); 672 673 syncer::SyncChange::SyncChangeType image_change = 674 syncer::SyncChange::ACTION_INVALID; 675 if (iter->second.new_image) 676 image_change = syncer::SyncChange::ACTION_ADD; 677 else if (iter->second.image_needs_rewrite) 678 image_change = syncer::SyncChange::ACTION_UPDATE; 679 syncer::SyncChange::SyncChangeType tracking_change = 680 syncer::SyncChange::ACTION_UPDATE; 681 if (iter->second.new_tracking) 682 tracking_change = syncer::SyncChange::ACTION_ADD; 683 UpdateSyncState(favicon_url, image_change, tracking_change); 684 } 685 } 686 687 void FaviconCache::UpdateSyncState( 688 const GURL& icon_url, 689 syncer::SyncChange::SyncChangeType image_change_type, 690 syncer::SyncChange::SyncChangeType tracking_change_type) { 691 DCHECK(icon_url.is_valid()); 692 // It's possible that we'll receive a favicon update before both types 693 // have finished setting up. In that case ignore the update. 694 // TODO(zea): consider tracking these skipped updates somehow? 695 if (!favicon_images_sync_processor_.get() || 696 !favicon_tracking_sync_processor_.get()) { 697 return; 698 } 699 700 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url); 701 DCHECK(iter != synced_favicons_.end()); 702 const SyncedFaviconInfo* favicon_info = iter->second.get(); 703 704 syncer::SyncChangeList image_changes; 705 syncer::SyncChangeList tracking_changes; 706 if (image_change_type != syncer::SyncChange::ACTION_INVALID) { 707 sync_pb::EntitySpecifics new_specifics; 708 sync_pb::FaviconImageSpecifics* image_specifics = 709 new_specifics.mutable_favicon_image(); 710 BuildImageSpecifics(favicon_info, image_specifics); 711 712 image_changes.push_back( 713 syncer::SyncChange(FROM_HERE, 714 image_change_type, 715 syncer::SyncData::CreateLocalData( 716 icon_url.spec(), 717 icon_url.spec(), 718 new_specifics))); 719 } 720 if (tracking_change_type != syncer::SyncChange::ACTION_INVALID) { 721 sync_pb::EntitySpecifics new_specifics; 722 sync_pb::FaviconTrackingSpecifics* tracking_specifics = 723 new_specifics.mutable_favicon_tracking(); 724 BuildTrackingSpecifics(favicon_info, tracking_specifics); 725 726 tracking_changes.push_back( 727 syncer::SyncChange(FROM_HERE, 728 tracking_change_type, 729 syncer::SyncData::CreateLocalData( 730 icon_url.spec(), 731 icon_url.spec(), 732 new_specifics))); 733 } 734 ExpireFaviconsIfNecessary(&image_changes, &tracking_changes); 735 if (!image_changes.empty()) { 736 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE, 737 image_changes); 738 } 739 if (!tracking_changes.empty()) { 740 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE, 741 tracking_changes); 742 } 743 } 744 745 SyncedFaviconInfo* FaviconCache::GetFaviconInfo( 746 const GURL& icon_url) { 747 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size()); 748 if (synced_favicons_.count(icon_url) != 0) 749 return synced_favicons_[icon_url].get(); 750 751 // TODO(zea): implement in-memory eviction. 752 DVLOG(1) << "Adding favicon info for " << icon_url.spec(); 753 SyncedFaviconInfo* favicon_info = new SyncedFaviconInfo(icon_url); 754 synced_favicons_[icon_url] = make_linked_ptr(favicon_info); 755 recent_favicons_.insert(synced_favicons_[icon_url]); 756 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size()); 757 return favicon_info; 758 } 759 760 void FaviconCache::UpdateFaviconVisitTime(const GURL& icon_url, 761 base::Time time) { 762 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size()); 763 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url); 764 DCHECK(iter != synced_favicons_.end()); 765 if (iter->second->last_visit_time >= time) 766 return; 767 // Erase, update the time, then re-insert to maintain ordering. 768 recent_favicons_.erase(iter->second); 769 DVLOG(1) << "Updating " << icon_url.spec() << " visit time to " 770 << syncer::GetTimeDebugString(time); 771 iter->second->last_visit_time = time; 772 recent_favicons_.insert(iter->second); 773 774 if (VLOG_IS_ON(2)) { 775 for (RecencySet::const_iterator iter = recent_favicons_.begin(); 776 iter != recent_favicons_.end(); ++iter) { 777 DVLOG(2) << "Favicon " << iter->get()->favicon_url.spec() << ": " 778 << syncer::GetTimeDebugString(iter->get()->last_visit_time); 779 } 780 } 781 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size()); 782 } 783 784 void FaviconCache::ExpireFaviconsIfNecessary( 785 syncer::SyncChangeList* image_changes, 786 syncer::SyncChangeList* tracking_changes) { 787 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size()); 788 // TODO(zea): once we have in-memory eviction, we'll need to track sync 789 // favicon count separately from the synced_favicons_/recent_favicons_. 790 791 // Iterate until we've removed the necessary amount. |recent_favicons_| is 792 // already in recency order, so just start from the beginning. 793 // TODO(zea): to reduce thrashing, consider removing more than the minimum. 794 while (recent_favicons_.size() > max_sync_favicon_limit_) { 795 linked_ptr<SyncedFaviconInfo> candidate = *recent_favicons_.begin(); 796 DVLOG(1) << "Expiring favicon " << candidate->favicon_url.spec(); 797 DeleteSyncedFavicon(synced_favicons_.find(candidate->favicon_url), 798 image_changes, 799 tracking_changes); 800 } 801 DCHECK_EQ(recent_favicons_.size(), synced_favicons_.size()); 802 } 803 804 GURL FaviconCache::GetLocalFaviconFromSyncedData( 805 const syncer::SyncData& sync_favicon) const { 806 syncer::ModelType type = sync_favicon.GetDataType(); 807 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING); 808 GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics()); 809 return (synced_favicons_.count(favicon_url) > 0 ? favicon_url : GURL()); 810 } 811 812 void FaviconCache::MergeSyncFavicon(const syncer::SyncData& sync_favicon, 813 syncer::SyncChangeList* sync_changes) { 814 syncer::ModelType type = sync_favicon.GetDataType(); 815 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING); 816 sync_pb::EntitySpecifics new_specifics; 817 GURL favicon_url = GetFaviconURLFromSpecifics(sync_favicon.GetSpecifics()); 818 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url); 819 DCHECK(iter != synced_favicons_.end()); 820 SyncedFaviconInfo* favicon_info = iter->second.get(); 821 if (type == syncer::FAVICON_IMAGES) { 822 sync_pb::FaviconImageSpecifics image_specifics = 823 sync_favicon.GetSpecifics().favicon_image(); 824 825 // Remote image data always clobbers local image data. 826 bool needs_update = false; 827 if (image_specifics.has_favicon_web()) { 828 favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics( 829 image_specifics.favicon_web()); 830 } else if (favicon_info->bitmap_data[SIZE_16].bitmap_data.get()) { 831 needs_update = true; 832 } 833 if (image_specifics.has_favicon_web_32()) { 834 favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics( 835 image_specifics.favicon_web_32()); 836 } else if (favicon_info->bitmap_data[SIZE_32].bitmap_data.get()) { 837 needs_update = true; 838 } 839 if (image_specifics.has_favicon_touch_64()) { 840 favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics( 841 image_specifics.favicon_touch_64()); 842 } else if (favicon_info->bitmap_data[SIZE_64].bitmap_data.get()) { 843 needs_update = true; 844 } 845 846 if (needs_update) 847 BuildImageSpecifics(favicon_info, new_specifics.mutable_favicon_image()); 848 } else { 849 sync_pb::FaviconTrackingSpecifics tracking_specifics = 850 sync_favicon.GetSpecifics().favicon_tracking(); 851 852 // Tracking data is merged, such that bookmark data is the logical OR 853 // of the two, and last visit time is the most recent. 854 855 base::Time last_visit = syncer::ProtoTimeToTime( 856 tracking_specifics.last_visit_time_ms()); 857 // Due to crbug.com/258196, there are tracking nodes out there with 858 // null visit times. If this is one of those, artificially make it a valid 859 // visit time, so we know the node exists and update it properly on the next 860 // real visit. 861 if (last_visit.is_null()) 862 last_visit = last_visit + base::TimeDelta::FromMilliseconds(1); 863 UpdateFaviconVisitTime(favicon_url, last_visit); 864 favicon_info->is_bookmarked = (favicon_info->is_bookmarked || 865 tracking_specifics.is_bookmarked()); 866 867 if (syncer::TimeToProtoTime(favicon_info->last_visit_time) != 868 tracking_specifics.last_visit_time_ms() || 869 favicon_info->is_bookmarked != tracking_specifics.is_bookmarked()) { 870 BuildTrackingSpecifics(favicon_info, 871 new_specifics.mutable_favicon_tracking()); 872 } 873 DCHECK(!favicon_info->last_visit_time.is_null()); 874 } 875 876 if (new_specifics.has_favicon_image() || 877 new_specifics.has_favicon_tracking()) { 878 sync_changes->push_back(syncer::SyncChange( 879 FROM_HERE, 880 syncer::SyncChange::ACTION_UPDATE, 881 syncer::SyncData::CreateLocalData(favicon_url.spec(), 882 favicon_url.spec(), 883 new_specifics))); 884 } 885 } 886 887 void FaviconCache::AddLocalFaviconFromSyncedData( 888 const syncer::SyncData& sync_favicon) { 889 syncer::ModelType type = sync_favicon.GetDataType(); 890 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING); 891 if (type == syncer::FAVICON_IMAGES) { 892 sync_pb::FaviconImageSpecifics image_specifics = 893 sync_favicon.GetSpecifics().favicon_image(); 894 GURL favicon_url = GURL(image_specifics.favicon_url()); 895 DCHECK(favicon_url.is_valid()); 896 DCHECK(!synced_favicons_.count(favicon_url)); 897 898 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url); 899 if (!favicon_info) 900 return; // We reached the in-memory limit. 901 if (image_specifics.has_favicon_web()) { 902 favicon_info->bitmap_data[SIZE_16] = GetImageDataFromSpecifics( 903 image_specifics.favicon_web()); 904 } 905 if (image_specifics.has_favicon_web_32()) { 906 favicon_info->bitmap_data[SIZE_32] = GetImageDataFromSpecifics( 907 image_specifics.favicon_web_32()); 908 } 909 if (image_specifics.has_favicon_touch_64()) { 910 favicon_info->bitmap_data[SIZE_64] = GetImageDataFromSpecifics( 911 image_specifics.favicon_touch_64()); 912 } 913 } else { 914 sync_pb::FaviconTrackingSpecifics tracking_specifics = 915 sync_favicon.GetSpecifics().favicon_tracking(); 916 GURL favicon_url = GURL(tracking_specifics.favicon_url()); 917 DCHECK(favicon_url.is_valid()); 918 DCHECK(!synced_favicons_.count(favicon_url)); 919 920 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url); 921 if (!favicon_info) 922 return; // We reached the in-memory limit. 923 base::Time last_visit = syncer::ProtoTimeToTime( 924 tracking_specifics.last_visit_time_ms()); 925 // Due to crbug.com/258196, there are tracking nodes out there with 926 // null visit times. If this is one of those, artificially make it a valid 927 // visit time, so we know the node exists and update it properly on the next 928 // real visit. 929 if (last_visit.is_null()) 930 last_visit = last_visit + base::TimeDelta::FromMilliseconds(1); 931 UpdateFaviconVisitTime(favicon_url, last_visit); 932 favicon_info->is_bookmarked = tracking_specifics.is_bookmarked(); 933 DCHECK(!favicon_info->last_visit_time.is_null()); 934 } 935 } 936 937 syncer::SyncData FaviconCache::CreateSyncDataFromLocalFavicon( 938 syncer::ModelType type, 939 const GURL& favicon_url) const { 940 DCHECK(type == syncer::FAVICON_IMAGES || type == syncer::FAVICON_TRACKING); 941 DCHECK(favicon_url.is_valid()); 942 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url); 943 DCHECK(iter != synced_favicons_.end()); 944 SyncedFaviconInfo* favicon_info = iter->second.get(); 945 946 syncer::SyncData data; 947 sync_pb::EntitySpecifics specifics; 948 if (type == syncer::FAVICON_IMAGES) { 949 sync_pb::FaviconImageSpecifics* image_specifics = 950 specifics.mutable_favicon_image(); 951 BuildImageSpecifics(favicon_info, image_specifics); 952 } else { 953 sync_pb::FaviconTrackingSpecifics* tracking_specifics = 954 specifics.mutable_favicon_tracking(); 955 BuildTrackingSpecifics(favicon_info, tracking_specifics); 956 } 957 data = syncer::SyncData::CreateLocalData(favicon_url.spec(), 958 favicon_url.spec(), 959 specifics); 960 return data; 961 } 962 963 void FaviconCache::DeleteSyncedFavicons(const std::set<GURL>& favicon_urls) { 964 syncer::SyncChangeList image_deletions, tracking_deletions; 965 for (std::set<GURL>::const_iterator iter = favicon_urls.begin(); 966 iter != favicon_urls.end(); ++iter) { 967 FaviconMap::iterator favicon_iter = synced_favicons_.find(*iter); 968 if (favicon_iter == synced_favicons_.end()) 969 continue; 970 DeleteSyncedFavicon(favicon_iter, 971 &image_deletions, 972 &tracking_deletions); 973 } 974 DVLOG(1) << "Deleting " << image_deletions.size() << " synced favicons."; 975 if (favicon_images_sync_processor_.get()) { 976 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE, 977 image_deletions); 978 } 979 if (favicon_tracking_sync_processor_.get()) { 980 favicon_tracking_sync_processor_->ProcessSyncChanges(FROM_HERE, 981 tracking_deletions); 982 } 983 } 984 985 void FaviconCache::DeleteSyncedFavicon( 986 FaviconMap::iterator favicon_iter, 987 syncer::SyncChangeList* image_changes, 988 syncer::SyncChangeList* tracking_changes) { 989 linked_ptr<SyncedFaviconInfo> favicon_info = favicon_iter->second; 990 if (FaviconInfoHasImages(*(favicon_iter->second))) { 991 DVLOG(1) << "Deleting image for " 992 << favicon_iter->second.get()->favicon_url; 993 image_changes->push_back( 994 syncer::SyncChange(FROM_HERE, 995 syncer::SyncChange::ACTION_DELETE, 996 syncer::SyncData::CreateLocalDelete( 997 favicon_info->favicon_url.spec(), 998 syncer::FAVICON_IMAGES))); 999 } 1000 if (FaviconInfoHasTracking(*(favicon_iter->second))) { 1001 DVLOG(1) << "Deleting tracking for " 1002 << favicon_iter->second.get()->favicon_url; 1003 tracking_changes->push_back( 1004 syncer::SyncChange(FROM_HERE, 1005 syncer::SyncChange::ACTION_DELETE, 1006 syncer::SyncData::CreateLocalDelete( 1007 favicon_info->favicon_url.spec(), 1008 syncer::FAVICON_TRACKING))); 1009 } 1010 DropSyncedFavicon(favicon_iter); 1011 } 1012 1013 void FaviconCache::DropSyncedFavicon(FaviconMap::iterator favicon_iter) { 1014 DVLOG(1) << "Dropping favicon " << favicon_iter->second.get()->favicon_url; 1015 recent_favicons_.erase(favicon_iter->second); 1016 synced_favicons_.erase(favicon_iter); 1017 } 1018 1019 void FaviconCache::DropPartialFavicon(FaviconMap::iterator favicon_iter, 1020 syncer::ModelType type) { 1021 // If the type being dropped has no valid data, do nothing. 1022 if ((type == syncer::FAVICON_TRACKING && 1023 !FaviconInfoHasTracking(*favicon_iter->second)) || 1024 (type == syncer::FAVICON_IMAGES && 1025 !FaviconInfoHasImages(*favicon_iter->second))) { 1026 return; 1027 } 1028 1029 // If the type being dropped is the only type with valid data, just delete 1030 // the favicon altogether. 1031 if ((type == syncer::FAVICON_TRACKING && 1032 !FaviconInfoHasImages(*favicon_iter->second)) || 1033 (type == syncer::FAVICON_IMAGES && 1034 !FaviconInfoHasTracking(*favicon_iter->second))) { 1035 DropSyncedFavicon(favicon_iter); 1036 return; 1037 } 1038 1039 if (type == syncer::FAVICON_IMAGES) { 1040 DVLOG(1) << "Dropping favicon image " 1041 << favicon_iter->second.get()->favicon_url; 1042 for (int i = 0; i < NUM_SIZES; ++i) { 1043 favicon_iter->second->bitmap_data[i] = 1044 favicon_base::FaviconRawBitmapResult(); 1045 } 1046 DCHECK(!FaviconInfoHasImages(*favicon_iter->second)); 1047 } else { 1048 DCHECK_EQ(type, syncer::FAVICON_TRACKING); 1049 DVLOG(1) << "Dropping favicon tracking " 1050 << favicon_iter->second.get()->favicon_url; 1051 recent_favicons_.erase(favicon_iter->second); 1052 favicon_iter->second->last_visit_time = base::Time(); 1053 favicon_iter->second->is_bookmarked = false; 1054 recent_favicons_.insert(favicon_iter->second); 1055 DCHECK(!FaviconInfoHasTracking(*favicon_iter->second)); 1056 } 1057 } 1058 1059 size_t FaviconCache::NumFaviconsForTest() const { 1060 return synced_favicons_.size(); 1061 } 1062 1063 size_t FaviconCache::NumTasksForTest() const { 1064 return page_task_map_.size(); 1065 } 1066 1067 } // namespace browser_sync 1068