Home | History | Annotate | Download | only in glue
      1 // Copyright 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/bookmark_change_processor.h"
      6 
      7 #include <map>
      8 #include <stack>
      9 #include <vector>
     10 
     11 #include "base/location.h"
     12 #include "base/strings/string16.h"
     13 #include "base/strings/string_number_conversions.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     17 #include "chrome/browser/favicon/favicon_service.h"
     18 #include "chrome/browser/favicon/favicon_service_factory.h"
     19 #include "chrome/browser/history/history_service.h"
     20 #include "chrome/browser/history/history_service_factory.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/sync/profile_sync_service.h"
     23 #include "chrome/browser/undo/bookmark_undo_service.h"
     24 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
     25 #include "chrome/browser/undo/bookmark_undo_utils.h"
     26 #include "components/bookmarks/browser/bookmark_client.h"
     27 #include "components/bookmarks/browser/bookmark_model.h"
     28 #include "components/bookmarks/browser/bookmark_utils.h"
     29 #include "content/public/browser/browser_thread.h"
     30 #include "sync/internal_api/public/change_record.h"
     31 #include "sync/internal_api/public/read_node.h"
     32 #include "sync/internal_api/public/write_node.h"
     33 #include "sync/internal_api/public/write_transaction.h"
     34 #include "sync/syncable/entry.h"  // TODO(tim): Investigating bug 121587.
     35 #include "sync/syncable/syncable_write_transaction.h"
     36 #include "ui/gfx/favicon_size.h"
     37 #include "ui/gfx/image/image_util.h"
     38 
     39 using content::BrowserThread;
     40 using syncer::ChangeRecord;
     41 using syncer::ChangeRecordList;
     42 
     43 namespace browser_sync {
     44 
     45 static const char kMobileBookmarksTag[] = "synced_bookmarks";
     46 
     47 BookmarkChangeProcessor::BookmarkChangeProcessor(
     48     Profile* profile,
     49     BookmarkModelAssociator* model_associator,
     50     sync_driver::DataTypeErrorHandler* error_handler)
     51     : sync_driver::ChangeProcessor(error_handler),
     52       bookmark_model_(NULL),
     53       profile_(profile),
     54       model_associator_(model_associator) {
     55   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     56   DCHECK(model_associator);
     57   DCHECK(profile);
     58   DCHECK(error_handler);
     59 }
     60 
     61 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
     62   if (bookmark_model_)
     63     bookmark_model_->RemoveObserver(this);
     64 }
     65 
     66 void BookmarkChangeProcessor::StartImpl() {
     67   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     68   DCHECK(!bookmark_model_);
     69   bookmark_model_ = BookmarkModelFactory::GetForProfile(profile_);
     70   DCHECK(bookmark_model_->loaded());
     71   bookmark_model_->AddObserver(this);
     72 }
     73 
     74 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
     75     const BookmarkNode* src,
     76     BookmarkModel* model,
     77     syncer::WriteNode* dst) {
     78   // Set the properties of the item.
     79   dst->SetIsFolder(src->is_folder());
     80   dst->SetTitle(base::UTF16ToUTF8(src->GetTitle()));
     81   sync_pb::BookmarkSpecifics bookmark_specifics(dst->GetBookmarkSpecifics());
     82   if (!src->is_folder())
     83     bookmark_specifics.set_url(src->url().spec());
     84   bookmark_specifics.set_creation_time_us(src->date_added().ToInternalValue());
     85   dst->SetBookmarkSpecifics(bookmark_specifics);
     86   SetSyncNodeFavicon(src, model, dst);
     87   SetSyncNodeMetaInfo(src, dst);
     88 }
     89 
     90 // static
     91 void BookmarkChangeProcessor::EncodeFavicon(
     92     const BookmarkNode* src,
     93     BookmarkModel* model,
     94     scoped_refptr<base::RefCountedMemory>* dst) {
     95   const gfx::Image& favicon = model->GetFavicon(src);
     96 
     97   // Check for empty images.  This can happen if the favicon is
     98   // still being loaded.
     99   if (favicon.IsEmpty())
    100     return;
    101 
    102   // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
    103   // sync subsystem.
    104   *dst = favicon.As1xPNGBytes();
    105 }
    106 
    107 void BookmarkChangeProcessor::RemoveOneSyncNode(syncer::WriteNode* sync_node) {
    108   // This node should have no children.
    109   DCHECK(!sync_node->HasChildren());
    110   // Remove association and delete the sync node.
    111   model_associator_->Disassociate(sync_node->GetId());
    112   sync_node->Tombstone();
    113 }
    114 
    115 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
    116     const BookmarkNode* topmost) {
    117   int64 new_version =
    118       syncer::syncable::kInvalidTransactionVersion;
    119   {
    120     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
    121     syncer::WriteNode topmost_sync_node(&trans);
    122     if (!model_associator_->InitSyncNodeFromChromeId(topmost->id(),
    123                                                      &topmost_sync_node)) {
    124       syncer::SyncError error(FROM_HERE,
    125                               syncer::SyncError::DATATYPE_ERROR,
    126                               "Failed to init sync node from chrome node",
    127                               syncer::BOOKMARKS);
    128       error_handler()->OnSingleDataTypeUnrecoverableError(error);
    129       return;
    130     }
    131     // Check that |topmost| has been unlinked.
    132     DCHECK(topmost->is_root());
    133     RemoveAllChildNodes(&trans, topmost->id());
    134     // Remove the node itself.
    135     RemoveOneSyncNode(&topmost_sync_node);
    136   }
    137 
    138   // Don't need to update versions of deleted nodes.
    139   UpdateTransactionVersion(new_version, bookmark_model_,
    140                            std::vector<const BookmarkNode*>());
    141 }
    142 
    143 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
    144   int64 new_version = syncer::syncable::kInvalidTransactionVersion;
    145   {
    146     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
    147 
    148     RemoveAllChildNodes(&trans, bookmark_model_->bookmark_bar_node()->id());
    149     RemoveAllChildNodes(&trans, bookmark_model_->other_node()->id());
    150     // Remove mobile bookmarks node only if it is present.
    151     const int64 mobile_bookmark_id = bookmark_model_->mobile_node()->id();
    152     if (model_associator_->GetSyncIdFromChromeId(mobile_bookmark_id) !=
    153             syncer::kInvalidId) {
    154       RemoveAllChildNodes(&trans, bookmark_model_->mobile_node()->id());
    155     }
    156     // Note: the root node may have additional extra nodes. Currently none of
    157     // them are meant to sync.
    158   }
    159 
    160   // Don't need to update versions of deleted nodes.
    161   UpdateTransactionVersion(new_version, bookmark_model_,
    162                            std::vector<const BookmarkNode*>());
    163 }
    164 
    165 void BookmarkChangeProcessor::RemoveAllChildNodes(
    166     syncer::WriteTransaction* trans, const int64& topmost_node_id) {
    167   syncer::WriteNode topmost_node(trans);
    168   if (!model_associator_->InitSyncNodeFromChromeId(topmost_node_id,
    169                                                    &topmost_node)) {
    170     syncer::SyncError error(FROM_HERE,
    171                             syncer::SyncError::DATATYPE_ERROR,
    172                             "Failed to init sync node from chrome node",
    173                             syncer::BOOKMARKS);
    174     error_handler()->OnSingleDataTypeUnrecoverableError(error);
    175     return;
    176   }
    177   const int64 topmost_sync_id = topmost_node.GetId();
    178 
    179   // Do a DFS and delete all the child sync nodes, use sync id instead of
    180   // bookmark node ids since the bookmark nodes may already be deleted.
    181   // The equivalent recursive version of this iterative DFS:
    182   // remove_all_children(node_id, topmost_node_id):
    183   //    node.initByIdLookup(node_id)
    184   //    while(node.GetFirstChildId() != syncer::kInvalidId)
    185   //      remove_all_children(node.GetFirstChildId(), topmost_node_id)
    186   //    if(node_id != topmost_node_id)
    187   //      delete node
    188 
    189   std::stack<int64> dfs_sync_id_stack;
    190   // Push the topmost node.
    191   dfs_sync_id_stack.push(topmost_sync_id);
    192   while (!dfs_sync_id_stack.empty()) {
    193     const int64 sync_node_id = dfs_sync_id_stack.top();
    194     syncer::WriteNode node(trans);
    195     node.InitByIdLookup(sync_node_id);
    196     if (!node.GetIsFolder() || node.GetFirstChildId() == syncer::kInvalidId) {
    197       // All children of the node has been processed, delete the node and
    198       // pop it off the stack.
    199       dfs_sync_id_stack.pop();
    200       // Do not delete the topmost node.
    201       if (sync_node_id != topmost_sync_id) {
    202         RemoveOneSyncNode(&node);
    203       } else {
    204         // if we are processing topmost node, all other nodes must be processed
    205         // the stack should be empty.
    206         DCHECK(dfs_sync_id_stack.empty());
    207       }
    208     } else {
    209       int64 child_id = node.GetFirstChildId();
    210       if (child_id != syncer::kInvalidId) {
    211         dfs_sync_id_stack.push(child_id);
    212       }
    213     }
    214   }
    215 }
    216 
    217 void BookmarkChangeProcessor::CreateOrUpdateSyncNode(const BookmarkNode* node) {
    218   if (!CanSyncNode(node)) {
    219     NOTREACHED();
    220     return;
    221   }
    222 
    223   const BookmarkNode* parent = node->parent();
    224   int index = node->parent()->GetIndexOf(node);
    225 
    226   int64 new_version = syncer::syncable::kInvalidTransactionVersion;
    227   int64 sync_id = syncer::kInvalidId;
    228   {
    229     // Acquire a scoped write lock via a transaction.
    230     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
    231     sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
    232     if (sync_id != syncer::kInvalidId) {
    233       UpdateSyncNode(
    234           node, bookmark_model_, &trans, model_associator_, error_handler());
    235     } else {
    236       sync_id = CreateSyncNode(parent,
    237                                bookmark_model_,
    238                                index,
    239                                &trans,
    240                                model_associator_,
    241                                error_handler());
    242     }
    243   }
    244 
    245   if (syncer::kInvalidId != sync_id) {
    246     // Siblings of added node in sync DB will also be updated to reflect new
    247     // PREV_ID/NEXT_ID and thus get a new version. But we only update version
    248     // of added node here. After switching to ordinals for positioning,
    249     // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
    250     UpdateTransactionVersion(
    251         new_version,
    252         bookmark_model_,
    253         std::vector<const BookmarkNode*>(1, parent->GetChild(index)));
    254   }
    255 }
    256 
    257 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
    258                                                   bool ids_reassigned) {
    259   NOTREACHED();
    260 }
    261 
    262 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) {
    263   NOTREACHED();
    264   bookmark_model_ = NULL;
    265 }
    266 
    267 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
    268                                                 const BookmarkNode* parent,
    269                                                 int index) {
    270   DCHECK(share_handle());
    271   const BookmarkNode* node = parent->GetChild(index);
    272   if (CanSyncNode(node))
    273     CreateOrUpdateSyncNode(node);
    274 }
    275 
    276 // static
    277 int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent,
    278     BookmarkModel* model, int index, syncer::WriteTransaction* trans,
    279     BookmarkModelAssociator* associator,
    280     sync_driver::DataTypeErrorHandler* error_handler) {
    281   const BookmarkNode* child = parent->GetChild(index);
    282   DCHECK(child);
    283 
    284   // Create a WriteNode container to hold the new node.
    285   syncer::WriteNode sync_child(trans);
    286 
    287   // Actually create the node with the appropriate initial position.
    288   if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) {
    289     syncer::SyncError error(FROM_HERE,
    290                             syncer::SyncError::DATATYPE_ERROR,
    291                             "Failed ot creat sync node.",
    292                             syncer::BOOKMARKS);
    293     error_handler->OnSingleDataTypeUnrecoverableError(error);
    294     return syncer::kInvalidId;
    295   }
    296 
    297   UpdateSyncNodeProperties(child, model, &sync_child);
    298 
    299   // Associate the ID from the sync domain with the bookmark node, so that we
    300   // can refer back to this item later.
    301   associator->Associate(child, sync_child.GetId());
    302 
    303   return sync_child.GetId();
    304 }
    305 
    306 void BookmarkChangeProcessor::BookmarkNodeRemoved(
    307     BookmarkModel* model,
    308     const BookmarkNode* parent,
    309     int index,
    310     const BookmarkNode* node,
    311     const std::set<GURL>& removed_urls) {
    312   if (CanSyncNode(node))
    313     RemoveSyncNodeHierarchy(node);
    314 }
    315 
    316 void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved(
    317     BookmarkModel* model,
    318     const std::set<GURL>& removed_urls) {
    319   RemoveAllSyncNodes();
    320 }
    321 
    322 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model,
    323                                                   const BookmarkNode* node) {
    324   if (!CanSyncNode(node))
    325     return;
    326   // We shouldn't see changes to the top-level nodes.
    327   if (model->is_permanent_node(node)) {
    328     NOTREACHED() << "Saw update to permanent node!";
    329     return;
    330   }
    331   CreateOrUpdateSyncNode(node);
    332 }
    333 
    334 // Static.
    335 int64 BookmarkChangeProcessor::UpdateSyncNode(
    336     const BookmarkNode* node,
    337     BookmarkModel* model,
    338     syncer::WriteTransaction* trans,
    339     BookmarkModelAssociator* associator,
    340     sync_driver::DataTypeErrorHandler* error_handler) {
    341   // Lookup the sync node that's associated with |node|.
    342   syncer::WriteNode sync_node(trans);
    343   if (!associator->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
    344     syncer::SyncError error(FROM_HERE,
    345                             syncer::SyncError::DATATYPE_ERROR,
    346                             "Failed to init sync node from chrome node",
    347                             syncer::BOOKMARKS);
    348     error_handler->OnSingleDataTypeUnrecoverableError(error);
    349     return syncer::kInvalidId;
    350   }
    351   UpdateSyncNodeProperties(node, model, &sync_node);
    352   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
    353   DCHECK_EQ(associator->GetChromeNodeFromSyncId(sync_node.GetParentId()),
    354             node->parent());
    355   DCHECK_EQ(node->parent()->GetIndexOf(node), sync_node.GetPositionIndex());
    356   return sync_node.GetId();
    357 }
    358 
    359 void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
    360     BookmarkModel* model, const BookmarkNode* node) {
    361   BookmarkNodeChanged(model, node);
    362 }
    363 
    364 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model,
    365       const BookmarkNode* old_parent, int old_index,
    366       const BookmarkNode* new_parent, int new_index) {
    367   const BookmarkNode* child = new_parent->GetChild(new_index);
    368 
    369   if (!CanSyncNode(child))
    370     return;
    371 
    372   // We shouldn't see changes to the top-level nodes.
    373   if (model->is_permanent_node(child)) {
    374     NOTREACHED() << "Saw update to permanent node!";
    375     return;
    376   }
    377 
    378   int64 new_version = syncer::syncable::kInvalidTransactionVersion;
    379   {
    380     // Acquire a scoped write lock via a transaction.
    381     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
    382 
    383     // Lookup the sync node that's associated with |child|.
    384     syncer::WriteNode sync_node(&trans);
    385     if (!model_associator_->InitSyncNodeFromChromeId(child->id(), &sync_node)) {
    386       syncer::SyncError error(FROM_HERE,
    387                               syncer::SyncError::DATATYPE_ERROR,
    388                               "Failed to init sync node from chrome node",
    389                               syncer::BOOKMARKS);
    390       error_handler()->OnSingleDataTypeUnrecoverableError(error);
    391       return;
    392     }
    393 
    394     if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node,
    395                        model_associator_)) {
    396       syncer::SyncError error(FROM_HERE,
    397                               syncer::SyncError::DATATYPE_ERROR,
    398                               "Failed to place sync node",
    399                               syncer::BOOKMARKS);
    400       error_handler()->OnSingleDataTypeUnrecoverableError(error);
    401       return;
    402     }
    403   }
    404 
    405   UpdateTransactionVersion(new_version, model,
    406                            std::vector<const BookmarkNode*>(1, child));
    407 }
    408 
    409 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
    410     BookmarkModel* model,
    411     const BookmarkNode* node) {
    412   BookmarkNodeChanged(model, node);
    413 }
    414 
    415 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
    416     BookmarkModel* model, const BookmarkNode* node) {
    417   if (!CanSyncNode(node))
    418     return;
    419   int64 new_version = syncer::syncable::kInvalidTransactionVersion;
    420   std::vector<const BookmarkNode*> children;
    421   {
    422     // Acquire a scoped write lock via a transaction.
    423     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
    424 
    425     // The given node's children got reordered. We need to reorder all the
    426     // children of the corresponding sync node.
    427     for (int i = 0; i < node->child_count(); ++i) {
    428       const BookmarkNode* child = node->GetChild(i);
    429       children.push_back(child);
    430 
    431       syncer::WriteNode sync_child(&trans);
    432       if (!model_associator_->InitSyncNodeFromChromeId(child->id(),
    433                                                        &sync_child)) {
    434         syncer::SyncError error(FROM_HERE,
    435                                 syncer::SyncError::DATATYPE_ERROR,
    436                                 "Failed to init sync node from chrome node",
    437                                 syncer::BOOKMARKS);
    438         error_handler()->OnSingleDataTypeUnrecoverableError(error);
    439         return;
    440       }
    441       DCHECK_EQ(sync_child.GetParentId(),
    442                 model_associator_->GetSyncIdFromChromeId(node->id()));
    443 
    444       if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child,
    445                          model_associator_)) {
    446         syncer::SyncError error(FROM_HERE,
    447                                 syncer::SyncError::DATATYPE_ERROR,
    448                                 "Failed to place sync node",
    449                                 syncer::BOOKMARKS);
    450         error_handler()->OnSingleDataTypeUnrecoverableError(error);
    451         return;
    452       }
    453     }
    454   }
    455 
    456   // TODO(haitaol): Filter out children that didn't actually change.
    457   UpdateTransactionVersion(new_version, model, children);
    458 }
    459 
    460 // static
    461 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation,
    462       const BookmarkNode* parent, int index, syncer::WriteTransaction* trans,
    463       syncer::WriteNode* dst, BookmarkModelAssociator* associator) {
    464   syncer::ReadNode sync_parent(trans);
    465   if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) {
    466     LOG(WARNING) << "Parent lookup failed";
    467     return false;
    468   }
    469 
    470   bool success = false;
    471   if (index == 0) {
    472     // Insert into first position.
    473     success = (operation == CREATE) ?
    474         dst->InitBookmarkByCreation(sync_parent, NULL) :
    475         dst->SetPosition(sync_parent, NULL);
    476     if (success) {
    477       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
    478       DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
    479       DCHECK_EQ(dst->GetPredecessorId(), syncer::kInvalidId);
    480     }
    481   } else {
    482     // Find the bookmark model predecessor, and insert after it.
    483     const BookmarkNode* prev = parent->GetChild(index - 1);
    484     syncer::ReadNode sync_prev(trans);
    485     if (!associator->InitSyncNodeFromChromeId(prev->id(), &sync_prev)) {
    486       LOG(WARNING) << "Predecessor lookup failed";
    487       return false;
    488     }
    489     success = (operation == CREATE) ?
    490         dst->InitBookmarkByCreation(sync_parent, &sync_prev) :
    491         dst->SetPosition(sync_parent, &sync_prev);
    492     if (success) {
    493       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
    494       DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
    495       DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
    496     }
    497   }
    498   return success;
    499 }
    500 
    501 // ApplyModelChanges is called by the sync backend after changes have been made
    502 // to the sync engine's model.  Apply these changes to the browser bookmark
    503 // model.
    504 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
    505     const syncer::BaseTransaction* trans,
    506     int64 model_version,
    507     const syncer::ImmutableChangeRecordList& changes) {
    508   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    509   // A note about ordering.  Sync backend is responsible for ordering the change
    510   // records in the following order:
    511   //
    512   // 1. Deletions, from leaves up to parents.
    513   // 2. Existing items with synced parents & predecessors.
    514   // 3. New items with synced parents & predecessors.
    515   // 4. Items with parents & predecessors in the list.
    516   // 5. Repeat #4 until all items are in the list.
    517   //
    518   // "Predecessor" here means the previous item within a given folder; an item
    519   // in the first position is always said to have a synced predecessor.
    520   // For the most part, applying these changes in the order given will yield
    521   // the correct result.  There is one exception, however: for items that are
    522   // moved away from a folder that is being deleted, we will process the delete
    523   // before the move.  Since deletions in the bookmark model propagate from
    524   // parent to child, we must move them to a temporary location.
    525   BookmarkModel* model = bookmark_model_;
    526 
    527   // We are going to make changes to the bookmarks model, but don't want to end
    528   // up in a feedback loop, so remove ourselves as an observer while applying
    529   // changes.
    530   model->RemoveObserver(this);
    531 
    532   // Changes made to the bookmark model due to sync should not be undoable.
    533   ScopedSuspendBookmarkUndo suspend_undo(profile_);
    534 
    535   // Notify UI intensive observers of BookmarkModel that we are about to make
    536   // potentially significant changes to it, so the updates may be batched. For
    537   // example, on Mac, the bookmarks bar displays animations when bookmark items
    538   // are added or deleted.
    539   model->BeginExtensiveChanges();
    540 
    541   // A parent to hold nodes temporarily orphaned by parent deletion.  It is
    542   // created only if it is needed.
    543   const BookmarkNode* foster_parent = NULL;
    544 
    545   // Iterate over the deletions, which are always at the front of the list.
    546   ChangeRecordList::const_iterator it;
    547   for (it = changes.Get().begin();
    548        it != changes.Get().end() && it->action == ChangeRecord::ACTION_DELETE;
    549        ++it) {
    550     const BookmarkNode* dst =
    551         model_associator_->GetChromeNodeFromSyncId(it->id);
    552 
    553     // Ignore changes to the permanent top-level nodes.  We only care about
    554     // their children.
    555     if (model->is_permanent_node(dst))
    556       continue;
    557 
    558     // Can't do anything if we can't find the chrome node.
    559     if (!dst)
    560       continue;
    561 
    562     // Children of a deleted node should not be deleted; they may be
    563     // reparented by a later change record.  Move them to a temporary place.
    564     if (!dst->empty()) {
    565       if (!foster_parent) {
    566         foster_parent = model->AddFolder(model->other_node(),
    567                                          model->other_node()->child_count(),
    568                                          base::string16());
    569         if (!foster_parent) {
    570           syncer::SyncError error(FROM_HERE,
    571                                   syncer::SyncError::DATATYPE_ERROR,
    572                                   "Failed to create foster parent",
    573                                   syncer::BOOKMARKS);
    574           error_handler()->OnSingleDataTypeUnrecoverableError(error);
    575           return;
    576         }
    577       }
    578       for (int i = dst->child_count() - 1; i >= 0; --i) {
    579         model->Move(dst->GetChild(i), foster_parent,
    580                     foster_parent->child_count());
    581       }
    582     }
    583     DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
    584 
    585     model_associator_->Disassociate(it->id);
    586 
    587     const BookmarkNode* parent = dst->parent();
    588     int index = parent->GetIndexOf(dst);
    589     if (index > -1)
    590       model->Remove(parent, index);
    591   }
    592 
    593   // A map to keep track of some reordering work we defer until later.
    594   std::multimap<int, const BookmarkNode*> to_reposition;
    595 
    596   syncer::ReadNode synced_bookmarks(trans);
    597   int64 synced_bookmarks_id = syncer::kInvalidId;
    598   if (synced_bookmarks.InitByTagLookupForBookmarks(kMobileBookmarksTag) ==
    599       syncer::BaseNode::INIT_OK) {
    600     synced_bookmarks_id = synced_bookmarks.GetId();
    601   }
    602 
    603   // Continue iterating where the previous loop left off.
    604   for ( ; it != changes.Get().end(); ++it) {
    605     const BookmarkNode* dst =
    606         model_associator_->GetChromeNodeFromSyncId(it->id);
    607 
    608     // Ignore changes to the permanent top-level nodes.  We only care about
    609     // their children.
    610     if (model->is_permanent_node(dst))
    611       continue;
    612 
    613     // Because the Synced Bookmarks node can be created server side, it's
    614     // possible it'll arrive at the client as an update. In that case it won't
    615     // have been associated at startup, the GetChromeNodeFromSyncId call above
    616     // will return NULL, and we won't detect it as a permanent node, resulting
    617     // in us trying to create it here (which will fail). Therefore, we add
    618     // special logic here just to detect the Synced Bookmarks folder.
    619     if (synced_bookmarks_id != syncer::kInvalidId &&
    620         it->id == synced_bookmarks_id) {
    621       // This is a newly created Synced Bookmarks node. Associate it.
    622       model_associator_->Associate(model->mobile_node(), it->id);
    623       continue;
    624     }
    625 
    626     DCHECK_NE(it->action, ChangeRecord::ACTION_DELETE)
    627         << "We should have passed all deletes by this point.";
    628 
    629     syncer::ReadNode src(trans);
    630     if (src.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) {
    631       syncer::SyncError error(FROM_HERE,
    632                               syncer::SyncError::DATATYPE_ERROR,
    633                               "Failed to load sync node",
    634                               syncer::BOOKMARKS);
    635       error_handler()->OnSingleDataTypeUnrecoverableError(error);
    636       return;
    637     }
    638 
    639     const BookmarkNode* parent =
    640         model_associator_->GetChromeNodeFromSyncId(src.GetParentId());
    641     if (!parent) {
    642       LOG(ERROR) << "Could not find parent of node being added/updated."
    643         << " Node title: " << src.GetTitle()
    644         << ", parent id = " << src.GetParentId();
    645       continue;
    646     }
    647 
    648     if (dst) {
    649       DCHECK(it->action == ChangeRecord::ACTION_UPDATE)
    650           << "ACTION_UPDATE should be seen if and only if the node is known.";
    651       UpdateBookmarkWithSyncData(src, model, dst, profile_);
    652 
    653       // Move all modified entries to the right.  We'll fix it later.
    654       model->Move(dst, parent, parent->child_count());
    655     } else {
    656       DCHECK(it->action == ChangeRecord::ACTION_ADD)
    657           << "ACTION_ADD should be seen if and only if the node is unknown.";
    658 
    659       dst = CreateBookmarkNode(&src,
    660                                parent,
    661                                model,
    662                                profile_,
    663                                parent->child_count());
    664       if (!dst) {
    665         // We ignore bookmarks we can't add. Chances are this is caused by
    666         // a bookmark that was not fully associated.
    667         LOG(ERROR) << "Failed to create bookmark node with title "
    668                    << src.GetTitle() + " and url "
    669                    << src.GetBookmarkSpecifics().url();
    670         continue;
    671       }
    672       model_associator_->Associate(dst, src.GetId());
    673     }
    674 
    675     to_reposition.insert(std::make_pair(src.GetPositionIndex(), dst));
    676     bookmark_model_->SetNodeSyncTransactionVersion(dst, model_version);
    677   }
    678 
    679   // When we added or updated bookmarks in the previous loop, we placed them to
    680   // the far right position.  Now we iterate over all these modified items in
    681   // sync order, left to right, moving them into their proper positions.
    682   for (std::multimap<int, const BookmarkNode*>::iterator it =
    683        to_reposition.begin(); it != to_reposition.end(); ++it) {
    684     const BookmarkNode* parent = it->second->parent();
    685     model->Move(it->second, parent, it->first);
    686   }
    687 
    688   // Clean up the temporary node.
    689   if (foster_parent) {
    690     // There should be no nodes left under the foster parent.
    691     DCHECK_EQ(foster_parent->child_count(), 0);
    692     model->Remove(foster_parent->parent(),
    693                   foster_parent->parent()->GetIndexOf(foster_parent));
    694     foster_parent = NULL;
    695   }
    696 
    697   // The visibility of the mobile node may need to change.
    698   model_associator_->UpdatePermanentNodeVisibility();
    699 
    700   // Notify UI intensive observers of BookmarkModel that all updates have been
    701   // applied, and that they may now be consumed. This prevents issues like the
    702   // one described in crbug.com/281562, where old and new items on the bookmarks
    703   // bar would overlap.
    704   model->EndExtensiveChanges();
    705 
    706   // We are now ready to hear about bookmarks changes again.
    707   model->AddObserver(this);
    708 
    709   // All changes are applied in bookmark model. Set transaction version on
    710   // bookmark model to mark as synced.
    711   model->SetNodeSyncTransactionVersion(model->root_node(), model_version);
    712 }
    713 
    714 // Static.
    715 // Update a bookmark node with specified sync data.
    716 void BookmarkChangeProcessor::UpdateBookmarkWithSyncData(
    717     const syncer::BaseNode& sync_node,
    718     BookmarkModel* model,
    719     const BookmarkNode* node,
    720     Profile* profile) {
    721   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
    722   const sync_pb::BookmarkSpecifics& specifics =
    723       sync_node.GetBookmarkSpecifics();
    724   if (!sync_node.GetIsFolder())
    725     model->SetURL(node, GURL(specifics.url()));
    726   model->SetTitle(node, base::UTF8ToUTF16(sync_node.GetTitle()));
    727   if (specifics.has_creation_time_us()) {
    728     model->SetDateAdded(
    729         node,
    730         base::Time::FromInternalValue(specifics.creation_time_us()));
    731   }
    732   SetBookmarkFavicon(&sync_node, node, model, profile);
    733   model->SetNodeMetaInfoMap(node, *GetBookmarkMetaInfo(&sync_node));
    734 }
    735 
    736 // static
    737 void BookmarkChangeProcessor::UpdateTransactionVersion(
    738     int64 new_version,
    739     BookmarkModel* model,
    740     const std::vector<const BookmarkNode*>& nodes) {
    741   if (new_version != syncer::syncable::kInvalidTransactionVersion) {
    742     model->SetNodeSyncTransactionVersion(model->root_node(), new_version);
    743     for (size_t i = 0; i < nodes.size(); ++i) {
    744       model->SetNodeSyncTransactionVersion(nodes[i], new_version);
    745     }
    746   }
    747 }
    748 
    749 // static
    750 // Creates a bookmark node under the given parent node from the given sync
    751 // node. Returns the newly created node.
    752 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
    753     syncer::BaseNode* sync_node,
    754     const BookmarkNode* parent,
    755     BookmarkModel* model,
    756     Profile* profile,
    757     int index) {
    758   DCHECK(parent);
    759 
    760   const BookmarkNode* node;
    761   if (sync_node->GetIsFolder()) {
    762     node =
    763         model->AddFolderWithMetaInfo(parent,
    764                                      index,
    765                                      base::UTF8ToUTF16(sync_node->GetTitle()),
    766                                      GetBookmarkMetaInfo(sync_node).get());
    767   } else {
    768     // 'creation_time_us' was added in m24. Assume a time of 0 means now.
    769     const sync_pb::BookmarkSpecifics& specifics =
    770         sync_node->GetBookmarkSpecifics();
    771     const int64 create_time_internal = specifics.creation_time_us();
    772     base::Time create_time = (create_time_internal == 0) ?
    773         base::Time::Now() : base::Time::FromInternalValue(create_time_internal);
    774     node = model->AddURLWithCreationTimeAndMetaInfo(
    775         parent,
    776         index,
    777         base::UTF8ToUTF16(sync_node->GetTitle()),
    778         GURL(specifics.url()),
    779         create_time,
    780         GetBookmarkMetaInfo(sync_node).get());
    781     if (node)
    782       SetBookmarkFavicon(sync_node, node, model, profile);
    783   }
    784 
    785   return node;
    786 }
    787 
    788 // static
    789 // Sets the favicon of the given bookmark node from the given sync node.
    790 bool BookmarkChangeProcessor::SetBookmarkFavicon(
    791     const syncer::BaseNode* sync_node,
    792     const BookmarkNode* bookmark_node,
    793     BookmarkModel* bookmark_model,
    794     Profile* profile) {
    795   const sync_pb::BookmarkSpecifics& specifics =
    796       sync_node->GetBookmarkSpecifics();
    797   const std::string& icon_bytes_str = specifics.favicon();
    798   if (icon_bytes_str.empty())
    799     return false;
    800 
    801   scoped_refptr<base::RefCountedString> icon_bytes(
    802       new base::RefCountedString());
    803   icon_bytes->data().assign(icon_bytes_str);
    804   GURL icon_url(specifics.icon_url());
    805 
    806   // Old clients may not be syncing the favicon URL. If the icon URL is not
    807   // synced, use the page URL as a fake icon URL as it is guaranteed to be
    808   // unique.
    809   if (icon_url.is_empty())
    810     icon_url = bookmark_node->url();
    811 
    812   ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
    813 
    814   return true;
    815 }
    816 
    817 // static
    818 scoped_ptr<BookmarkNode::MetaInfoMap>
    819 BookmarkChangeProcessor::GetBookmarkMetaInfo(
    820     const syncer::BaseNode* sync_node) {
    821   const sync_pb::BookmarkSpecifics& specifics =
    822       sync_node->GetBookmarkSpecifics();
    823   scoped_ptr<BookmarkNode::MetaInfoMap> meta_info_map(
    824       new BookmarkNode::MetaInfoMap);
    825   for (int i = 0; i < specifics.meta_info_size(); ++i) {
    826     (*meta_info_map)[specifics.meta_info(i).key()] =
    827         specifics.meta_info(i).value();
    828   }
    829   return meta_info_map.Pass();
    830 }
    831 
    832 // static
    833 void BookmarkChangeProcessor::SetSyncNodeMetaInfo(
    834     const BookmarkNode* node,
    835     syncer::WriteNode* sync_node) {
    836   sync_pb::BookmarkSpecifics specifics = sync_node->GetBookmarkSpecifics();
    837   specifics.clear_meta_info();
    838   const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
    839   if (meta_info_map) {
    840     for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map->begin();
    841         it != meta_info_map->end(); ++it) {
    842       sync_pb::MetaInfo* meta_info = specifics.add_meta_info();
    843       meta_info->set_key(it->first);
    844       meta_info->set_value(it->second);
    845     }
    846   }
    847   sync_node->SetBookmarkSpecifics(specifics);
    848 }
    849 
    850 // static
    851 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
    852     const BookmarkNode* bookmark_node,
    853     Profile* profile,
    854     const GURL& icon_url,
    855     const scoped_refptr<base::RefCountedMemory>& bitmap_data) {
    856   HistoryService* history =
    857       HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
    858   FaviconService* favicon_service =
    859       FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
    860 
    861   history->AddPageNoVisitForBookmark(bookmark_node->url(),
    862                                      bookmark_node->GetTitle());
    863   // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
    864   // overwrite the cached 2x favicon bitmap. Sync favicons are always
    865   // gfx::kFaviconSize in width and height. Store the favicon into history
    866   // as such.
    867   gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
    868   favicon_service->MergeFavicon(bookmark_node->url(),
    869                                 icon_url,
    870                                 favicon_base::FAVICON,
    871                                 bitmap_data,
    872                                 pixel_size);
    873 }
    874 
    875 // static
    876 void BookmarkChangeProcessor::SetSyncNodeFavicon(
    877     const BookmarkNode* bookmark_node,
    878     BookmarkModel* model,
    879     syncer::WriteNode* sync_node) {
    880   scoped_refptr<base::RefCountedMemory> favicon_bytes(NULL);
    881   EncodeFavicon(bookmark_node, model, &favicon_bytes);
    882   if (favicon_bytes.get() && favicon_bytes->size()) {
    883     sync_pb::BookmarkSpecifics updated_specifics(
    884         sync_node->GetBookmarkSpecifics());
    885     updated_specifics.set_favicon(favicon_bytes->front(),
    886                                   favicon_bytes->size());
    887     updated_specifics.set_icon_url(bookmark_node->icon_url().spec());
    888     sync_node->SetBookmarkSpecifics(updated_specifics);
    889   }
    890 }
    891 
    892 bool BookmarkChangeProcessor::CanSyncNode(const BookmarkNode* node) {
    893   return bookmark_model_->client()->CanSyncNode(node);
    894 }
    895 
    896 }  // namespace browser_sync
    897