• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
5 
6 #include <stack>
7 #include <vector>
8 
9 #include "base/string16.h"
10 #include "base/string_util.h"
11 
12 #include "base/utf_string_conversions.h"
13 #include "chrome/browser/bookmarks/bookmark_utils.h"
14 #include "chrome/browser/favicon_service.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sync/profile_sync_service.h"
17 #include "content/browser/browser_thread.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "ui/gfx/codec/png_codec.h"
20 
21 namespace browser_sync {
22 
BookmarkChangeProcessor(BookmarkModelAssociator * model_associator,UnrecoverableErrorHandler * error_handler)23 BookmarkChangeProcessor::BookmarkChangeProcessor(
24     BookmarkModelAssociator* model_associator,
25     UnrecoverableErrorHandler* error_handler)
26     : ChangeProcessor(error_handler),
27       bookmark_model_(NULL),
28       model_associator_(model_associator) {
29   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
30   DCHECK(model_associator);
31   DCHECK(error_handler);
32 }
33 
StartImpl(Profile * profile)34 void BookmarkChangeProcessor::StartImpl(Profile* profile) {
35   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
36   DCHECK(!bookmark_model_);
37   bookmark_model_ = profile->GetBookmarkModel();
38   DCHECK(bookmark_model_->IsLoaded());
39   bookmark_model_->AddObserver(this);
40 }
41 
StopImpl()42 void BookmarkChangeProcessor::StopImpl() {
43   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
44   DCHECK(bookmark_model_);
45   bookmark_model_->RemoveObserver(this);
46   bookmark_model_ = NULL;
47   model_associator_ = NULL;
48 }
49 
UpdateSyncNodeProperties(const BookmarkNode * src,BookmarkModel * model,sync_api::WriteNode * dst)50 void BookmarkChangeProcessor::UpdateSyncNodeProperties(
51     const BookmarkNode* src, BookmarkModel* model, sync_api::WriteNode* dst) {
52   // Set the properties of the item.
53   dst->SetIsFolder(src->is_folder());
54   dst->SetTitle(UTF16ToWideHack(src->GetTitle()));
55   if (!src->is_folder())
56     dst->SetURL(src->GetURL());
57   SetSyncNodeFavicon(src, model, dst);
58 }
59 
60 // static
EncodeFavicon(const BookmarkNode * src,BookmarkModel * model,std::vector<unsigned char> * dst)61 void BookmarkChangeProcessor::EncodeFavicon(const BookmarkNode* src,
62                                             BookmarkModel* model,
63                                             std::vector<unsigned char>* dst) {
64   const SkBitmap& favicon = model->GetFavicon(src);
65 
66   dst->clear();
67 
68   // Check for zero-dimension images.  This can happen if the favicon is
69   // still being loaded.
70   if (favicon.empty())
71     return;
72 
73   // Re-encode the BookmarkNode's favicon as a PNG, and pass the data to the
74   // sync subsystem.
75   if (!gfx::PNGCodec::EncodeBGRASkBitmap(favicon, false, dst))
76     return;
77 }
78 
RemoveOneSyncNode(sync_api::WriteTransaction * trans,const BookmarkNode * node)79 void BookmarkChangeProcessor::RemoveOneSyncNode(
80     sync_api::WriteTransaction* trans, const BookmarkNode* node) {
81   sync_api::WriteNode sync_node(trans);
82   if (!model_associator_->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
83     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
84     return;
85   }
86   // This node should have no children.
87   DCHECK(sync_node.GetFirstChildId() == sync_api::kInvalidId);
88   // Remove association and delete the sync node.
89   model_associator_->Disassociate(sync_node.GetId());
90   sync_node.Remove();
91 }
92 
RemoveSyncNodeHierarchy(const BookmarkNode * topmost)93 void BookmarkChangeProcessor::RemoveSyncNodeHierarchy(
94     const BookmarkNode* topmost) {
95   sync_api::WriteTransaction trans(share_handle());
96 
97   // Later logic assumes that |topmost| has been unlinked.
98   DCHECK(!topmost->parent());
99 
100   // A BookmarkModel deletion event means that |node| and all its children were
101   // deleted. Sync backend expects children to be deleted individually, so we do
102   // a depth-first-search here.  At each step, we consider the |index|-th child
103   // of |node|.  |index_stack| stores index values for the parent levels.
104   std::stack<int> index_stack;
105   index_stack.push(0);  // For the final pop.  It's never used.
106   const BookmarkNode* node = topmost;
107   int index = 0;
108   while (node) {
109     // The top of |index_stack| should always be |node|'s index.
110     DCHECK(!node->parent() || (node->parent()->GetIndexOf(node) ==
111       index_stack.top()));
112     if (index == node->child_count()) {
113       // If we've processed all of |node|'s children, delete |node| and move
114       // on to its successor.
115       RemoveOneSyncNode(&trans, node);
116       node = node->parent();
117       index = index_stack.top() + 1;      // (top() + 0) was what we removed.
118       index_stack.pop();
119     } else {
120       // If |node| has an unprocessed child, process it next after pushing the
121       // current state onto the stack.
122       DCHECK_LT(index, node->child_count());
123       index_stack.push(index);
124       node = node->GetChild(index);
125       index = 0;
126     }
127   }
128   DCHECK(index_stack.empty());  // Nothing should be left on the stack.
129 }
130 
Loaded(BookmarkModel * model)131 void BookmarkChangeProcessor::Loaded(BookmarkModel* model) {
132   NOTREACHED();
133 }
134 
BookmarkModelBeingDeleted(BookmarkModel * model)135 void BookmarkChangeProcessor::BookmarkModelBeingDeleted(
136     BookmarkModel* model) {
137   DCHECK(!running()) << "BookmarkModel deleted while ChangeProcessor running.";
138   bookmark_model_ = NULL;
139 }
140 
BookmarkNodeAdded(BookmarkModel * model,const BookmarkNode * parent,int index)141 void BookmarkChangeProcessor::BookmarkNodeAdded(BookmarkModel* model,
142                                                 const BookmarkNode* parent,
143                                                 int index) {
144   DCHECK(running());
145   DCHECK(share_handle());
146 
147   // Acquire a scoped write lock via a transaction.
148   sync_api::WriteTransaction trans(share_handle());
149 
150   CreateSyncNode(parent, model, index, &trans, model_associator_,
151                  error_handler());
152 }
153 
154 // static
CreateSyncNode(const BookmarkNode * parent,BookmarkModel * model,int index,sync_api::WriteTransaction * trans,BookmarkModelAssociator * associator,UnrecoverableErrorHandler * error_handler)155 int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent,
156     BookmarkModel* model, int index, sync_api::WriteTransaction* trans,
157     BookmarkModelAssociator* associator,
158     UnrecoverableErrorHandler* error_handler) {
159   const BookmarkNode* child = parent->GetChild(index);
160   DCHECK(child);
161 
162   // Create a WriteNode container to hold the new node.
163   sync_api::WriteNode sync_child(trans);
164 
165   // Actually create the node with the appropriate initial position.
166   if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) {
167     error_handler->OnUnrecoverableError(FROM_HERE,
168         "Sync node creation failed; recovery unlikely");
169     return sync_api::kInvalidId;
170   }
171 
172   UpdateSyncNodeProperties(child, model, &sync_child);
173 
174   // Associate the ID from the sync domain with the bookmark node, so that we
175   // can refer back to this item later.
176   associator->Associate(child, sync_child.GetId());
177 
178   return sync_child.GetId();
179 }
180 
181 
BookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,int index,const BookmarkNode * node)182 void BookmarkChangeProcessor::BookmarkNodeRemoved(BookmarkModel* model,
183                                                     const BookmarkNode* parent,
184                                                     int index,
185                                                     const BookmarkNode* node) {
186   DCHECK(running());
187   RemoveSyncNodeHierarchy(node);
188 }
189 
BookmarkNodeChanged(BookmarkModel * model,const BookmarkNode * node)190 void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model,
191                                                     const BookmarkNode* node) {
192   DCHECK(running());
193   // We shouldn't see changes to the top-level nodes.
194   if (node == model->GetBookmarkBarNode() || node == model->other_node()) {
195     NOTREACHED() << "Saw update to permanent node!";
196     return;
197   }
198 
199   // Acquire a scoped write lock via a transaction.
200   sync_api::WriteTransaction trans(share_handle());
201 
202   // Lookup the sync node that's associated with |node|.
203   sync_api::WriteNode sync_node(&trans);
204   if (!model_associator_->InitSyncNodeFromChromeId(node->id(), &sync_node)) {
205     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
206     return;
207   }
208 
209   UpdateSyncNodeProperties(node, model, &sync_node);
210 
211   DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder());
212   DCHECK_EQ(model_associator_->GetChromeNodeFromSyncId(
213             sync_node.GetParentId()),
214             node->parent());
215   // This node's index should be one more than the predecessor's index.
216   DCHECK_EQ(node->parent()->GetIndexOf(node),
217             CalculateBookmarkModelInsertionIndex(node->parent(),
218                                                  &sync_node));
219 }
220 
221 
BookmarkNodeMoved(BookmarkModel * model,const BookmarkNode * old_parent,int old_index,const BookmarkNode * new_parent,int new_index)222 void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model,
223       const BookmarkNode* old_parent, int old_index,
224       const BookmarkNode* new_parent, int new_index) {
225   DCHECK(running());
226   const BookmarkNode* child = new_parent->GetChild(new_index);
227   // We shouldn't see changes to the top-level nodes.
228   if (child == model->GetBookmarkBarNode() || child == model->other_node()) {
229     NOTREACHED() << "Saw update to permanent node!";
230     return;
231   }
232 
233   // Acquire a scoped write lock via a transaction.
234   sync_api::WriteTransaction trans(share_handle());
235 
236   // Lookup the sync node that's associated with |child|.
237   sync_api::WriteNode sync_node(&trans);
238   if (!model_associator_->InitSyncNodeFromChromeId(child->id(), &sync_node)) {
239     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
240     return;
241   }
242 
243   if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node,
244                      model_associator_)) {
245     error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
246     return;
247   }
248 }
249 
BookmarkNodeFaviconLoaded(BookmarkModel * model,const BookmarkNode * node)250 void BookmarkChangeProcessor::BookmarkNodeFaviconLoaded(BookmarkModel* model,
251       const BookmarkNode* node) {
252   DCHECK(running());
253   BookmarkNodeChanged(model, node);
254 }
255 
BookmarkNodeChildrenReordered(BookmarkModel * model,const BookmarkNode * node)256 void BookmarkChangeProcessor::BookmarkNodeChildrenReordered(
257     BookmarkModel* model, const BookmarkNode* node) {
258 
259   // Acquire a scoped write lock via a transaction.
260   sync_api::WriteTransaction trans(share_handle());
261 
262   // The given node's children got reordered. We need to reorder all the
263   // children of the corresponding sync node.
264   for (int i = 0; i < node->child_count(); ++i) {
265     sync_api::WriteNode sync_child(&trans);
266     if (!model_associator_->InitSyncNodeFromChromeId(node->GetChild(i)->id(),
267                                                      &sync_child)) {
268       error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
269       return;
270     }
271     DCHECK_EQ(sync_child.GetParentId(),
272               model_associator_->GetSyncIdFromChromeId(node->id()));
273 
274     if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child,
275                        model_associator_)) {
276       error_handler()->OnUnrecoverableError(FROM_HERE, std::string());
277       return;
278     }
279   }
280 }
281 
282 // static
PlaceSyncNode(MoveOrCreate operation,const BookmarkNode * parent,int index,sync_api::WriteTransaction * trans,sync_api::WriteNode * dst,BookmarkModelAssociator * associator)283 bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation,
284       const BookmarkNode* parent, int index, sync_api::WriteTransaction* trans,
285       sync_api::WriteNode* dst, BookmarkModelAssociator* associator) {
286   sync_api::ReadNode sync_parent(trans);
287   if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) {
288     LOG(WARNING) << "Parent lookup failed";
289     return false;
290   }
291 
292   bool success = false;
293   if (index == 0) {
294     // Insert into first position.
295     success = (operation == CREATE) ?
296         dst->InitByCreation(syncable::BOOKMARKS, sync_parent, NULL) :
297         dst->SetPosition(sync_parent, NULL);
298     if (success) {
299       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
300       DCHECK_EQ(dst->GetId(), sync_parent.GetFirstChildId());
301       DCHECK_EQ(dst->GetPredecessorId(), sync_api::kInvalidId);
302     }
303   } else {
304     // Find the bookmark model predecessor, and insert after it.
305     const BookmarkNode* prev = parent->GetChild(index - 1);
306     sync_api::ReadNode sync_prev(trans);
307     if (!associator->InitSyncNodeFromChromeId(prev->id(), &sync_prev)) {
308       LOG(WARNING) << "Predecessor lookup failed";
309       return false;
310     }
311     success = (operation == CREATE) ?
312         dst->InitByCreation(syncable::BOOKMARKS, sync_parent, &sync_prev) :
313         dst->SetPosition(sync_parent, &sync_prev);
314     if (success) {
315       DCHECK_EQ(dst->GetParentId(), sync_parent.GetId());
316       DCHECK_EQ(dst->GetPredecessorId(), sync_prev.GetId());
317       DCHECK_EQ(dst->GetId(), sync_prev.GetSuccessorId());
318     }
319   }
320   return success;
321 }
322 
323 // Determine the bookmark model index to which a node must be moved so that
324 // predecessor of the node (in the bookmark model) matches the predecessor of
325 // |source| (in the sync model).
326 // As a precondition, this assumes that the predecessor of |source| has been
327 // updated and is already in the correct position in the bookmark model.
CalculateBookmarkModelInsertionIndex(const BookmarkNode * parent,const sync_api::BaseNode * child_info) const328 int BookmarkChangeProcessor::CalculateBookmarkModelInsertionIndex(
329     const BookmarkNode* parent,
330     const sync_api::BaseNode* child_info) const {
331   DCHECK(parent);
332   DCHECK(child_info);
333   int64 predecessor_id = child_info->GetPredecessorId();
334   // A return ID of kInvalidId indicates no predecessor.
335   if (predecessor_id == sync_api::kInvalidId)
336     return 0;
337 
338   // Otherwise, insert after the predecessor bookmark node.
339   const BookmarkNode* predecessor =
340       model_associator_->GetChromeNodeFromSyncId(predecessor_id);
341   DCHECK(predecessor);
342   DCHECK_EQ(predecessor->parent(), parent);
343   return parent->GetIndexOf(predecessor) + 1;
344 }
345 
346 // ApplyModelChanges is called by the sync backend after changes have been made
347 // to the sync engine's model.  Apply these changes to the browser bookmark
348 // model.
ApplyChangesFromSyncModel(const sync_api::BaseTransaction * trans,const sync_api::SyncManager::ChangeRecord * changes,int change_count)349 void BookmarkChangeProcessor::ApplyChangesFromSyncModel(
350     const sync_api::BaseTransaction* trans,
351     const sync_api::SyncManager::ChangeRecord* changes,
352     int change_count) {
353   if (!running())
354     return;
355   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
356   // A note about ordering.  Sync backend is responsible for ordering the change
357   // records in the following order:
358   //
359   // 1. Deletions, from leaves up to parents.
360   // 2. Existing items with synced parents & predecessors.
361   // 3. New items with synced parents & predecessors.
362   // 4. Items with parents & predecessors in the list.
363   // 5. Repeat #4 until all items are in the list.
364   //
365   // "Predecessor" here means the previous item within a given folder; an item
366   // in the first position is always said to have a synced predecessor.
367   // For the most part, applying these changes in the order given will yield
368   // the correct result.  There is one exception, however: for items that are
369   // moved away from a folder that is being deleted, we will process the delete
370   // before the move.  Since deletions in the bookmark model propagate from
371   // parent to child, we must move them to a temporary location.
372   BookmarkModel* model = bookmark_model_;
373 
374   // We are going to make changes to the bookmarks model, but don't want to end
375   // up in a feedback loop, so remove ourselves as an observer while applying
376   // changes.
377   model->RemoveObserver(this);
378 
379   // A parent to hold nodes temporarily orphaned by parent deletion.  It is
380   // lazily created inside the loop.
381   const BookmarkNode* foster_parent = NULL;
382   for (int i = 0; i < change_count; ++i) {
383     const BookmarkNode* dst =
384         model_associator_->GetChromeNodeFromSyncId(changes[i].id);
385     // Ignore changes to the permanent top-level nodes.  We only care about
386     // their children.
387     if ((dst == model->GetBookmarkBarNode()) || (dst == model->other_node()))
388       continue;
389     if (changes[i].action ==
390         sync_api::SyncManager::ChangeRecord::ACTION_DELETE) {
391       // Deletions should always be at the front of the list.
392       DCHECK(i == 0 || changes[i-1].action == changes[i].action);
393       // Children of a deleted node should not be deleted; they may be
394       // reparented by a later change record.  Move them to a temporary place.
395       DCHECK(dst) << "Could not find node to be deleted";
396       if (!dst) // Can't do anything if we can't find the chrome node.
397         continue;
398       const BookmarkNode* parent = dst->parent();
399       if (dst->child_count()) {
400         if (!foster_parent) {
401           foster_parent = model->AddFolder(model->other_node(),
402                                            model->other_node()->child_count(),
403                                            string16());
404         }
405         for (int i = dst->child_count() - 1; i >= 0; --i) {
406           model->Move(dst->GetChild(i), foster_parent,
407                       foster_parent->child_count());
408         }
409       }
410       DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children";
411       model_associator_->Disassociate(changes[i].id);
412       int index = parent->GetIndexOf(dst);
413       if (index > -1)
414         model->Remove(parent, index);
415       dst = NULL;
416     } else {
417       DCHECK_EQ((changes[i].action ==
418           sync_api::SyncManager::ChangeRecord::ACTION_ADD), (dst == NULL))
419           << "ACTION_ADD should be seen if and only if the node is unknown.";
420 
421       sync_api::ReadNode src(trans);
422       if (!src.InitByIdLookup(changes[i].id)) {
423         error_handler()->OnUnrecoverableError(FROM_HERE,
424             "ApplyModelChanges was passed a bad ID");
425         return;
426       }
427 
428       CreateOrUpdateBookmarkNode(&src, model);
429     }
430   }
431   // Clean up the temporary node.
432   if (foster_parent) {
433     // There should be no nodes left under the foster parent.
434     DCHECK_EQ(foster_parent->child_count(), 0);
435     model->Remove(foster_parent->parent(),
436                   foster_parent->parent()->GetIndexOf(foster_parent));
437     foster_parent = NULL;
438   }
439 
440   // We are now ready to hear about bookmarks changes again.
441   model->AddObserver(this);
442 }
443 
444 // Create a bookmark node corresponding to |src| if one is not already
445 // associated with |src|.
CreateOrUpdateBookmarkNode(sync_api::BaseNode * src,BookmarkModel * model)446 const BookmarkNode* BookmarkChangeProcessor::CreateOrUpdateBookmarkNode(
447     sync_api::BaseNode* src,
448     BookmarkModel* model) {
449   const BookmarkNode* parent =
450       model_associator_->GetChromeNodeFromSyncId(src->GetParentId());
451   if (!parent) {
452     DLOG(WARNING) << "Could not find parent of node being added/updated."
453       << " Node title: " << src->GetTitle()
454       << ", parent id = " << src->GetParentId();
455 
456     return NULL;
457   }
458   int index = CalculateBookmarkModelInsertionIndex(parent, src);
459   const BookmarkNode* dst = model_associator_->GetChromeNodeFromSyncId(
460       src->GetId());
461   if (!dst) {
462     dst = CreateBookmarkNode(src, parent, model, index);
463     model_associator_->Associate(dst, src->GetId());
464   } else {
465     // URL and is_folder are not expected to change.
466     // TODO(ncarter): Determine if such changes should be legal or not.
467     DCHECK_EQ(src->GetIsFolder(), dst->is_folder());
468 
469     // Handle reparenting and/or repositioning.
470     model->Move(dst, parent, index);
471 
472     if (!src->GetIsFolder())
473       model->SetURL(dst, src->GetURL());
474     model->SetTitle(dst, WideToUTF16Hack(src->GetTitle()));
475 
476     SetBookmarkFavicon(src, dst, model);
477   }
478 
479   return dst;
480 }
481 
482 // static
483 // Creates a bookmark node under the given parent node from the given sync
484 // node. Returns the newly created node.
CreateBookmarkNode(sync_api::BaseNode * sync_node,const BookmarkNode * parent,BookmarkModel * model,int index)485 const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode(
486     sync_api::BaseNode* sync_node,
487     const BookmarkNode* parent,
488     BookmarkModel* model,
489     int index) {
490   DCHECK(parent);
491   DCHECK(index >= 0 && index <= parent->child_count());
492 
493   const BookmarkNode* node;
494   if (sync_node->GetIsFolder()) {
495     node = model->AddFolder(parent, index,
496                             WideToUTF16Hack(sync_node->GetTitle()));
497   } else {
498     node = model->AddURL(parent, index,
499                          WideToUTF16Hack(sync_node->GetTitle()),
500                          sync_node->GetURL());
501     SetBookmarkFavicon(sync_node, node, model);
502   }
503   return node;
504 }
505 
506 // static
507 // Sets the favicon of the given bookmark node from the given sync node.
SetBookmarkFavicon(sync_api::BaseNode * sync_node,const BookmarkNode * bookmark_node,BookmarkModel * bookmark_model)508 bool BookmarkChangeProcessor::SetBookmarkFavicon(
509     sync_api::BaseNode* sync_node,
510     const BookmarkNode* bookmark_node,
511     BookmarkModel* bookmark_model) {
512   std::vector<unsigned char> icon_bytes_vector;
513   sync_node->GetFaviconBytes(&icon_bytes_vector);
514   if (icon_bytes_vector.empty())
515     return false;
516 
517   ApplyBookmarkFavicon(bookmark_node, bookmark_model->profile(),
518                        icon_bytes_vector);
519 
520   return true;
521 }
522 
523 // static
524 // Applies the given favicon bytes vector to the given bookmark node.
ApplyBookmarkFavicon(const BookmarkNode * bookmark_node,Profile * profile,const std::vector<unsigned char> & icon_bytes_vector)525 void BookmarkChangeProcessor::ApplyBookmarkFavicon(
526     const BookmarkNode* bookmark_node,
527     Profile* profile,
528     const std::vector<unsigned char>& icon_bytes_vector) {
529   // Registering a favicon requires that we provide a source URL, but we
530   // don't know where these came from.  Currently we just use the
531   // destination URL, which is not correct, but since the favicon URL
532   // is used as a key in the history's thumbnail DB, this gives us a value
533   // which does not collide with others.
534   GURL fake_icon_url = bookmark_node->GetURL();
535 
536   HistoryService* history =
537       profile->GetHistoryService(Profile::EXPLICIT_ACCESS);
538   FaviconService* favicon_service =
539       profile->GetFaviconService(Profile::EXPLICIT_ACCESS);
540 
541   history->AddPageNoVisitForBookmark(bookmark_node->GetURL());
542   favicon_service->SetFavicon(bookmark_node->GetURL(),
543                               fake_icon_url,
544                               icon_bytes_vector,
545                               history::FAVICON);
546 }
547 
548 // static
SetSyncNodeFavicon(const BookmarkNode * bookmark_node,BookmarkModel * model,sync_api::WriteNode * sync_node)549 void BookmarkChangeProcessor::SetSyncNodeFavicon(
550     const BookmarkNode* bookmark_node,
551     BookmarkModel* model,
552     sync_api::WriteNode* sync_node) {
553   std::vector<unsigned char> favicon_bytes;
554   EncodeFavicon(bookmark_node, model, &favicon_bytes);
555   if (!favicon_bytes.empty())
556     sync_node->SetFaviconBytes(favicon_bytes);
557 }
558 
559 }  // namespace browser_sync
560