Home | History | Annotate | Download | only in glue
      1 // Copyright (c) 2012 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/typed_url_model_associator.h"
      6 
      7 #include <algorithm>
      8 #include <set>
      9 
     10 #include "base/location.h"
     11 #include "base/logging.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/browser/history/history_backend.h"
     15 #include "chrome/browser/sync/profile_sync_service.h"
     16 #include "net/base/net_util.h"
     17 #include "sync/api/sync_error.h"
     18 #include "sync/internal_api/public/read_node.h"
     19 #include "sync/internal_api/public/read_transaction.h"
     20 #include "sync/internal_api/public/write_node.h"
     21 #include "sync/internal_api/public/write_transaction.h"
     22 #include "sync/protocol/typed_url_specifics.pb.h"
     23 
     24 using content::BrowserThread;
     25 
     26 namespace browser_sync {
     27 
     28 // The server backend can't handle arbitrarily large node sizes, so to keep
     29 // the size under control we limit the visit array.
     30 static const int kMaxTypedUrlVisits = 100;
     31 
     32 // There's no limit on how many visits the history DB could have for a given
     33 // typed URL, so we limit how many we fetch from the DB to avoid crashes due to
     34 // running out of memory (http://crbug.com/89793). This value is different
     35 // from kMaxTypedUrlVisits, as some of the visits fetched from the DB may be
     36 // RELOAD visits, which will be stripped.
     37 static const int kMaxVisitsToFetch = 1000;
     38 
     39 const char kTypedUrlTag[] = "google_chrome_typed_urls";
     40 
     41 static bool CheckVisitOrdering(const history::VisitVector& visits) {
     42   int64 previous_visit_time = 0;
     43   for (history::VisitVector::const_iterator visit = visits.begin();
     44        visit != visits.end(); ++visit) {
     45     if (visit != visits.begin()) {
     46       // We allow duplicate visits here - they shouldn't really be allowed, but
     47       // they still seem to show up sometimes and we haven't figured out the
     48       // source, so we just log an error instead of failing an assertion.
     49       // (http://crbug.com/91473).
     50       if (previous_visit_time == visit->visit_time.ToInternalValue())
     51         DVLOG(1) << "Duplicate visit time encountered";
     52       else if (previous_visit_time > visit->visit_time.ToInternalValue())
     53         return false;
     54     }
     55 
     56     previous_visit_time = visit->visit_time.ToInternalValue();
     57   }
     58   return true;
     59 }
     60 
     61 TypedUrlModelAssociator::TypedUrlModelAssociator(
     62     ProfileSyncService* sync_service,
     63     history::HistoryBackend* history_backend,
     64     DataTypeErrorHandler* error_handler)
     65     : sync_service_(sync_service),
     66       history_backend_(history_backend),
     67       expected_loop_(base::MessageLoop::current()),
     68       abort_requested_(false),
     69       error_handler_(error_handler),
     70       num_db_accesses_(0),
     71       num_db_errors_(0) {
     72   DCHECK(sync_service_);
     73   // history_backend_ may be null for unit tests (since it's not mockable).
     74   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
     75 }
     76 
     77 TypedUrlModelAssociator::~TypedUrlModelAssociator() {}
     78 
     79 
     80 bool TypedUrlModelAssociator::FixupURLAndGetVisits(
     81     history::URLRow* url,
     82     history::VisitVector* visits) {
     83   ++num_db_accesses_;
     84   CHECK(history_backend_);
     85   if (!history_backend_->GetMostRecentVisitsForURL(
     86           url->id(), kMaxVisitsToFetch, visits)) {
     87     ++num_db_errors_;
     88     return false;
     89   }
     90 
     91   // Sometimes (due to a bug elsewhere in the history or sync code, or due to
     92   // a crash between adding a URL to the history database and updating the
     93   // visit DB) the visit vector for a URL can be empty. If this happens, just
     94   // create a new visit whose timestamp is the same as the last_visit time.
     95   // This is a workaround for http://crbug.com/84258.
     96   if (visits->empty()) {
     97     DVLOG(1) << "Found empty visits for URL: " << url->url();
     98     history::VisitRow visit(
     99         url->id(), url->last_visit(), 0, content::PAGE_TRANSITION_TYPED, 0);
    100     visits->push_back(visit);
    101   }
    102 
    103   // GetMostRecentVisitsForURL() returns the data in the opposite order that
    104   // we need it, so reverse it.
    105   std::reverse(visits->begin(), visits->end());
    106 
    107   // Sometimes, the last_visit field in the URL doesn't match the timestamp of
    108   // the last visit in our visit array (they come from different tables, so
    109   // crashes/bugs can cause them to mismatch), so just set it here.
    110   url->set_last_visit(visits->back().visit_time);
    111   DCHECK(CheckVisitOrdering(*visits));
    112   return true;
    113 }
    114 
    115 bool TypedUrlModelAssociator::ShouldIgnoreUrl(const GURL& url) {
    116   // Ignore empty URLs. Not sure how this can happen (maybe import from other
    117   // busted browsers, or misuse of the history API, or just plain bugs) but we
    118   // can't deal with them.
    119   if (url.spec().empty())
    120     return true;
    121 
    122   // Ignore local file URLs.
    123   if (url.SchemeIsFile())
    124     return true;
    125 
    126   // Ignore localhost URLs.
    127   if (net::IsLocalhost(url.host()))
    128     return true;
    129 
    130   return false;
    131 }
    132 
    133 bool TypedUrlModelAssociator::ShouldIgnoreVisits(
    134     const history::VisitVector& visits) {
    135   // We ignore URLs that were imported, but have never been visited by
    136   // chromium.
    137   static const int kLastImportedSource = history::SOURCE_EXTENSION;
    138   history::VisitSourceMap map;
    139   if (!history_backend_->GetVisitsSource(visits, &map))
    140     return false;  // If we can't read the visit, assume it's not imported.
    141 
    142   // Walk the list of visits and look for a non-imported item.
    143   for (history::VisitVector::const_iterator it = visits.begin();
    144        it != visits.end(); ++it) {
    145     if (map.count(it->visit_id) == 0 ||
    146         map[it->visit_id] <= kLastImportedSource) {
    147       return false;
    148     }
    149   }
    150   // We only saw imported visits, so tell the caller to ignore them.
    151   return true;
    152 }
    153 
    154 syncer::SyncError TypedUrlModelAssociator::AssociateModels(
    155     syncer::SyncMergeResult* local_merge_result,
    156     syncer::SyncMergeResult* syncer_merge_result) {
    157   ClearErrorStats();
    158   syncer::SyncError error = DoAssociateModels();
    159   UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlModelAssociationErrors",
    160                            GetErrorPercentage());
    161   ClearErrorStats();
    162   return error;
    163 }
    164 
    165 void TypedUrlModelAssociator::ClearErrorStats() {
    166   num_db_accesses_ = 0;
    167   num_db_errors_ = 0;
    168 }
    169 
    170 int TypedUrlModelAssociator::GetErrorPercentage() const {
    171   return num_db_accesses_ ? (100 * num_db_errors_ / num_db_accesses_) : 0;
    172 }
    173 
    174 syncer::SyncError TypedUrlModelAssociator::DoAssociateModels() {
    175   DVLOG(1) << "Associating TypedUrl Models";
    176   DCHECK(expected_loop_ == base::MessageLoop::current());
    177 
    178   history::URLRows typed_urls;
    179   ++num_db_accesses_;
    180   bool query_succeeded =
    181       history_backend_ && history_backend_->GetAllTypedURLs(&typed_urls);
    182 
    183   history::URLRows new_urls;
    184   TypedUrlVisitVector new_visits;
    185   TypedUrlUpdateVector updated_urls;
    186   {
    187     base::AutoLock au(abort_lock_);
    188     if (abort_requested_) {
    189       return syncer::SyncError(FROM_HERE,
    190                                syncer::SyncError::DATATYPE_ERROR,
    191                                "Association was aborted.",
    192                                model_type());
    193     }
    194 
    195     // Must lock and check first to make sure |error_handler_| is valid.
    196     if (!query_succeeded) {
    197       ++num_db_errors_;
    198       return error_handler_->CreateAndUploadError(
    199           FROM_HERE,
    200           "Could not get the typed_url entries.",
    201           model_type());
    202     }
    203 
    204     // Get all the visits.
    205     std::map<history::URLID, history::VisitVector> visit_vectors;
    206     for (history::URLRows::iterator ix = typed_urls.begin();
    207          ix != typed_urls.end();) {
    208       DCHECK_EQ(0U, visit_vectors.count(ix->id()));
    209       if (!FixupURLAndGetVisits(&(*ix), &(visit_vectors[ix->id()])) ||
    210           ShouldIgnoreUrl(ix->url()) ||
    211           ShouldIgnoreVisits(visit_vectors[ix->id()])) {
    212         // Ignore this URL if we couldn't load the visits or if there's some
    213         // other problem with it (it was empty, or imported and never visited).
    214         ix = typed_urls.erase(ix);
    215       } else {
    216         ++ix;
    217       }
    218     }
    219 
    220     syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    221     syncer::ReadNode typed_url_root(&trans);
    222     if (typed_url_root.InitByTagLookup(kTypedUrlTag) !=
    223             syncer::BaseNode::INIT_OK) {
    224       return error_handler_->CreateAndUploadError(
    225           FROM_HERE,
    226           "Server did not create the top-level typed_url node. We "
    227           "might be running against an out-of-date server.",
    228           model_type());
    229     }
    230 
    231     std::set<std::string> current_urls;
    232     for (history::URLRows::iterator ix = typed_urls.begin();
    233          ix != typed_urls.end(); ++ix) {
    234       std::string tag = ix->url().spec();
    235       // Empty URLs should be filtered out by ShouldIgnoreUrl() previously.
    236       DCHECK(!tag.empty());
    237       history::VisitVector& visits = visit_vectors[ix->id()];
    238 
    239       syncer::ReadNode node(&trans);
    240       if (node.InitByClientTagLookup(syncer::TYPED_URLS, tag) ==
    241               syncer::BaseNode::INIT_OK) {
    242         // Same URL exists in sync data and in history data - compare the
    243         // entries to see if there's any difference.
    244         sync_pb::TypedUrlSpecifics typed_url(
    245             FilterExpiredVisits(node.GetTypedUrlSpecifics()));
    246         DCHECK_EQ(tag, typed_url.url());
    247 
    248         // Initialize fields in |new_url| to the same values as the fields in
    249         // the existing URLRow in the history DB. This is needed because we
    250         // overwrite the existing value below in WriteToHistoryBackend(), but
    251         // some of the values in that structure are not synced (like
    252         // typed_count).
    253         history::URLRow new_url(*ix);
    254 
    255         std::vector<history::VisitInfo> added_visits;
    256         MergeResult difference =
    257             MergeUrls(typed_url, *ix, &visits, &new_url, &added_visits);
    258         if (difference & DIFF_UPDATE_NODE) {
    259           syncer::WriteNode write_node(&trans);
    260           if (write_node.InitByClientTagLookup(syncer::TYPED_URLS, tag) !=
    261                   syncer::BaseNode::INIT_OK) {
    262             return error_handler_->CreateAndUploadError(
    263                 FROM_HERE,
    264                 "Failed to edit typed_url sync node.",
    265                 model_type());
    266           }
    267           // We don't want to resurrect old visits that have been aged out by
    268           // other clients, so remove all visits that are older than the
    269           // earliest existing visit in the sync node.
    270           if (typed_url.visits_size() > 0) {
    271             base::Time earliest_visit =
    272                 base::Time::FromInternalValue(typed_url.visits(0));
    273             for (history::VisitVector::iterator it = visits.begin();
    274                  it != visits.end() && it->visit_time < earliest_visit; ) {
    275               it = visits.erase(it);
    276             }
    277             // Should never be possible to delete all the items, since the
    278             // visit vector contains all the items in typed_url.visits.
    279             DCHECK(visits.size() > 0);
    280           }
    281           DCHECK_EQ(new_url.last_visit().ToInternalValue(),
    282                     visits.back().visit_time.ToInternalValue());
    283           WriteToSyncNode(new_url, visits, &write_node);
    284         }
    285         if (difference & DIFF_LOCAL_ROW_CHANGED) {
    286           updated_urls.push_back(
    287               std::pair<history::URLID, history::URLRow>(ix->id(), new_url));
    288         }
    289         if (difference & DIFF_LOCAL_VISITS_ADDED) {
    290           new_visits.push_back(
    291               std::pair<GURL, std::vector<history::VisitInfo> >(ix->url(),
    292                                                                 added_visits));
    293         }
    294       } else {
    295         // Sync has never seen this URL before.
    296         syncer::WriteNode node(&trans);
    297         syncer::WriteNode::InitUniqueByCreationResult result =
    298             node.InitUniqueByCreation(syncer::TYPED_URLS,
    299                                       typed_url_root, tag);
    300         if (result != syncer::WriteNode::INIT_SUCCESS) {
    301           return error_handler_->CreateAndUploadError(
    302               FROM_HERE,
    303               "Failed to create typed_url sync node: " + tag,
    304               model_type());
    305         }
    306 
    307         node.SetTitle(UTF8ToWide(tag));
    308         WriteToSyncNode(*ix, visits, &node);
    309       }
    310 
    311       current_urls.insert(tag);
    312     }
    313 
    314     // Now walk the sync nodes and detect any URLs that exist there, but not in
    315     // the history DB, so we can add them to our local history DB.
    316     std::vector<int64> obsolete_nodes;
    317     int64 sync_child_id = typed_url_root.GetFirstChildId();
    318     while (sync_child_id != syncer::kInvalidId) {
    319       syncer::ReadNode sync_child_node(&trans);
    320       if (sync_child_node.InitByIdLookup(sync_child_id) !=
    321               syncer::BaseNode::INIT_OK) {
    322         return error_handler_->CreateAndUploadError(
    323             FROM_HERE,
    324             "Failed to fetch child node.",
    325             model_type());
    326       }
    327       const sync_pb::TypedUrlSpecifics& typed_url(
    328           sync_child_node.GetTypedUrlSpecifics());
    329 
    330       sync_child_id = sync_child_node.GetSuccessorId();
    331 
    332       // Ignore old sync nodes that don't have any transition data stored with
    333       // them, or transition data that does not match the visit data (will be
    334       // deleted below).
    335       if (typed_url.visit_transitions_size() == 0 ||
    336           typed_url.visit_transitions_size() != typed_url.visits_size()) {
    337         // Generate a debug assertion to help track down http://crbug.com/91473,
    338         // even though we gracefully handle this case by throwing away this
    339         // node.
    340         DCHECK_EQ(typed_url.visits_size(), typed_url.visit_transitions_size());
    341         DVLOG(1) << "Deleting obsolete sync node with no visit "
    342                  << "transition info.";
    343         obsolete_nodes.push_back(sync_child_node.GetId());
    344         continue;
    345       }
    346 
    347       if (typed_url.url().empty()) {
    348         DVLOG(1) << "Ignoring empty URL in sync DB";
    349         continue;
    350       }
    351 
    352       // Now, get rid of the expired visits, and if there are no un-expired
    353       // visits left, just ignore this node.
    354       sync_pb::TypedUrlSpecifics filtered_url = FilterExpiredVisits(typed_url);
    355       if (filtered_url.visits_size() == 0) {
    356         DVLOG(1) << "Ignoring expired URL in sync DB: " << filtered_url.url();
    357         continue;
    358       }
    359 
    360       if (current_urls.find(filtered_url.url()) == current_urls.end()) {
    361         // Update the local DB from the sync DB. Since we are doing our
    362         // initial model association, we don't want to remove any of the
    363         // existing visits (pass NULL as |visits_to_remove|).
    364         UpdateFromSyncDB(filtered_url,
    365                          &new_visits,
    366                          NULL,
    367                          &updated_urls,
    368                          &new_urls);
    369       }
    370     }
    371 
    372     // If we encountered any obsolete nodes, remove them so they don't hang
    373     // around and confuse people looking at the sync node browser.
    374     if (!obsolete_nodes.empty()) {
    375       for (std::vector<int64>::const_iterator it = obsolete_nodes.begin();
    376            it != obsolete_nodes.end();
    377            ++it) {
    378         syncer::WriteNode sync_node(&trans);
    379         if (sync_node.InitByIdLookup(*it) != syncer::BaseNode::INIT_OK) {
    380           return error_handler_->CreateAndUploadError(
    381               FROM_HERE,
    382               "Failed to fetch obsolete node.",
    383               model_type());
    384         }
    385         sync_node.Tombstone();
    386       }
    387     }
    388   }
    389 
    390   // Since we're on the history thread, we don't have to worry about updating
    391   // the history database after closing the write transaction, since
    392   // this is the only thread that writes to the database.  We also don't have
    393   // to worry about the sync model getting out of sync, because changes are
    394   // propagated to the ChangeProcessor on this thread.
    395   WriteToHistoryBackend(&new_urls, &updated_urls, &new_visits, NULL);
    396   return syncer::SyncError();
    397 }
    398 
    399 void TypedUrlModelAssociator::UpdateFromSyncDB(
    400     const sync_pb::TypedUrlSpecifics& typed_url,
    401     TypedUrlVisitVector* visits_to_add,
    402     history::VisitVector* visits_to_remove,
    403     TypedUrlUpdateVector* updated_urls,
    404     history::URLRows* new_urls) {
    405   history::URLRow new_url(GURL(typed_url.url()));
    406   history::VisitVector existing_visits;
    407   bool existing_url = history_backend_->GetURL(new_url.url(), &new_url);
    408   if (existing_url) {
    409     // This URL already exists locally - fetch the visits so we can
    410     // merge them below.
    411     if (!FixupURLAndGetVisits(&new_url, &existing_visits)) {
    412       // Couldn't load the visits for this URL due to some kind of DB error.
    413       // Don't bother writing this URL to the history DB (if we ignore the
    414       // error and continue, we might end up duplicating existing visits).
    415       DLOG(ERROR) << "Could not load visits for url: " << new_url.url();
    416       return;
    417     }
    418   }
    419   visits_to_add->push_back(std::pair<GURL, std::vector<history::VisitInfo> >(
    420       new_url.url(), std::vector<history::VisitInfo>()));
    421 
    422   // Update the URL with information from the typed URL.
    423   UpdateURLRowFromTypedUrlSpecifics(typed_url, &new_url);
    424 
    425   // Figure out which visits we need to add.
    426   DiffVisits(existing_visits, typed_url, &visits_to_add->back().second,
    427              visits_to_remove);
    428 
    429   if (existing_url) {
    430     updated_urls->push_back(
    431         std::pair<history::URLID, history::URLRow>(new_url.id(), new_url));
    432   } else {
    433     new_urls->push_back(new_url);
    434   }
    435 }
    436 
    437 sync_pb::TypedUrlSpecifics TypedUrlModelAssociator::FilterExpiredVisits(
    438     const sync_pb::TypedUrlSpecifics& source) {
    439   // Make a copy of the source, then regenerate the visits.
    440   sync_pb::TypedUrlSpecifics specifics(source);
    441   specifics.clear_visits();
    442   specifics.clear_visit_transitions();
    443   for (int i = 0; i < source.visits_size(); ++i) {
    444     base::Time time = base::Time::FromInternalValue(source.visits(i));
    445     if (!history_backend_->IsExpiredVisitTime(time)) {
    446       specifics.add_visits(source.visits(i));
    447       specifics.add_visit_transitions(source.visit_transitions(i));
    448     }
    449   }
    450   DCHECK(specifics.visits_size() == specifics.visit_transitions_size());
    451   return specifics;
    452 }
    453 
    454 bool TypedUrlModelAssociator::DeleteAllNodes(
    455     syncer::WriteTransaction* trans) {
    456   DCHECK(expected_loop_ == base::MessageLoop::current());
    457 
    458   // Just walk through all our child nodes and delete them.
    459   syncer::ReadNode typed_url_root(trans);
    460   if (typed_url_root.InitByTagLookup(kTypedUrlTag) !=
    461           syncer::BaseNode::INIT_OK) {
    462     LOG(ERROR) << "Could not lookup root node";
    463     return false;
    464   }
    465   int64 sync_child_id = typed_url_root.GetFirstChildId();
    466   while (sync_child_id != syncer::kInvalidId) {
    467     syncer::WriteNode sync_child_node(trans);
    468     if (sync_child_node.InitByIdLookup(sync_child_id) !=
    469             syncer::BaseNode::INIT_OK) {
    470       LOG(ERROR) << "Typed url node lookup failed.";
    471       return false;
    472     }
    473     sync_child_id = sync_child_node.GetSuccessorId();
    474     sync_child_node.Tombstone();
    475   }
    476   return true;
    477 }
    478 
    479 syncer::SyncError TypedUrlModelAssociator::DisassociateModels() {
    480   return syncer::SyncError();
    481 }
    482 
    483 void TypedUrlModelAssociator::AbortAssociation() {
    484   base::AutoLock lock(abort_lock_);
    485   abort_requested_ = true;
    486 }
    487 
    488 bool TypedUrlModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
    489   DCHECK(has_nodes);
    490   *has_nodes = false;
    491   syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    492   syncer::ReadNode sync_node(&trans);
    493   if (sync_node.InitByTagLookup(kTypedUrlTag) != syncer::BaseNode::INIT_OK) {
    494     LOG(ERROR) << "Server did not create the top-level typed_url node. We "
    495                << "might be running against an out-of-date server.";
    496     return false;
    497   }
    498 
    499   // The sync model has user created nodes if the typed_url folder has any
    500   // children.
    501   *has_nodes = sync_node.HasChildren();
    502   return true;
    503 }
    504 
    505 void TypedUrlModelAssociator::WriteToHistoryBackend(
    506     const history::URLRows* new_urls,
    507     const TypedUrlUpdateVector* updated_urls,
    508     const TypedUrlVisitVector* new_visits,
    509     const history::VisitVector* deleted_visits) {
    510   if (new_urls) {
    511     history_backend_->AddPagesWithDetails(*new_urls, history::SOURCE_SYNCED);
    512   }
    513   if (updated_urls) {
    514     for (TypedUrlUpdateVector::const_iterator url = updated_urls->begin();
    515          url != updated_urls->end(); ++url) {
    516       // This is an existing entry in the URL database. We don't verify the
    517       // visit_count or typed_count values here, because either one (or both)
    518       // could be zero in the case of bookmarks, or in the case of a URL
    519       // transitioning from non-typed to typed as a result of this sync.
    520       ++num_db_accesses_;
    521       if (!history_backend_->UpdateURL(url->first, url->second)) {
    522         // In the field we sometimes run into errors on specific URLs. It's OK
    523         // to just continue on (we can try writing again on the next model
    524         // association).
    525         ++num_db_errors_;
    526         DLOG(ERROR) << "Could not update page: " << url->second.url().spec();
    527       }
    528     }
    529   }
    530   if (new_visits) {
    531     for (TypedUrlVisitVector::const_iterator visits = new_visits->begin();
    532          visits != new_visits->end(); ++visits) {
    533       // If there are no visits to add, just skip this.
    534       if (visits->second.empty())
    535         continue;
    536       ++num_db_accesses_;
    537       if (!history_backend_->AddVisits(visits->first, visits->second,
    538                                        history::SOURCE_SYNCED)) {
    539         ++num_db_errors_;
    540         DLOG(ERROR) << "Could not add visits.";
    541       }
    542     }
    543   }
    544   if (deleted_visits) {
    545     ++num_db_accesses_;
    546     if (!history_backend_->RemoveVisits(*deleted_visits)) {
    547       ++num_db_errors_;
    548       DLOG(ERROR) << "Could not remove visits.";
    549       // This is bad news, since it means we may end up resurrecting history
    550       // entries on the next reload. It's unavoidable so we'll just keep on
    551       // syncing.
    552     }
    553   }
    554 }
    555 
    556 // static
    557 TypedUrlModelAssociator::MergeResult TypedUrlModelAssociator::MergeUrls(
    558     const sync_pb::TypedUrlSpecifics& node,
    559     const history::URLRow& url,
    560     history::VisitVector* visits,
    561     history::URLRow* new_url,
    562     std::vector<history::VisitInfo>* new_visits) {
    563   DCHECK(new_url);
    564   DCHECK(!node.url().compare(url.url().spec()));
    565   DCHECK(!node.url().compare(new_url->url().spec()));
    566   DCHECK(visits->size());
    567   CHECK_EQ(node.visits_size(), node.visit_transitions_size());
    568 
    569   // If we have an old-format node (before we added the visits and
    570   // visit_transitions arrays to the protobuf) or else the node only contained
    571   // expired visits, so just overwrite it with our local history data.
    572   if (node.visits_size() == 0)
    573     return DIFF_UPDATE_NODE;
    574 
    575   // Convert these values only once.
    576   string16 node_title(UTF8ToUTF16(node.title()));
    577   base::Time node_last_visit = base::Time::FromInternalValue(
    578       node.visits(node.visits_size() - 1));
    579 
    580   // This is a bitfield representing what we'll need to update with the output
    581   // value.
    582   MergeResult different = DIFF_NONE;
    583 
    584   // Check if the non-incremented values changed.
    585   if ((node_title.compare(url.title()) != 0) ||
    586       (node.hidden() != url.hidden())) {
    587     // Use the values from the most recent visit.
    588     if (node_last_visit >= url.last_visit()) {
    589       new_url->set_title(node_title);
    590       new_url->set_hidden(node.hidden());
    591       different |= DIFF_LOCAL_ROW_CHANGED;
    592     } else {
    593       new_url->set_title(url.title());
    594       new_url->set_hidden(url.hidden());
    595       different |= DIFF_UPDATE_NODE;
    596     }
    597   } else {
    598     // No difference.
    599     new_url->set_title(url.title());
    600     new_url->set_hidden(url.hidden());
    601   }
    602 
    603   size_t node_num_visits = node.visits_size();
    604   size_t history_num_visits = visits->size();
    605   size_t node_visit_index = 0;
    606   size_t history_visit_index = 0;
    607   base::Time earliest_history_time = (*visits)[0].visit_time;
    608   // Walk through the two sets of visits and figure out if any new visits were
    609   // added on either side.
    610   while (node_visit_index < node_num_visits ||
    611          history_visit_index < history_num_visits) {
    612     // Time objects are initialized to "earliest possible time".
    613     base::Time node_time, history_time;
    614     if (node_visit_index < node_num_visits)
    615       node_time = base::Time::FromInternalValue(node.visits(node_visit_index));
    616     if (history_visit_index < history_num_visits)
    617       history_time = (*visits)[history_visit_index].visit_time;
    618     if (node_visit_index >= node_num_visits ||
    619         (history_visit_index < history_num_visits &&
    620          node_time > history_time)) {
    621       // We found a visit in the history DB that doesn't exist in the sync DB,
    622       // so mark the node as modified so the caller will update the sync node.
    623       different |= DIFF_UPDATE_NODE;
    624       ++history_visit_index;
    625     } else if (history_visit_index >= history_num_visits ||
    626                node_time < history_time) {
    627       // Found a visit in the sync node that doesn't exist in the history DB, so
    628       // add it to our list of new visits and set the appropriate flag so the
    629       // caller will update the history DB.
    630       // If the node visit is older than any existing visit in the history DB,
    631       // don't re-add it - this keeps us from resurrecting visits that were
    632       // aged out locally.
    633       if (node_time > earliest_history_time) {
    634         different |= DIFF_LOCAL_VISITS_ADDED;
    635         new_visits->push_back(history::VisitInfo(
    636             node_time,
    637             content::PageTransitionFromInt(
    638                 node.visit_transitions(node_visit_index))));
    639       }
    640       // This visit is added to visits below.
    641       ++node_visit_index;
    642     } else {
    643       // Same (already synced) entry found in both DBs - no need to do anything.
    644       ++node_visit_index;
    645       ++history_visit_index;
    646     }
    647   }
    648 
    649   DCHECK(CheckVisitOrdering(*visits));
    650   if (different & DIFF_LOCAL_VISITS_ADDED) {
    651     // Insert new visits into the apropriate place in the visits vector.
    652     history::VisitVector::iterator visit_ix = visits->begin();
    653     for (std::vector<history::VisitInfo>::iterator new_visit =
    654              new_visits->begin();
    655          new_visit != new_visits->end(); ++new_visit) {
    656       while (visit_ix != visits->end() &&
    657              new_visit->first > visit_ix->visit_time) {
    658         ++visit_ix;
    659       }
    660       visit_ix = visits->insert(visit_ix,
    661                                 history::VisitRow(url.id(), new_visit->first,
    662                                                   0, new_visit->second, 0));
    663       ++visit_ix;
    664     }
    665   }
    666   DCHECK(CheckVisitOrdering(*visits));
    667 
    668   new_url->set_last_visit(visits->back().visit_time);
    669   return different;
    670 }
    671 
    672 // static
    673 void TypedUrlModelAssociator::WriteToSyncNode(
    674     const history::URLRow& url,
    675     const history::VisitVector& visits,
    676     syncer::WriteNode* node) {
    677   sync_pb::TypedUrlSpecifics typed_url;
    678   WriteToTypedUrlSpecifics(url, visits, &typed_url);
    679   node->SetTypedUrlSpecifics(typed_url);
    680 }
    681 
    682 void TypedUrlModelAssociator::WriteToTypedUrlSpecifics(
    683     const history::URLRow& url,
    684     const history::VisitVector& visits,
    685     sync_pb::TypedUrlSpecifics* typed_url) {
    686 
    687   DCHECK(!url.last_visit().is_null());
    688   DCHECK(!visits.empty());
    689   DCHECK_EQ(url.last_visit().ToInternalValue(),
    690             visits.back().visit_time.ToInternalValue());
    691 
    692   typed_url->set_url(url.url().spec());
    693   typed_url->set_title(UTF16ToUTF8(url.title()));
    694   typed_url->set_hidden(url.hidden());
    695 
    696   DCHECK(CheckVisitOrdering(visits));
    697 
    698   bool only_typed = false;
    699   int skip_count = 0;
    700 
    701   if (visits.size() > static_cast<size_t>(kMaxTypedUrlVisits)) {
    702     int typed_count = 0;
    703     int total = 0;
    704     // Walk the passed-in visit vector and count the # of typed visits.
    705     for (history::VisitVector::const_iterator visit = visits.begin();
    706          visit != visits.end(); ++visit) {
    707       content::PageTransition transition = content::PageTransitionFromInt(
    708           visit->transition & content::PAGE_TRANSITION_CORE_MASK);
    709       // We ignore reload visits.
    710       if (transition == content::PAGE_TRANSITION_RELOAD)
    711         continue;
    712       ++total;
    713       if (transition == content::PAGE_TRANSITION_TYPED)
    714         ++typed_count;
    715     }
    716     // We should have at least one typed visit. This can sometimes happen if
    717     // the history DB has an inaccurate count for some reason (there's been
    718     // bugs in the history code in the past which has left users in the wild
    719     // with incorrect counts - http://crbug.com/84258).
    720     DCHECK(typed_count > 0);
    721 
    722     if (typed_count > kMaxTypedUrlVisits) {
    723       only_typed = true;
    724       skip_count = typed_count - kMaxTypedUrlVisits;
    725     } else if (total > kMaxTypedUrlVisits) {
    726       skip_count = total - kMaxTypedUrlVisits;
    727     }
    728   }
    729 
    730 
    731   for (history::VisitVector::const_iterator visit = visits.begin();
    732        visit != visits.end(); ++visit) {
    733     content::PageTransition transition = content::PageTransitionFromInt(
    734         visit->transition & content::PAGE_TRANSITION_CORE_MASK);
    735     // Skip reload visits.
    736     if (transition == content::PAGE_TRANSITION_RELOAD)
    737       continue;
    738 
    739     // If we only have room for typed visits, then only add typed visits.
    740     if (only_typed && transition != content::PAGE_TRANSITION_TYPED)
    741       continue;
    742 
    743     if (skip_count > 0) {
    744       // We have too many entries to fit, so we need to skip the oldest ones.
    745       // Only skip typed URLs if there are too many typed URLs to fit.
    746       if (only_typed || transition != content::PAGE_TRANSITION_TYPED) {
    747         --skip_count;
    748         continue;
    749       }
    750     }
    751     typed_url->add_visits(visit->visit_time.ToInternalValue());
    752     typed_url->add_visit_transitions(visit->transition);
    753   }
    754   DCHECK_EQ(skip_count, 0);
    755 
    756   if (typed_url->visits_size() == 0) {
    757     // If we get here, it's because we don't actually have any TYPED visits
    758     // even though the visit's typed_count > 0 (corrupted typed_count). So
    759     // let's go ahead and add a RELOAD visit at the most recent visit since
    760     // it's not legal to have an empty visit array (yet another workaround
    761     // for http://crbug.com/84258).
    762     typed_url->add_visits(url.last_visit().ToInternalValue());
    763     typed_url->add_visit_transitions(content::PAGE_TRANSITION_RELOAD);
    764   }
    765   CHECK_GT(typed_url->visits_size(), 0);
    766   CHECK_LE(typed_url->visits_size(), kMaxTypedUrlVisits);
    767   CHECK_EQ(typed_url->visits_size(), typed_url->visit_transitions_size());
    768 }
    769 
    770 // static
    771 void TypedUrlModelAssociator::DiffVisits(
    772     const history::VisitVector& old_visits,
    773     const sync_pb::TypedUrlSpecifics& new_url,
    774     std::vector<history::VisitInfo>* new_visits,
    775     history::VisitVector* removed_visits) {
    776   DCHECK(new_visits);
    777   size_t old_visit_count = old_visits.size();
    778   size_t new_visit_count = new_url.visits_size();
    779   size_t old_index = 0;
    780   size_t new_index = 0;
    781   while (old_index < old_visit_count && new_index < new_visit_count) {
    782     base::Time new_visit_time =
    783         base::Time::FromInternalValue(new_url.visits(new_index));
    784     if (old_visits[old_index].visit_time < new_visit_time) {
    785       if (new_index > 0 && removed_visits) {
    786         // If there are visits missing from the start of the node, that
    787         // means that they were probably clipped off due to our code that
    788         // limits the size of the sync nodes - don't delete them from our
    789         // local history.
    790         removed_visits->push_back(old_visits[old_index]);
    791       }
    792       ++old_index;
    793     } else if (old_visits[old_index].visit_time > new_visit_time) {
    794       new_visits->push_back(history::VisitInfo(
    795           new_visit_time,
    796           content::PageTransitionFromInt(
    797               new_url.visit_transitions(new_index))));
    798       ++new_index;
    799     } else {
    800       ++old_index;
    801       ++new_index;
    802     }
    803   }
    804 
    805   if (removed_visits) {
    806     for ( ; old_index < old_visit_count; ++old_index) {
    807       removed_visits->push_back(old_visits[old_index]);
    808     }
    809   }
    810 
    811   for ( ; new_index < new_visit_count; ++new_index) {
    812     new_visits->push_back(history::VisitInfo(
    813         base::Time::FromInternalValue(new_url.visits(new_index)),
    814         content::PageTransitionFromInt(new_url.visit_transitions(new_index))));
    815   }
    816 }
    817 
    818 
    819 // static
    820 void TypedUrlModelAssociator::UpdateURLRowFromTypedUrlSpecifics(
    821     const sync_pb::TypedUrlSpecifics& typed_url, history::URLRow* new_url) {
    822   DCHECK_GT(typed_url.visits_size(), 0);
    823   CHECK_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
    824   new_url->set_title(UTF8ToUTF16(typed_url.title()));
    825   new_url->set_hidden(typed_url.hidden());
    826   // Only provide the initial value for the last_visit field - after that, let
    827   // the history code update the last_visit field on its own.
    828   if (new_url->last_visit().is_null()) {
    829     new_url->set_last_visit(base::Time::FromInternalValue(
    830         typed_url.visits(typed_url.visits_size() - 1)));
    831   }
    832 }
    833 
    834 bool TypedUrlModelAssociator::CryptoReadyIfNecessary() {
    835   // We only access the cryptographer while holding a transaction.
    836   syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    837   const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes();
    838   return !encrypted_types.Has(syncer::TYPED_URLS) ||
    839          sync_service_->IsCryptographerReady(&trans);
    840 }
    841 
    842 }  // namespace browser_sync
    843