bookmarks_helper.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1// Copyright (c) 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. 4 5#include "chrome/browser/sync/test/integration/bookmarks_helper.h" 6 7#include "base/compiler_specific.h" 8#include "base/file_util.h" 9#include "base/path_service.h" 10#include "base/rand_util.h" 11#include "base/strings/string_number_conversions.h" 12#include "base/strings/string_util.h" 13#include "base/strings/stringprintf.h" 14#include "base/strings/utf_string_conversions.h" 15#include "base/synchronization/waitable_event.h" 16#include "chrome/browser/bookmarks/bookmark_model.h" 17#include "chrome/browser/bookmarks/bookmark_model_factory.h" 18#include "chrome/browser/bookmarks/bookmark_model_observer.h" 19#include "chrome/browser/bookmarks/bookmark_utils.h" 20#include "chrome/browser/favicon/favicon_service_factory.h" 21#include "chrome/browser/favicon/favicon_util.h" 22#include "chrome/browser/history/history_db_task.h" 23#include "chrome/browser/history/history_service_factory.h" 24#include "chrome/browser/history/history_types.h" 25#include "chrome/browser/profiles/profile.h" 26#include "chrome/browser/sync/glue/bookmark_change_processor.h" 27#include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h" 28#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h" 29#include "chrome/browser/sync/test/integration/sync_datatype_helper.h" 30#include "chrome/browser/sync/test/integration/sync_test.h" 31#include "chrome/common/chrome_paths.h" 32#include "chrome/test/base/ui_test_utils.h" 33#include "testing/gtest/include/gtest/gtest.h" 34#include "third_party/skia/include/core/SkBitmap.h" 35#include "ui/base/models/tree_node_iterator.h" 36#include "ui/gfx/image/image_skia.h" 37 38using sync_datatype_helper::test; 39 40namespace { 41 42// History task which runs all pending tasks on the history thread and 43// signals when the tasks have completed. 44class HistoryEmptyTask : public history::HistoryDBTask { 45 public: 46 explicit HistoryEmptyTask(base::WaitableEvent* done) : done_(done) {} 47 48 virtual bool RunOnDBThread(history::HistoryBackend* backend, 49 history::HistoryDatabase* db) OVERRIDE { 50 content::RunAllPendingInMessageLoop(); 51 done_->Signal(); 52 return true; 53 } 54 55 virtual void DoneRunOnMainThread() OVERRIDE {} 56 57 private: 58 virtual ~HistoryEmptyTask() {} 59 60 base::WaitableEvent* done_; 61}; 62 63// Helper class used to wait for changes to take effect on the favicon of a 64// particular bookmark node in a particular bookmark model. 65class FaviconChangeObserver : public BookmarkModelObserver { 66 public: 67 FaviconChangeObserver(BookmarkModel* model, const BookmarkNode* node) 68 : model_(model), 69 node_(node), 70 wait_for_load_(false) { 71 model->AddObserver(this); 72 } 73 virtual ~FaviconChangeObserver() { 74 model_->RemoveObserver(this); 75 } 76 void WaitForGetFavicon() { 77 wait_for_load_ = true; 78 content::RunMessageLoop(); 79 ASSERT_TRUE(node_->is_favicon_loaded()); 80 ASSERT_FALSE(model_->GetFavicon(node_).IsEmpty()); 81 } 82 void WaitForSetFavicon() { 83 wait_for_load_ = false; 84 content::RunMessageLoop(); 85 } 86 virtual void BookmarkModelLoaded(BookmarkModel* model, 87 bool ids_reassigned) OVERRIDE {} 88 virtual void BookmarkNodeMoved(BookmarkModel* model, 89 const BookmarkNode* old_parent, 90 int old_index, 91 const BookmarkNode* new_parent, 92 int new_index) OVERRIDE {} 93 virtual void BookmarkNodeAdded(BookmarkModel* model, 94 const BookmarkNode* parent, 95 int index) OVERRIDE {} 96 virtual void BookmarkNodeRemoved(BookmarkModel* model, 97 const BookmarkNode* parent, 98 int old_index, 99 const BookmarkNode* node) OVERRIDE {} 100 virtual void BookmarkAllNodesRemoved(BookmarkModel* model) OVERRIDE {} 101 102 virtual void BookmarkNodeChanged(BookmarkModel* model, 103 const BookmarkNode* node) OVERRIDE { 104 if (model == model_ && node == node_) 105 model->GetFavicon(node); 106 } 107 virtual void BookmarkNodeChildrenReordered( 108 BookmarkModel* model, 109 const BookmarkNode* node) OVERRIDE {} 110 virtual void BookmarkNodeFaviconChanged( 111 BookmarkModel* model, 112 const BookmarkNode* node) OVERRIDE { 113 if (model == model_ && node == node_) { 114 if (!wait_for_load_ || (wait_for_load_ && node->is_favicon_loaded())) 115 base::MessageLoopForUI::current()->Quit(); 116 } 117 } 118 119 private: 120 BookmarkModel* model_; 121 const BookmarkNode* node_; 122 bool wait_for_load_; 123 DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver); 124}; 125 126// A collection of URLs for which we have added favicons. Since loading a 127// favicon is an asynchronous operation and doesn't necessarily invoke a 128// callback, this collection is used to determine if we must wait for a URL's 129// favicon to load or not. 130std::set<GURL>* urls_with_favicons_ = NULL; 131 132// Returns the number of nodes of node type |node_type| in |model| whose 133// titles match the string |title|. 134int CountNodesWithTitlesMatching(BookmarkModel* model, 135 BookmarkNode::Type node_type, 136 const base::string16& title) { 137 ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node()); 138 // Walk through the model tree looking for bookmark nodes of node type 139 // |node_type| whose titles match |title|. 140 int count = 0; 141 while (iterator.has_next()) { 142 const BookmarkNode* node = iterator.Next(); 143 if ((node->type() == node_type) && (node->GetTitle() == title)) 144 ++count; 145 } 146 return count; 147} 148 149// Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent. 150// Returns true if they match. 151bool FaviconBitmapsMatch(const SkBitmap& bitmap_a, const SkBitmap& bitmap_b) { 152 if (bitmap_a.getSize() == 0U && bitmap_b.getSize() == 0U) 153 return true; 154 if ((bitmap_a.getSize() != bitmap_b.getSize()) || 155 (bitmap_a.width() != bitmap_b.width()) || 156 (bitmap_a.height() != bitmap_b.height())) { 157 LOG(ERROR) << "Favicon size mismatch: " << bitmap_a.getSize() << " (" 158 << bitmap_a.width() << "x" << bitmap_a.height() << ") vs. " 159 << bitmap_b.getSize() << " (" << bitmap_b.width() << "x" 160 << bitmap_b.height() << ")"; 161 return false; 162 } 163 SkAutoLockPixels bitmap_lock_a(bitmap_a); 164 SkAutoLockPixels bitmap_lock_b(bitmap_b); 165 void* node_pixel_addr_a = bitmap_a.getPixels(); 166 EXPECT_TRUE(node_pixel_addr_a); 167 void* node_pixel_addr_b = bitmap_b.getPixels(); 168 EXPECT_TRUE(node_pixel_addr_b); 169 if (memcmp(node_pixel_addr_a, node_pixel_addr_b, bitmap_a.getSize()) != 0) { 170 LOG(ERROR) << "Favicon bitmap mismatch"; 171 return false; 172 } else { 173 return true; 174 } 175} 176 177// Represents a favicon image and the icon URL associated with it. 178struct FaviconData { 179 FaviconData() { 180 } 181 182 FaviconData(const gfx::Image& favicon_image, 183 const GURL& favicon_url) 184 : image(favicon_image), 185 icon_url(favicon_url) { 186 } 187 188 ~FaviconData() { 189 } 190 191 gfx::Image image; 192 GURL icon_url; 193}; 194 195// Gets the favicon and icon URL associated with |node| in |model|. 196FaviconData GetFaviconData(BookmarkModel* model, 197 const BookmarkNode* node) { 198 // If a favicon wasn't explicitly set for a particular URL, simply return its 199 // blank favicon. 200 if (!urls_with_favicons_ || 201 urls_with_favicons_->find(node->url()) == urls_with_favicons_->end()) { 202 return FaviconData(); 203 } 204 // If a favicon was explicitly set, we may need to wait for it to be loaded 205 // via BookmarkModel::GetFavicon(), which is an asynchronous operation. 206 if (!node->is_favicon_loaded()) { 207 FaviconChangeObserver observer(model, node); 208 model->GetFavicon(node); 209 observer.WaitForGetFavicon(); 210 } 211 EXPECT_TRUE(node->is_favicon_loaded()); 212 EXPECT_FALSE(model->GetFavicon(node).IsEmpty()); 213 return FaviconData(model->GetFavicon(node), node->icon_url()); 214} 215 216// Sets the favicon for |profile| and |node|. |profile| may be 217// |test()->verifier()|. 218void SetFaviconImpl(Profile* profile, 219 const BookmarkNode* node, 220 const GURL& icon_url, 221 const gfx::Image& image, 222 bookmarks_helper::FaviconSource favicon_source) { 223 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile); 224 225 FaviconChangeObserver observer(model, node); 226 FaviconService* favicon_service = 227 FaviconServiceFactory::GetForProfile(profile, 228 Profile::EXPLICIT_ACCESS); 229 if (favicon_source == bookmarks_helper::FROM_UI) { 230 favicon_service->SetFavicons( 231 node->url(), icon_url, favicon_base::FAVICON, image); 232 } else { 233 browser_sync::BookmarkChangeProcessor::ApplyBookmarkFavicon( 234 node, profile, icon_url, image.As1xPNGBytes()); 235 } 236 237 // Wait for the favicon for |node| to be invalidated. 238 observer.WaitForSetFavicon(); 239 // Wait for the BookmarkModel to fetch the updated favicon and for the new 240 // favicon to be sent to BookmarkChangeProcessor. 241 GetFaviconData(model, node); 242} 243 244// Wait for all currently scheduled tasks on the history thread for all 245// profiles to complete and any notifications sent to the UI thread to have 246// finished processing. 247void WaitForHistoryToProcessPendingTasks() { 248 // Skip waiting for history to complete for tests without favicons. 249 if (!urls_with_favicons_) 250 return; 251 252 std::vector<Profile*> profiles_which_need_to_wait; 253 if (test()->use_verifier()) 254 profiles_which_need_to_wait.push_back(test()->verifier()); 255 for (int i = 0; i < test()->num_clients(); ++i) 256 profiles_which_need_to_wait.push_back(test()->GetProfile(i)); 257 258 for (size_t i = 0; i < profiles_which_need_to_wait.size(); ++i) { 259 Profile* profile = profiles_which_need_to_wait[i]; 260 HistoryService* history_service = 261 HistoryServiceFactory::GetForProfileWithoutCreating(profile); 262 base::WaitableEvent done(false, false); 263 CancelableRequestConsumer request_consumer; 264 history_service->ScheduleDBTask(new HistoryEmptyTask(&done), 265 &request_consumer); 266 done.Wait(); 267 } 268 // Wait such that any notifications broadcast from one of the history threads 269 // to the UI thread are processed. 270 content::RunAllPendingInMessageLoop(); 271} 272 273// Checks if the favicon in |node_a| from |model_a| matches that of |node_b| 274// from |model_b|. Returns true if they match. 275bool FaviconsMatch(BookmarkModel* model_a, 276 BookmarkModel* model_b, 277 const BookmarkNode* node_a, 278 const BookmarkNode* node_b) { 279 FaviconData favicon_data_a = GetFaviconData(model_a, node_a); 280 FaviconData favicon_data_b = GetFaviconData(model_b, node_b); 281 282 if (favicon_data_a.icon_url != favicon_data_b.icon_url) 283 return false; 284 285 gfx::Image image_a = favicon_data_a.image; 286 gfx::Image image_b = favicon_data_b.image; 287 288 if (image_a.IsEmpty() && image_b.IsEmpty()) 289 return true; // Two empty images are equivalent. 290 291 if (image_a.IsEmpty() != image_b.IsEmpty()) 292 return false; 293 294 // Compare only the 1x bitmaps as only those are synced. 295 SkBitmap bitmap_a = image_a.AsImageSkia().GetRepresentation( 296 1.0f).sk_bitmap(); 297 SkBitmap bitmap_b = image_b.AsImageSkia().GetRepresentation( 298 1.0f).sk_bitmap(); 299 return FaviconBitmapsMatch(bitmap_a, bitmap_b); 300} 301 302// Does a deep comparison of BookmarkNode fields in |model_a| and |model_b|. 303// Returns true if they are all equal. 304bool NodesMatch(const BookmarkNode* node_a, const BookmarkNode* node_b) { 305 if (node_a == NULL || node_b == NULL) 306 return node_a == node_b; 307 if (node_a->is_folder() != node_b->is_folder()) { 308 LOG(ERROR) << "Cannot compare folder with bookmark"; 309 return false; 310 } 311 if (node_a->GetTitle() != node_b->GetTitle()) { 312 LOG(ERROR) << "Title mismatch: " << node_a->GetTitle() << " vs. " 313 << node_b->GetTitle(); 314 return false; 315 } 316 if (node_a->url() != node_b->url()) { 317 LOG(ERROR) << "URL mismatch: " << node_a->url() << " vs. " 318 << node_b->url(); 319 return false; 320 } 321 if (node_a->parent()->GetIndexOf(node_a) != 322 node_b->parent()->GetIndexOf(node_b)) { 323 LOG(ERROR) << "Index mismatch: " 324 << node_a->parent()->GetIndexOf(node_a) << " vs. " 325 << node_b->parent()->GetIndexOf(node_b); 326 return false; 327 } 328 return true; 329} 330 331// Checks if the hierarchies in |model_a| and |model_b| are equivalent in 332// terms of the data model and favicon. Returns true if they both match. 333// Note: Some peripheral fields like creation times are allowed to mismatch. 334bool BookmarkModelsMatch(BookmarkModel* model_a, BookmarkModel* model_b) { 335 bool ret_val = true; 336 ui::TreeNodeIterator<const BookmarkNode> iterator_a(model_a->root_node()); 337 ui::TreeNodeIterator<const BookmarkNode> iterator_b(model_b->root_node()); 338 while (iterator_a.has_next()) { 339 const BookmarkNode* node_a = iterator_a.Next(); 340 if (!iterator_b.has_next()) { 341 LOG(ERROR) << "Models do not match."; 342 return false; 343 } 344 const BookmarkNode* node_b = iterator_b.Next(); 345 ret_val = ret_val && NodesMatch(node_a, node_b); 346 if (node_a->is_folder() || node_b->is_folder()) 347 continue; 348 ret_val = ret_val && FaviconsMatch(model_a, model_b, node_a, node_b); 349 } 350 ret_val = ret_val && (!iterator_b.has_next()); 351 return ret_val; 352} 353 354// Finds the node in the verifier bookmark model that corresponds to 355// |foreign_node| in |foreign_model| and stores its address in |result|. 356void FindNodeInVerifier(BookmarkModel* foreign_model, 357 const BookmarkNode* foreign_node, 358 const BookmarkNode** result) { 359 // Climb the tree. 360 std::stack<int> path; 361 const BookmarkNode* walker = foreign_node; 362 while (walker != foreign_model->root_node()) { 363 path.push(walker->parent()->GetIndexOf(walker)); 364 walker = walker->parent(); 365 } 366 367 // Swing over to the other tree. 368 walker = bookmarks_helper::GetVerifierBookmarkModel()->root_node(); 369 370 // Climb down. 371 while (!path.empty()) { 372 ASSERT_TRUE(walker->is_folder()); 373 ASSERT_LT(path.top(), walker->child_count()); 374 walker = walker->GetChild(path.top()); 375 path.pop(); 376 } 377 378 ASSERT_TRUE(NodesMatch(foreign_node, walker)); 379 *result = walker; 380} 381 382} // namespace 383 384 385namespace bookmarks_helper { 386 387BookmarkModel* GetBookmarkModel(int index) { 388 return BookmarkModelFactory::GetForProfile(test()->GetProfile(index)); 389} 390 391const BookmarkNode* GetBookmarkBarNode(int index) { 392 return GetBookmarkModel(index)->bookmark_bar_node(); 393} 394 395const BookmarkNode* GetOtherNode(int index) { 396 return GetBookmarkModel(index)->other_node(); 397} 398 399const BookmarkNode* GetSyncedBookmarksNode(int index) { 400 return GetBookmarkModel(index)->mobile_node(); 401} 402 403BookmarkModel* GetVerifierBookmarkModel() { 404 return BookmarkModelFactory::GetForProfile(test()->verifier()); 405} 406 407const BookmarkNode* AddURL(int profile, 408 const std::wstring& title, 409 const GURL& url) { 410 return AddURL(profile, GetBookmarkBarNode(profile), 0, title, url); 411} 412 413const BookmarkNode* AddURL(int profile, 414 int index, 415 const std::wstring& title, 416 const GURL& url) { 417 return AddURL(profile, GetBookmarkBarNode(profile), index, title, url); 418} 419 420const BookmarkNode* AddURL(int profile, 421 const BookmarkNode* parent, 422 int index, 423 const std::wstring& title, 424 const GURL& url) { 425 BookmarkModel* model = GetBookmarkModel(profile); 426 if (GetBookmarkNodeByID(model, parent->id()) != parent) { 427 LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to " 428 << "Profile " << profile; 429 return NULL; 430 } 431 const BookmarkNode* result = 432 model->AddURL(parent, index, base::WideToUTF16(title), url); 433 if (!result) { 434 LOG(ERROR) << "Could not add bookmark " << title << " to Profile " 435 << profile; 436 return NULL; 437 } 438 if (test()->use_verifier()) { 439 const BookmarkNode* v_parent = NULL; 440 FindNodeInVerifier(model, parent, &v_parent); 441 const BookmarkNode* v_node = GetVerifierBookmarkModel()->AddURL( 442 v_parent, index, base::WideToUTF16(title), url); 443 if (!v_node) { 444 LOG(ERROR) << "Could not add bookmark " << title << " to the verifier"; 445 return NULL; 446 } 447 EXPECT_TRUE(NodesMatch(v_node, result)); 448 } 449 return result; 450} 451 452const BookmarkNode* AddFolder(int profile, 453 const std::wstring& title) { 454 return AddFolder(profile, GetBookmarkBarNode(profile), 0, title); 455} 456 457const BookmarkNode* AddFolder(int profile, 458 int index, 459 const std::wstring& title) { 460 return AddFolder(profile, GetBookmarkBarNode(profile), index, title); 461} 462 463const BookmarkNode* AddFolder(int profile, 464 const BookmarkNode* parent, 465 int index, 466 const std::wstring& title) { 467 BookmarkModel* model = GetBookmarkModel(profile); 468 if (GetBookmarkNodeByID(model, parent->id()) != parent) { 469 LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to " 470 << "Profile " << profile; 471 return NULL; 472 } 473 const BookmarkNode* result = 474 model->AddFolder(parent, index, base::WideToUTF16(title)); 475 EXPECT_TRUE(result); 476 if (!result) { 477 LOG(ERROR) << "Could not add folder " << title << " to Profile " 478 << profile; 479 return NULL; 480 } 481 if (test()->use_verifier()) { 482 const BookmarkNode* v_parent = NULL; 483 FindNodeInVerifier(model, parent, &v_parent); 484 const BookmarkNode* v_node = GetVerifierBookmarkModel()->AddFolder( 485 v_parent, index, base::WideToUTF16(title)); 486 if (!v_node) { 487 LOG(ERROR) << "Could not add folder " << title << " to the verifier"; 488 return NULL; 489 } 490 EXPECT_TRUE(NodesMatch(v_node, result)); 491 } 492 return result; 493} 494 495void SetTitle(int profile, 496 const BookmarkNode* node, 497 const std::wstring& new_title) { 498 BookmarkModel* model = GetBookmarkModel(profile); 499 ASSERT_EQ(GetBookmarkNodeByID(model, node->id()), node) 500 << "Node " << node->GetTitle() << " does not belong to " 501 << "Profile " << profile; 502 if (test()->use_verifier()) { 503 const BookmarkNode* v_node = NULL; 504 FindNodeInVerifier(model, node, &v_node); 505 GetVerifierBookmarkModel()->SetTitle(v_node, base::WideToUTF16(new_title)); 506 } 507 model->SetTitle(node, base::WideToUTF16(new_title)); 508} 509 510void SetFavicon(int profile, 511 const BookmarkNode* node, 512 const GURL& icon_url, 513 const gfx::Image& image, 514 FaviconSource favicon_source) { 515 BookmarkModel* model = GetBookmarkModel(profile); 516 ASSERT_EQ(GetBookmarkNodeByID(model, node->id()), node) 517 << "Node " << node->GetTitle() << " does not belong to " 518 << "Profile " << profile; 519 ASSERT_EQ(BookmarkNode::URL, node->type()) << "Node " << node->GetTitle() 520 << " must be a url."; 521 if (urls_with_favicons_ == NULL) 522 urls_with_favicons_ = new std::set<GURL>(); 523 urls_with_favicons_->insert(node->url()); 524 if (test()->use_verifier()) { 525 const BookmarkNode* v_node = NULL; 526 FindNodeInVerifier(model, node, &v_node); 527 SetFaviconImpl(test()->verifier(), v_node, icon_url, image, favicon_source); 528 } 529 SetFaviconImpl(test()->GetProfile(profile), node, icon_url, image, 530 favicon_source); 531} 532 533const BookmarkNode* SetURL(int profile, 534 const BookmarkNode* node, 535 const GURL& new_url) { 536 BookmarkModel* model = GetBookmarkModel(profile); 537 if (GetBookmarkNodeByID(model, node->id()) != node) { 538 LOG(ERROR) << "Node " << node->GetTitle() << " does not belong to " 539 << "Profile " << profile; 540 return NULL; 541 } 542 if (test()->use_verifier()) { 543 const BookmarkNode* v_node = NULL; 544 FindNodeInVerifier(model, node, &v_node); 545 if (v_node->is_url()) 546 GetVerifierBookmarkModel()->SetURL(v_node, new_url); 547 } 548 if (node->is_url()) 549 model->SetURL(node, new_url); 550 return node; 551} 552 553void Move(int profile, 554 const BookmarkNode* node, 555 const BookmarkNode* new_parent, 556 int index) { 557 BookmarkModel* model = GetBookmarkModel(profile); 558 ASSERT_EQ(GetBookmarkNodeByID(model, node->id()), node) 559 << "Node " << node->GetTitle() << " does not belong to " 560 << "Profile " << profile; 561 if (test()->use_verifier()) { 562 const BookmarkNode* v_new_parent = NULL; 563 const BookmarkNode* v_node = NULL; 564 FindNodeInVerifier(model, new_parent, &v_new_parent); 565 FindNodeInVerifier(model, node, &v_node); 566 GetVerifierBookmarkModel()->Move(v_node, v_new_parent, index); 567 } 568 model->Move(node, new_parent, index); 569} 570 571void Remove(int profile, const BookmarkNode* parent, int index) { 572 BookmarkModel* model = GetBookmarkModel(profile); 573 ASSERT_EQ(GetBookmarkNodeByID(model, parent->id()), parent) 574 << "Node " << parent->GetTitle() << " does not belong to " 575 << "Profile " << profile; 576 if (test()->use_verifier()) { 577 const BookmarkNode* v_parent = NULL; 578 FindNodeInVerifier(model, parent, &v_parent); 579 ASSERT_TRUE(NodesMatch(parent->GetChild(index), v_parent->GetChild(index))); 580 GetVerifierBookmarkModel()->Remove(v_parent, index); 581 } 582 model->Remove(parent, index); 583} 584 585void RemoveAll(int profile) { 586 if (test()->use_verifier()) { 587 const BookmarkNode* root_node = GetVerifierBookmarkModel()->root_node(); 588 for (int i = 0; i < root_node->child_count(); ++i) { 589 const BookmarkNode* permanent_node = root_node->GetChild(i); 590 for (int j = permanent_node->child_count() - 1; j >= 0; --j) { 591 GetVerifierBookmarkModel()->Remove(permanent_node, j); 592 } 593 } 594 } 595 GetBookmarkModel(profile)->RemoveAll(); 596} 597 598void SortChildren(int profile, const BookmarkNode* parent) { 599 BookmarkModel* model = GetBookmarkModel(profile); 600 ASSERT_EQ(GetBookmarkNodeByID(model, parent->id()), parent) 601 << "Node " << parent->GetTitle() << " does not belong to " 602 << "Profile " << profile; 603 if (test()->use_verifier()) { 604 const BookmarkNode* v_parent = NULL; 605 FindNodeInVerifier(model, parent, &v_parent); 606 GetVerifierBookmarkModel()->SortChildren(v_parent); 607 } 608 model->SortChildren(parent); 609} 610 611void ReverseChildOrder(int profile, const BookmarkNode* parent) { 612 ASSERT_EQ(GetBookmarkNodeByID(GetBookmarkModel(profile), parent->id()), 613 parent) 614 << "Node " << parent->GetTitle() << " does not belong to " 615 << "Profile " << profile; 616 int child_count = parent->child_count(); 617 if (child_count <= 0) 618 return; 619 for (int index = 0; index < child_count; ++index) { 620 Move(profile, parent->GetChild(index), parent, child_count - index); 621 } 622} 623 624bool ModelMatchesVerifier(int profile) { 625 if (!test()->use_verifier()) { 626 LOG(ERROR) << "Illegal to call ModelMatchesVerifier() after " 627 << "DisableVerifier(). Use ModelsMatch() instead."; 628 return false; 629 } 630 return BookmarkModelsMatch(GetVerifierBookmarkModel(), 631 GetBookmarkModel(profile)); 632} 633 634bool AllModelsMatchVerifier() { 635 // Ensure that all tasks have finished processing on the history thread 636 // and that any notifications the history thread may have sent have been 637 // processed before comparing models. 638 WaitForHistoryToProcessPendingTasks(); 639 640 for (int i = 0; i < test()->num_clients(); ++i) { 641 if (!ModelMatchesVerifier(i)) { 642 LOG(ERROR) << "Model " << i << " does not match the verifier."; 643 return false; 644 } 645 } 646 return true; 647} 648 649bool ModelsMatch(int profile_a, int profile_b) { 650 return BookmarkModelsMatch(GetBookmarkModel(profile_a), 651 GetBookmarkModel(profile_b)); 652} 653 654bool AllModelsMatch() { 655 // Ensure that all tasks have finished processing on the history thread 656 // and that any notifications the history thread may have sent have been 657 // processed before comparing models. 658 WaitForHistoryToProcessPendingTasks(); 659 660 for (int i = 1; i < test()->num_clients(); ++i) { 661 if (!ModelsMatch(0, i)) { 662 LOG(ERROR) << "Model " << i << " does not match Model 0."; 663 return false; 664 } 665 } 666 return true; 667} 668 669namespace { 670 671// Helper class used in the implementation of AwaitAllModelsMatch. 672class AllModelsMatchChecker : public MultiClientStatusChangeChecker { 673 public: 674 AllModelsMatchChecker(); 675 virtual ~AllModelsMatchChecker(); 676 677 virtual bool IsExitConditionSatisfied() OVERRIDE; 678 virtual std::string GetDebugMessage() const OVERRIDE; 679}; 680 681AllModelsMatchChecker::AllModelsMatchChecker() 682 : MultiClientStatusChangeChecker( 683 sync_datatype_helper::test()->GetSyncServices()) {} 684 685AllModelsMatchChecker::~AllModelsMatchChecker() {} 686 687bool AllModelsMatchChecker::IsExitConditionSatisfied() { 688 return AllModelsMatch(); 689} 690 691std::string AllModelsMatchChecker::GetDebugMessage() const { 692 return "Waiting for matching models"; 693} 694 695} // namespace 696 697bool AwaitAllModelsMatch() { 698 AllModelsMatchChecker checker; 699 checker.Wait(); 700 return !checker.TimedOut(); 701} 702 703 704bool ContainsDuplicateBookmarks(int profile) { 705 ui::TreeNodeIterator<const BookmarkNode> iterator( 706 GetBookmarkModel(profile)->root_node()); 707 while (iterator.has_next()) { 708 const BookmarkNode* node = iterator.Next(); 709 if (node->is_folder()) 710 continue; 711 std::vector<const BookmarkNode*> nodes; 712 GetBookmarkModel(profile)->GetNodesByURL(node->url(), &nodes); 713 EXPECT_TRUE(nodes.size() >= 1); 714 for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); 715 it != nodes.end(); ++it) { 716 if (node->id() != (*it)->id() && 717 node->parent() == (*it)->parent() && 718 node->GetTitle() == (*it)->GetTitle()){ 719 return true; 720 } 721 } 722 } 723 return false; 724} 725 726bool HasNodeWithURL(int profile, const GURL& url) { 727 std::vector<const BookmarkNode*> nodes; 728 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes); 729 return !nodes.empty(); 730} 731 732const BookmarkNode* GetUniqueNodeByURL(int profile, const GURL& url) { 733 std::vector<const BookmarkNode*> nodes; 734 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes); 735 EXPECT_EQ(1U, nodes.size()); 736 if (nodes.empty()) 737 return NULL; 738 return nodes[0]; 739} 740 741int CountBookmarksWithTitlesMatching(int profile, const std::wstring& title) { 742 return CountNodesWithTitlesMatching(GetBookmarkModel(profile), 743 BookmarkNode::URL, 744 base::WideToUTF16(title)); 745} 746 747int CountFoldersWithTitlesMatching(int profile, const std::wstring& title) { 748 return CountNodesWithTitlesMatching(GetBookmarkModel(profile), 749 BookmarkNode::FOLDER, 750 base::WideToUTF16(title)); 751} 752 753gfx::Image CreateFavicon(SkColor color) { 754 const int dip_width = 16; 755 const int dip_height = 16; 756 std::vector<ui::ScaleFactor> favicon_scale_factors = 757 FaviconUtil::GetFaviconScaleFactors(); 758 gfx::ImageSkia favicon; 759 for (size_t i = 0; i < favicon_scale_factors.size(); ++i) { 760 float scale = ui::GetImageScale(favicon_scale_factors[i]); 761 int pixel_width = dip_width * scale; 762 int pixel_height = dip_height * scale; 763 SkBitmap bmp; 764 bmp.setConfig(SkBitmap::kARGB_8888_Config, pixel_width, pixel_height); 765 bmp.allocPixels(); 766 bmp.eraseColor(color); 767 favicon.AddRepresentation( 768 gfx::ImageSkiaRep(bmp, 769 ui::GetImageScale(favicon_scale_factors[i]))); 770 } 771 return gfx::Image(favicon); 772} 773 774gfx::Image Create1xFaviconFromPNGFile(const std::string& path) { 775 const char* kPNGExtension = ".png"; 776 if (!EndsWith(path, kPNGExtension, false)) 777 return gfx::Image(); 778 779 base::FilePath full_path; 780 if (!PathService::Get(chrome::DIR_TEST_DATA, &full_path)) 781 return gfx::Image(); 782 783 full_path = full_path.AppendASCII("sync").AppendASCII(path); 784 std::string contents; 785 base::ReadFileToString(full_path, &contents); 786 return gfx::Image::CreateFrom1xPNGBytes( 787 base::RefCountedString::TakeString(&contents)); 788} 789 790std::string IndexedURL(int i) { 791 return base::StringPrintf("http://www.host.ext:1234/path/filename/%d", i); 792} 793 794std::wstring IndexedURLTitle(int i) { 795 return base::StringPrintf(L"URL Title %d", i); 796} 797 798std::wstring IndexedFolderName(int i) { 799 return base::StringPrintf(L"Folder Name %d", i); 800} 801 802std::wstring IndexedSubfolderName(int i) { 803 return base::StringPrintf(L"Subfolder Name %d", i); 804} 805 806std::wstring IndexedSubsubfolderName(int i) { 807 return base::StringPrintf(L"Subsubfolder Name %d", i); 808} 809 810} // namespace bookmarks_helper 811