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 <stddef.h>
8 
9 #include <functional>
10 #include <memory>
11 #include <set>
12 #include <vector>
13 
14 #include "base/bind.h"
15 #include "base/containers/stack.h"
16 #include "base/files/file_util.h"
17 #include "base/macros.h"
18 #include "base/path_service.h"
19 #include "base/rand_util.h"
20 #include "base/run_loop.h"
21 #include "base/sequenced_task_runner.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "base/synchronization/waitable_event.h"
27 #include "base/task/cancelable_task_tracker.h"
28 #include "base/threading/sequenced_task_runner_handle.h"
29 #include "base/threading/thread_restrictions.h"
30 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
31 #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
32 #include "chrome/browser/favicon/favicon_service_factory.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
35 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
36 #include "chrome/browser/sync/test/integration/sync_test.h"
37 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
38 #include "chrome/common/chrome_paths.h"
39 #include "components/bookmarks/browser/bookmark_client.h"
40 #include "components/bookmarks/browser/bookmark_model.h"
41 #include "components/bookmarks/browser/bookmark_utils.h"
42 #include "components/bookmarks/managed/managed_bookmark_service.h"
43 #include "components/favicon/core/favicon_service.h"
44 #include "components/favicon_base/favicon_util.h"
45 #include "components/sync/driver/profile_sync_service.h"
46 #include "components/sync/test/fake_server/entity_builder_factory.h"
47 #include "content/public/test/test_utils.h"
48 #include "testing/gtest/include/gtest/gtest.h"
49 #include "third_party/skia/include/core/SkBitmap.h"
50 #include "ui/base/models/tree_node_iterator.h"
51 #include "ui/gfx/favicon_size.h"
52 #include "ui/gfx/image/image_skia.h"
53 
54 namespace bookmarks_helper {
55 
56 namespace {
57 
58 using bookmarks::BookmarkModel;
59 using bookmarks::BookmarkNode;
60 
ApplyBookmarkFavicon(const BookmarkNode * bookmark_node,favicon::FaviconService * favicon_service,const GURL & icon_url,const scoped_refptr<base::RefCountedMemory> & bitmap_data)61 void ApplyBookmarkFavicon(
62     const BookmarkNode* bookmark_node,
63     favicon::FaviconService* favicon_service,
64     const GURL& icon_url,
65     const scoped_refptr<base::RefCountedMemory>& bitmap_data) {
66   // Some tests use no services.
67   if (favicon_service == nullptr)
68     return;
69 
70   favicon_service->AddPageNoVisitForBookmark(bookmark_node->url(),
71                                              bookmark_node->GetTitle());
72 
73   GURL icon_url_to_use = icon_url;
74 
75   if (icon_url.is_empty()) {
76     if (bitmap_data->size() == 0) {
77       // Empty icon URL and no bitmap data means no icon mapping.
78       favicon_service->DeleteFaviconMappings({bookmark_node->url()},
79                                              favicon_base::IconType::kFavicon);
80       return;
81     } else {
82       // Ancient clients (prior to M25) may not be syncing the favicon URL. If
83       // the icon URL is not synced, use the page URL as a fake icon URL as it
84       // is guaranteed to be unique.
85       icon_url_to_use = bookmark_node->url();
86     }
87   }
88 
89   // The client may have cached the favicon at 2x. Use MergeFavicon() as not to
90   // overwrite the cached 2x favicon bitmap. Sync favicons are always
91   // gfx::kFaviconSize in width and height. Store the favicon into history
92   // as such.
93   gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
94   favicon_service->MergeFavicon(bookmark_node->url(), icon_url_to_use,
95                                 favicon_base::IconType::kFavicon, bitmap_data,
96                                 pixel_size);
97 }
98 
99 // Helper class used to wait for changes to take effect on the favicon of a
100 // particular bookmark node in a particular bookmark model.
101 class FaviconChangeObserver : public bookmarks::BookmarkModelObserver {
102  public:
FaviconChangeObserver(BookmarkModel * model,const BookmarkNode * node)103   FaviconChangeObserver(BookmarkModel* model, const BookmarkNode* node)
104       : model_(model), node_(node) {
105     model->AddObserver(this);
106   }
~FaviconChangeObserver()107   ~FaviconChangeObserver() override { model_->RemoveObserver(this); }
WaitForSetFavicon()108   void WaitForSetFavicon() {
109     DCHECK(!run_loop_.running());
110     content::RunThisRunLoop(&run_loop_);
111   }
112 
113   // bookmarks::BookmarkModelObserver:
BookmarkModelLoaded(BookmarkModel * model,bool ids_reassigned)114   void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override {
115   }
BookmarkNodeMoved(BookmarkModel * model,const BookmarkNode * old_parent,size_t old_index,const BookmarkNode * new_parent,size_t new_index)116   void BookmarkNodeMoved(BookmarkModel* model,
117                          const BookmarkNode* old_parent,
118                          size_t old_index,
119                          const BookmarkNode* new_parent,
120                          size_t new_index) override {}
BookmarkNodeAdded(BookmarkModel * model,const BookmarkNode * parent,size_t index)121   void BookmarkNodeAdded(BookmarkModel* model,
122                          const BookmarkNode* parent,
123                          size_t index) override {}
BookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,size_t old_index,const BookmarkNode * node,const std::set<GURL> & removed_urls)124   void BookmarkNodeRemoved(BookmarkModel* model,
125                            const BookmarkNode* parent,
126                            size_t old_index,
127                            const BookmarkNode* node,
128                            const std::set<GURL>& removed_urls) override {}
BookmarkAllUserNodesRemoved(BookmarkModel * model,const std::set<GURL> & removed_urls)129   void BookmarkAllUserNodesRemoved(
130       BookmarkModel* model,
131       const std::set<GURL>& removed_urls) override {}
132 
BookmarkNodeChanged(BookmarkModel * model,const BookmarkNode * node)133   void BookmarkNodeChanged(BookmarkModel* model,
134                            const BookmarkNode* node) override {
135     if (model == model_ && node == node_)
136       model->GetFavicon(node);
137   }
BookmarkNodeChildrenReordered(BookmarkModel * model,const BookmarkNode * node)138   void BookmarkNodeChildrenReordered(BookmarkModel* model,
139                                      const BookmarkNode* node) override {}
BookmarkNodeFaviconChanged(BookmarkModel * model,const BookmarkNode * node)140   void BookmarkNodeFaviconChanged(BookmarkModel* model,
141                                   const BookmarkNode* node) override {
142     if (model == model_ && node == node_)
143       run_loop_.Quit();
144   }
145 
146  private:
147   BookmarkModel* model_;
148   const BookmarkNode* node_;
149   base::RunLoop run_loop_;
150   DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver);
151 };
152 
153 // Returns the number of nodes of node type |node_type| in |model| whose
154 // titles match the string |title|.
CountNodesWithTitlesMatching(BookmarkModel * model,BookmarkNode::Type node_type,const base::string16 & title)155 size_t CountNodesWithTitlesMatching(BookmarkModel* model,
156                                     BookmarkNode::Type node_type,
157                                     const base::string16& title) {
158   ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
159   // Walk through the model tree looking for bookmark nodes of node type
160   // |node_type| whose titles match |title|.
161   size_t count = 0;
162   while (iterator.has_next()) {
163     const BookmarkNode* node = iterator.Next();
164     if ((node->type() == node_type) && (node->GetTitle() == title))
165       ++count;
166   }
167   return count;
168 }
169 
170 // Returns the number of nodes of node type |node_type| in |model|.
CountNodes(BookmarkModel * model,BookmarkNode::Type node_type)171 size_t CountNodes(BookmarkModel* model, BookmarkNode::Type node_type) {
172   ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
173   // Walk through the model tree looking for bookmark nodes of node type
174   // |node_type|.
175   size_t count = 0;
176   while (iterator.has_next()) {
177     const BookmarkNode* node = iterator.Next();
178     if (node->type() == node_type)
179       ++count;
180   }
181   return count;
182 }
183 
184 // Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent.
185 // Returns true if they match.
FaviconRawBitmapsMatch(const SkBitmap & bitmap_a,const SkBitmap & bitmap_b)186 bool FaviconRawBitmapsMatch(const SkBitmap& bitmap_a,
187                             const SkBitmap& bitmap_b) {
188   if (bitmap_a.computeByteSize() == 0U && bitmap_b.computeByteSize() == 0U)
189     return true;
190   if ((bitmap_a.computeByteSize() != bitmap_b.computeByteSize()) ||
191       (bitmap_a.width() != bitmap_b.width()) ||
192       (bitmap_a.height() != bitmap_b.height())) {
193     LOG(ERROR) << "Favicon size mismatch: " << bitmap_a.computeByteSize()
194                << " (" << bitmap_a.width() << "x" << bitmap_a.height()
195                << ") vs. " << bitmap_b.computeByteSize() << " ("
196                << bitmap_b.width() << "x" << bitmap_b.height() << ")";
197     return false;
198   }
199   void* node_pixel_addr_a = bitmap_a.getPixels();
200   EXPECT_TRUE(node_pixel_addr_a);
201   void* node_pixel_addr_b = bitmap_b.getPixels();
202   EXPECT_TRUE(node_pixel_addr_b);
203   if (memcmp(node_pixel_addr_a, node_pixel_addr_b,
204              bitmap_a.computeByteSize()) != 0) {
205     LOG(ERROR) << "Favicon bitmap mismatch";
206     return false;
207   } else {
208     return true;
209   }
210 }
211 
212 // Represents a favicon image and the icon URL associated with it.
213 struct FaviconData {
FaviconDatabookmarks_helper::__anonb9c982af0111::FaviconData214   FaviconData() {
215   }
216 
FaviconDatabookmarks_helper::__anonb9c982af0111::FaviconData217   FaviconData(const gfx::Image& favicon_image,
218               const GURL& favicon_url)
219       : image(favicon_image),
220         icon_url(favicon_url) {
221   }
222 
223   gfx::Image image;
224   GURL icon_url;
225 };
226 
227 // Gets the favicon and icon URL associated with |node| in |model|. Returns
228 // nullopt if the favicon is still loading.
GetFaviconData(BookmarkModel * model,const BookmarkNode * node)229 base::Optional<FaviconData> GetFaviconData(BookmarkModel* model,
230                                            const BookmarkNode* node) {
231   // We may need to wait for the favicon to be loaded via
232   // BookmarkModel::GetFavicon(), which is an asynchronous operation.
233   if (!node->is_favicon_loaded()) {
234     model->GetFavicon(node);
235     // Favicon still loading, no data available just yet.
236     return base::nullopt;
237   }
238 
239   // Favicon loaded: return actual image, if there is one (the no-favicon case
240   // is also considered loaded).
241   return FaviconData(model->GetFavicon(node),
242                      node->icon_url() ? *node->icon_url() : GURL());
243 }
244 
245 // Sets the favicon for |profile| and |node|. |profile| may be
246 // |test()->verifier()|.
SetFaviconImpl(Profile * profile,const BookmarkNode * node,const GURL & icon_url,const gfx::Image & image,FaviconSource favicon_source)247 void SetFaviconImpl(Profile* profile,
248                     const BookmarkNode* node,
249                     const GURL& icon_url,
250                     const gfx::Image& image,
251                     FaviconSource favicon_source) {
252   BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
253 
254   FaviconChangeObserver observer(model, node);
255   favicon::FaviconService* favicon_service =
256       FaviconServiceFactory::GetForProfile(profile,
257                                            ServiceAccessType::EXPLICIT_ACCESS);
258   if (favicon_source == FROM_UI) {
259     favicon_service->SetFavicons({node->url()}, icon_url,
260                                  favicon_base::IconType::kFavicon, image);
261   } else {
262     ApplyBookmarkFavicon(node, favicon_service, icon_url, image.As1xPNGBytes());
263   }
264 
265   // Wait for the favicon for |node| to be invalidated.
266   observer.WaitForSetFavicon();
267   model->GetFavicon(node);
268 }
269 
270 // Expires the favicon for |profile| and |node|. |profile| may be
271 // |test()->verifier()|.
ExpireFaviconImpl(Profile * profile,const BookmarkNode * node)272 void ExpireFaviconImpl(Profile* profile, const BookmarkNode* node) {
273   favicon::FaviconService* favicon_service =
274       FaviconServiceFactory::GetForProfile(profile,
275                                            ServiceAccessType::EXPLICIT_ACCESS);
276   favicon_service->SetFaviconOutOfDateForPage(node->url());
277 }
278 
279 // Used to call FaviconService APIs synchronously by making |callback| quit a
280 // RunLoop.
OnGotFaviconData(base::OnceClosure callback,favicon_base::FaviconRawBitmapResult * output_bitmap_result,const favicon_base::FaviconRawBitmapResult & bitmap_result)281 void OnGotFaviconData(
282     base::OnceClosure callback,
283     favicon_base::FaviconRawBitmapResult* output_bitmap_result,
284     const favicon_base::FaviconRawBitmapResult& bitmap_result) {
285   *output_bitmap_result = bitmap_result;
286   std::move(callback).Run();
287 }
288 
289 // Deletes favicon mappings for |profile| and |node|. |profile| may be
290 // |test()->verifier()|.
DeleteFaviconMappingsImpl(Profile * profile,const BookmarkNode * node,FaviconSource favicon_source)291 void DeleteFaviconMappingsImpl(Profile* profile,
292                                const BookmarkNode* node,
293                                FaviconSource favicon_source) {
294   BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
295 
296   FaviconChangeObserver observer(model, node);
297   favicon::FaviconService* favicon_service =
298       FaviconServiceFactory::GetForProfile(profile,
299                                            ServiceAccessType::EXPLICIT_ACCESS);
300 
301   if (favicon_source == FROM_UI) {
302     favicon_service->DeleteFaviconMappings({node->url()},
303                                            favicon_base::IconType::kFavicon);
304   } else {
305     ApplyBookmarkFavicon(
306         node, favicon_service, /*icon_url=*/GURL(),
307         scoped_refptr<base::RefCountedString>(new base::RefCountedString()));
308   }
309 
310   // Wait for the favicon for |node| to be invalidated.
311   observer.WaitForSetFavicon();
312   model->GetFavicon(node);
313 }
314 
315 // Checks if the favicon in |node_a| from |model_a| matches that of |node_b|
316 // from |model_b|. Returns true if they match.
FaviconsMatch(BookmarkModel * model_a,BookmarkModel * model_b,const BookmarkNode * node_a,const BookmarkNode * node_b)317 bool FaviconsMatch(BookmarkModel* model_a,
318                    BookmarkModel* model_b,
319                    const BookmarkNode* node_a,
320                    const BookmarkNode* node_b) {
321   DCHECK(!node_a->is_folder());
322   DCHECK(!node_b->is_folder());
323 
324   base::Optional<FaviconData> favicon_data_a = GetFaviconData(model_a, node_a);
325   base::Optional<FaviconData> favicon_data_b = GetFaviconData(model_b, node_b);
326 
327   // If either of the two favicons is still loading, let's return false now
328   // because observers will get notified when the load completes. Note that even
329   // the lack of favicon is considered a loaded favicon.
330   if (!favicon_data_a.has_value() || !favicon_data_b.has_value())
331     return false;
332 
333   if (favicon_data_a->icon_url != favicon_data_b->icon_url)
334     return false;
335 
336   gfx::Image image_a = favicon_data_a->image;
337   gfx::Image image_b = favicon_data_b->image;
338 
339   if (image_a.IsEmpty() && image_b.IsEmpty())
340     return true;  // Two empty images are equivalent.
341 
342   if (image_a.IsEmpty() != image_b.IsEmpty())
343     return false;
344 
345   // Compare only the 1x bitmaps as only those are synced.
346   SkBitmap bitmap_a = image_a.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
347   SkBitmap bitmap_b = image_b.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
348   return FaviconRawBitmapsMatch(bitmap_a, bitmap_b);
349 }
350 
351 // Does a deep comparison of BookmarkNode fields in |model_a| and |model_b|.
352 // Returns true if they are all equal.
NodesMatch(const BookmarkNode * node_a,const BookmarkNode * node_b)353 bool NodesMatch(const BookmarkNode* node_a, const BookmarkNode* node_b) {
354   if (node_a == nullptr || node_b == nullptr)
355     return node_a == node_b;
356   if (node_a->is_folder() != node_b->is_folder()) {
357     LOG(ERROR) << "Cannot compare folder with bookmark";
358     return false;
359   }
360   if (node_a->GetTitle() != node_b->GetTitle()) {
361     LOG(ERROR) << "Title mismatch: " << node_a->GetTitle() << " vs. "
362                << node_b->GetTitle();
363     return false;
364   }
365   if (node_a->url() != node_b->url()) {
366     LOG(ERROR) << "URL mismatch: " << node_a->url() << " vs. "
367                << node_b->url();
368     return false;
369   }
370   if (node_a->parent()->GetIndexOf(node_a) !=
371       node_b->parent()->GetIndexOf(node_b)) {
372     LOG(ERROR) << "Index mismatch: "
373                << node_a->parent()->GetIndexOf(node_a) << " vs. "
374                << node_b->parent()->GetIndexOf(node_b);
375     return false;
376   }
377   if (node_a->guid() != node_b->guid()) {
378     LOG(ERROR) << "GUID mismatch: " << node_a->guid() << " vs. "
379                << node_b->guid();
380     return false;
381   }
382   return true;
383 }
384 
385 // Helper for BookmarkModelsMatch.
NodeCantBeSynced(bookmarks::BookmarkClient * client,const BookmarkNode * node)386 bool NodeCantBeSynced(bookmarks::BookmarkClient* client,
387                       const BookmarkNode* node) {
388   // Return true to skip a node.
389   return !client->CanSyncNode(node);
390 }
391 
392 // Checks if the hierarchies in |model_a| and |model_b| are equivalent in
393 // terms of the data model and favicon. Returns true if they both match.
394 // Note: Some peripheral fields like creation times are allowed to mismatch.
BookmarkModelsMatch(BookmarkModel * model_a,BookmarkModel * model_b)395 bool BookmarkModelsMatch(BookmarkModel* model_a, BookmarkModel* model_b) {
396   ui::TreeNodeIterator<const BookmarkNode> iterator_a(
397       model_a->root_node(),
398       base::BindRepeating(&NodeCantBeSynced, model_a->client()));
399   ui::TreeNodeIterator<const BookmarkNode> iterator_b(
400       model_b->root_node(),
401       base::BindRepeating(&NodeCantBeSynced, model_b->client()));
402   while (iterator_a.has_next()) {
403     const BookmarkNode* node_a = iterator_a.Next();
404     if (!iterator_b.has_next()) {
405       LOG(ERROR) << "Models do not match.";
406       return false;
407     }
408     const BookmarkNode* node_b = iterator_b.Next();
409     if (!NodesMatch(node_a, node_b)) {
410       LOG(ERROR) << "Nodes do not match";
411       return false;
412     }
413     if (node_a->is_folder() || node_b->is_folder()) {
414       continue;
415     }
416     if (!FaviconsMatch(model_a, model_b, node_a, node_b)) {
417       LOG(ERROR) << "Favicons do not match";
418       return false;
419     }
420   }
421   return !iterator_b.has_next();
422 }
423 
424 // Finds the node in the verifier bookmark model that corresponds to
425 // |foreign_node| in |foreign_model| and stores its address in |result|.
FindNodeInVerifier(BookmarkModel * foreign_model,const BookmarkNode * foreign_node,const BookmarkNode ** result)426 void FindNodeInVerifier(BookmarkModel* foreign_model,
427                         const BookmarkNode* foreign_node,
428                         const BookmarkNode** result) {
429   // Climb the tree.
430   base::stack<size_t> path;
431   const BookmarkNode* walker = foreign_node;
432   while (walker != foreign_model->root_node()) {
433     path.push(size_t{walker->parent()->GetIndexOf(walker)});
434     walker = walker->parent();
435   }
436 
437   // Swing over to the other tree.
438   walker = GetVerifierBookmarkModel()->root_node();
439 
440   // Climb down.
441   while (!path.empty()) {
442     ASSERT_TRUE(walker->is_folder());
443     ASSERT_LT(path.top(), walker->children().size());
444     walker = walker->children()[path.top()].get();
445     path.pop();
446   }
447 
448   ASSERT_TRUE(NodesMatch(foreign_node, walker));
449   *result = walker;
450 }
451 
GetAllBookmarkNodes(const BookmarkModel * model)452 std::vector<const BookmarkNode*> GetAllBookmarkNodes(
453     const BookmarkModel* model) {
454   std::vector<const BookmarkNode*> all_nodes;
455 
456   // Add root node separately as iterator does not include it.
457   all_nodes.push_back(model->root_node());
458 
459   ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
460   while (iterator.has_next()) {
461     all_nodes.push_back(iterator.Next());
462   }
463 
464   return all_nodes;
465 }
466 
TriggerAllFaviconLoading(BookmarkModel * model)467 void TriggerAllFaviconLoading(BookmarkModel* model) {
468   for (const BookmarkNode* node : GetAllBookmarkNodes(model)) {
469     if (!node->is_favicon_loaded()) {
470       // GetFavicon() kicks off the loading.
471       model->GetFavicon(node);
472     }
473   }
474 }
475 
476 }  // namespace
477 
GetBookmarkUndoService(int index)478 BookmarkUndoService* GetBookmarkUndoService(int index) {
479   return BookmarkUndoServiceFactory::GetForProfile(
480       sync_datatype_helper::test()->GetProfile(index));
481 }
482 
GetBookmarkModel(int index)483 BookmarkModel* GetBookmarkModel(int index) {
484   return BookmarkModelFactory::GetForBrowserContext(
485       sync_datatype_helper::test()->GetProfile(index));
486 }
487 
GetBookmarkBarNode(int index)488 const BookmarkNode* GetBookmarkBarNode(int index) {
489   return GetBookmarkModel(index)->bookmark_bar_node();
490 }
491 
GetOtherNode(int index)492 const BookmarkNode* GetOtherNode(int index) {
493   return GetBookmarkModel(index)->other_node();
494 }
495 
GetSyncedBookmarksNode(int index)496 const BookmarkNode* GetSyncedBookmarksNode(int index) {
497   return GetBookmarkModel(index)->mobile_node();
498 }
499 
GetManagedNode(int index)500 const BookmarkNode* GetManagedNode(int index) {
501   return ManagedBookmarkServiceFactory::GetForProfile(
502              sync_datatype_helper::test()->GetProfile(index))
503       ->managed_node();
504 }
505 
GetVerifierBookmarkModel()506 BookmarkModel* GetVerifierBookmarkModel() {
507   return BookmarkModelFactory::GetForBrowserContext(
508       sync_datatype_helper::test()->verifier());
509 }
510 
AddURL(int profile,const std::string & title,const GURL & url)511 const BookmarkNode* AddURL(int profile,
512                            const std::string& title,
513                            const GURL& url) {
514   return AddURL(profile, GetBookmarkBarNode(profile), 0, title,  url);
515 }
516 
AddURL(int profile,size_t index,const std::string & title,const GURL & url)517 const BookmarkNode* AddURL(int profile,
518                            size_t index,
519                            const std::string& title,
520                            const GURL& url) {
521   return AddURL(profile, GetBookmarkBarNode(profile), index, title, url);
522 }
523 
AddURL(int profile,const BookmarkNode * parent,size_t index,const std::string & title,const GURL & url)524 const BookmarkNode* AddURL(int profile,
525                            const BookmarkNode* parent,
526                            size_t index,
527                            const std::string& title,
528                            const GURL& url) {
529   BookmarkModel* model = GetBookmarkModel(profile);
530   if (bookmarks::GetBookmarkNodeByID(model, parent->id()) != parent) {
531     LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to "
532                << "Profile " << profile;
533     return nullptr;
534   }
535   const BookmarkNode* result =
536       model->AddURL(parent, index, base::UTF8ToUTF16(title), url);
537   if (!result) {
538     LOG(ERROR) << "Could not add bookmark " << title << " to Profile "
539                << profile;
540     return nullptr;
541   }
542   if (sync_datatype_helper::test()->UseVerifier()) {
543     const BookmarkNode* v_parent = nullptr;
544     FindNodeInVerifier(model, parent, &v_parent);
545     const BookmarkNode* v_node = GetVerifierBookmarkModel()->AddURL(
546         v_parent, index, base::UTF8ToUTF16(title), url,
547         /*meta_info=*/nullptr, result->date_added(), result->guid());
548     if (!v_node) {
549       LOG(ERROR) << "Could not add bookmark " << title << " to the verifier";
550       return nullptr;
551     }
552     EXPECT_TRUE(NodesMatch(v_node, result));
553   }
554   return result;
555 }
556 
AddFolder(int profile,const std::string & title)557 const BookmarkNode* AddFolder(int profile,
558                               const std::string& title) {
559   return AddFolder(profile, GetBookmarkBarNode(profile), 0, title);
560 }
561 
AddFolder(int profile,size_t index,const std::string & title)562 const BookmarkNode* AddFolder(int profile,
563                               size_t index,
564                               const std::string& title) {
565   return AddFolder(profile, GetBookmarkBarNode(profile), index, title);
566 }
567 
AddFolder(int profile,const BookmarkNode * parent,size_t index,const std::string & title)568 const BookmarkNode* AddFolder(int profile,
569                               const BookmarkNode* parent,
570                               size_t index,
571                               const std::string& title) {
572   BookmarkModel* model = GetBookmarkModel(profile);
573   if (bookmarks::GetBookmarkNodeByID(model, parent->id()) != parent) {
574     LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to "
575                << "Profile " << profile;
576     return nullptr;
577   }
578   const BookmarkNode* result =
579       model->AddFolder(parent, index, base::UTF8ToUTF16(title));
580   EXPECT_TRUE(result);
581   if (!result) {
582     LOG(ERROR) << "Could not add folder " << title << " to Profile "
583                << profile;
584     return nullptr;
585   }
586   if (sync_datatype_helper::test()->UseVerifier()) {
587     const BookmarkNode* v_parent = nullptr;
588     FindNodeInVerifier(model, parent, &v_parent);
589     const BookmarkNode* v_node = GetVerifierBookmarkModel()->AddFolder(
590         v_parent, index, base::UTF8ToUTF16(title),
591         /*meta_info=*/nullptr, result->guid());
592     if (!v_node) {
593       LOG(ERROR) << "Could not add folder " << title << " to the verifier";
594       return nullptr;
595     }
596     EXPECT_TRUE(NodesMatch(v_node, result));
597   }
598   return result;
599 }
600 
SetTitle(int profile,const BookmarkNode * node,const std::string & new_title)601 void SetTitle(int profile,
602               const BookmarkNode* node,
603               const std::string& new_title) {
604   BookmarkModel* model = GetBookmarkModel(profile);
605   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
606       << "Node " << node->GetTitle() << " does not belong to "
607       << "Profile " << profile;
608   if (sync_datatype_helper::test()->UseVerifier()) {
609     const BookmarkNode* v_node = nullptr;
610     FindNodeInVerifier(model, node, &v_node);
611     GetVerifierBookmarkModel()->SetTitle(v_node, base::UTF8ToUTF16(new_title));
612   }
613   model->SetTitle(node, base::UTF8ToUTF16(new_title));
614 }
615 
SetFavicon(int profile,const BookmarkNode * node,const GURL & icon_url,const gfx::Image & image,FaviconSource favicon_source)616 void SetFavicon(int profile,
617                 const BookmarkNode* node,
618                 const GURL& icon_url,
619                 const gfx::Image& image,
620                 FaviconSource favicon_source) {
621   BookmarkModel* model = GetBookmarkModel(profile);
622   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
623       << "Node " << node->GetTitle() << " does not belong to "
624       << "Profile " << profile;
625   ASSERT_EQ(BookmarkNode::URL, node->type()) << "Node " << node->GetTitle()
626                                              << " must be a url.";
627   if (sync_datatype_helper::test()->UseVerifier()) {
628     const BookmarkNode* v_node = nullptr;
629     FindNodeInVerifier(model, node, &v_node);
630     SetFaviconImpl(sync_datatype_helper::test()->verifier(),
631                    v_node,
632                    icon_url,
633                    image,
634                    favicon_source);
635   }
636   SetFaviconImpl(sync_datatype_helper::test()->GetProfile(profile),
637                  node,
638                  icon_url,
639                  image,
640                  favicon_source);
641 }
642 
ExpireFavicon(int profile,const BookmarkNode * node)643 void ExpireFavicon(int profile, const BookmarkNode* node) {
644   BookmarkModel* model = GetBookmarkModel(profile);
645   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
646       << "Node " << node->GetTitle() << " does not belong to "
647       << "Profile " << profile;
648   ASSERT_EQ(BookmarkNode::URL, node->type()) << "Node " << node->GetTitle()
649                                              << " must be a url.";
650 
651   if (sync_datatype_helper::test()->UseVerifier()) {
652     const BookmarkNode* v_node = nullptr;
653     FindNodeInVerifier(model, node, &v_node);
654     ExpireFaviconImpl(sync_datatype_helper::test()->verifier(), node);
655   }
656   ExpireFaviconImpl(sync_datatype_helper::test()->GetProfile(profile), node);
657 }
658 
CheckFaviconExpired(int profile,const GURL & icon_url)659 void CheckFaviconExpired(int profile, const GURL& icon_url) {
660   base::RunLoop run_loop;
661 
662   favicon::FaviconService* favicon_service =
663       FaviconServiceFactory::GetForProfile(
664           sync_datatype_helper::test()->GetProfile(profile),
665           ServiceAccessType::EXPLICIT_ACCESS);
666   base::CancelableTaskTracker task_tracker;
667   favicon_base::FaviconRawBitmapResult bitmap_result;
668   favicon_service->GetRawFavicon(
669       icon_url, favicon_base::IconType::kFavicon, 0,
670       base::BindOnce(&OnGotFaviconData, run_loop.QuitClosure(), &bitmap_result),
671       &task_tracker);
672   run_loop.Run();
673 
674   ASSERT_TRUE(bitmap_result.is_valid());
675   ASSERT_TRUE(bitmap_result.expired);
676 }
677 
CheckHasNoFavicon(int profile,const GURL & page_url)678 void CheckHasNoFavicon(int profile, const GURL& page_url) {
679   base::RunLoop run_loop;
680 
681   favicon::FaviconService* favicon_service =
682       FaviconServiceFactory::GetForProfile(
683           sync_datatype_helper::test()->GetProfile(profile),
684           ServiceAccessType::EXPLICIT_ACCESS);
685   base::CancelableTaskTracker task_tracker;
686   favicon_base::FaviconRawBitmapResult bitmap_result;
687   favicon_service->GetRawFaviconForPageURL(
688       page_url, {favicon_base::IconType::kFavicon}, 0,
689       /*fallback_to_host=*/false,
690       base::BindOnce(&OnGotFaviconData, run_loop.QuitClosure(), &bitmap_result),
691       &task_tracker);
692   run_loop.Run();
693 
694   ASSERT_FALSE(bitmap_result.is_valid());
695 }
696 
DeleteFaviconMappings(int profile,const BookmarkNode * node,FaviconSource favicon_source)697 void DeleteFaviconMappings(int profile,
698                            const BookmarkNode* node,
699                            FaviconSource favicon_source) {
700   BookmarkModel* model = GetBookmarkModel(profile);
701   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
702       << "Node " << node->GetTitle() << " does not belong to "
703       << "Profile " << profile;
704   ASSERT_EQ(BookmarkNode::URL, node->type())
705       << "Node " << node->GetTitle() << " must be a url.";
706 
707   if (sync_datatype_helper::test()->UseVerifier()) {
708     const BookmarkNode* v_node = nullptr;
709     FindNodeInVerifier(model, node, &v_node);
710     DeleteFaviconMappingsImpl(sync_datatype_helper::test()->verifier(), v_node,
711                               favicon_source);
712   }
713   DeleteFaviconMappingsImpl(sync_datatype_helper::test()->GetProfile(profile),
714                             node, favicon_source);
715 }
716 
SetURL(int profile,const BookmarkNode * node,const GURL & new_url)717 const BookmarkNode* SetURL(int profile,
718                            const BookmarkNode* node,
719                            const GURL& new_url) {
720   BookmarkModel* model = GetBookmarkModel(profile);
721   if (bookmarks::GetBookmarkNodeByID(model, node->id()) != node) {
722     LOG(ERROR) << "Node " << node->GetTitle() << " does not belong to "
723                << "Profile " << profile;
724     return nullptr;
725   }
726   if (sync_datatype_helper::test()->UseVerifier()) {
727     const BookmarkNode* v_node = nullptr;
728     FindNodeInVerifier(model, node, &v_node);
729     if (v_node->is_url())
730       GetVerifierBookmarkModel()->SetURL(v_node, new_url);
731   }
732   if (node->is_url())
733     model->SetURL(node, new_url);
734   return node;
735 }
736 
Move(int profile,const BookmarkNode * node,const BookmarkNode * new_parent,size_t index)737 void Move(int profile,
738           const BookmarkNode* node,
739           const BookmarkNode* new_parent,
740           size_t index) {
741   BookmarkModel* model = GetBookmarkModel(profile);
742   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
743       << "Node " << node->GetTitle() << " does not belong to "
744       << "Profile " << profile;
745   if (sync_datatype_helper::test()->UseVerifier()) {
746     const BookmarkNode* v_new_parent = nullptr;
747     const BookmarkNode* v_node = nullptr;
748     FindNodeInVerifier(model, new_parent, &v_new_parent);
749     FindNodeInVerifier(model, node, &v_node);
750     GetVerifierBookmarkModel()->Move(v_node, v_new_parent, index);
751   }
752   model->Move(node, new_parent, index);
753 }
754 
Remove(int profile,const BookmarkNode * parent,size_t index)755 void Remove(int profile, const BookmarkNode* parent, size_t index) {
756   BookmarkModel* model = GetBookmarkModel(profile);
757   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, parent->id()), parent)
758       << "Node " << parent->GetTitle() << " does not belong to "
759       << "Profile " << profile;
760   if (sync_datatype_helper::test()->UseVerifier()) {
761     const BookmarkNode* v_parent = nullptr;
762     FindNodeInVerifier(model, parent, &v_parent);
763     ASSERT_TRUE(NodesMatch(parent->children()[index].get(),
764                            v_parent->children()[index].get()));
765     GetVerifierBookmarkModel()->Remove(v_parent->children()[index].get());
766   }
767   model->Remove(parent->children()[index].get());
768 }
769 
RemoveAll(int profile)770 void RemoveAll(int profile) {
771   if (sync_datatype_helper::test()->UseVerifier()) {
772     const BookmarkNode* root_node = GetVerifierBookmarkModel()->root_node();
773     for (const auto& permanent_node : root_node->children()) {
774       while (!permanent_node->children().empty()) {
775         GetVerifierBookmarkModel()->Remove(
776             permanent_node->children().back().get());
777       }
778     }
779   }
780   GetBookmarkModel(profile)->RemoveAllUserBookmarks();
781 }
782 
SortChildren(int profile,const BookmarkNode * parent)783 void SortChildren(int profile, const BookmarkNode* parent) {
784   BookmarkModel* model = GetBookmarkModel(profile);
785   ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, parent->id()), parent)
786       << "Node " << parent->GetTitle() << " does not belong to "
787       << "Profile " << profile;
788   if (sync_datatype_helper::test()->UseVerifier()) {
789     const BookmarkNode* v_parent = nullptr;
790     FindNodeInVerifier(model, parent, &v_parent);
791     GetVerifierBookmarkModel()->SortChildren(v_parent);
792   }
793   model->SortChildren(parent);
794 }
795 
ReverseChildOrder(int profile,const BookmarkNode * parent)796 void ReverseChildOrder(int profile, const BookmarkNode* parent) {
797   ASSERT_EQ(
798       bookmarks::GetBookmarkNodeByID(GetBookmarkModel(profile), parent->id()),
799       parent)
800       << "Node " << parent->GetTitle() << " does not belong to "
801       << "Profile " << profile;
802   if (parent->children().empty())
803     return;
804   for (size_t i = 0; i < parent->children().size(); ++i) {
805     Move(profile, parent->children()[i].get(), parent,
806          parent->children().size() - i);
807   }
808 }
809 
ModelMatchesVerifier(int profile)810 bool ModelMatchesVerifier(int profile) {
811   if (!sync_datatype_helper::test()->UseVerifier()) {
812     LOG(ERROR) << "Illegal to call ModelMatchesVerifier() when verifier isn't "
813                << "enabled. Use ModelsMatch() instead.";
814     return false;
815   }
816   return BookmarkModelsMatch(GetVerifierBookmarkModel(),
817                              GetBookmarkModel(profile));
818 }
819 
AllModelsMatchVerifier()820 bool AllModelsMatchVerifier() {
821   for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
822     if (!ModelMatchesVerifier(i)) {
823       LOG(ERROR) << "Model " << i << " does not match the verifier.";
824       return false;
825     }
826   }
827   return true;
828 }
829 
ModelsMatch(int profile_a,int profile_b)830 bool ModelsMatch(int profile_a, int profile_b) {
831   return BookmarkModelsMatch(GetBookmarkModel(profile_a),
832                              GetBookmarkModel(profile_b));
833 }
834 
AllModelsMatch()835 bool AllModelsMatch() {
836   for (int i = 1; i < sync_datatype_helper::test()->num_clients(); ++i) {
837     if (!ModelsMatch(0, i)) {
838       LOG(ERROR) << "Model " << i << " does not match Model 0.";
839       return false;
840     }
841   }
842   return true;
843 }
844 
ContainsDuplicateBookmarks(int profile)845 bool ContainsDuplicateBookmarks(int profile) {
846   ui::TreeNodeIterator<const BookmarkNode> iterator(
847       GetBookmarkModel(profile)->root_node());
848   while (iterator.has_next()) {
849     const BookmarkNode* node = iterator.Next();
850     if (node->is_folder())
851       continue;
852     std::vector<const BookmarkNode*> nodes;
853     GetBookmarkModel(profile)->GetNodesByURL(node->url(), &nodes);
854     EXPECT_GE(nodes.size(), 1U);
855     for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin();
856          it != nodes.end(); ++it) {
857       if (node->id() != (*it)->id() &&
858           node->parent() == (*it)->parent() &&
859           node->GetTitle() == (*it)->GetTitle()) {
860         return true;
861       }
862     }
863   }
864   return false;
865 }
866 
HasNodeWithURL(int profile,const GURL & url)867 bool HasNodeWithURL(int profile, const GURL& url) {
868   std::vector<const BookmarkNode*> nodes;
869   GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
870   return !nodes.empty();
871 }
872 
GetUniqueNodeByURL(int profile,const GURL & url)873 const BookmarkNode* GetUniqueNodeByURL(int profile, const GURL& url) {
874   std::vector<const BookmarkNode*> nodes;
875   GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
876   EXPECT_EQ(1U, nodes.size());
877   if (nodes.empty())
878     return nullptr;
879   return nodes[0];
880 }
881 
CountAllBookmarks(int profile)882 size_t CountAllBookmarks(int profile) {
883   return CountNodes(GetBookmarkModel(profile), BookmarkNode::URL);
884 }
885 
CountBookmarksWithTitlesMatching(int profile,const std::string & title)886 size_t CountBookmarksWithTitlesMatching(int profile, const std::string& title) {
887   return CountNodesWithTitlesMatching(GetBookmarkModel(profile),
888                                       BookmarkNode::URL,
889                                       base::UTF8ToUTF16(title));
890 }
891 
CountBookmarksWithUrlsMatching(int profile,const GURL & url)892 size_t CountBookmarksWithUrlsMatching(int profile, const GURL& url) {
893   std::vector<const BookmarkNode*> nodes;
894   GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
895   return nodes.size();
896 }
897 
CountFoldersWithTitlesMatching(int profile,const std::string & title)898 size_t CountFoldersWithTitlesMatching(int profile, const std::string& title) {
899   return CountNodesWithTitlesMatching(GetBookmarkModel(profile),
900                                       BookmarkNode::FOLDER,
901                                       base::UTF8ToUTF16(title));
902 }
903 
ContainsBookmarkNodeWithGUID(int profile,const std::string & guid)904 bool ContainsBookmarkNodeWithGUID(int profile, const std::string& guid) {
905   for (const BookmarkNode* node :
906        GetAllBookmarkNodes(GetBookmarkModel(profile))) {
907     if (node->guid() == guid) {
908       return true;
909     }
910   }
911   return false;
912 }
913 
CreateFavicon(SkColor color)914 gfx::Image CreateFavicon(SkColor color) {
915   const int dip_width = 16;
916   const int dip_height = 16;
917   std::vector<float> favicon_scales = favicon_base::GetFaviconScales();
918   gfx::ImageSkia favicon;
919   for (size_t i = 0; i < favicon_scales.size(); ++i) {
920     float scale = favicon_scales[i];
921     int pixel_width = dip_width * scale;
922     int pixel_height = dip_height * scale;
923     SkBitmap bmp;
924     bmp.allocN32Pixels(pixel_width, pixel_height);
925     bmp.eraseColor(color);
926     favicon.AddRepresentation(gfx::ImageSkiaRep(bmp, scale));
927   }
928   return gfx::Image(favicon);
929 }
930 
Create1xFaviconFromPNGFile(const std::string & path)931 gfx::Image Create1xFaviconFromPNGFile(const std::string& path) {
932   base::ScopedAllowBlockingForTesting allow_blocking;
933   const char* kPNGExtension = ".png";
934   if (!base::EndsWith(path, kPNGExtension,
935                       base::CompareCase::INSENSITIVE_ASCII))
936     return gfx::Image();
937 
938   base::FilePath full_path;
939   if (!base::PathService::Get(chrome::DIR_TEST_DATA, &full_path))
940     return gfx::Image();
941 
942   full_path = full_path.AppendASCII("sync").AppendASCII(path);
943   std::string contents;
944   base::ReadFileToString(full_path, &contents);
945   return gfx::Image::CreateFrom1xPNGBytes(
946       base::RefCountedString::TakeString(&contents));
947 }
948 
IndexedURL(size_t i)949 std::string IndexedURL(size_t i) {
950   return "http://www.host.ext:1234/path/filename/" + base::NumberToString(i);
951 }
952 
IndexedURLTitle(size_t i)953 std::string IndexedURLTitle(size_t i) {
954   return "URL Title " + base::NumberToString(i);
955 }
956 
IndexedFolderName(size_t i)957 std::string IndexedFolderName(size_t i) {
958   return "Folder Name " + base::NumberToString(i);
959 }
960 
IndexedSubfolderName(size_t i)961 std::string IndexedSubfolderName(size_t i) {
962   return "Subfolder Name " + base::NumberToString(i);
963 }
964 
IndexedSubsubfolderName(size_t i)965 std::string IndexedSubsubfolderName(size_t i) {
966   return "Subsubfolder Name " + base::NumberToString(i);
967 }
968 
CreateBookmarkServerEntity(const std::string & title,const GURL & url)969 std::unique_ptr<syncer::LoopbackServerEntity> CreateBookmarkServerEntity(
970     const std::string& title,
971     const GURL& url) {
972   fake_server::EntityBuilderFactory entity_builder_factory;
973   fake_server::BookmarkEntityBuilder bookmark_builder =
974       entity_builder_factory.NewBookmarkEntityBuilder(title);
975   return bookmark_builder.BuildBookmark(url);
976 }
977 
AnyBookmarkChangeObserver(const base::RepeatingClosure & cb)978 AnyBookmarkChangeObserver::AnyBookmarkChangeObserver(
979     const base::RepeatingClosure& cb)
980     : cb_(cb) {}
981 
982 AnyBookmarkChangeObserver::~AnyBookmarkChangeObserver() = default;
983 
BookmarkModelLoaded(BookmarkModel * model,bool ids_reassigned)984 void AnyBookmarkChangeObserver::BookmarkModelLoaded(BookmarkModel* model,
985                                                     bool ids_reassigned) {
986   cb_.Run();
987 }
988 
BookmarkModelBeingDeleted(BookmarkModel * model)989 void AnyBookmarkChangeObserver::BookmarkModelBeingDeleted(
990     BookmarkModel* model) {
991   cb_.Run();
992 }
993 
BookmarkNodeMoved(BookmarkModel * model,const BookmarkNode * old_parent,size_t old_index,const BookmarkNode * new_parent,size_t new_index)994 void AnyBookmarkChangeObserver::BookmarkNodeMoved(
995     BookmarkModel* model,
996     const BookmarkNode* old_parent,
997     size_t old_index,
998     const BookmarkNode* new_parent,
999     size_t new_index) {
1000   cb_.Run();
1001 }
1002 
BookmarkNodeAdded(BookmarkModel * model,const BookmarkNode * parent,size_t index)1003 void AnyBookmarkChangeObserver::BookmarkNodeAdded(BookmarkModel* model,
1004                                                   const BookmarkNode* parent,
1005                                                   size_t index) {
1006   cb_.Run();
1007 }
1008 
OnWillRemoveBookmarks(BookmarkModel * model,const BookmarkNode * parent,size_t old_index,const BookmarkNode * node)1009 void AnyBookmarkChangeObserver::OnWillRemoveBookmarks(
1010     BookmarkModel* model,
1011     const BookmarkNode* parent,
1012     size_t old_index,
1013     const BookmarkNode* node) {
1014   cb_.Run();
1015 }
1016 
BookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,size_t old_index,const BookmarkNode * node,const std::set<GURL> & no_longer_bookmarked)1017 void AnyBookmarkChangeObserver::BookmarkNodeRemoved(
1018     BookmarkModel* model,
1019     const BookmarkNode* parent,
1020     size_t old_index,
1021     const BookmarkNode* node,
1022     const std::set<GURL>& no_longer_bookmarked) {
1023   cb_.Run();
1024 }
1025 
OnWillChangeBookmarkNode(BookmarkModel * model,const BookmarkNode * node)1026 void AnyBookmarkChangeObserver::OnWillChangeBookmarkNode(
1027     BookmarkModel* model,
1028     const BookmarkNode* node) {
1029   cb_.Run();
1030 }
1031 
BookmarkNodeChanged(BookmarkModel * model,const BookmarkNode * node)1032 void AnyBookmarkChangeObserver::BookmarkNodeChanged(BookmarkModel* model,
1033                                                     const BookmarkNode* node) {
1034   cb_.Run();
1035 }
1036 
OnWillChangeBookmarkMetaInfo(BookmarkModel * model,const BookmarkNode * node)1037 void AnyBookmarkChangeObserver::OnWillChangeBookmarkMetaInfo(
1038     BookmarkModel* model,
1039     const BookmarkNode* node) {
1040   cb_.Run();
1041 }
1042 
BookmarkMetaInfoChanged(BookmarkModel * model,const BookmarkNode * node)1043 void AnyBookmarkChangeObserver::BookmarkMetaInfoChanged(
1044     BookmarkModel* model,
1045     const BookmarkNode* node) {
1046   cb_.Run();
1047 }
1048 
BookmarkNodeFaviconChanged(BookmarkModel * model,const BookmarkNode * node)1049 void AnyBookmarkChangeObserver::BookmarkNodeFaviconChanged(
1050     BookmarkModel* model,
1051     const BookmarkNode* node) {
1052   cb_.Run();
1053 }
1054 
OnWillReorderBookmarkNode(BookmarkModel * model,const BookmarkNode * node)1055 void AnyBookmarkChangeObserver::OnWillReorderBookmarkNode(
1056     BookmarkModel* model,
1057     const BookmarkNode* node) {
1058   cb_.Run();
1059 }
1060 
BookmarkNodeChildrenReordered(BookmarkModel * model,const BookmarkNode * node)1061 void AnyBookmarkChangeObserver::BookmarkNodeChildrenReordered(
1062     BookmarkModel* model,
1063     const BookmarkNode* node) {
1064   cb_.Run();
1065 }
1066 
ExtensiveBookmarkChangesBeginning(BookmarkModel * model)1067 void AnyBookmarkChangeObserver::ExtensiveBookmarkChangesBeginning(
1068     BookmarkModel* model) {
1069   cb_.Run();
1070 }
1071 
ExtensiveBookmarkChangesEnded(BookmarkModel * model)1072 void AnyBookmarkChangeObserver::ExtensiveBookmarkChangesEnded(
1073     BookmarkModel* model) {
1074   cb_.Run();
1075 }
1076 
OnWillRemoveAllUserBookmarks(BookmarkModel * model)1077 void AnyBookmarkChangeObserver::OnWillRemoveAllUserBookmarks(
1078     BookmarkModel* model) {
1079   cb_.Run();
1080 }
1081 
BookmarkAllUserNodesRemoved(BookmarkModel * model,const std::set<GURL> & removed_urls)1082 void AnyBookmarkChangeObserver::BookmarkAllUserNodesRemoved(
1083     BookmarkModel* model,
1084     const std::set<GURL>& removed_urls) {
1085   cb_.Run();
1086 }
1087 
GroupedBookmarkChangesBeginning(BookmarkModel * model)1088 void AnyBookmarkChangeObserver::GroupedBookmarkChangesBeginning(
1089     BookmarkModel* model) {
1090   cb_.Run();
1091 }
1092 
GroupedBookmarkChangesEnded(BookmarkModel * model)1093 void AnyBookmarkChangeObserver::GroupedBookmarkChangesEnded(
1094     BookmarkModel* model) {
1095   cb_.Run();
1096 }
1097 
1098 BookmarkModelStatusChangeChecker::BookmarkModelStatusChangeChecker() = default;
1099 
~BookmarkModelStatusChangeChecker()1100 BookmarkModelStatusChangeChecker::~BookmarkModelStatusChangeChecker() {
1101   for (const auto& model_and_observer : observers_) {
1102     model_and_observer.first->RemoveObserver(model_and_observer.second.get());
1103   }
1104 }
1105 
Observe(bookmarks::BookmarkModel * model)1106 void BookmarkModelStatusChangeChecker::Observe(
1107     bookmarks::BookmarkModel* model) {
1108   auto observer =
1109       std::make_unique<AnyBookmarkChangeObserver>(base::BindRepeating(
1110           &SingleBookmarkModelStatusChangeChecker::PostCheckExitCondition,
1111           weak_ptr_factory_.GetWeakPtr()));
1112   model->AddObserver(observer.get());
1113   observers_.emplace_back(model, std::move(observer));
1114 }
1115 
CheckExitCondition()1116 void BookmarkModelStatusChangeChecker::CheckExitCondition() {
1117   pending_check_exit_condition_ = false;
1118   StatusChangeChecker::CheckExitCondition();
1119 }
1120 
PostCheckExitCondition()1121 void BookmarkModelStatusChangeChecker::PostCheckExitCondition() {
1122   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
1123 
1124   if (pending_check_exit_condition_) {
1125     // Already posted.
1126     return;
1127   }
1128 
1129   pending_check_exit_condition_ = true;
1130 
1131   // Use base::PostTask() instead of CheckExitCondition() directly to make sure
1132   // that the checker doesn't immediately kick in while bookmarks are modified.
1133   base::SequencedTaskRunnerHandle::Get()->PostTask(
1134       FROM_HERE,
1135       base::BindOnce(&BookmarkModelStatusChangeChecker::CheckExitCondition,
1136                      weak_ptr_factory_.GetWeakPtr()));
1137 }
1138 
BookmarksMatchChecker()1139 BookmarksMatchChecker::BookmarksMatchChecker() {
1140   for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
1141     Observe(GetBookmarkModel(i));
1142   }
1143 }
1144 
IsExitConditionSatisfied(std::ostream * os)1145 bool BookmarksMatchChecker::IsExitConditionSatisfied(std::ostream* os) {
1146   *os << "Waiting for matching models";
1147   return AllModelsMatch();
1148 }
1149 
Wait()1150 bool BookmarksMatchChecker::Wait() {
1151   for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
1152     TriggerAllFaviconLoading(GetBookmarkModel(i));
1153   }
1154   return BookmarkModelStatusChangeChecker::Wait();
1155 }
1156 
BookmarksMatchVerifierChecker()1157 BookmarksMatchVerifierChecker::BookmarksMatchVerifierChecker() {
1158   Observe(GetVerifierBookmarkModel());
1159   for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
1160     Observe(GetBookmarkModel(i));
1161   }
1162 }
1163 
IsExitConditionSatisfied(std::ostream * os)1164 bool BookmarksMatchVerifierChecker::IsExitConditionSatisfied(std::ostream* os) {
1165   *os << "Waiting for model to match verifier";
1166   return AllModelsMatchVerifier();
1167 }
1168 
Wait()1169 bool BookmarksMatchVerifierChecker::Wait() {
1170   TriggerAllFaviconLoading(GetVerifierBookmarkModel());
1171   for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
1172     TriggerAllFaviconLoading(GetBookmarkModel(i));
1173   }
1174   return BookmarkModelStatusChangeChecker::Wait();
1175 }
1176 
SingleBookmarkModelStatusChangeChecker(int profile_index)1177 SingleBookmarkModelStatusChangeChecker::SingleBookmarkModelStatusChangeChecker(
1178     int profile_index)
1179     : profile_index_(profile_index),
1180       bookmark_model_(GetBookmarkModel(profile_index)) {
1181   Observe(bookmark_model_);
1182 }
1183 
1184 SingleBookmarkModelStatusChangeChecker::
1185     ~SingleBookmarkModelStatusChangeChecker() = default;
1186 
profile_index() const1187 int SingleBookmarkModelStatusChangeChecker::profile_index() const {
1188   return profile_index_;
1189 }
1190 
bookmark_model() const1191 BookmarkModel* SingleBookmarkModelStatusChangeChecker::bookmark_model() const {
1192   return bookmark_model_;
1193 }
1194 
SingleBookmarksModelMatcherChecker(int profile_index,const Matcher & matcher)1195 SingleBookmarksModelMatcherChecker::SingleBookmarksModelMatcherChecker(
1196     int profile_index,
1197     const Matcher& matcher)
1198     : SingleBookmarkModelStatusChangeChecker(profile_index),
1199       matcher_(matcher) {}
1200 
~SingleBookmarksModelMatcherChecker()1201 SingleBookmarksModelMatcherChecker::~SingleBookmarksModelMatcherChecker() {}
1202 
IsExitConditionSatisfied(std::ostream * os)1203 bool SingleBookmarksModelMatcherChecker::IsExitConditionSatisfied(
1204     std::ostream* os) {
1205   const std::vector<const BookmarkNode*> all_bookmark_nodes =
1206       GetAllBookmarkNodes(bookmark_model());
1207 
1208   testing::StringMatchResultListener result_listener;
1209   const bool matches = testing::ExplainMatchResult(matcher_, all_bookmark_nodes,
1210                                                    &result_listener);
1211   if (TimedOut() && !matches && result_listener.str().empty()) {
1212     // Some matchers don't provide details via ExplainMatchResult().
1213     *os << "Expected: ";
1214     matcher_.DescribeTo(os);
1215     *os << "  Actual: " << testing::PrintToString(all_bookmark_nodes);
1216   } else {
1217     *os << result_listener.str();
1218   }
1219   return matches;
1220 }
1221 
BookmarksTitleChecker(int profile_index,const std::string & title,int expected_count)1222 BookmarksTitleChecker::BookmarksTitleChecker(int profile_index,
1223                                              const std::string& title,
1224                                              int expected_count)
1225     : SingleBookmarkModelStatusChangeChecker(profile_index),
1226       profile_index_(profile_index),
1227       title_(title),
1228       expected_count_(expected_count) {
1229   DCHECK_GE(expected_count, 0) << "expected_count must be non-negative.";
1230 }
1231 
IsExitConditionSatisfied(std::ostream * os)1232 bool BookmarksTitleChecker::IsExitConditionSatisfied(std::ostream* os) {
1233   *os << "Waiting for bookmark count to match";
1234   int actual_count = CountBookmarksWithTitlesMatching(profile_index_, title_);
1235   return expected_count_ == actual_count;
1236 }
1237 
BookmarkFaviconLoadedChecker(int profile_index,const GURL & page_url)1238 BookmarkFaviconLoadedChecker::BookmarkFaviconLoadedChecker(int profile_index,
1239                                                            const GURL& page_url)
1240     : SingleBookmarkModelStatusChangeChecker(profile_index),
1241       bookmark_node_(GetUniqueNodeByURL(profile_index, page_url)) {
1242   DCHECK_NE(nullptr, bookmark_node_);
1243 }
1244 
IsExitConditionSatisfied(std::ostream * os)1245 bool BookmarkFaviconLoadedChecker::IsExitConditionSatisfied(std::ostream* os) {
1246   *os << "Waiting for the favicon to be loaded for " << bookmark_node_->url();
1247   return bookmark_node_->is_favicon_loaded();
1248 }
1249 
ServerBookmarksEqualityChecker(syncer::ProfileSyncService * service,fake_server::FakeServer * fake_server,std::vector<ExpectedBookmark> expected_bookmarks,syncer::Cryptographer * cryptographer)1250 ServerBookmarksEqualityChecker::ServerBookmarksEqualityChecker(
1251     syncer::ProfileSyncService* service,
1252     fake_server::FakeServer* fake_server,
1253     std::vector<ExpectedBookmark> expected_bookmarks,
1254     syncer::Cryptographer* cryptographer)
1255     : SingleClientStatusChangeChecker(service),
1256       fake_server_(fake_server),
1257       cryptographer_(cryptographer),
1258       expected_bookmarks_(std::move(expected_bookmarks)) {}
1259 
IsExitConditionSatisfied(std::ostream * os)1260 bool ServerBookmarksEqualityChecker::IsExitConditionSatisfied(
1261     std::ostream* os) {
1262   *os << "Waiting for server-side bookmarks to match expected.";
1263 
1264   std::vector<sync_pb::SyncEntity> entities =
1265       fake_server_->GetSyncEntitiesByModelType(syncer::BOOKMARKS);
1266   if (expected_bookmarks_.size() != entities.size()) {
1267     return false;
1268   }
1269 
1270   // Make a copy so we can remove bookmarks that were found.
1271   std::vector<ExpectedBookmark> expected = expected_bookmarks_;
1272   for (const sync_pb::SyncEntity& entity : entities) {
1273     sync_pb::BookmarkSpecifics actual_specifics;
1274     if (entity.specifics().has_encrypted()) {
1275       // If no cryptographer was provided, we expect the specifics to have
1276       // unencrypted data.
1277       if (!cryptographer_) {
1278         return false;
1279       }
1280       sync_pb::EntitySpecifics entity_specifics;
1281       EXPECT_TRUE(cryptographer_->Decrypt(entity.specifics().encrypted(),
1282                                           &entity_specifics));
1283       actual_specifics = entity_specifics.bookmark();
1284     } else {
1285       // If the cryptographer was provided, we expect the specifics to have
1286       // encrypted data.
1287       if (cryptographer_) {
1288         return false;
1289       }
1290       actual_specifics = entity.specifics().bookmark();
1291     }
1292 
1293     auto it =
1294         std::find_if(expected.begin(), expected.end(),
1295                      [actual_specifics](const ExpectedBookmark& bookmark) {
1296                        return actual_specifics.legacy_canonicalized_title() ==
1297                                   bookmark.title &&
1298                               actual_specifics.full_title() == bookmark.title &&
1299                               actual_specifics.url() == bookmark.url;
1300                      });
1301     if (it != expected.end()) {
1302       expected.erase(it);
1303     } else {
1304       *os << "Could not find expected bookmark with title '"
1305           << actual_specifics.legacy_canonicalized_title() << "' and URL '"
1306           << actual_specifics.url() << "'";
1307       return false;
1308     }
1309   }
1310 
1311   return true;
1312 }
1313 
~ServerBookmarksEqualityChecker()1314 ServerBookmarksEqualityChecker::~ServerBookmarksEqualityChecker() {}
1315 
BookmarksUrlChecker(int profile,const GURL & url,int expected_count)1316 BookmarksUrlChecker::BookmarksUrlChecker(int profile,
1317                                          const GURL& url,
1318                                          int expected_count)
1319     : SingleBookmarkModelStatusChangeChecker(profile),
1320       url_(url),
1321       expected_count_(expected_count) {}
1322 
IsExitConditionSatisfied(std::ostream * os)1323 bool BookmarksUrlChecker::IsExitConditionSatisfied(std::ostream* os) {
1324   int actual_count = CountBookmarksWithUrlsMatching(profile_index(), url_);
1325   *os << "Expected " << expected_count_ << " bookmarks with URL " << url_
1326       << " but found " << actual_count;
1327   if (TimedOut()) {
1328     *os << " in "
1329         << testing::PrintToString(
1330                GetAllBookmarkNodes(GetBookmarkModel(profile_index())));
1331   }
1332   return expected_count_ == actual_count;
1333 }
1334 
BookmarksGUIDChecker(int profile,const std::string & guid)1335 BookmarksGUIDChecker::BookmarksGUIDChecker(int profile, const std::string& guid)
1336     : SingleBookmarksModelMatcherChecker(profile,
1337                                          testing::Contains(HasGuid(guid))) {}
1338 
~BookmarksGUIDChecker()1339 BookmarksGUIDChecker::~BookmarksGUIDChecker() {}
1340 
1341 }  // namespace bookmarks_helper
1342