Only grant permissions to new extensions from sync if they have the expected version
[chromium-blink-merge.git] / chrome / browser / sync / glue / bookmark_change_processor.cc
blobc93cb70976557b6b284dd2601f89a65fda1cbc19
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_factory.h"
18 #include "chrome/browser/history/history_service_factory.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sync/profile_sync_service.h"
21 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
22 #include "components/bookmarks/browser/bookmark_client.h"
23 #include "components/bookmarks/browser/bookmark_model.h"
24 #include "components/bookmarks/browser/bookmark_utils.h"
25 #include "components/favicon/core/favicon_service.h"
26 #include "components/history/core/browser/history_service.h"
27 #include "components/undo/bookmark_undo_service.h"
28 #include "components/undo/bookmark_undo_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 bookmarks::BookmarkModel;
40 using bookmarks::BookmarkNode;
41 using content::BrowserThread;
42 using syncer::ChangeRecord;
43 using syncer::ChangeRecordList;
45 namespace browser_sync {
47 static const char kMobileBookmarksTag[] = "synced_bookmarks";
49 BookmarkChangeProcessor::BookmarkChangeProcessor(
50 Profile* profile,
51 BookmarkModelAssociator* model_associator,
52 sync_driver::DataTypeErrorHandler* error_handler)
53 : sync_driver::ChangeProcessor(error_handler),
54 bookmark_model_(NULL),
55 profile_(profile),
56 model_associator_(model_associator) {
57 DCHECK_CURRENTLY_ON(BrowserThread::UI);
58 DCHECK(model_associator);
59 DCHECK(profile);
60 DCHECK(error_handler);
63 BookmarkChangeProcessor::~BookmarkChangeProcessor() {
64 if (bookmark_model_)
65 bookmark_model_->RemoveObserver(this);
68 void BookmarkChangeProcessor::StartImpl() {
69 DCHECK_CURRENTLY_ON(BrowserThread::UI);
70 DCHECK(!bookmark_model_);
71 bookmark_model_ = BookmarkModelFactory::GetForProfile(profile_);
72 DCHECK(bookmark_model_->loaded());
73 bookmark_model_->AddObserver(this);
76 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
77 const BookmarkNode* src,
78 BookmarkModel* model,
79 syncer::WriteNode* dst,
80 sync_driver::DataTypeErrorHandler* error_handler) {
81 // Set the properties of the item.
82 dst->SetIsFolder(src->is_folder());
83 dst->SetTitle(base::UTF16ToUTF8(src->GetTitle()));
84 sync_pb::BookmarkSpecifics bookmark_specifics(dst->GetBookmarkSpecifics());
85 if (!src->is_folder()) {
86 if (!src->url().is_valid()) {
87 // Report the invalid URL and continue.
88 // TODO(stanisc): crbug/482155: Revisit this once the root cause for
89 // invalid URLs is understood.
90 error_handler->CreateAndUploadError(
91 FROM_HERE, "Creating sync bookmark with invalid url " +
92 src->url().possibly_invalid_spec(),
93 syncer::BOOKMARKS);
95 bookmark_specifics.set_url(src->url().spec());
97 bookmark_specifics.set_creation_time_us(src->date_added().ToInternalValue());
98 dst->SetBookmarkSpecifics(bookmark_specifics);
99 SetSyncNodeFavicon(src, model, dst);
100 SetSyncNodeMetaInfo(src, dst);
103 // static
104 void BookmarkChangeProcessor::EncodeFavicon(
105 const BookmarkNode* src,
106 BookmarkModel* model,
107 scoped_refptr<base::RefCountedMemory>* dst) {
108 const gfx::Image& favicon = model->GetFavicon(src);
110 // Check for empty images. This can happen if the favicon is
111 // still being loaded.
112 if (favicon.IsEmpty())
113 return;
115 // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
116 // sync subsystem.
117 *dst = favicon.As1xPNGBytes();
120 // static
121 int BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
122 syncer::WriteTransaction* trans,
123 syncer::WriteNode* sync_node,
124 BookmarkModelAssociator* associator) {
125 // Remove children.
126 int num_removed = RemoveAllChildNodes(trans, sync_node->GetId(), associator);
127 // Remove the node itself.
128 RemoveOneSyncNode(sync_node, associator);
129 return num_removed + 1;
132 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
133 const BookmarkNode* topmost) {
134 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
136 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
137 syncer::WriteNode topmost_sync_node(&trans);
138 if (!model_associator_->InitSyncNodeFromChromeId(topmost->id(),
139 &topmost_sync_node)) {
140 syncer::SyncError error(FROM_HERE,
141 syncer::SyncError::DATATYPE_ERROR,
142 "Failed to init sync node from chrome node",
143 syncer::BOOKMARKS);
144 error_handler()->OnSingleDataTypeUnrecoverableError(error);
145 return;
147 RemoveSyncNodeHierarchy(&trans, &topmost_sync_node, model_associator_);
150 // Don't need to update versions of deleted nodes.
151 UpdateTransactionVersion(new_version, bookmark_model_,
152 std::vector<const BookmarkNode*>());
155 void BookmarkChangeProcessor::RemoveAllSyncNodes() {
156 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
158 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
160 int64 bookmark_bar_node_sync_id = model_associator_->GetSyncIdFromChromeId(
161 bookmark_model_->bookmark_bar_node()->id());
162 DCHECK_NE(syncer::kInvalidId, bookmark_bar_node_sync_id);
163 RemoveAllChildNodes(&trans, bookmark_bar_node_sync_id, model_associator_);
165 int64 other_node_sync_id = model_associator_->GetSyncIdFromChromeId(
166 bookmark_model_->other_node()->id());
167 DCHECK_NE(syncer::kInvalidId, other_node_sync_id);
168 RemoveAllChildNodes(&trans, other_node_sync_id, model_associator_);
170 // Remove mobile bookmarks node only if it is present.
171 int64 mobile_node_sync_id = model_associator_->GetSyncIdFromChromeId(
172 bookmark_model_->mobile_node()->id());
173 if (mobile_node_sync_id != syncer::kInvalidId) {
174 RemoveAllChildNodes(&trans, mobile_node_sync_id, model_associator_);
177 // Note: the root node may have additional extra nodes. Currently none of
178 // them are meant to sync.
181 // Don't need to update versions of deleted nodes.
182 UpdateTransactionVersion(new_version, bookmark_model_,
183 std::vector<const BookmarkNode*>());
186 // static
187 int BookmarkChangeProcessor::RemoveAllChildNodes(
188 syncer::WriteTransaction* trans,
189 int64 topmost_sync_id,
190 BookmarkModelAssociator* associator) {
191 // Do a DFS and delete all the child sync nodes, use sync id instead of
192 // bookmark node ids since the bookmark nodes may already be deleted.
193 // The equivalent recursive version of this iterative DFS:
194 // remove_all_children(node_id, topmost_node_id):
195 // node.initByIdLookup(node_id)
196 // while(node.GetFirstChildId() != syncer::kInvalidId)
197 // remove_all_children(node.GetFirstChildId(), topmost_node_id)
198 // if(node_id != topmost_node_id)
199 // delete node
201 int num_removed = 0;
202 std::stack<int64> dfs_sync_id_stack;
203 // Push the topmost node.
204 dfs_sync_id_stack.push(topmost_sync_id);
205 while (!dfs_sync_id_stack.empty()) {
206 const int64 sync_node_id = dfs_sync_id_stack.top();
207 syncer::WriteNode node(trans);
208 node.InitByIdLookup(sync_node_id);
209 if (!node.GetIsFolder() || node.GetFirstChildId() == syncer::kInvalidId) {
210 // All children of the node has been processed, delete the node and
211 // pop it off the stack.
212 dfs_sync_id_stack.pop();
213 // Do not delete the topmost node.
214 if (sync_node_id != topmost_sync_id) {
215 RemoveOneSyncNode(&node, associator);
216 num_removed++;
217 } else {
218 // if we are processing topmost node, all other nodes must be processed
219 // the stack should be empty.
220 DCHECK(dfs_sync_id_stack.empty());
222 } else {
223 int64 child_id = node.GetFirstChildId();
224 if (child_id != syncer::kInvalidId) {
225 dfs_sync_id_stack.push(child_id);
229 return num_removed;
232 // static
233 void BookmarkChangeProcessor::RemoveOneSyncNode(
234 syncer::WriteNode* sync_node,
235 BookmarkModelAssociator* associator) {
236 // This node should have no children.
237 DCHECK(!sync_node->HasChildren());
238 // Remove association and delete the sync node.
239 associator->Disassociate(sync_node->GetId());
240 sync_node->Tombstone();
243 void BookmarkChangeProcessor::CreateOrUpdateSyncNode(const BookmarkNode* node) {
244 if (!CanSyncNode(node)) {
245 NOTREACHED();
246 return;
249 const BookmarkNode* parent = node->parent();
250 int index = node->parent()->GetIndexOf(node);
252 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
253 int64 sync_id = syncer::kInvalidId;
255 // Acquire a scoped write lock via a transaction.
256 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
257 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
258 if (sync_id != syncer::kInvalidId) {
259 UpdateSyncNode(
260 node, bookmark_model_, &trans, model_associator_, error_handler());
261 } else {
262 sync_id = CreateSyncNode(parent,
263 bookmark_model_,
264 index,
265 &trans,
266 model_associator_,
267 error_handler());
271 if (syncer::kInvalidId != sync_id) {
272 // Siblings of added node in sync DB will also be updated to reflect new
273 // PREV_ID/NEXT_ID and thus get a new version. But we only update version
274 // of added node here. After switching to ordinals for positioning,
275 // PREV_ID/NEXT_ID will be deprecated and siblings will not be updated.
276 UpdateTransactionVersion(
277 new_version,
278 bookmark_model_,
279 std::vector<const BookmarkNode*>(1, parent->GetChild(index)));
283 void BookmarkChangeProcessor::BookmarkModelLoaded(BookmarkModel* model,
284 bool ids_reassigned) {
285 NOTREACHED();
288 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(BookmarkModel* model) {
289 NOTREACHED();
290 bookmark_model_ = NULL;
293 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
294 const BookmarkNode* parent,
295 int index) {
296 DCHECK(share_handle());
297 const BookmarkNode* node = parent->GetChild(index);
298 if (CanSyncNode(node))
299 CreateOrUpdateSyncNode(node);
302 // static
303 int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent,
304 BookmarkModel* model, int index, syncer::WriteTransaction* trans,
305 BookmarkModelAssociator* associator,
306 sync_driver::DataTypeErrorHandler* error_handler) {
307 const BookmarkNode* child = parent->GetChild(index);
308 DCHECK(child);
310 // Create a WriteNode container to hold the new node.
311 syncer::WriteNode sync_child(trans);
313 // Actually create the node with the appropriate initial position.
314 if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) {
315 syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR,
316 "Failed to create sync node.", syncer::BOOKMARKS);
317 error_handler->OnSingleDataTypeUnrecoverableError(error);
318 return syncer::kInvalidId;
321 UpdateSyncNodeProperties(child, model, &sync_child, error_handler);
323 // Associate the ID from the sync domain with the bookmark node, so that we
324 // can refer back to this item later.
325 associator->Associate(child, sync_child);
327 return sync_child.GetId();
330 void BookmarkChangeProcessor::OnWillRemoveBookmarks(BookmarkModel* model,
331 const BookmarkNode* parent,
332 int old_index,
333 const BookmarkNode* node) {
334 if (CanSyncNode(node))
335 RemoveSyncNodeHierarchy(node);
338 void BookmarkChangeProcessor::BookmarkNodeRemoved(
339 BookmarkModel* model,
340 const BookmarkNode* parent,
341 int old_index,
342 const BookmarkNode* node,
343 const std::set<GURL>& no_longer_bookmarked) {
344 // All the work should have already been done in OnWillRemoveBookmarks.
345 DCHECK_EQ(syncer::kInvalidId,
346 model_associator_->GetSyncIdFromChromeId(node->id()));
349 void BookmarkChangeProcessor::BookmarkAllUserNodesRemoved(
350 BookmarkModel* model,
351 const std::set<GURL>& removed_urls) {
352 RemoveAllSyncNodes();
355 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model,
356 const BookmarkNode* node) {
357 if (!CanSyncNode(node))
358 return;
359 // We shouldn't see changes to the top-level nodes.
360 if (model->is_permanent_node(node)) {
361 NOTREACHED() << "Saw update to permanent node!";
362 return;
364 CreateOrUpdateSyncNode(node);
367 // Static.
368 int64 BookmarkChangeProcessor::UpdateSyncNode(
369 const BookmarkNode* node,
370 BookmarkModel* model,
371 syncer::WriteTransaction* trans,
372 BookmarkModelAssociator* associator,
373 sync_driver::DataTypeErrorHandler* error_handler) {
374 // Lookup the sync node that's associated with |node|.
375 syncer::WriteNode sync_node(trans);
376 if (!associator->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
377 syncer::SyncError error(FROM_HERE,
378 syncer::SyncError::DATATYPE_ERROR,
379 "Failed to init sync node from chrome node",
380 syncer::BOOKMARKS);
381 error_handler->OnSingleDataTypeUnrecoverableError(error);
382 return syncer::kInvalidId;
384 UpdateSyncNodeProperties(node, model, &sync_node, error_handler);
385 DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
386 DCHECK_EQ(associator->GetChromeNodeFromSyncId(sync_node.GetParentId()),
387 node->parent());
388 DCHECK_EQ(node->parent()->GetIndexOf(node), sync_node.GetPositionIndex());
389 return sync_node.GetId();
392 void BookmarkChangeProcessor::BookmarkMetaInfoChanged(
393 BookmarkModel* model, const BookmarkNode* node) {
394 BookmarkNodeChanged(model, node);
397 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model,
398 const BookmarkNode* old_parent, int old_index,
399 const BookmarkNode* new_parent, int new_index) {
400 const BookmarkNode* child = new_parent->GetChild(new_index);
402 if (!CanSyncNode(child))
403 return;
405 // We shouldn't see changes to the top-level nodes.
406 if (model->is_permanent_node(child)) {
407 NOTREACHED() << "Saw update to permanent node!";
408 return;
411 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
413 // Acquire a scoped write lock via a transaction.
414 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
416 // Lookup the sync node that's associated with |child|.
417 syncer::WriteNode sync_node(&trans);
418 if (!model_associator_->InitSyncNodeFromChromeId(child->id(), &sync_node)) {
419 syncer::SyncError error(FROM_HERE,
420 syncer::SyncError::DATATYPE_ERROR,
421 "Failed to init sync node from chrome node",
422 syncer::BOOKMARKS);
423 error_handler()->OnSingleDataTypeUnrecoverableError(error);
424 return;
427 if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node,
428 model_associator_)) {
429 syncer::SyncError error(FROM_HERE,
430 syncer::SyncError::DATATYPE_ERROR,
431 "Failed to place sync node",
432 syncer::BOOKMARKS);
433 error_handler()->OnSingleDataTypeUnrecoverableError(error);
434 return;
438 UpdateTransactionVersion(new_version, model,
439 std::vector<const BookmarkNode*>(1, child));
442 void BookmarkChangeProcessor::BookmarkNodeFaviconChanged(
443 BookmarkModel* model,
444 const BookmarkNode* node) {
445 if (!CanSyncNode(node)) {
446 return;
449 // We shouldn't see changes to the top-level nodes.
450 if (model->is_permanent_node(node)) {
451 NOTREACHED() << "Saw Favicon update to permanent node!";
452 return;
455 // Ignore changes with empty images. This can happen if the favicon is
456 // still being loaded.
457 const gfx::Image& favicon = model->GetFavicon(node);
458 if (favicon.IsEmpty()) {
459 return;
462 BookmarkNodeChanged(model, node);
465 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
466 BookmarkModel* model, const BookmarkNode* node) {
467 if (!CanSyncNode(node))
468 return;
469 int64 new_version = syncer::syncable::kInvalidTransactionVersion;
470 std::vector<const BookmarkNode*> children;
472 // Acquire a scoped write lock via a transaction.
473 syncer::WriteTransaction trans(FROM_HERE, share_handle(), &new_version);
475 // The given node's children got reordered. We need to reorder all the
476 // children of the corresponding sync node.
477 for (int i = 0; i < node->child_count(); ++i) {
478 const BookmarkNode* child = node->GetChild(i);
479 children.push_back(child);
481 syncer::WriteNode sync_child(&trans);
482 if (!model_associator_->InitSyncNodeFromChromeId(child->id(),
483 &sync_child)) {
484 syncer::SyncError error(FROM_HERE,
485 syncer::SyncError::DATATYPE_ERROR,
486 "Failed to init sync node from chrome node",
487 syncer::BOOKMARKS);
488 error_handler()->OnSingleDataTypeUnrecoverableError(error);
489 return;
491 DCHECK_EQ(sync_child.GetParentId(),
492 model_associator_->GetSyncIdFromChromeId(node->id()));
494 if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child,
495 model_associator_)) {
496 syncer::SyncError error(FROM_HERE,
497 syncer::SyncError::DATATYPE_ERROR,
498 "Failed to place sync node",
499 syncer::BOOKMARKS);
500 error_handler()->OnSingleDataTypeUnrecoverableError(error);
501 return;
506 // TODO(haitaol): Filter out children that didn't actually change.
507 UpdateTransactionVersion(new_version, model, children);
510 // static
511 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation,
512 const BookmarkNode* parent, int index, syncer::WriteTransaction* trans,
513 syncer::WriteNode* dst, BookmarkModelAssociator* associator) {
514 syncer::ReadNode sync_parent(trans);
515 if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) {
516 LOG(WARNING) << "Parent lookup failed";
517 return false;
520 bool success = false;
521 if (index == 0) {
522 // Insert into first position.
523 success = (operation == CREATE) ?
524 dst->InitBookmarkByCreation(sync_parent, NULL) :
525 dst->SetPosition(sync_parent, NULL);
526 if (success) {
527 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
528 DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
529 DCHECK_EQ(dst->GetPredecessorId(), syncer::kInvalidId);
531 } else {
532 // Find the bookmark model predecessor, and insert after it.
533 const BookmarkNode* prev = parent->GetChild(index - 1);
534 syncer::ReadNode sync_prev(trans);
535 if (!associator->InitSyncNodeFromChromeId(prev->id(), &sync_prev)) {
536 LOG(WARNING) << "Predecessor lookup failed";
537 return false;
539 success = (operation == CREATE) ?
540 dst->InitBookmarkByCreation(sync_parent, &sync_prev) :
541 dst->SetPosition(sync_parent, &sync_prev);
542 if (success) {
543 DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
544 DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
545 DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
548 return success;
551 // ApplyModelChanges is called by the sync backend after changes have been made
552 // to the sync engine's model. Apply these changes to the browser bookmark
553 // model.
554 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
555 const syncer::BaseTransaction* trans,
556 int64 model_version,
557 const syncer::ImmutableChangeRecordList& changes) {
558 DCHECK_CURRENTLY_ON(BrowserThread::UI);
559 // A note about ordering. Sync backend is responsible for ordering the change
560 // records in the following order:
562 // 1. Deletions, from leaves up to parents.
563 // 2. Existing items with synced parents & predecessors.
564 // 3. New items with synced parents & predecessors.
565 // 4. Items with parents & predecessors in the list.
566 // 5. Repeat #4 until all items are in the list.
568 // "Predecessor" here means the previous item within a given folder; an item
569 // in the first position is always said to have a synced predecessor.
570 // For the most part, applying these changes in the order given will yield
571 // the correct result. There is one exception, however: for items that are
572 // moved away from a folder that is being deleted, we will process the delete
573 // before the move. Since deletions in the bookmark model propagate from
574 // parent to child, we must move them to a temporary location.
575 BookmarkModel* model = bookmark_model_;
577 // We are going to make changes to the bookmarks model, but don't want to end
578 // up in a feedback loop, so remove ourselves as an observer while applying
579 // changes.
580 model->RemoveObserver(this);
582 // Changes made to the bookmark model due to sync should not be undoable.
583 ScopedSuspendBookmarkUndo suspend_undo(
584 BookmarkUndoServiceFactory::GetForProfileIfExists(profile_));
586 // Notify UI intensive observers of BookmarkModel that we are about to make
587 // potentially significant changes to it, so the updates may be batched. For
588 // example, on Mac, the bookmarks bar displays animations when bookmark items
589 // are added or deleted.
590 model->BeginExtensiveChanges();
592 // A parent to hold nodes temporarily orphaned by parent deletion. It is
593 // created only if it is needed.
594 const BookmarkNode* foster_parent = NULL;
596 // Iterate over the deletions, which are always at the front of the list.
597 ChangeRecordList::const_iterator it;
598 for (it = changes.Get().begin();
599 it != changes.Get().end() && it->action == ChangeRecord::ACTION_DELETE;
600 ++it) {
601 const BookmarkNode* dst =
602 model_associator_->GetChromeNodeFromSyncId(it->id);
604 // Ignore changes to the permanent top-level nodes. We only care about
605 // their children.
606 if (model->is_permanent_node(dst))
607 continue;
609 // Can't do anything if we can't find the chrome node.
610 if (!dst)
611 continue;
613 // Children of a deleted node should not be deleted; they may be
614 // reparented by a later change record. Move them to a temporary place.
615 if (!dst->empty()) {
616 if (!foster_parent) {
617 foster_parent = model->AddFolder(model->other_node(),
618 model->other_node()->child_count(),
619 base::string16());
620 if (!foster_parent) {
621 syncer::SyncError error(FROM_HERE,
622 syncer::SyncError::DATATYPE_ERROR,
623 "Failed to create foster parent",
624 syncer::BOOKMARKS);
625 error_handler()->OnSingleDataTypeUnrecoverableError(error);
626 return;
629 for (int i = dst->child_count() - 1; i >= 0; --i) {
630 model->Move(dst->GetChild(i), foster_parent,
631 foster_parent->child_count());
634 DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
636 model_associator_->Disassociate(it->id);
638 const BookmarkNode* parent = dst->parent();
639 int index = parent->GetIndexOf(dst);
640 if (index > -1)
641 model->Remove(parent->GetChild(index));
644 // A map to keep track of some reordering work we defer until later.
645 std::multimap<int, const BookmarkNode*> to_reposition;
647 syncer::ReadNode synced_bookmarks(trans);
648 int64 synced_bookmarks_id = syncer::kInvalidId;
649 if (synced_bookmarks.InitByTagLookupForBookmarks(kMobileBookmarksTag) ==
650 syncer::BaseNode::INIT_OK) {
651 synced_bookmarks_id = synced_bookmarks.GetId();
654 // Continue iterating where the previous loop left off.
655 for ( ; it != changes.Get().end(); ++it) {
656 const BookmarkNode* dst =
657 model_associator_->GetChromeNodeFromSyncId(it->id);
659 // Ignore changes to the permanent top-level nodes. We only care about
660 // their children.
661 if (model->is_permanent_node(dst))
662 continue;
664 // Because the Synced Bookmarks node can be created server side, it's
665 // possible it'll arrive at the client as an update. In that case it won't
666 // have been associated at startup, the GetChromeNodeFromSyncId call above
667 // will return NULL, and we won't detect it as a permanent node, resulting
668 // in us trying to create it here (which will fail). Therefore, we add
669 // special logic here just to detect the Synced Bookmarks folder.
670 if (synced_bookmarks_id != syncer::kInvalidId &&
671 it->id == synced_bookmarks_id) {
672 // This is a newly created Synced Bookmarks node. Associate it.
673 model_associator_->Associate(model->mobile_node(), synced_bookmarks);
674 continue;
677 DCHECK_NE(it->action, ChangeRecord::ACTION_DELETE)
678 << "We should have passed all deletes by this point.";
680 syncer::ReadNode src(trans);
681 if (src.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) {
682 syncer::SyncError error(FROM_HERE,
683 syncer::SyncError::DATATYPE_ERROR,
684 "Failed to load sync node",
685 syncer::BOOKMARKS);
686 error_handler()->OnSingleDataTypeUnrecoverableError(error);
687 return;
690 const BookmarkNode* parent =
691 model_associator_->GetChromeNodeFromSyncId(src.GetParentId());
692 if (!parent) {
693 LOG(ERROR) << "Could not find parent of node being added/updated."
694 << " Node title: " << src.GetTitle()
695 << ", parent id = " << src.GetParentId();
696 continue;
699 if (dst) {
700 DCHECK(it->action == ChangeRecord::ACTION_UPDATE)
701 << "ACTION_UPDATE should be seen if and only if the node is known.";
702 UpdateBookmarkWithSyncData(src, model, dst, profile_);
704 // Move all modified entries to the right. We'll fix it later.
705 model->Move(dst, parent, parent->child_count());
706 } else {
707 DCHECK(it->action == ChangeRecord::ACTION_ADD)
708 << "ACTION_ADD should be seen if and only if the node is unknown.";
710 dst = CreateBookmarkNode(&src,
711 parent,
712 model,
713 profile_,
714 parent->child_count());
715 if (!dst) {
716 // We ignore bookmarks we can't add. Chances are this is caused by
717 // a bookmark that was not fully associated.
718 LOG(ERROR) << "Failed to create bookmark node with title "
719 << src.GetTitle() + " and url "
720 << src.GetBookmarkSpecifics().url();
721 continue;
723 model_associator_->Associate(dst, src);
726 to_reposition.insert(std::make_pair(src.GetPositionIndex(), dst));
727 bookmark_model_->SetNodeSyncTransactionVersion(dst, model_version);
730 // When we added or updated bookmarks in the previous loop, we placed them to
731 // the far right position. Now we iterate over all these modified items in
732 // sync order, left to right, moving them into their proper positions.
733 for (std::multimap<int, const BookmarkNode*>::iterator it =
734 to_reposition.begin(); it != to_reposition.end(); ++it) {
735 const BookmarkNode* parent = it->second->parent();
736 model->Move(it->second, parent, it->first);
739 // Clean up the temporary node.
740 if (foster_parent) {
741 // There should be no nodes left under the foster parent.
742 DCHECK_EQ(foster_parent->child_count(), 0);
743 model->Remove(foster_parent);
744 foster_parent = NULL;
747 // The visibility of the mobile node may need to change.
748 model_associator_->UpdatePermanentNodeVisibility();
750 // Notify UI intensive observers of BookmarkModel that all updates have been
751 // applied, and that they may now be consumed. This prevents issues like the
752 // one described in crbug.com/281562, where old and new items on the bookmarks
753 // bar would overlap.
754 model->EndExtensiveChanges();
756 // We are now ready to hear about bookmarks changes again.
757 model->AddObserver(this);
759 // All changes are applied in bookmark model. Set transaction version on
760 // bookmark model to mark as synced.
761 model->SetNodeSyncTransactionVersion(model->root_node(), model_version);
764 // Static.
765 // Update a bookmark node with specified sync data.
766 void BookmarkChangeProcessor::UpdateBookmarkWithSyncData(
767 const syncer::BaseNode& sync_node,
768 BookmarkModel* model,
769 const BookmarkNode* node,
770 Profile* profile) {
771 DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
772 const sync_pb::BookmarkSpecifics& specifics =
773 sync_node.GetBookmarkSpecifics();
774 if (!sync_node.GetIsFolder())
775 model->SetURL(node, GURL(specifics.url()));
776 model->SetTitle(node, base::UTF8ToUTF16(sync_node.GetTitle()));
777 if (specifics.has_creation_time_us()) {
778 model->SetDateAdded(
779 node,
780 base::Time::FromInternalValue(specifics.creation_time_us()));
782 SetBookmarkFavicon(&sync_node, node, model, profile);
783 model->SetNodeMetaInfoMap(node, *GetBookmarkMetaInfo(&sync_node));
786 // static
787 void BookmarkChangeProcessor::UpdateTransactionVersion(
788 int64 new_version,
789 BookmarkModel* model,
790 const std::vector<const BookmarkNode*>& nodes) {
791 if (new_version != syncer::syncable::kInvalidTransactionVersion) {
792 model->SetNodeSyncTransactionVersion(model->root_node(), new_version);
793 for (size_t i = 0; i < nodes.size(); ++i) {
794 model->SetNodeSyncTransactionVersion(nodes[i], new_version);
799 // static
800 // Creates a bookmark node under the given parent node from the given sync
801 // node. Returns the newly created node.
802 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
803 const syncer::BaseNode* sync_node,
804 const BookmarkNode* parent,
805 BookmarkModel* model,
806 Profile* profile,
807 int index) {
808 return CreateBookmarkNode(base::UTF8ToUTF16(sync_node->GetTitle()),
809 GURL(sync_node->GetBookmarkSpecifics().url()),
810 sync_node, parent, model, profile, index);
813 // static
814 // Creates a bookmark node under the given parent node from the given sync
815 // node. Returns the newly created node.
816 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
817 const base::string16& title,
818 const GURL& url,
819 const syncer::BaseNode* sync_node,
820 const BookmarkNode* parent,
821 BookmarkModel* model,
822 Profile* profile,
823 int index) {
824 DCHECK(parent);
826 const BookmarkNode* node;
827 if (sync_node->GetIsFolder()) {
828 node = model->AddFolderWithMetaInfo(parent, index, title,
829 GetBookmarkMetaInfo(sync_node).get());
830 } else {
831 // 'creation_time_us' was added in m24. Assume a time of 0 means now.
832 const sync_pb::BookmarkSpecifics& specifics =
833 sync_node->GetBookmarkSpecifics();
834 const int64 create_time_internal = specifics.creation_time_us();
835 base::Time create_time = (create_time_internal == 0) ?
836 base::Time::Now() : base::Time::FromInternalValue(create_time_internal);
837 node = model->AddURLWithCreationTimeAndMetaInfo(
838 parent, index, title, url, create_time,
839 GetBookmarkMetaInfo(sync_node).get());
840 if (node)
841 SetBookmarkFavicon(sync_node, node, model, profile);
844 return node;
847 // static
848 // Sets the favicon of the given bookmark node from the given sync node.
849 bool BookmarkChangeProcessor::SetBookmarkFavicon(
850 const syncer::BaseNode* sync_node,
851 const BookmarkNode* bookmark_node,
852 BookmarkModel* bookmark_model,
853 Profile* profile) {
854 const sync_pb::BookmarkSpecifics& specifics =
855 sync_node->GetBookmarkSpecifics();
856 const std::string& icon_bytes_str = specifics.favicon();
857 if (icon_bytes_str.empty())
858 return false;
860 scoped_refptr<base::RefCountedString> icon_bytes(
861 new base::RefCountedString());
862 icon_bytes->data().assign(icon_bytes_str);
863 GURL icon_url(specifics.icon_url());
865 // Old clients may not be syncing the favicon URL. If the icon URL is not
866 // synced, use the page URL as a fake icon URL as it is guaranteed to be
867 // unique.
868 if (icon_url.is_empty())
869 icon_url = bookmark_node->url();
871 ApplyBookmarkFavicon(bookmark_node, profile, icon_url, icon_bytes);
873 return true;
876 // static
877 scoped_ptr<BookmarkNode::MetaInfoMap>
878 BookmarkChangeProcessor::GetBookmarkMetaInfo(
879 const syncer::BaseNode* sync_node) {
880 const sync_pb::BookmarkSpecifics& specifics =
881 sync_node->GetBookmarkSpecifics();
882 scoped_ptr<BookmarkNode::MetaInfoMap> meta_info_map(
883 new BookmarkNode::MetaInfoMap);
884 for (int i = 0; i < specifics.meta_info_size(); ++i) {
885 (*meta_info_map)[specifics.meta_info(i).key()] =
886 specifics.meta_info(i).value();
888 // Verifies that all entries had unique keys.
889 DCHECK_EQ(static_cast<size_t>(specifics.meta_info_size()),
890 meta_info_map->size());
891 return meta_info_map.Pass();
894 // static
895 void BookmarkChangeProcessor::SetSyncNodeMetaInfo(
896 const BookmarkNode* node,
897 syncer::WriteNode* sync_node) {
898 sync_pb::BookmarkSpecifics specifics = sync_node->GetBookmarkSpecifics();
899 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
901 // Compare specifics meta info to node meta info before making the change.
902 // Please note that the original specifics meta info is unordered while
903 // meta_info_map is ordered by key. Setting the meta info blindly into
904 // the specifics might cause an unnecessary change.
905 size_t size = meta_info_map ? meta_info_map->size() : 0;
906 if (static_cast<size_t>(specifics.meta_info_size()) == size) {
907 size_t index = 0;
908 for (; index < size; index++) {
909 const sync_pb::MetaInfo& meta_info = specifics.meta_info(index);
910 BookmarkNode::MetaInfoMap::const_iterator it =
911 meta_info_map->find(meta_info.key());
912 if (it == meta_info_map->end() || it->second != meta_info.value()) {
913 // One of original meta info entries is missing in |meta_info_map| or
914 // different.
915 break;
918 if (index == size) {
919 // The original meta info from the sync model is already equivalent to
920 // |meta_info_map|.
921 return;
925 // Clear and reset meta info in bookmark specifics.
926 specifics.clear_meta_info();
927 if (meta_info_map) {
928 for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map->begin();
929 it != meta_info_map->end(); ++it) {
930 sync_pb::MetaInfo* meta_info = specifics.add_meta_info();
931 meta_info->set_key(it->first);
932 meta_info->set_value(it->second);
936 sync_node->SetBookmarkSpecifics(specifics);
939 // static
940 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
941 const BookmarkNode* bookmark_node,
942 Profile* profile,
943 const GURL& icon_url,
944 const scoped_refptr<base::RefCountedMemory>& bitmap_data) {
945 history::HistoryService* history = HistoryServiceFactory::GetForProfile(
946 profile, ServiceAccessType::EXPLICIT_ACCESS);
947 favicon::FaviconService* favicon_service =
948 FaviconServiceFactory::GetForProfile(profile,
949 ServiceAccessType::EXPLICIT_ACCESS);
951 history->AddPageNoVisitForBookmark(bookmark_node->url(),
952 bookmark_node->GetTitle());
953 // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
954 // overwrite the cached 2x favicon bitmap. Sync favicons are always
955 // gfx::kFaviconSize in width and height. Store the favicon into history
956 // as such.
957 gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
958 favicon_service->MergeFavicon(bookmark_node->url(),
959 icon_url,
960 favicon_base::FAVICON,
961 bitmap_data,
962 pixel_size);
965 // static
966 void BookmarkChangeProcessor::SetSyncNodeFavicon(
967 const BookmarkNode* bookmark_node,
968 BookmarkModel* model,
969 syncer::WriteNode* sync_node) {
970 scoped_refptr<base::RefCountedMemory> favicon_bytes(NULL);
971 EncodeFavicon(bookmark_node, model, &favicon_bytes);
972 if (favicon_bytes.get() && favicon_bytes->size()) {
973 sync_pb::BookmarkSpecifics updated_specifics(
974 sync_node->GetBookmarkSpecifics());
975 updated_specifics.set_favicon(favicon_bytes->front(),
976 favicon_bytes->size());
977 updated_specifics.set_icon_url(bookmark_node->icon_url().spec());
978 sync_node->SetBookmarkSpecifics(updated_specifics);
982 bool BookmarkChangeProcessor::CanSyncNode(const BookmarkNode* node) {
983 return bookmark_model_->client()->CanSyncNode(node);
986 } // namespace browser_sync