| // Copyright 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "components/sync_bookmarks/bookmark_change_processor.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <map> | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/containers/stack.h" | 
 | #include "base/location.h" | 
 | #include "base/strings/string16.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "components/bookmarks/browser/bookmark_client.h" | 
 | #include "components/bookmarks/browser/bookmark_model.h" | 
 | #include "components/bookmarks/browser/bookmark_utils.h" | 
 | #include "components/favicon/core/favicon_service.h" | 
 | #include "components/history/core/browser/history_service.h" | 
 | #include "components/sync/driver/sync_client.h" | 
 | #include "components/sync/syncable/change_record.h" | 
 | #include "components/sync/syncable/entry.h"  // TODO(tim): Investigating bug 121587. | 
 | #include "components/sync/syncable/read_node.h" | 
 | #include "components/sync/syncable/syncable_write_transaction.h" | 
 | #include "components/sync/syncable/write_node.h" | 
 | #include "components/sync/syncable/write_transaction.h" | 
 | #include "components/undo/bookmark_undo_service.h" | 
 | #include "components/undo/bookmark_undo_utils.h" | 
 | #include "ui/gfx/favicon_size.h" | 
 | #include "ui/gfx/image/image_util.h" | 
 |  | 
 | using bookmarks::BookmarkModel; | 
 | using bookmarks::BookmarkNode; | 
 | using syncer::ChangeRecord; | 
 | using syncer::ChangeRecordList; | 
 |  | 
 | namespace sync_bookmarks { | 
 |  | 
 | static const char kMobileBookmarksTag[] = "synced_bookmarks"; | 
 |  | 
 | BookmarkChangeProcessor::BookmarkChangeProcessor( | 
 |     syncer::SyncClient* sync_client, | 
 |     BookmarkModelAssociator* model_associator, | 
 |     std::unique_ptr<syncer::DataTypeErrorHandler> err_handler) | 
 |     : syncer::ChangeProcessor(std::move(err_handler)), | 
 |       bookmark_model_(nullptr), | 
 |       sync_client_(sync_client), | 
 |       model_associator_(model_associator) { | 
 |   DCHECK(model_associator); | 
 |   DCHECK(sync_client); | 
 |   DCHECK(error_handler()); | 
 | } | 
 |  | 
 | BookmarkChangeProcessor::~BookmarkChangeProcessor() { | 
 |   if (bookmark_model_) | 
 |     bookmark_model_->RemoveObserver(this); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::StartImpl() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK(!bookmark_model_); | 
 |   bookmark_model_ = sync_client_->GetBookmarkModel(); | 
 |   DCHECK(bookmark_model_->loaded()); | 
 |   bookmark_model_->AddObserver(this); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::UpdateSyncNodeProperties( | 
 |     const BookmarkNode* src, | 
 |     BookmarkModel* model, | 
 |     syncer::WriteNode* dst, | 
 |     syncer::DataTypeErrorHandler* error_handler) { | 
 |   // Set the properties of the item. | 
 |   dst->SetIsFolder(src->is_folder()); | 
 |   dst->SetTitle(base::UTF16ToUTF8(src->GetTitle())); | 
 |   sync_pb::BookmarkSpecifics bookmark_specifics(dst->GetBookmarkSpecifics()); | 
 |   if (!src->is_folder()) { | 
 |     if (!src->url().is_valid()) { | 
 |       // Report the invalid URL and continue. | 
 |       // TODO(stanisc): crbug/482155: Revisit this once the root cause for | 
 |       // invalid URLs is understood. | 
 |       error_handler->CreateAndUploadError( | 
 |           FROM_HERE, "Creating sync bookmark with invalid url " + | 
 |                          src->url().possibly_invalid_spec(), | 
 |           syncer::BOOKMARKS); | 
 |     } | 
 |     bookmark_specifics.set_url(src->url().spec()); | 
 |   } | 
 |   bookmark_specifics.set_creation_time_us(src->date_added().ToInternalValue()); | 
 |   dst->SetBookmarkSpecifics(bookmark_specifics); | 
 |   SetSyncNodeFavicon(src, model, dst); | 
 |   SetSyncNodeMetaInfo(src, dst); | 
 | } | 
 |  | 
 | // static | 
 | void BookmarkChangeProcessor::EncodeFavicon( | 
 |     const BookmarkNode* src, | 
 |     BookmarkModel* model, | 
 |     scoped_refptr<base::RefCountedMemory>* dst) { | 
 |   const gfx::Image& favicon = model->GetFavicon(src); | 
 |  | 
 |   // Check for empty images.  This can happen if the favicon is | 
 |   // still being loaded.  Also avoid syncing touch icons. | 
 |   if (favicon.IsEmpty() || | 
 |       model->GetFaviconType(src) != favicon_base::IconType::kFavicon) | 
 |     return; | 
 |  | 
 |   // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the | 
 |   // sync subsystem. | 
 |   *dst = favicon.As1xPNGBytes(); | 
 | } | 
 |  | 
 | // static | 
 | int BookmarkChangeProcessor::RemoveSyncNodeHierarchy( | 
 |     syncer::WriteTransaction* trans, | 
 |     syncer::WriteNode* sync_node, | 
 |     BookmarkModelAssociator* associator) { | 
 |   // Remove children. | 
 |   int num_removed = RemoveAllChildNodes(trans, sync_node->GetId(), associator); | 
 |   // Remove the node itself. | 
 |   RemoveOneSyncNode(sync_node, associator); | 
 |   return num_removed + 1; | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::RemoveSyncNodeHierarchy( | 
 |     const BookmarkNode* topmost) { | 
 |   int64_t new_version = syncer::syncable::kInvalidTransactionVersion; | 
 |   { | 
 |     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); | 
 |     syncer::WriteNode topmost_sync_node(&trans); | 
 |     if (!model_associator_->InitSyncNodeFromChromeId(topmost->id(), | 
 |                                                      &topmost_sync_node)) { | 
 |       syncer::SyncError error(FROM_HERE, | 
 |                               syncer::SyncError::DATATYPE_ERROR, | 
 |                               "Failed to init sync node from chrome node", | 
 |                               syncer::BOOKMARKS); | 
 |       error_handler()->OnUnrecoverableError(error); | 
 |       return; | 
 |     } | 
 |     RemoveSyncNodeHierarchy(&trans, &topmost_sync_node, model_associator_); | 
 |   } | 
 |  | 
 |   // Don't need to update versions of deleted nodes. | 
 |   UpdateTransactionVersion(new_version, bookmark_model_, | 
 |                            std::vector<const BookmarkNode*>()); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::RemoveAllSyncNodes() { | 
 |   int64_t new_version = syncer::syncable::kInvalidTransactionVersion; | 
 |   { | 
 |     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); | 
 |  | 
 |     int64_t bookmark_bar_node_sync_id = | 
 |         model_associator_->GetSyncIdFromChromeId( | 
 |             bookmark_model_->bookmark_bar_node()->id()); | 
 |     DCHECK_NE(syncer::kInvalidId, bookmark_bar_node_sync_id); | 
 |     RemoveAllChildNodes(&trans, bookmark_bar_node_sync_id, model_associator_); | 
 |  | 
 |     int64_t other_node_sync_id = model_associator_->GetSyncIdFromChromeId( | 
 |         bookmark_model_->other_node()->id()); | 
 |     DCHECK_NE(syncer::kInvalidId, other_node_sync_id); | 
 |     RemoveAllChildNodes(&trans, other_node_sync_id, model_associator_); | 
 |  | 
 |     // Remove mobile bookmarks node only if it is present. | 
 |     int64_t mobile_node_sync_id = model_associator_->GetSyncIdFromChromeId( | 
 |         bookmark_model_->mobile_node()->id()); | 
 |     if (mobile_node_sync_id != syncer::kInvalidId) { | 
 |       RemoveAllChildNodes(&trans, mobile_node_sync_id, model_associator_); | 
 |     } | 
 |  | 
 |     // Note: the root node may have additional extra nodes. Currently none of | 
 |     // them are meant to sync. | 
 |   } | 
 |  | 
 |   // Don't need to update versions of deleted nodes. | 
 |   UpdateTransactionVersion(new_version, bookmark_model_, | 
 |                            std::vector<const BookmarkNode*>()); | 
 | } | 
 |  | 
 | // static | 
 | int BookmarkChangeProcessor::RemoveAllChildNodes( | 
 |     syncer::WriteTransaction* trans, | 
 |     int64_t topmost_sync_id, | 
 |     BookmarkModelAssociator* associator) { | 
 |   // Do a DFS and delete all the child sync nodes, use sync id instead of | 
 |   // bookmark node ids since the bookmark nodes may already be deleted. | 
 |   // The equivalent recursive version of this iterative DFS: | 
 |   // remove_all_children(node_id, topmost_node_id): | 
 |   //    node.initByIdLookup(node_id) | 
 |   //    while(node.GetFirstChildId() != syncer::kInvalidId) | 
 |   //      remove_all_children(node.GetFirstChildId(), topmost_node_id) | 
 |   //    if(node_id != topmost_node_id) | 
 |   //      delete node | 
 |  | 
 |   int num_removed = 0; | 
 |   base::stack<int64_t> dfs_sync_id_stack; | 
 |   // Push the topmost node. | 
 |   dfs_sync_id_stack.push(topmost_sync_id); | 
 |   while (!dfs_sync_id_stack.empty()) { | 
 |     const int64_t sync_node_id = dfs_sync_id_stack.top(); | 
 |     syncer::WriteNode node(trans); | 
 |     node.InitByIdLookup(sync_node_id); | 
 |     if (!node.GetIsFolder() || node.GetFirstChildId() == syncer::kInvalidId) { | 
 |       // All children of the node has been processed, delete the node and | 
 |       // pop it off the stack. | 
 |       dfs_sync_id_stack.pop(); | 
 |       // Do not delete the topmost node. | 
 |       if (sync_node_id != topmost_sync_id) { | 
 |         RemoveOneSyncNode(&node, associator); | 
 |         num_removed++; | 
 |       } else { | 
 |         // if we are processing topmost node, all other nodes must be processed | 
 |         // the stack should be empty. | 
 |         DCHECK(dfs_sync_id_stack.empty()); | 
 |       } | 
 |     } else { | 
 |       int64_t child_id = node.GetFirstChildId(); | 
 |       if (child_id != syncer::kInvalidId) { | 
 |         dfs_sync_id_stack.push(child_id); | 
 |       } | 
 |     } | 
 |   } | 
 |   return num_removed; | 
 | } | 
 |  | 
 | // static | 
 | void BookmarkChangeProcessor::RemoveOneSyncNode( | 
 |     syncer::WriteNode* sync_node, | 
 |     BookmarkModelAssociator* associator) { | 
 |   // This node should have no children. | 
 |   DCHECK(!sync_node->HasChildren()); | 
 |   // Remove association and delete the sync node. | 
 |   associator->Disassociate(sync_node->GetId()); | 
 |   sync_node->Tombstone(); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::CreateOrUpdateSyncNode(const BookmarkNode* node) { | 
 |   if (!CanSyncNode(node)) { | 
 |     NOTREACHED(); | 
 |     return; | 
 |   } | 
 |  | 
 |   int64_t new_version = syncer::syncable::kInvalidTransactionVersion; | 
 |   int64_t sync_id = syncer::kInvalidId; | 
 |   { | 
 |     // Acquire a scoped write lock via a transaction. | 
 |     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); | 
 |     sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); | 
 |     if (sync_id != syncer::kInvalidId) { | 
 |       UpdateSyncNode( | 
 |           node, bookmark_model_, &trans, model_associator_, error_handler()); | 
 |     } else { | 
 |       const BookmarkNode* parent = node->parent(); | 
 |       int index = parent->GetIndexOf(node); | 
 |       sync_id = CreateSyncNode(parent, | 
 |                                bookmark_model_, | 
 |                                index, | 
 |                                &trans, | 
 |                                model_associator_, | 
 |                                error_handler()); | 
 |     } | 
 |   } | 
 |  | 
 |   if (syncer::kInvalidId != sync_id) { | 
 |     // Siblings of added node in sync DB will also be updated to reflect new | 
 |     // PREV_ID/NEXT_ID and thus get a new version. But we only update version | 
 |     // of added node here. After switching to ordinals for positioning, | 
 |     // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated. | 
 |     UpdateTransactionVersion(new_version, bookmark_model_, | 
 |                              std::vector<const BookmarkNode*>(1, node)); | 
 |   } | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model, | 
 |                                                   bool ids_reassigned) { | 
 |   NOTREACHED(); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) { | 
 |   NOTREACHED(); | 
 |   bookmark_model_ = nullptr; | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model, | 
 |                                                 const BookmarkNode* parent, | 
 |                                                 int index) { | 
 |   DCHECK(share_handle()); | 
 |   const BookmarkNode* node = parent->GetChild(index); | 
 |   if (CanSyncNode(node)) | 
 |     CreateOrUpdateSyncNode(node); | 
 | } | 
 |  | 
 | // static | 
 | int64_t BookmarkChangeProcessor::CreateSyncNode( | 
 |     const BookmarkNode* parent, | 
 |     BookmarkModel* model, | 
 |     int index, | 
 |     syncer::WriteTransaction* trans, | 
 |     BookmarkModelAssociator* associator, | 
 |     syncer::DataTypeErrorHandler* error_handler) { | 
 |   const BookmarkNode* child = parent->GetChild(index); | 
 |   DCHECK(child); | 
 |  | 
 |   // Create a WriteNode container to hold the new node. | 
 |   syncer::WriteNode sync_child(trans); | 
 |  | 
 |   // Actually create the node with the appropriate initial position. | 
 |   if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) { | 
 |     syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, | 
 |                             "Failed to create sync node.", syncer::BOOKMARKS); | 
 |     error_handler->OnUnrecoverableError(error); | 
 |     return syncer::kInvalidId; | 
 |   } | 
 |  | 
 |   UpdateSyncNodeProperties(child, model, &sync_child, error_handler); | 
 |  | 
 |   // Associate the ID from the sync domain with the bookmark node, so that we | 
 |   // can refer back to this item later. | 
 |   associator->Associate(child, sync_child); | 
 |  | 
 |   return sync_child.GetId(); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::OnWillRemoveBookmarks(BookmarkModel* model, | 
 |                                                     const BookmarkNode* parent, | 
 |                                                     int old_index, | 
 |                                                     const BookmarkNode* node) { | 
 |   if (CanSyncNode(node)) | 
 |     RemoveSyncNodeHierarchy(node); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkNodeRemoved( | 
 |     BookmarkModel* model, | 
 |     const BookmarkNode* parent, | 
 |     int old_index, | 
 |     const BookmarkNode* node, | 
 |     const std::set<GURL>& no_longer_bookmarked) { | 
 |   // All the work should have already been done in OnWillRemoveBookmarks. | 
 |   DCHECK_EQ(syncer::kInvalidId, | 
 |             model_associator_->GetSyncIdFromChromeId(node->id())); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved( | 
 |     BookmarkModel* model, | 
 |     const std::set<GURL>& removed_urls) { | 
 |   RemoveAllSyncNodes(); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model, | 
 |                                                   const BookmarkNode* node) { | 
 |   if (!CanSyncNode(node)) | 
 |     return; | 
 |   // We shouldn't see changes to the top-level nodes. | 
 |   if (model->is_permanent_node(node)) { | 
 |     NOTREACHED() << "Saw update to permanent node!"; | 
 |     return; | 
 |   } | 
 |   CreateOrUpdateSyncNode(node); | 
 | } | 
 |  | 
 | // Static. | 
 | int64_t BookmarkChangeProcessor::UpdateSyncNode( | 
 |     const BookmarkNode* node, | 
 |     BookmarkModel* model, | 
 |     syncer::WriteTransaction* trans, | 
 |     BookmarkModelAssociator* associator, | 
 |     syncer::DataTypeErrorHandler* error_handler) { | 
 |   // Lookup the sync node that's associated with |node|. | 
 |   syncer::WriteNode sync_node(trans); | 
 |   if (!associator->InitSyncNodeFromChromeId(node->id(), &sync_node)) { | 
 |     syncer::SyncError error(FROM_HERE, | 
 |                             syncer::SyncError::DATATYPE_ERROR, | 
 |                             "Failed to init sync node from chrome node", | 
 |                             syncer::BOOKMARKS); | 
 |     error_handler->OnUnrecoverableError(error); | 
 |     return syncer::kInvalidId; | 
 |   } | 
 |   UpdateSyncNodeProperties(node, model, &sync_node, error_handler); | 
 |   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder()); | 
 |   DCHECK_EQ(associator->GetChromeNodeFromSyncId(sync_node.GetParentId()), | 
 |             node->parent()); | 
 |   DCHECK_EQ(node->parent()->GetIndexOf(node), sync_node.GetPositionIndex()); | 
 |   return sync_node.GetId(); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkMetaInfoChanged( | 
 |     BookmarkModel* model, const BookmarkNode* node) { | 
 |   BookmarkNodeChanged(model, node); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model, | 
 |       const BookmarkNode* old_parent, int old_index, | 
 |       const BookmarkNode* new_parent, int new_index) { | 
 |   const BookmarkNode* child = new_parent->GetChild(new_index); | 
 |  | 
 |   if (!CanSyncNode(child)) | 
 |     return; | 
 |  | 
 |   // We shouldn't see changes to the top-level nodes. | 
 |   if (model->is_permanent_node(child)) { | 
 |     NOTREACHED() << "Saw update to permanent node!"; | 
 |     return; | 
 |   } | 
 |  | 
 |   int64_t new_version = syncer::syncable::kInvalidTransactionVersion; | 
 |   { | 
 |     // Acquire a scoped write lock via a transaction. | 
 |     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); | 
 |  | 
 |     // Lookup the sync node that's associated with |child|. | 
 |     syncer::WriteNode sync_node(&trans); | 
 |     if (!model_associator_->InitSyncNodeFromChromeId(child->id(), &sync_node)) { | 
 |       syncer::SyncError error(FROM_HERE, | 
 |                               syncer::SyncError::DATATYPE_ERROR, | 
 |                               "Failed to init sync node from chrome node", | 
 |                               syncer::BOOKMARKS); | 
 |       error_handler()->OnUnrecoverableError(error); | 
 |       return; | 
 |     } | 
 |  | 
 |     if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node, | 
 |                        model_associator_)) { | 
 |       syncer::SyncError error(FROM_HERE, | 
 |                               syncer::SyncError::DATATYPE_ERROR, | 
 |                               "Failed to place sync node", | 
 |                               syncer::BOOKMARKS); | 
 |       error_handler()->OnUnrecoverableError(error); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   UpdateTransactionVersion(new_version, model, | 
 |                            std::vector<const BookmarkNode*>(1, child)); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkNodeFaviconChanged( | 
 |     BookmarkModel* model, | 
 |     const BookmarkNode* node) { | 
 |   if (!CanSyncNode(node)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // We shouldn't see changes to the top-level nodes. | 
 |   if (model->is_permanent_node(node)) { | 
 |     NOTREACHED() << "Saw Favicon update to permanent node!"; | 
 |     return; | 
 |   } | 
 |  | 
 |   // Ignore favicons that are being loaded. | 
 |   if (!node->is_favicon_loaded()) { | 
 |     // Sutble way to trigger a load of the favicon. | 
 |     model->GetFavicon(node); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Ignore updates to favicon if model associator doesn't know about this | 
 |   // bookmark node. | 
 |   if (model_associator_->GetSyncIdFromChromeId(node->id()) == | 
 |       syncer::kInvalidId) { | 
 |     return; | 
 |   } | 
 |  | 
 |   CreateOrUpdateSyncNode(node); | 
 | } | 
 |  | 
 | void BookmarkChangeProcessor::BookmarkNodeChildrenReordered( | 
 |     BookmarkModel* model, const BookmarkNode* node) { | 
 |   if (!CanSyncNode(node)) | 
 |     return; | 
 |   int64_t new_version = syncer::syncable::kInvalidTransactionVersion; | 
 |   std::vector<const BookmarkNode*> children; | 
 |   { | 
 |     // Acquire a scoped write lock via a transaction. | 
 |     syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version); | 
 |  | 
 |     // The given node's children got reordered. We need to reorder all the | 
 |     // children of the corresponding sync node. | 
 |     for (int i = 0; i < node->child_count(); ++i) { | 
 |       const BookmarkNode* child = node->GetChild(i); | 
 |       children.push_back(child); | 
 |  | 
 |       syncer::WriteNode sync_child(&trans); | 
 |       if (!model_associator_->InitSyncNodeFromChromeId(child->id(), | 
 |                                                        &sync_child)) { | 
 |         syncer::SyncError error(FROM_HERE, | 
 |                                 syncer::SyncError::DATATYPE_ERROR, | 
 |                                 "Failed to init sync node from chrome node", | 
 |                                 syncer::BOOKMARKS); | 
 |         error_handler()->OnUnrecoverableError(error); | 
 |         return; | 
 |       } | 
 |       DCHECK_EQ(sync_child.GetParentId(), | 
 |                 model_associator_->GetSyncIdFromChromeId(node->id())); | 
 |  | 
 |       if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child, | 
 |                          model_associator_)) { | 
 |         syncer::SyncError error(FROM_HERE, | 
 |                                 syncer::SyncError::DATATYPE_ERROR, | 
 |                                 "Failed to place sync node", | 
 |                                 syncer::BOOKMARKS); | 
 |         error_handler()->OnUnrecoverableError(error); | 
 |         return; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // TODO(haitaol): Filter out children that didn't actually change. | 
 |   UpdateTransactionVersion(new_version, model, children); | 
 | } | 
 |  | 
 | // static | 
 | bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation, | 
 |       const BookmarkNode* parent, int index, syncer::WriteTransaction* trans, | 
 |       syncer::WriteNode* dst, BookmarkModelAssociator* associator) { | 
 |   syncer::ReadNode sync_parent(trans); | 
 |   if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) { | 
 |     LOG(WARNING) << "Parent lookup failed"; | 
 |     return false; | 
 |   } | 
 |  | 
 |   bool success = false; | 
 |   if (index == 0) { | 
 |     // Insert into first position. | 
 |     success = (operation == CREATE) | 
 |                   ? dst->InitBookmarkByCreation(sync_parent, nullptr) | 
 |                   : dst->SetPosition(sync_parent, nullptr); | 
 |     if (success) { | 
 |       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId()); | 
 |       DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId()); | 
 |       DCHECK_EQ(dst->GetPredecessorId(), syncer::kInvalidId); | 
 |     } | 
 |   } else { | 
 |     // Find the bookmark model predecessor, and insert after it. | 
 |     const BookmarkNode* prev = parent->GetChild(index - 1); | 
 |     syncer::ReadNode sync_prev(trans); | 
 |     if (!associator->InitSyncNodeFromChromeId(prev->id(), &sync_prev)) { | 
 |       LOG(WARNING) << "Predecessor lookup failed"; | 
 |       return false; | 
 |     } | 
 |     success = (operation == CREATE) ? | 
 |         dst->InitBookmarkByCreation(sync_parent, &sync_prev) : | 
 |         dst->SetPosition(sync_parent, &sync_prev); | 
 |     if (success) { | 
 |       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId()); | 
 |       DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId()); | 
 |       DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId()); | 
 |     } | 
 |   } | 
 |   return success; | 
 | } | 
 |  | 
 | // ApplyModelChanges is called by the sync backend after changes have been made | 
 | // to the sync engine's model.  Apply these changes to the browser bookmark | 
 | // model. | 
 | void BookmarkChangeProcessor::ApplyChangesFromSyncModel( | 
 |     const syncer::BaseTransaction* trans, | 
 |     int64_t model_version, | 
 |     const syncer::ImmutableChangeRecordList& changes) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   // A note about ordering.  Sync backend is responsible for ordering the change | 
 |   // records in the following order: | 
 |   // | 
 |   // 1. Deletions, from leaves up to parents. | 
 |   // 2. Existing items with synced parents & predecessors. | 
 |   // 3. New items with synced parents & predecessors. | 
 |   // 4. Items with parents & predecessors in the list. | 
 |   // 5. Repeat #4 until all items are in the list. | 
 |   // | 
 |   // "Predecessor" here means the previous item within a given folder; an item | 
 |   // in the first position is always said to have a synced predecessor. | 
 |   // For the most part, applying these changes in the order given will yield | 
 |   // the correct result.  There is one exception, however: for items that are | 
 |   // moved away from a folder that is being deleted, we will process the delete | 
 |   // before the move.  Since deletions in the bookmark model propagate from | 
 |   // parent to child, we must move them to a temporary location. | 
 |   BookmarkModel* model = bookmark_model_; | 
 |  | 
 |   // We are going to make changes to the bookmarks model, but don't want to end | 
 |   // up in a feedback loop, so remove ourselves as an observer while applying | 
 |   // changes. | 
 |   model->RemoveObserver(this); | 
 |  | 
 |   // Changes made to the bookmark model due to sync should not be undoable. | 
 |   ScopedSuspendBookmarkUndo suspend_undo( | 
 |       sync_client_->GetBookmarkUndoServiceIfExists()); | 
 |  | 
 |   // Notify UI intensive observers of BookmarkModel that we are about to make | 
 |   // potentially significant changes to it, so the updates may be batched. For | 
 |   // example, on Mac, the bookmarks bar displays animations when bookmark items | 
 |   // are added or deleted. | 
 |   model->BeginExtensiveChanges(); | 
 |  | 
 |   // A parent to hold nodes temporarily orphaned by parent deletion.  It is | 
 |   // created only if it is needed. | 
 |   const BookmarkNode* foster_parent = nullptr; | 
 |  | 
 |   // Iterate over the deletions, which are always at the front of the list. | 
 |   ChangeRecordList::const_iterator it; | 
 |   for (it = changes.Get().begin(); | 
 |        it != changes.Get().end() && it->action == ChangeRecord::ACTION_DELETE; | 
 |        ++it) { | 
 |     const BookmarkNode* dst = | 
 |         model_associator_->GetChromeNodeFromSyncId(it->id); | 
 |  | 
 |     // Ignore changes to the permanent top-level nodes.  We only care about | 
 |     // their children. | 
 |     if (model->is_permanent_node(dst)) | 
 |       continue; | 
 |  | 
 |     // Can't do anything if we can't find the chrome node. | 
 |     if (!dst) | 
 |       continue; | 
 |  | 
 |     // Children of a deleted node should not be deleted; they may be | 
 |     // reparented by a later change record.  Move them to a temporary place. | 
 |     if (!dst->empty()) { | 
 |       if (!foster_parent) { | 
 |         foster_parent = model->AddFolder(model->other_node(), | 
 |                                          model->other_node()->child_count(), | 
 |                                          base::string16()); | 
 |         if (!foster_parent) { | 
 |           syncer::SyncError error(FROM_HERE, | 
 |                                   syncer::SyncError::DATATYPE_ERROR, | 
 |                                   "Failed to create foster parent", | 
 |                                   syncer::BOOKMARKS); | 
 |           error_handler()->OnUnrecoverableError(error); | 
 |           return; | 
 |         } | 
 |       } | 
 |       for (int i = dst->child_count() - 1; i >= 0; --i) { | 
 |         model->Move(dst->GetChild(i), foster_parent, | 
 |                     foster_parent->child_count()); | 
 |       } | 
 |     } | 
 |     DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children"; | 
 |  | 
 |     model_associator_->Disassociate(it->id); | 
 |  | 
 |     const BookmarkNode* parent = dst->parent(); | 
 |     int index = parent->GetIndexOf(dst); | 
 |     if (index > -1) | 
 |       model->Remove(parent->GetChild(index)); | 
 |   } | 
 |  | 
 |   // A map to keep track of some reordering work we defer until later. | 
 |   std::multimap<int, const BookmarkNode*> to_reposition; | 
 |  | 
 |   syncer::ReadNode synced_bookmarks(trans); | 
 |   int64_t synced_bookmarks_id = syncer::kInvalidId; | 
 |   if (synced_bookmarks.InitByTagLookupForBookmarks(kMobileBookmarksTag) == | 
 |       syncer::BaseNode::INIT_OK) { | 
 |     synced_bookmarks_id = synced_bookmarks.GetId(); | 
 |   } | 
 |  | 
 |   // Continue iterating where the previous loop left off. | 
 |   for ( ; it != changes.Get().end(); ++it) { | 
 |     const BookmarkNode* dst = | 
 |         model_associator_->GetChromeNodeFromSyncId(it->id); | 
 |  | 
 |     // Ignore changes to the permanent top-level nodes.  We only care about | 
 |     // their children. | 
 |     if (model->is_permanent_node(dst)) | 
 |       continue; | 
 |  | 
 |     // Because the Synced Bookmarks node can be created server side, it's | 
 |     // possible it'll arrive at the client as an update. In that case it won't | 
 |     // have been associated at startup, the GetChromeNodeFromSyncId call above | 
 |     // will return null, and we won't detect it as a permanent node, resulting | 
 |     // in us trying to create it here (which will fail). Therefore, we add | 
 |     // special logic here just to detect the Synced Bookmarks folder. | 
 |     if (synced_bookmarks_id != syncer::kInvalidId && | 
 |         it->id == synced_bookmarks_id) { | 
 |       // This is a newly created Synced Bookmarks node. Associate it. | 
 |       model_associator_->Associate(model->mobile_node(), synced_bookmarks); | 
 |       continue; | 
 |     } | 
 |  | 
 |     DCHECK_NE(it->action, ChangeRecord::ACTION_DELETE) | 
 |         << "We should have passed all deletes by this point."; | 
 |  | 
 |     syncer::ReadNode src(trans); | 
 |     if (src.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) { | 
 |       syncer::SyncError error(FROM_HERE, | 
 |                               syncer::SyncError::DATATYPE_ERROR, | 
 |                               "Failed to load sync node", | 
 |                               syncer::BOOKMARKS); | 
 |       error_handler()->OnUnrecoverableError(error); | 
 |       return; | 
 |     } | 
 |  | 
 |     const BookmarkNode* parent = | 
 |         model_associator_->GetChromeNodeFromSyncId(src.GetParentId()); | 
 |     if (!parent) { | 
 |       LOG(ERROR) << "Could not find parent of node being added/updated." | 
 |         << " Node title: " << src.GetTitle() | 
 |         << ", parent id = " << src.GetParentId(); | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (dst) { | 
 |       DCHECK(it->action == ChangeRecord::ACTION_UPDATE) | 
 |           << "ACTION_UPDATE should be seen if and only if the node is known."; | 
 |       UpdateBookmarkWithSyncData(src, model, dst, sync_client_); | 
 |  | 
 |       // Move all modified entries to the right.  We'll fix it later. | 
 |       model->Move(dst, parent, parent->child_count()); | 
 |     } else { | 
 |       DCHECK(it->action == ChangeRecord::ACTION_ADD) | 
 |           << "ACTION_ADD should be seen if and only if the node is unknown."; | 
 |  | 
 |       dst = CreateBookmarkNode(&src, parent, model, sync_client_, | 
 |                                parent->child_count()); | 
 |       if (!dst) { | 
 |         // We ignore bookmarks we can't add. Chances are this is caused by | 
 |         // a bookmark that was not fully associated. | 
 |         LOG(ERROR) << "Failed to create bookmark node with title " | 
 |                    << src.GetTitle() + " and url " | 
 |                    << src.GetBookmarkSpecifics().url(); | 
 |         continue; | 
 |       } | 
 |       model_associator_->Associate(dst, src); | 
 |     } | 
 |  | 
 |     to_reposition.insert(std::make_pair(src.GetPositionIndex(), dst)); | 
 |     bookmark_model_->SetNodeSyncTransactionVersion(dst, model_version); | 
 |   } | 
 |  | 
 |   // When we added or updated bookmarks in the previous loop, we placed them to | 
 |   // the far right position.  Now we iterate over all these modified items in | 
 |   // sync order, left to right, moving them into their proper positions. | 
 |   for (std::multimap<int, const BookmarkNode*>::iterator it = | 
 |        to_reposition.begin(); it != to_reposition.end(); ++it) { | 
 |     const BookmarkNode* parent = it->second->parent(); | 
 |     model->Move(it->second, parent, it->first); | 
 |   } | 
 |  | 
 |   // Clean up the temporary node. | 
 |   if (foster_parent) { | 
 |     // There should be no nodes left under the foster parent. | 
 |     DCHECK_EQ(foster_parent->child_count(), 0); | 
 |     model->Remove(foster_parent); | 
 |     foster_parent = nullptr; | 
 |   } | 
 |  | 
 |   // Notify UI intensive observers of BookmarkModel that all updates have been | 
 |   // applied, and that they may now be consumed. This prevents issues like the | 
 |   // one described in crbug.com/281562, where old and new items on the bookmarks | 
 |   // bar would overlap. | 
 |   model->EndExtensiveChanges(); | 
 |  | 
 |   // We are now ready to hear about bookmarks changes again. | 
 |   model->AddObserver(this); | 
 |  | 
 |   // All changes are applied in bookmark model. Set transaction version on | 
 |   // bookmark model to mark as synced. | 
 |   model->SetNodeSyncTransactionVersion(model->root_node(), model_version); | 
 | } | 
 |  | 
 | // Static. | 
 | // Update a bookmark node with specified sync data. | 
 | void BookmarkChangeProcessor::UpdateBookmarkWithSyncData( | 
 |     const syncer::BaseNode& sync_node, | 
 |     BookmarkModel* model, | 
 |     const BookmarkNode* node, | 
 |     syncer::SyncClient* sync_client) { | 
 |   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder()); | 
 |   const sync_pb::BookmarkSpecifics& specifics = | 
 |       sync_node.GetBookmarkSpecifics(); | 
 |   if (!sync_node.GetIsFolder()) | 
 |     model->SetURL(node, GURL(specifics.url())); | 
 |   model->SetTitle(node, base::UTF8ToUTF16(sync_node.GetTitle())); | 
 |   if (specifics.has_creation_time_us()) { | 
 |     model->SetDateAdded( | 
 |         node, | 
 |         base::Time::FromInternalValue(specifics.creation_time_us())); | 
 |   } | 
 |   SetBookmarkFavicon(&sync_node, node, model, sync_client); | 
 |   model->SetNodeMetaInfoMap(node, *GetBookmarkMetaInfo(&sync_node)); | 
 | } | 
 |  | 
 | // static | 
 | void BookmarkChangeProcessor::UpdateTransactionVersion( | 
 |     int64_t new_version, | 
 |     BookmarkModel* model, | 
 |     const std::vector<const BookmarkNode*>& nodes) { | 
 |   if (new_version != syncer::syncable::kInvalidTransactionVersion) { | 
 |     model->SetNodeSyncTransactionVersion(model->root_node(), new_version); | 
 |     for (size_t i = 0; i < nodes.size(); ++i) { | 
 |       model->SetNodeSyncTransactionVersion(nodes[i], new_version); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // static | 
 | // Creates a bookmark node under the given parent node from the given sync | 
 | // node. Returns the newly created node. | 
 | const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode( | 
 |     const syncer::BaseNode* sync_node, | 
 |     const BookmarkNode* parent, | 
 |     BookmarkModel* model, | 
 |     syncer::SyncClient* sync_client, | 
 |     int index) { | 
 |   return CreateBookmarkNode(base::UTF8ToUTF16(sync_node->GetTitle()), | 
 |                             GURL(sync_node->GetBookmarkSpecifics().url()), | 
 |                             sync_node, parent, model, sync_client, index); | 
 | } | 
 |  | 
 | // static | 
 | // Creates a bookmark node under the given parent node from the given sync | 
 | // node. Returns the newly created node. | 
 | const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode( | 
 |     const base::string16& title, | 
 |     const GURL& url, | 
 |     const syncer::BaseNode* sync_node, | 
 |     const BookmarkNode* parent, | 
 |     BookmarkModel* model, | 
 |     syncer::SyncClient* sync_client, | 
 |     int index) { | 
 |   DCHECK(parent); | 
 |  | 
 |   const BookmarkNode* node; | 
 |   if (sync_node->GetIsFolder()) { | 
 |     node = model->AddFolderWithMetaInfo(parent, index, title, | 
 |                                         GetBookmarkMetaInfo(sync_node).get()); | 
 |   } else { | 
 |     // 'creation_time_us' was added in m24. Assume a time of 0 means now. | 
 |     const sync_pb::BookmarkSpecifics& specifics = | 
 |         sync_node->GetBookmarkSpecifics(); | 
 |     const int64_t create_time_internal = specifics.creation_time_us(); | 
 |     base::Time create_time = (create_time_internal == 0) ? | 
 |         base::Time::Now() : base::Time::FromInternalValue(create_time_internal); | 
 |     node = model->AddURLWithCreationTimeAndMetaInfo( | 
 |         parent, index, title, url, create_time, | 
 |         GetBookmarkMetaInfo(sync_node).get()); | 
 |     if (node) | 
 |       SetBookmarkFavicon(sync_node, node, model, sync_client); | 
 |   } | 
 |  | 
 |   return node; | 
 | } | 
 |  | 
 | // static | 
 | // Sets the favicon of the given bookmark node from the given sync node. | 
 | void BookmarkChangeProcessor::SetBookmarkFavicon( | 
 |     const syncer::BaseNode* sync_node, | 
 |     const BookmarkNode* bookmark_node, | 
 |     BookmarkModel* bookmark_model, | 
 |     syncer::SyncClient* sync_client) { | 
 |   const sync_pb::BookmarkSpecifics& specifics = | 
 |       sync_node->GetBookmarkSpecifics(); | 
 |   const std::string& icon_bytes_str = specifics.favicon(); | 
 |   scoped_refptr<base::RefCountedString> icon_bytes( | 
 |       new base::RefCountedString()); | 
 |   icon_bytes->data().assign(icon_bytes_str); | 
 |  | 
 |   ApplyBookmarkFavicon(bookmark_node, sync_client, GURL(specifics.icon_url()), | 
 |                        icon_bytes); | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<BookmarkNode::MetaInfoMap> | 
 | BookmarkChangeProcessor::GetBookmarkMetaInfo( | 
 |     const syncer::BaseNode* sync_node) { | 
 |   const sync_pb::BookmarkSpecifics& specifics = | 
 |       sync_node->GetBookmarkSpecifics(); | 
 |   std::unique_ptr<BookmarkNode::MetaInfoMap> meta_info_map( | 
 |       new BookmarkNode::MetaInfoMap); | 
 |   for (int i = 0; i < specifics.meta_info_size(); ++i) { | 
 |     (*meta_info_map)[specifics.meta_info(i).key()] = | 
 |         specifics.meta_info(i).value(); | 
 |   } | 
 |   // Verifies that all entries had unique keys. | 
 |   DCHECK_EQ(static_cast<size_t>(specifics.meta_info_size()), | 
 |             meta_info_map->size()); | 
 |   return meta_info_map; | 
 | } | 
 |  | 
 | // static | 
 | void BookmarkChangeProcessor::SetSyncNodeMetaInfo( | 
 |     const BookmarkNode* node, | 
 |     syncer::WriteNode* sync_node) { | 
 |   sync_pb::BookmarkSpecifics specifics = sync_node->GetBookmarkSpecifics(); | 
 |   const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap(); | 
 |  | 
 |   // Compare specifics meta info to node meta info before making the change. | 
 |   // Please note that the original specifics meta info is unordered while | 
 |   //  meta_info_map is ordered by key. Setting the meta info blindly into | 
 |   // the specifics might cause an unnecessary change. | 
 |   size_t size = meta_info_map ? meta_info_map->size() : 0; | 
 |   if (static_cast<size_t>(specifics.meta_info_size()) == size) { | 
 |     size_t index = 0; | 
 |     for (; index < size; index++) { | 
 |       const sync_pb::MetaInfo& meta_info = specifics.meta_info(index); | 
 |       BookmarkNode::MetaInfoMap::const_iterator it = | 
 |           meta_info_map->find(meta_info.key()); | 
 |       if (it == meta_info_map->end() || it->second != meta_info.value()) { | 
 |         // One of original meta info entries is missing in |meta_info_map| or | 
 |         // different. | 
 |         break; | 
 |       } | 
 |     } | 
 |     if (index == size) { | 
 |       // The original meta info from the sync model is already equivalent to | 
 |       // |meta_info_map|. | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   // Clear and reset meta info in bookmark specifics. | 
 |   specifics.clear_meta_info(); | 
 |   if (meta_info_map) { | 
 |     for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map->begin(); | 
 |         it != meta_info_map->end(); ++it) { | 
 |       sync_pb::MetaInfo* meta_info = specifics.add_meta_info(); | 
 |       meta_info->set_key(it->first); | 
 |       meta_info->set_value(it->second); | 
 |     } | 
 |   } | 
 |  | 
 |   sync_node->SetBookmarkSpecifics(specifics); | 
 | } | 
 |  | 
 | // static | 
 | void BookmarkChangeProcessor::ApplyBookmarkFavicon( | 
 |     const BookmarkNode* bookmark_node, | 
 |     syncer::SyncClient* sync_client, | 
 |     const GURL& icon_url, | 
 |     const scoped_refptr<base::RefCountedMemory>& bitmap_data) { | 
 |   history::HistoryService* history = sync_client->GetHistoryService(); | 
 |   favicon::FaviconService* favicon_service = sync_client->GetFaviconService(); | 
 |  | 
 |   // Some tests (that use FakeSyncClient) use no services. | 
 |   if (history == nullptr) | 
 |     return; | 
 |  | 
 |   DCHECK(favicon_service); | 
 |   history->AddPageNoVisitForBookmark(bookmark_node->url(), | 
 |                                      bookmark_node->GetTitle()); | 
 |  | 
 |   GURL icon_url_to_use = icon_url; | 
 |  | 
 |   if (icon_url.is_empty()) { | 
 |     if (bitmap_data->size() == 0) { | 
 |       // Empty icon URL and no bitmap data means no icon mapping. | 
 |       favicon_service->DeleteFaviconMappings({bookmark_node->url()}, | 
 |                                              favicon_base::IconType::kFavicon); | 
 |       return; | 
 |     } else { | 
 |       // Ancient clients (prior to M25) may not be syncing the favicon URL. If | 
 |       // the icon URL is not synced, use the page URL as a fake icon URL as it | 
 |       // is guaranteed to be unique. | 
 |       icon_url_to_use = bookmark_node->url(); | 
 |     } | 
 |   } | 
 |  | 
 |   // The client may have cached the favicon at 2x. Use MergeFavicon() as not to | 
 |   // overwrite the cached 2x favicon bitmap. Sync favicons are always | 
 |   // gfx::kFaviconSize in width and height. Store the favicon into history | 
 |   // as such. | 
 |   gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize); | 
 |   favicon_service->MergeFavicon(bookmark_node->url(), icon_url_to_use, | 
 |                                 favicon_base::IconType::kFavicon, bitmap_data, | 
 |                                 pixel_size); | 
 | } | 
 |  | 
 | // static | 
 | void BookmarkChangeProcessor::SetSyncNodeFavicon( | 
 |     const BookmarkNode* bookmark_node, | 
 |     BookmarkModel* model, | 
 |     syncer::WriteNode* sync_node) { | 
 |   scoped_refptr<base::RefCountedMemory> favicon_bytes(nullptr); | 
 |   EncodeFavicon(bookmark_node, model, &favicon_bytes); | 
 |   sync_pb::BookmarkSpecifics updated_specifics( | 
 |       sync_node->GetBookmarkSpecifics()); | 
 |  | 
 |   if (favicon_bytes.get() && favicon_bytes->size()) { | 
 |     updated_specifics.set_favicon(favicon_bytes->front(), | 
 |                                   favicon_bytes->size()); | 
 |     updated_specifics.set_icon_url(bookmark_node->icon_url() | 
 |                                        ? bookmark_node->icon_url()->spec() | 
 |                                        : std::string()); | 
 |   } else { | 
 |     updated_specifics.clear_favicon(); | 
 |     updated_specifics.clear_icon_url(); | 
 |   } | 
 |  | 
 |   sync_node->SetBookmarkSpecifics(updated_specifics); | 
 | } | 
 |  | 
 | bool BookmarkChangeProcessor::CanSyncNode(const BookmarkNode* node) { | 
 |   return bookmark_model_->client()->CanSyncNode(node); | 
 | } | 
 |  | 
 | }  // namespace sync_bookmarks |