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