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