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 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 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 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 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 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 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 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 131 void BookmarkChangeProcessor::Loaded(BookmarkModel* model) { 132 NOTREACHED(); 133 } 134 135 void BookmarkChangeProcessor::BookmarkModelBeingDeleted( 136 BookmarkModel* model) { 137 DCHECK(!running()) << "BookmarkModel deleted while ChangeProcessor running."; 138 bookmark_model_ = NULL; 139 } 140 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 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 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 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 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 250 void BookmarkChangeProcessor::BookmarkNodeFaviconLoaded(BookmarkModel* model, 251 const BookmarkNode* node) { 252 DCHECK(running()); 253 BookmarkNodeChanged(model, node); 254 } 255 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 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. 328 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. 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|. 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. 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. 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. 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 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