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