Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(298)

Side by Side Diff: chrome/browser/sync/glue/favicon_cache.cc

Issue 13666003: [Sync] Fix favicon updates to handle orphan nodes (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Self review Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/sync/glue/favicon_cache.h" 5 #include "chrome/browser/sync/glue/favicon_cache.h"
6 6
7 #include "base/message_loop.h" 7 #include "base/message_loop.h"
8 #include "base/metrics/histogram.h" 8 #include "base/metrics/histogram.h"
9 #include "chrome/browser/favicon/favicon_service.h" 9 #include "chrome/browser/favicon/favicon_service.h"
10 #include "chrome/browser/favicon/favicon_service_factory.h" 10 #include "chrome/browser/favicon/favicon_service_factory.h"
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 // Note: Do not modify this directly! It should only be modified via 45 // Note: Do not modify this directly! It should only be modified via
46 // UpdateFaviconVisitTime(..). 46 // UpdateFaviconVisitTime(..).
47 base::Time last_visit_time; 47 base::Time last_visit_time;
48 // Whether we've received a local update for this favicon since starting up. 48 // Whether we've received a local update for this favicon since starting up.
49 bool received_local_update; 49 bool received_local_update;
50 50
51 private: 51 private:
52 DISALLOW_COPY_AND_ASSIGN(SyncedFaviconInfo); 52 DISALLOW_COPY_AND_ASSIGN(SyncedFaviconInfo);
53 }; 53 };
54 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
55 namespace { 70 namespace {
56 71
57 // Maximum number of favicons to keep in memory (0 means no limit). 72 // Maximum number of favicons to keep in memory (0 means no limit).
58 const size_t kMaxFaviconsInMem = 0; 73 const size_t kMaxFaviconsInMem = 0;
59 74
60 // Maximum width/height resolution supported. 75 // Maximum width/height resolution supported.
61 const int kMaxFaviconResolution = 16; 76 const int kMaxFaviconResolution = 16;
62 77
63 // Returns a mask of the supported favicon types. 78 // Returns a mask of the supported favicon types.
64 // TODO(zea): Supporting other favicons types will involve some work in the 79 // TODO(zea): Supporting other favicons types will involve some work in the
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after
180 favicon_info->bitmap_data[icon_size] = bitmap_result; 195 favicon_info->bitmap_data[icon_size] = bitmap_result;
181 favicon_info->received_local_update = true; 196 favicon_info->received_local_update = true;
182 return true; 197 return true;
183 } else { 198 } else {
184 // We only allow updating the image data once per restart. 199 // We only allow updating the image data once per restart.
185 DVLOG(2) << "Ignoring local update for " << bitmap_result.icon_url.spec(); 200 DVLOG(2) << "Ignoring local update for " << bitmap_result.icon_url.spec();
186 return false; 201 return false;
187 } 202 }
188 } 203 }
189 204
205 bool FaviconInfoHasImages(const SyncedFaviconInfo& favicon_info) {
206 return favicon_info.bitmap_data[SIZE_16].bitmap_data.get() ||
207 favicon_info.bitmap_data[SIZE_32].bitmap_data.get() ||
208 favicon_info.bitmap_data[SIZE_64].bitmap_data.get();
209 }
210
211 bool FaviconInfoHasTracking(const SyncedFaviconInfo& favicon_info) {
212 return !favicon_info.last_visit_time.is_null();
213 }
214
190 } // namespace 215 } // namespace
191 216
192 FaviconCacheObserver::~FaviconCacheObserver() {} 217 FaviconCacheObserver::~FaviconCacheObserver() {}
193 218
194 FaviconCache::FaviconCache(Profile* profile, int max_sync_favicon_limit) 219 FaviconCache::FaviconCache(Profile* profile, int max_sync_favicon_limit)
195 : profile_(profile), 220 : profile_(profile),
196 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), 221 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
197 legacy_delegate_(NULL), 222 legacy_delegate_(NULL),
198 max_sync_favicon_limit_(max_sync_favicon_limit) { 223 max_sync_favicon_limit_(max_sync_favicon_limit) {
199 notification_registrar_.Add(this, 224 notification_registrar_.Add(this,
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
318 } 343 }
319 FaviconMap::iterator favicon_iter = synced_favicons_.find(favicon_url); 344 FaviconMap::iterator favicon_iter = synced_favicons_.find(favicon_url);
320 if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) { 345 if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
321 if (favicon_iter == synced_favicons_.end()) { 346 if (favicon_iter == synced_favicons_.end()) {
322 // Two clients might wind up deleting different parts of the same 347 // Two clients might wind up deleting different parts of the same
323 // favicon, so ignore this. 348 // favicon, so ignore this.
324 continue; 349 continue;
325 } else { 350 } else {
326 DVLOG(1) << "Deleting favicon at " << favicon_url.spec(); 351 DVLOG(1) << "Deleting favicon at " << favicon_url.spec();
327 DropSyncedFavicon(favicon_iter); 352 DropSyncedFavicon(favicon_iter);
328 // TODO(zea): it's possible that we'll receive a deletion for an image,
329 // but not a tracking data, or vice versa, resulting in an orphan
330 // favicon node in one of the types. We should track how often this
331 // happens, and if it becomes a problem delete each part individually
332 // from the local model.
333 } 353 }
334 } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE || 354 } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE ||
335 iter->change_type() == syncer::SyncChange::ACTION_ADD) { 355 iter->change_type() == syncer::SyncChange::ACTION_ADD) {
336 // Adds and updates are treated the same due to the lack of strong 356 // Adds and updates are treated the same due to the lack of strong
337 // consistency (it's possible we'll receive an update for a tracking info 357 // consistency (it's possible we'll receive an update for a tracking info
338 // before we've received the add for the image, and should handle both 358 // before we've received the add for the image, and should handle both
339 // gracefully). 359 // gracefully).
340 if (favicon_iter == synced_favicons_.end()) { 360 if (favicon_iter == synced_favicons_.end()) {
341 DVLOG(1) << "Adding favicon at " << favicon_url.spec(); 361 DVLOG(1) << "Adding favicon at " << favicon_url.spec();
342 AddLocalFaviconFromSyncedData(iter->sync_data()); 362 AddLocalFaviconFromSyncedData(iter->sync_data());
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
380 FaviconMap::const_iterator icon_iter = 400 FaviconMap::const_iterator icon_iter =
381 synced_favicons_.find(url_iter->second); 401 synced_favicons_.find(url_iter->second);
382 // TODO(zea): consider what to do when only a subset of supported 402 // TODO(zea): consider what to do when only a subset of supported
383 // resolutions are available. 403 // resolutions are available.
384 if (icon_iter != synced_favicons_.end() && 404 if (icon_iter != synced_favicons_.end() &&
385 icon_iter->second->bitmap_data[SIZE_16].bitmap_data.get()) { 405 icon_iter->second->bitmap_data[SIZE_16].bitmap_data.get()) {
386 DVLOG(2) << "Using cached favicon url for " << page_url.spec() 406 DVLOG(2) << "Using cached favicon url for " << page_url.spec()
387 << ": " << icon_iter->second->favicon_url.spec(); 407 << ": " << icon_iter->second->favicon_url.spec();
388 UpdateFaviconVisitTime(icon_iter->second->favicon_url, base::Time::Now()); 408 UpdateFaviconVisitTime(icon_iter->second->favicon_url, base::Time::Now());
389 UpdateSyncState(icon_iter->second->favicon_url, 409 UpdateSyncState(icon_iter->second->favicon_url,
390 SYNC_TRACKING, 410 syncer::SyncChange::ACTION_INVALID,
391 syncer::SyncChange::ACTION_UPDATE); 411 syncer::SyncChange::ACTION_UPDATE);
392 return; 412 return;
393 } 413 }
394 } 414 }
395 415
396 DVLOG(1) << "Triggering favicon load for url " << page_url.spec(); 416 DVLOG(1) << "Triggering favicon load for url " << page_url.spec();
397 417
398 if (!profile_) { 418 if (!profile_) {
399 page_task_map_[page_url] = 0; // For testing only. 419 page_task_map_[page_url] = 0; // For testing only.
400 return; 420 return;
(...skipping 23 matching lines...) Expand all
424 // all desired resolutions? 444 // all desired resolutions?
425 OnPageFaviconUpdated(page_url); 445 OnPageFaviconUpdated(page_url);
426 return; 446 return;
427 } 447 }
428 448
429 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at " 449 DVLOG(1) << "Associating " << page_url.spec() << " with favicon at "
430 << favicon_url.spec() << " and marking visited."; 450 << favicon_url.spec() << " and marking visited.";
431 page_favicon_map_[page_url] = favicon_url; 451 page_favicon_map_[page_url] = favicon_url;
432 UpdateFaviconVisitTime(favicon_url, base::Time::Now()); 452 UpdateFaviconVisitTime(favicon_url, base::Time::Now());
433 UpdateSyncState(favicon_url, 453 UpdateSyncState(favicon_url,
434 SYNC_TRACKING, 454 syncer::SyncChange::ACTION_INVALID,
435 syncer::SyncChange::ACTION_UPDATE); 455 (FaviconInfoHasTracking(
456 *synced_favicons_.find(favicon_url)->second) ?
457 syncer::SyncChange::ACTION_UPDATE :
458 syncer::SyncChange::ACTION_ADD));
436 } 459 }
437 460
438 bool FaviconCache::GetSyncedFaviconForFaviconURL( 461 bool FaviconCache::GetSyncedFaviconForFaviconURL(
439 const GURL& favicon_url, 462 const GURL& favicon_url,
440 scoped_refptr<base::RefCountedMemory>* favicon_png) const { 463 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
441 if (!favicon_url.is_valid()) 464 if (!favicon_url.is_valid())
442 return false; 465 return false;
443 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url); 466 FaviconMap::const_iterator iter = synced_favicons_.find(favicon_url);
444 467
445 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded", 468 UMA_HISTOGRAM_BOOLEAN("Sync.FaviconCacheLookupSucceeded",
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
510 533
511 SyncedFaviconInfo* favicon_info = GetFaviconInfo(icon_url); 534 SyncedFaviconInfo* favicon_info = GetFaviconInfo(icon_url);
512 if (!favicon_info) 535 if (!favicon_info)
513 return; // We reached the in-memory limit. 536 return; // We reached the in-memory limit.
514 base::RefCountedString* temp_string = new base::RefCountedString(); 537 base::RefCountedString* temp_string = new base::RefCountedString();
515 temp_string->data() = icon_bytes; 538 temp_string->data() = icon_bytes;
516 favicon_info->bitmap_data[SIZE_16].bitmap_data = temp_string; 539 favicon_info->bitmap_data[SIZE_16].bitmap_data = temp_string;
517 // We assume legacy favicons are 16x16. 540 // We assume legacy favicons are 16x16.
518 favicon_info->bitmap_data[SIZE_16].pixel_size.set_width(16); 541 favicon_info->bitmap_data[SIZE_16].pixel_size.set_width(16);
519 favicon_info->bitmap_data[SIZE_16].pixel_size.set_height(16); 542 favicon_info->bitmap_data[SIZE_16].pixel_size.set_height(16);
520 UpdateFaviconVisitTime(icon_url, syncer::ProtoTimeToTime(visit_time_ms)); 543 bool added_tracking = !FaviconInfoHasTracking(*favicon_info);
544 UpdateFaviconVisitTime(icon_url,
545 syncer::ProtoTimeToTime(visit_time_ms));
521 546
522 UpdateSyncState(icon_url, 547 UpdateSyncState(icon_url,
523 SYNC_BOTH, 548 syncer::SyncChange::ACTION_ADD,
524 syncer::SyncChange::ACTION_ADD); 549 (added_tracking ?
550 syncer::SyncChange::ACTION_ADD :
551 syncer::SyncChange::ACTION_UPDATE));
525 } 552 }
526 553
527 void FaviconCache::SetLegacyDelegate(FaviconCacheObserver* observer) { 554 void FaviconCache::SetLegacyDelegate(FaviconCacheObserver* observer) {
528 legacy_delegate_ = observer; 555 legacy_delegate_ = observer;
529 } 556 }
530 557
531 void FaviconCache::RemoveLegacyDelegate() { 558 void FaviconCache::RemoveLegacyDelegate() {
532 legacy_delegate_ = NULL; 559 legacy_delegate_ = NULL;
533 } 560 }
534 561
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
587 page_task_map_.erase(page_iter); 614 page_task_map_.erase(page_iter);
588 615
589 if (bitmap_results.size() == 0) { 616 if (bitmap_results.size() == 0) {
590 // Either the favicon isn't loaded yet or there is no valid favicon. 617 // Either the favicon isn't loaded yet or there is no valid favicon.
591 // We already cleared the task id, so just return. 618 // We already cleared the task id, so just return.
592 DVLOG(1) << "Favicon load failed for page " << page_url.spec(); 619 DVLOG(1) << "Favicon load failed for page " << page_url.spec();
593 return; 620 return;
594 } 621 }
595 622
596 base::Time now = base::Time::Now(); 623 base::Time now = base::Time::Now();
597 std::set<SyncedFaviconInfo*> favicon_updates; 624 std::map<GURL, LocalFaviconUpdateInfo> favicon_updates;
598 for (size_t i = 0; i < bitmap_results.size(); ++i) { 625 for (size_t i = 0; i < bitmap_results.size(); ++i) {
599 const history::FaviconBitmapResult& bitmap_result = bitmap_results[i]; 626 const history::FaviconBitmapResult& bitmap_result = bitmap_results[i];
600 GURL favicon_url = bitmap_result.icon_url; 627 GURL favicon_url = bitmap_result.icon_url;
601 if (!favicon_url.is_valid() || favicon_url.SchemeIs("data")) 628 if (!favicon_url.is_valid() || favicon_url.SchemeIs("data"))
602 continue; // Can happen if the page is still loading. 629 continue; // Can happen if the page is still loading.
603 630
604 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url); 631 SyncedFaviconInfo* favicon_info = GetFaviconInfo(favicon_url);
605 if (!favicon_info) 632 if (!favicon_info)
606 return; // We reached the in-memory limit. 633 return; // We reached the in-memory limit.
607 634
608 if (!UpdateFaviconFromBitmapResult(bitmap_result, favicon_info)) 635 favicon_updates[favicon_url].new_image |=
609 continue; // Invalid favicon or no change. 636 !FaviconInfoHasImages(*favicon_info);
610 637 favicon_updates[favicon_url].new_tracking |=
611 favicon_updates.insert(favicon_info); 638 !FaviconInfoHasTracking(*favicon_info);
639 favicon_updates[favicon_url].image_needs_rewrite |=
640 UpdateFaviconFromBitmapResult(bitmap_result, favicon_info);
641 favicon_updates[favicon_url].favicon_info = favicon_info;
612 } 642 }
613 643
614 for (std::set<SyncedFaviconInfo*>::iterator iter = favicon_updates.begin(); 644 for (std::map<GURL, LocalFaviconUpdateInfo>::const_iterator
615 iter != favicon_updates.end(); ++iter) { 645 iter = favicon_updates.begin(); iter != favicon_updates.end();
616 SyncedFaviconInfo* favicon_info = *iter; 646 ++iter) {
647 SyncedFaviconInfo* favicon_info = iter->second.favicon_info;
617 const GURL& favicon_url = favicon_info->favicon_url; 648 const GURL& favicon_url = favicon_info->favicon_url;
618 if (!favicon_info->last_visit_time.is_null()) { 649 if (!favicon_info->last_visit_time.is_null()) {
619 UMA_HISTOGRAM_COUNTS_10000( 650 UMA_HISTOGRAM_COUNTS_10000(
620 "Sync.FaviconVisitPeriod", 651 "Sync.FaviconVisitPeriod",
621 (now - favicon_info->last_visit_time).InHours()); 652 (now - favicon_info->last_visit_time).InHours());
622 } 653 }
623 favicon_info->received_local_update = true; 654 favicon_info->received_local_update = true;
624 bool added_favicon = favicon_info->last_visit_time.is_null();
625 UpdateFaviconVisitTime(favicon_url, now); 655 UpdateFaviconVisitTime(favicon_url, now);
626 UpdateSyncState(favicon_url, 656 UpdateSyncState(favicon_url,
rlarocque 2013/04/04 23:29:07 I dislike ternary operators, and wouldn't use them
Nicolas Zea 2013/04/04 23:42:29 Haha, ok, done.
627 SYNC_BOTH, 657 (iter->second.new_image ?
628 (added_favicon ? 658 syncer::SyncChange::ACTION_ADD :
659 (iter->second.image_needs_rewrite ?
660 syncer::SyncChange::ACTION_UPDATE :
661 syncer::SyncChange::ACTION_INVALID)),
662 (iter->second.new_tracking ?
629 syncer::SyncChange::ACTION_ADD : 663 syncer::SyncChange::ACTION_ADD :
630 syncer::SyncChange::ACTION_UPDATE)); 664 syncer::SyncChange::ACTION_UPDATE));
631 if (legacy_delegate_) 665 if (legacy_delegate_)
632 legacy_delegate_->OnFaviconUpdated(page_url, favicon_url); 666 legacy_delegate_->OnFaviconUpdated(page_url, favicon_url);
633 667
634 // TODO(zea): support multiple favicon urls per page. 668 // TODO(zea): support multiple favicon urls per page.
635 page_favicon_map_[page_url] = favicon_url; 669 page_favicon_map_[page_url] = favicon_url;
636 } 670 }
637 } 671 }
638 672
639 void FaviconCache::UpdateSyncState( 673 void FaviconCache::UpdateSyncState(
640 const GURL& icon_url, 674 const GURL& icon_url,
641 SyncState state_to_update, 675 syncer::SyncChange::SyncChangeType image_change_type,
642 syncer::SyncChange::SyncChangeType change_type) { 676 syncer::SyncChange::SyncChangeType tracking_change_type) {
643 DCHECK(icon_url.is_valid()); 677 DCHECK(icon_url.is_valid());
644 // It's possible that we'll receive a favicon update before both types 678 // It's possible that we'll receive a favicon update before both types
645 // have finished setting up. In that case ignore the update. 679 // have finished setting up. In that case ignore the update.
646 // TODO(zea): consider tracking these skipped updates somehow? 680 // TODO(zea): consider tracking these skipped updates somehow?
647 if (!favicon_images_sync_processor_.get() || 681 if (!favicon_images_sync_processor_.get() ||
648 !favicon_tracking_sync_processor_.get()) 682 !favicon_tracking_sync_processor_.get())
649 return; 683 return;
650 684
651 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url); 685 FaviconMap::const_iterator iter = synced_favicons_.find(icon_url);
652 DCHECK(iter != synced_favicons_.end()); 686 DCHECK(iter != synced_favicons_.end());
653 const SyncedFaviconInfo* favicon_info = iter->second.get(); 687 const SyncedFaviconInfo* favicon_info = iter->second.get();
654 688
655 syncer::SyncChangeList image_changes; 689 syncer::SyncChangeList image_changes;
656 syncer::SyncChangeList tracking_changes; 690 syncer::SyncChangeList tracking_changes;
657 if (state_to_update == SYNC_IMAGE || state_to_update == SYNC_BOTH) { 691 if (image_change_type != syncer::SyncChange::ACTION_INVALID) {
658 sync_pb::EntitySpecifics new_specifics; 692 sync_pb::EntitySpecifics new_specifics;
659 sync_pb::FaviconImageSpecifics* image_specifics = 693 sync_pb::FaviconImageSpecifics* image_specifics =
660 new_specifics.mutable_favicon_image(); 694 new_specifics.mutable_favicon_image();
661 BuildImageSpecifics(favicon_info, image_specifics); 695 BuildImageSpecifics(favicon_info, image_specifics);
662 696
663 image_changes.push_back( 697 image_changes.push_back(
664 syncer::SyncChange(FROM_HERE, 698 syncer::SyncChange(FROM_HERE,
665 change_type, 699 image_change_type,
666 syncer::SyncData::CreateLocalData( 700 syncer::SyncData::CreateLocalData(
667 icon_url.spec(), 701 icon_url.spec(),
668 icon_url.spec(), 702 icon_url.spec(),
669 new_specifics))); 703 new_specifics)));
670 } 704 }
671 if (state_to_update == SYNC_TRACKING || state_to_update == SYNC_BOTH) { 705 if (tracking_change_type != syncer::SyncChange::ACTION_INVALID) {
672 sync_pb::EntitySpecifics new_specifics; 706 sync_pb::EntitySpecifics new_specifics;
673 sync_pb::FaviconTrackingSpecifics* tracking_specifics = 707 sync_pb::FaviconTrackingSpecifics* tracking_specifics =
674 new_specifics.mutable_favicon_tracking(); 708 new_specifics.mutable_favicon_tracking();
675 BuildTrackingSpecifics(favicon_info, tracking_specifics); 709 BuildTrackingSpecifics(favicon_info, tracking_specifics);
676 710
677 tracking_changes.push_back( 711 tracking_changes.push_back(
678 syncer::SyncChange(FROM_HERE, 712 syncer::SyncChange(FROM_HERE,
679 change_type, 713 tracking_change_type,
680 syncer::SyncData::CreateLocalData( 714 syncer::SyncData::CreateLocalData(
681 icon_url.spec(), 715 icon_url.spec(),
682 icon_url.spec(), 716 icon_url.spec(),
683 new_specifics))); 717 new_specifics)));
684 } 718 }
685 ExpireFaviconsIfNecessary(&image_changes, &tracking_changes); 719 ExpireFaviconsIfNecessary(&image_changes, &tracking_changes);
686 if (!image_changes.empty()) { 720 if (!image_changes.empty()) {
687 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE, 721 favicon_images_sync_processor_->ProcessSyncChanges(FROM_HERE,
688 image_changes); 722 image_changes);
689 } 723 }
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after
950 984
951 size_t FaviconCache::NumFaviconsForTest() const { 985 size_t FaviconCache::NumFaviconsForTest() const {
952 return synced_favicons_.size(); 986 return synced_favicons_.size();
953 } 987 }
954 988
955 size_t FaviconCache::NumTasksForTest() const { 989 size_t FaviconCache::NumTasksForTest() const {
956 return page_task_map_.size(); 990 return page_task_map_.size();
957 } 991 }
958 992
959 } // namespace browser_sync 993 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698