Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / sync / glue / bookmark_change_processor.cc
blobae0e854ab6b1481d461a8162089cf931aa7765a1
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.
5 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
7 #include <map>
8 #include <stack>
9 #include <vector>
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"
39 using content::BrowserThread;
40 using syncer::ChangeRecord;
41 using syncer::ChangeRecordList;
43 namespace browser_sync {
45 static const char kMobileBookmarksTag[] = "synced_bookmarks";
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);
61 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
62 if (bookmark_model_)
63 bookmark_model_->RemoveObserver(this);
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);
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);
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);
97 // Check for empty images. This can happen if the favicon is
98 // still being loaded.
99 if (favicon.IsEmpty())
100 return;
102 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
103 // sync subsystem.
104 *dst = favicon.As1xPNGBytes();
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();
115 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
116 const BookmarkNode* topmost) {
117 int64 new_version =
118 syncer::syncable::kInvalidTransactionVersion;
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;
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);
138 // Don't need to update versions of deleted nodes.
139 UpdateTransactionVersion(new_version, bookmark_model_,
140 std::vector<const BookmarkNode*>());
143 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
144 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
146 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
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());
156 // Note: the root node may have additional extra nodes. Currently none of
157 // them are meant to sync.
160 // Don't need to update versions of deleted nodes.
161 UpdateTransactionVersion(new_version, bookmark_model_,
162 std::vector<const BookmarkNode*>());
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;
177 const int64 topmost_sync_id = topmost_node.GetId();
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
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());
208 } else {
209 int64 child_id = node.GetFirstChildId();
210 if (child_id != syncer::kInvalidId) {
211 dfs_sync_id_stack.push(child_id);
217 void BookmarkChangeProcessor::CreateOrUpdateSyncNode(const BookmarkNode* node) {
218 if (!CanSyncNode(node)) {
219 NOTREACHED();
220 return;
223 const BookmarkNode* parent = node->parent();
224 int index = node->parent()->GetIndexOf(node);
226 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
227 int64 sync_id = syncer::kInvalidId;
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());
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)));
257 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
258 bool ids_reassigned) {
259 NOTREACHED();
262 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) {
263 NOTREACHED();
264 bookmark_model_ = NULL;
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);
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);
284 // Create a WriteNode container to hold the new node.
285 syncer::WriteNode sync_child(trans);
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;
297 UpdateSyncNodeProperties(child, model, &sync_child);
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());
303 return sync_child.GetId();
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);
316 void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved(
317 BookmarkModel* model,
318 const std::set<GURL>& removed_urls) {
319 RemoveAllSyncNodes();
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;
331 CreateOrUpdateSyncNode(node);
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;
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();
359 void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
360 BookmarkModel* model, const BookmarkNode* node) {
361 BookmarkNodeChanged(model, node);
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);
369 if (!CanSyncNode(child))
370 return;
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;
378 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
380 // Acquire a scoped write lock via a transaction.
381 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
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;
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;
405 UpdateTransactionVersion(new_version, model,
406 std::vector<const BookmarkNode*>(1, child));
409 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
410 BookmarkModel* model,
411 const BookmarkNode* node) {
412 BookmarkNodeChanged(model, node);
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;
422 // Acquire a scoped write lock via a transaction.
423 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
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);
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;
441 DCHECK_EQ(sync_child.GetParentId(),
442 model_associator_->GetSyncIdFromChromeId(node->id()));
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;
456 // TODO(haitaol): Filter out children that didn't actually change.
457 UpdateTransactionVersion(new_version, model, children);
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;
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);
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;
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());
498 return success;
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:
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.
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_;
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);
532 // Changes made to the bookmark model due to sync should not be undoable.
533 ScopedSuspendBookmarkUndo suspend_undo(profile_);
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();
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;
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);
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;
558 // Can't do anything if we can't find the chrome node.
559 if (!dst)
560 continue;
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;
578 for (int i = dst->child_count() - 1; i >= 0; --i) {
579 model->Move(dst->GetChild(i), foster_parent,
580 foster_parent->child_count());
583 DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
585 model_associator_->Disassociate(it->id);
587 const BookmarkNode* parent = dst->parent();
588 int index = parent->GetIndexOf(dst);
589 if (index > -1)
590 model->Remove(parent, index);
593 // A map to keep track of some reordering work we defer until later.
594 std::multimap<int, const BookmarkNode*> to_reposition;
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();
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);
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;
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;
626 DCHECK_NE(it->action, ChangeRecord::ACTION_DELETE)
627 << "We should have passed all deletes by this point.";
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;
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;
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_);
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.";
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;
672 model_associator_->Associate(dst, src.GetId());
675 to_reposition.insert(std::make_pair(src.GetPositionIndex(), dst));
676 bookmark_model_->SetNodeSyncTransactionVersion(dst, model_version);
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);
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;
697 // The visibility of the mobile node may need to change.
698 model_associator_->UpdatePermanentNodeVisibility();
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();
706 // We are now ready to hear about bookmarks changes again.
707 model->AddObserver(this);
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);
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()));
732 SetBookmarkFavicon(&sync_node, node, model, profile);
733 model->SetNodeMetaInfoMap(node, *GetBookmarkMetaInfo(&sync_node));
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);
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);
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);
785 return node;
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;
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());
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();
812 ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
814 return true;
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();
829 return meta_info_map.Pass();
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);
847 sync_node->SetBookmarkSpecifics(specifics);
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);
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);
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);
892 bool BookmarkChangeProcessor::CanSyncNode(const BookmarkNode* node) {
893 return bookmark_model_->client()->CanSyncNode(node);
896 } // namespace browser_sync