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