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