blob: b9fedc5413794ec48d9fd0bcba5931171197786c [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include <stddef.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/containers/stack.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/undo/bookmark_undo_service_factory.h"
#include "chrome/common/chrome_paths.h"
#include "components/bookmarks/browser/bookmark_client.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/bookmarks/managed/managed_bookmark_service.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_util.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/driver/profile_sync_service.h"
#include "components/sync/test/fake_server/entity_builder_factory.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/models/tree_node_iterator.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/image/image_skia.h"
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
namespace bookmarks_helper {
namespace {
const char kBookmarkBarTag[] = "bookmark_bar";
const char kSyncedBookmarksTag[] = "synced_bookmarks";
const char kOtherBookmarksTag[] = "other_bookmarks";
const BookmarkNode* GetPermanentNodeForServerTag(
int profile_index,
const std::string& server_defined_unique_tag) {
if (server_defined_unique_tag == kBookmarkBarTag) {
return GetBookmarkBarNode(profile_index);
}
if (server_defined_unique_tag == kSyncedBookmarksTag) {
return GetSyncedBookmarksNode(profile_index);
}
if (server_defined_unique_tag == kOtherBookmarksTag) {
return GetOtherNode(profile_index);
}
return nullptr;
}
void ApplyBookmarkFavicon(
const BookmarkNode* bookmark_node,
favicon::FaviconService* favicon_service,
const GURL& icon_url,
const scoped_refptr<base::RefCountedMemory>& bitmap_data) {
// Some tests use no services.
if (favicon_service == nullptr)
return;
favicon_service->AddPageNoVisitForBookmark(bookmark_node->url(),
bookmark_node->GetTitle());
GURL icon_url_to_use = icon_url;
if (icon_url.is_empty()) {
if (bitmap_data->size() == 0) {
// Empty icon URL and no bitmap data means no icon mapping.
favicon_service->DeleteFaviconMappings({bookmark_node->url()},
favicon_base::IconType::kFavicon);
return;
} else {
// Ancient clients (prior to M25) may not be syncing the favicon URL. If
// the icon URL is not synced, use the page URL as a fake icon URL as it
// is guaranteed to be unique.
icon_url_to_use = bookmark_node->url();
}
}
// The client may have cached the favicon at 2x. Use MergeFavicon() as not to
// overwrite the cached 2x favicon bitmap. Sync favicons are always
// gfx::kFaviconSize in width and height. Store the favicon into history
// as such.
gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
favicon_service->MergeFavicon(bookmark_node->url(), icon_url_to_use,
favicon_base::IconType::kFavicon, bitmap_data,
pixel_size);
}
// Helper class used to wait for changes to take effect on the favicon of a
// particular bookmark node in a particular bookmark model.
class FaviconChangeObserver : public bookmarks::BookmarkModelObserver {
public:
FaviconChangeObserver(BookmarkModel* model, const BookmarkNode* node)
: model_(model), node_(node) {
model->AddObserver(this);
}
~FaviconChangeObserver() override { model_->RemoveObserver(this); }
void WaitForSetFavicon() {
DCHECK(!run_loop_.running());
content::RunThisRunLoop(&run_loop_);
}
// bookmarks::BookmarkModelObserver:
void BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) override {
}
void BookmarkNodeMoved(BookmarkModel* model,
const BookmarkNode* old_parent,
size_t old_index,
const BookmarkNode* new_parent,
size_t new_index) override {}
void BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
size_t index) override {}
void BookmarkNodeRemoved(BookmarkModel* model,
const BookmarkNode* parent,
size_t old_index,
const BookmarkNode* node,
const std::set<GURL>& removed_urls) override {}
void BookmarkAllUserNodesRemoved(
BookmarkModel* model,
const std::set<GURL>& removed_urls) override {}
void BookmarkNodeChanged(BookmarkModel* model,
const BookmarkNode* node) override {
if (model == model_ && node == node_)
model->GetFavicon(node);
}
void BookmarkNodeChildrenReordered(BookmarkModel* model,
const BookmarkNode* node) override {}
void BookmarkNodeFaviconChanged(BookmarkModel* model,
const BookmarkNode* node) override {
if (model == model_ && node == node_)
run_loop_.Quit();
}
private:
BookmarkModel* model_;
const BookmarkNode* node_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver);
};
// Returns the number of nodes of node type |node_type| in |model| whose
// titles match the string |title|.
size_t CountNodesWithTitlesMatching(BookmarkModel* model,
BookmarkNode::Type node_type,
const std::u16string& title) {
ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
// Walk through the model tree looking for bookmark nodes of node type
// |node_type| whose titles match |title|.
size_t count = 0;
while (iterator.has_next()) {
const BookmarkNode* node = iterator.Next();
if ((node->type() == node_type) && (node->GetTitle() == title))
++count;
}
return count;
}
// Returns the number of nodes of node type |node_type| in |model|.
size_t CountNodes(BookmarkModel* model, BookmarkNode::Type node_type) {
ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
// Walk through the model tree looking for bookmark nodes of node type
// |node_type|.
size_t count = 0;
while (iterator.has_next()) {
const BookmarkNode* node = iterator.Next();
if (node->type() == node_type)
++count;
}
return count;
}
// Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent.
// Returns true if they match.
bool FaviconRawBitmapsMatch(const SkBitmap& bitmap_a,
const SkBitmap& bitmap_b) {
if (bitmap_a.computeByteSize() == 0U && bitmap_b.computeByteSize() == 0U)
return true;
if ((bitmap_a.computeByteSize() != bitmap_b.computeByteSize()) ||
(bitmap_a.width() != bitmap_b.width()) ||
(bitmap_a.height() != bitmap_b.height())) {
LOG(ERROR) << "Favicon size mismatch: " << bitmap_a.computeByteSize()
<< " (" << bitmap_a.width() << "x" << bitmap_a.height()
<< ") vs. " << bitmap_b.computeByteSize() << " ("
<< bitmap_b.width() << "x" << bitmap_b.height() << ")";
return false;
}
void* node_pixel_addr_a = bitmap_a.getPixels();
EXPECT_TRUE(node_pixel_addr_a);
void* node_pixel_addr_b = bitmap_b.getPixels();
EXPECT_TRUE(node_pixel_addr_b);
if (memcmp(node_pixel_addr_a, node_pixel_addr_b,
bitmap_a.computeByteSize()) != 0) {
LOG(ERROR) << "Favicon bitmap mismatch";
return false;
} else {
return true;
}
}
// Represents a favicon image and the icon URL associated with it.
struct FaviconData {
FaviconData() {
}
FaviconData(const gfx::Image& favicon_image,
const GURL& favicon_url)
: image(favicon_image),
icon_url(favicon_url) {
}
gfx::Image image;
GURL icon_url;
};
// Gets the favicon and icon URL associated with |node| in |model|. Returns
// nullopt if the favicon is still loading.
base::Optional<FaviconData> GetFaviconData(BookmarkModel* model,
const BookmarkNode* node) {
// We may need to wait for the favicon to be loaded via
// BookmarkModel::GetFavicon(), which is an asynchronous operation.
if (!node->is_favicon_loaded()) {
model->GetFavicon(node);
// Favicon still loading, no data available just yet.
return base::nullopt;
}
// Favicon loaded: return actual image, if there is one (the no-favicon case
// is also considered loaded).
return FaviconData(model->GetFavicon(node),
node->icon_url() ? *node->icon_url() : GURL());
}
// Sets the favicon for |profile| and |node|. |profile| may be
// |test()->verifier()|.
void SetFaviconImpl(Profile* profile,
const BookmarkNode* node,
const GURL& icon_url,
const gfx::Image& image,
FaviconSource favicon_source) {
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
FaviconChangeObserver observer(model, node);
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(profile,
ServiceAccessType::EXPLICIT_ACCESS);
if (favicon_source == FROM_UI) {
favicon_service->SetFavicons({node->url()}, icon_url,
favicon_base::IconType::kFavicon, image);
} else {
ApplyBookmarkFavicon(node, favicon_service, icon_url, image.As1xPNGBytes());
}
// Wait for the favicon for |node| to be invalidated.
observer.WaitForSetFavicon();
model->GetFavicon(node);
}
// Expires the favicon for |profile| and |node|. |profile| may be
// |test()->verifier()|.
void ExpireFaviconImpl(Profile* profile, const BookmarkNode* node) {
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(profile,
ServiceAccessType::EXPLICIT_ACCESS);
favicon_service->SetFaviconOutOfDateForPage(node->url());
}
// Used to call FaviconService APIs synchronously by making |callback| quit a
// RunLoop.
void OnGotFaviconData(
base::OnceClosure callback,
favicon_base::FaviconRawBitmapResult* output_bitmap_result,
const favicon_base::FaviconRawBitmapResult& bitmap_result) {
*output_bitmap_result = bitmap_result;
std::move(callback).Run();
}
// Deletes favicon mappings for |profile| and |node|. |profile| may be
// |test()->verifier()|.
void DeleteFaviconMappingsImpl(Profile* profile,
const BookmarkNode* node,
FaviconSource favicon_source) {
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
FaviconChangeObserver observer(model, node);
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(profile,
ServiceAccessType::EXPLICIT_ACCESS);
if (favicon_source == FROM_UI) {
favicon_service->DeleteFaviconMappings({node->url()},
favicon_base::IconType::kFavicon);
} else {
ApplyBookmarkFavicon(
node, favicon_service, /*icon_url=*/GURL(),
scoped_refptr<base::RefCountedString>(new base::RefCountedString()));
}
// Wait for the favicon for |node| to be invalidated.
observer.WaitForSetFavicon();
model->GetFavicon(node);
}
// Checks if the favicon in |node_a| from |model_a| matches that of |node_b|
// from |model_b|. Returns true if they match.
bool FaviconsMatch(BookmarkModel* model_a,
BookmarkModel* model_b,
const BookmarkNode* node_a,
const BookmarkNode* node_b) {
DCHECK(!node_a->is_folder());
DCHECK(!node_b->is_folder());
base::Optional<FaviconData> favicon_data_a = GetFaviconData(model_a, node_a);
base::Optional<FaviconData> favicon_data_b = GetFaviconData(model_b, node_b);
// If either of the two favicons is still loading, let's return false now
// because observers will get notified when the load completes. Note that even
// the lack of favicon is considered a loaded favicon.
if (!favicon_data_a.has_value() || !favicon_data_b.has_value()) {
return false;
}
if (favicon_data_a->icon_url != favicon_data_b->icon_url) {
return false;
}
gfx::Image image_a = favicon_data_a->image;
gfx::Image image_b = favicon_data_b->image;
if (image_a.IsEmpty() && image_b.IsEmpty())
return true; // Two empty images are equivalent.
if (image_a.IsEmpty() != image_b.IsEmpty()) {
return false;
}
// Compare only the 1x bitmaps as only those are synced.
SkBitmap bitmap_a = image_a.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
SkBitmap bitmap_b = image_b.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
return FaviconRawBitmapsMatch(bitmap_a, bitmap_b);
}
// Does a deep comparison of BookmarkNode fields in |model_a| and |model_b|.
// Returns true if they are all equal.
bool NodesMatch(const BookmarkNode* node_a, const BookmarkNode* node_b) {
if (node_a == nullptr || node_b == nullptr)
return node_a == node_b;
if (node_a->is_folder() != node_b->is_folder()) {
LOG(ERROR) << "Cannot compare folder with bookmark";
return false;
}
if (node_a->GetTitle() != node_b->GetTitle()) {
LOG(ERROR) << "Title mismatch: " << node_a->GetTitle() << " vs. "
<< node_b->GetTitle();
return false;
}
if (node_a->url() != node_b->url()) {
LOG(ERROR) << "URL mismatch: " << node_a->url() << " vs. "
<< node_b->url();
return false;
}
if (node_a->parent()->GetIndexOf(node_a) !=
node_b->parent()->GetIndexOf(node_b)) {
LOG(ERROR) << "Index mismatch: "
<< node_a->parent()->GetIndexOf(node_a) << " vs. "
<< node_b->parent()->GetIndexOf(node_b);
return false;
}
if (node_a->guid() != node_b->guid()) {
LOG(ERROR) << "GUID mismatch: " << node_a->guid() << " vs. "
<< node_b->guid();
return false;
}
if (node_a->parent()->is_root() != node_b->parent()->is_root() ||
node_a->parent()->is_permanent_node() !=
node_b->parent()->is_permanent_node()) {
LOG(ERROR) << "Permanent parent node mismatch: "
<< node_a->parent()->is_permanent_node() << " vs. "
<< node_b->parent()->is_permanent_node();
return false;
}
if (!node_a->parent()->is_root() &&
node_a->parent()->guid() != node_b->parent()->guid()) {
LOG(ERROR) << "Parent node mismatch: " << node_a->parent()->GetTitle()
<< " vs. " << node_b->parent()->GetTitle();
return false;
}
return true;
}
// Helper for BookmarkModelsMatch.
bool NodeCantBeSynced(bookmarks::BookmarkClient* client,
const BookmarkNode* node) {
// Return true to skip a node.
return !client->CanSyncNode(node);
}
// Checks if the hierarchies in |model_a| and |model_b| are equivalent in
// terms of the data model and favicon. Returns true if they both match.
// Note: Some peripheral fields like creation times are allowed to mismatch.
bool BookmarkModelsMatch(BookmarkModel* model_a, BookmarkModel* model_b) {
ui::TreeNodeIterator<const BookmarkNode> iterator_a(
model_a->root_node(),
base::BindRepeating(&NodeCantBeSynced, model_a->client()));
ui::TreeNodeIterator<const BookmarkNode> iterator_b(
model_b->root_node(),
base::BindRepeating(&NodeCantBeSynced, model_b->client()));
while (iterator_a.has_next()) {
const BookmarkNode* node_a = iterator_a.Next();
if (!iterator_b.has_next()) {
LOG(ERROR) << "Models do not match.";
return false;
}
const BookmarkNode* node_b = iterator_b.Next();
if (!NodesMatch(node_a, node_b)) {
LOG(ERROR) << "Nodes do not match";
return false;
}
if (node_a->is_folder() || node_b->is_folder()) {
continue;
}
if (!FaviconsMatch(model_a, model_b, node_a, node_b)) {
LOG(ERROR) << "Favicons do not match";
return false;
}
}
return !iterator_b.has_next();
}
std::vector<const BookmarkNode*> GetAllBookmarkNodes(
const BookmarkModel* model) {
std::vector<const BookmarkNode*> all_nodes;
// Add root node separately as iterator does not include it.
all_nodes.push_back(model->root_node());
ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
while (iterator.has_next()) {
all_nodes.push_back(iterator.Next());
}
return all_nodes;
}
void TriggerAllFaviconLoading(BookmarkModel* model) {
for (const BookmarkNode* node : GetAllBookmarkNodes(model)) {
if (!node->is_favicon_loaded()) {
// GetFavicon() kicks off the loading.
model->GetFavicon(node);
}
}
}
} // namespace
testing::Matcher<std::unique_ptr<bookmarks::BookmarkNode>>
IsFolderWithTitleAndChildren(
const std::string& title,
testing::Matcher<BookmarkNode::TreeNodes> children_matcher) {
return testing::AllOf(
IsFolderWithTitle(title),
testing::Pointee(testing::Property(&bookmarks::BookmarkNode::children,
std::move(children_matcher))));
}
BookmarkUndoService* GetBookmarkUndoService(int index) {
return BookmarkUndoServiceFactory::GetForProfile(
sync_datatype_helper::test()->GetProfile(index));
}
BookmarkModel* GetBookmarkModel(int index) {
return BookmarkModelFactory::GetForBrowserContext(
sync_datatype_helper::test()->GetProfile(index));
}
const BookmarkNode* GetBookmarkBarNode(int index) {
return GetBookmarkModel(index)->bookmark_bar_node();
}
const BookmarkNode* GetOtherNode(int index) {
return GetBookmarkModel(index)->other_node();
}
const BookmarkNode* GetSyncedBookmarksNode(int index) {
return GetBookmarkModel(index)->mobile_node();
}
const BookmarkNode* GetManagedNode(int index) {
return ManagedBookmarkServiceFactory::GetForProfile(
sync_datatype_helper::test()->GetProfile(index))
->managed_node();
}
const BookmarkNode* AddURL(int profile,
const std::string& title,
const GURL& url) {
return AddURL(profile, GetBookmarkBarNode(profile), 0, title, url);
}
const BookmarkNode* AddURL(int profile,
size_t index,
const std::string& title,
const GURL& url) {
return AddURL(profile, GetBookmarkBarNode(profile), index, title, url);
}
const BookmarkNode* AddURL(int profile,
const BookmarkNode* parent,
size_t index,
const std::string& title,
const GURL& url) {
BookmarkModel* model = GetBookmarkModel(profile);
if (bookmarks::GetBookmarkNodeByID(model, parent->id()) != parent) {
LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to "
<< "Profile " << profile;
return nullptr;
}
const BookmarkNode* result =
model->AddURL(parent, index, base::UTF8ToUTF16(title), url);
if (!result) {
LOG(ERROR) << "Could not add bookmark " << title << " to Profile "
<< profile;
return nullptr;
}
return result;
}
const BookmarkNode* AddFolder(int profile,
const std::string& title) {
return AddFolder(profile, GetBookmarkBarNode(profile), 0, title);
}
const BookmarkNode* AddFolder(int profile,
size_t index,
const std::string& title) {
return AddFolder(profile, GetBookmarkBarNode(profile), index, title);
}
const BookmarkNode* AddFolder(int profile,
const BookmarkNode* parent,
size_t index,
const std::string& title) {
BookmarkModel* model = GetBookmarkModel(profile);
if (bookmarks::GetBookmarkNodeByID(model, parent->id()) != parent) {
LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to "
<< "Profile " << profile;
return nullptr;
}
const BookmarkNode* result =
model->AddFolder(parent, index, base::UTF8ToUTF16(title));
EXPECT_TRUE(result);
if (!result) {
LOG(ERROR) << "Could not add folder " << title << " to Profile "
<< profile;
return nullptr;
}
return result;
}
void SetTitle(int profile,
const BookmarkNode* node,
const std::string& new_title) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
<< "Node " << node->GetTitle() << " does not belong to "
<< "Profile " << profile;
model->SetTitle(node, base::UTF8ToUTF16(new_title));
}
void SetFavicon(int profile,
const BookmarkNode* node,
const GURL& icon_url,
const gfx::Image& image,
FaviconSource favicon_source) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
<< "Node " << node->GetTitle() << " does not belong to "
<< "Profile " << profile;
ASSERT_EQ(BookmarkNode::URL, node->type()) << "Node " << node->GetTitle()
<< " must be a url.";
SetFaviconImpl(sync_datatype_helper::test()->GetProfile(profile),
node,
icon_url,
image,
favicon_source);
}
void ExpireFavicon(int profile, const BookmarkNode* node) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
<< "Node " << node->GetTitle() << " does not belong to "
<< "Profile " << profile;
ASSERT_EQ(BookmarkNode::URL, node->type()) << "Node " << node->GetTitle()
<< " must be a url.";
ExpireFaviconImpl(sync_datatype_helper::test()->GetProfile(profile), node);
}
void CheckFaviconExpired(int profile, const GURL& icon_url) {
base::RunLoop run_loop;
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(
sync_datatype_helper::test()->GetProfile(profile),
ServiceAccessType::EXPLICIT_ACCESS);
base::CancelableTaskTracker task_tracker;
favicon_base::FaviconRawBitmapResult bitmap_result;
favicon_service->GetRawFavicon(
icon_url, favicon_base::IconType::kFavicon, 0,
base::BindOnce(&OnGotFaviconData, run_loop.QuitClosure(), &bitmap_result),
&task_tracker);
run_loop.Run();
ASSERT_TRUE(bitmap_result.is_valid());
ASSERT_TRUE(bitmap_result.expired);
}
void CheckHasNoFavicon(int profile, const GURL& page_url) {
base::RunLoop run_loop;
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(
sync_datatype_helper::test()->GetProfile(profile),
ServiceAccessType::EXPLICIT_ACCESS);
base::CancelableTaskTracker task_tracker;
favicon_base::FaviconRawBitmapResult bitmap_result;
favicon_service->GetRawFaviconForPageURL(
page_url, {favicon_base::IconType::kFavicon}, 0,
/*fallback_to_host=*/false,
base::BindOnce(&OnGotFaviconData, run_loop.QuitClosure(), &bitmap_result),
&task_tracker);
run_loop.Run();
ASSERT_FALSE(bitmap_result.is_valid());
}
void DeleteFaviconMappings(int profile,
const BookmarkNode* node,
FaviconSource favicon_source) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
<< "Node " << node->GetTitle() << " does not belong to "
<< "Profile " << profile;
ASSERT_EQ(BookmarkNode::URL, node->type())
<< "Node " << node->GetTitle() << " must be a url.";
DeleteFaviconMappingsImpl(sync_datatype_helper::test()->GetProfile(profile),
node, favicon_source);
}
const BookmarkNode* SetURL(int profile,
const BookmarkNode* node,
const GURL& new_url) {
BookmarkModel* model = GetBookmarkModel(profile);
if (bookmarks::GetBookmarkNodeByID(model, node->id()) != node) {
LOG(ERROR) << "Node " << node->GetTitle() << " does not belong to "
<< "Profile " << profile;
return nullptr;
}
if (node->is_url())
model->SetURL(node, new_url);
return node;
}
void Move(int profile,
const BookmarkNode* node,
const BookmarkNode* new_parent,
size_t index) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node)
<< "Node " << node->GetTitle() << " does not belong to "
<< "Profile " << profile;
model->Move(node, new_parent, index);
}
void Remove(int profile, const BookmarkNode* parent, size_t index) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, parent->id()), parent)
<< "Node " << parent->GetTitle() << " does not belong to "
<< "Profile " << profile;
model->Remove(parent->children()[index].get());
}
void RemoveAll(int profile) {
GetBookmarkModel(profile)->RemoveAllUserBookmarks();
}
void SortChildren(int profile, const BookmarkNode* parent) {
BookmarkModel* model = GetBookmarkModel(profile);
ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, parent->id()), parent)
<< "Node " << parent->GetTitle() << " does not belong to "
<< "Profile " << profile;
model->SortChildren(parent);
}
void ReverseChildOrder(int profile, const BookmarkNode* parent) {
ASSERT_EQ(
bookmarks::GetBookmarkNodeByID(GetBookmarkModel(profile), parent->id()),
parent)
<< "Node " << parent->GetTitle() << " does not belong to "
<< "Profile " << profile;
if (parent->children().empty()) {
return;
}
for (size_t i = 0; i < parent->children().size(); ++i) {
Move(profile, parent->children().back().get(), parent, i);
}
}
bool ModelsMatch(int profile_a, int profile_b) {
return BookmarkModelsMatch(GetBookmarkModel(profile_a),
GetBookmarkModel(profile_b));
}
bool AllModelsMatch() {
for (int i = 1; i < sync_datatype_helper::test()->num_clients(); ++i) {
if (!ModelsMatch(0, i)) {
LOG(ERROR) << "Model " << i << " does not match Model 0.";
return false;
}
}
return true;
}
bool ContainsDuplicateBookmarks(int profile) {
ui::TreeNodeIterator<const BookmarkNode> iterator(
GetBookmarkModel(profile)->root_node());
while (iterator.has_next()) {
const BookmarkNode* node = iterator.Next();
if (node->is_folder())
continue;
std::vector<const BookmarkNode*> nodes;
GetBookmarkModel(profile)->GetNodesByURL(node->url(), &nodes);
EXPECT_GE(nodes.size(), 1U);
for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin();
it != nodes.end(); ++it) {
if (node->id() != (*it)->id() &&
node->parent() == (*it)->parent() &&
node->GetTitle() == (*it)->GetTitle()) {
return true;
}
}
}
return false;
}
bool HasNodeWithURL(int profile, const GURL& url) {
std::vector<const BookmarkNode*> nodes;
GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
return !nodes.empty();
}
const BookmarkNode* GetUniqueNodeByURL(int profile, const GURL& url) {
std::vector<const BookmarkNode*> nodes;
GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
EXPECT_EQ(1U, nodes.size());
if (nodes.empty())
return nullptr;
return nodes[0];
}
size_t CountAllBookmarks(int profile) {
return CountNodes(GetBookmarkModel(profile), BookmarkNode::URL);
}
size_t CountBookmarksWithTitlesMatching(int profile, const std::string& title) {
return CountNodesWithTitlesMatching(GetBookmarkModel(profile),
BookmarkNode::URL,
base::UTF8ToUTF16(title));
}
size_t CountBookmarksWithUrlsMatching(int profile, const GURL& url) {
std::vector<const BookmarkNode*> nodes;
GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
return nodes.size();
}
size_t CountFoldersWithTitlesMatching(int profile, const std::string& title) {
return CountNodesWithTitlesMatching(GetBookmarkModel(profile),
BookmarkNode::FOLDER,
base::UTF8ToUTF16(title));
}
bool ContainsBookmarkNodeWithGUID(int profile, const base::GUID& guid) {
for (const BookmarkNode* node :
GetAllBookmarkNodes(GetBookmarkModel(profile))) {
if (node->guid() == guid) {
return true;
}
}
return false;
}
gfx::Image CreateFavicon(SkColor color) {
const int dip_width = 16;
const int dip_height = 16;
std::vector<float> favicon_scales = favicon_base::GetFaviconScales();
gfx::ImageSkia favicon;
for (size_t i = 0; i < favicon_scales.size(); ++i) {
float scale = favicon_scales[i];
int pixel_width = dip_width * scale;
int pixel_height = dip_height * scale;
SkBitmap bmp;
bmp.allocN32Pixels(pixel_width, pixel_height);
bmp.eraseColor(color);
favicon.AddRepresentation(gfx::ImageSkiaRep(bmp, scale));
}
return gfx::Image(favicon);
}
gfx::Image Create1xFaviconFromPNGFile(const std::string& path) {
base::ScopedAllowBlockingForTesting allow_blocking;
const char* kPNGExtension = ".png";
if (!base::EndsWith(path, kPNGExtension,
base::CompareCase::INSENSITIVE_ASCII))
return gfx::Image();
base::FilePath full_path;
if (!base::PathService::Get(chrome::DIR_TEST_DATA, &full_path))
return gfx::Image();
full_path = full_path.AppendASCII("sync").AppendASCII(path);
std::string contents;
base::ReadFileToString(full_path, &contents);
return gfx::Image::CreateFrom1xPNGBytes(
base::RefCountedString::TakeString(&contents));
}
std::string IndexedURL(size_t i) {
return "http://www.host.ext:1234/path/filename/" + base::NumberToString(i);
}
std::string IndexedURLTitle(size_t i) {
return "URL Title " + base::NumberToString(i);
}
std::string IndexedFolderName(size_t i) {
return "Folder Name " + base::NumberToString(i);
}
std::string IndexedSubfolderName(size_t i) {
return "Subfolder Name " + base::NumberToString(i);
}
std::string IndexedSubsubfolderName(size_t i) {
return "Subsubfolder Name " + base::NumberToString(i);
}
std::unique_ptr<syncer::LoopbackServerEntity> CreateBookmarkServerEntity(
const std::string& title,
const GURL& url) {
fake_server::EntityBuilderFactory entity_builder_factory;
fake_server::BookmarkEntityBuilder bookmark_builder =
entity_builder_factory.NewBookmarkEntityBuilder(title);
return bookmark_builder.BuildBookmark(url);
}
AnyBookmarkChangeObserver::AnyBookmarkChangeObserver(
const base::RepeatingClosure& cb)
: cb_(cb) {}
AnyBookmarkChangeObserver::~AnyBookmarkChangeObserver() = default;
void AnyBookmarkChangeObserver::BookmarkModelLoaded(BookmarkModel* model,
bool ids_reassigned) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkModelBeingDeleted(
BookmarkModel* model) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkNodeMoved(
BookmarkModel* model,
const BookmarkNode* old_parent,
size_t old_index,
const BookmarkNode* new_parent,
size_t new_index) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
size_t index) {
cb_.Run();
}
void AnyBookmarkChangeObserver::OnWillRemoveBookmarks(
BookmarkModel* model,
const BookmarkNode* parent,
size_t old_index,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkNodeRemoved(
BookmarkModel* model,
const BookmarkNode* parent,
size_t old_index,
const BookmarkNode* node,
const std::set<GURL>& no_longer_bookmarked) {
cb_.Run();
}
void AnyBookmarkChangeObserver::OnWillChangeBookmarkNode(
BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkNodeChanged(BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::OnWillChangeBookmarkMetaInfo(
BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkMetaInfoChanged(
BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkNodeFaviconChanged(
BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::OnWillReorderBookmarkNode(
BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkNodeChildrenReordered(
BookmarkModel* model,
const BookmarkNode* node) {
cb_.Run();
}
void AnyBookmarkChangeObserver::ExtensiveBookmarkChangesBeginning(
BookmarkModel* model) {
cb_.Run();
}
void AnyBookmarkChangeObserver::ExtensiveBookmarkChangesEnded(
BookmarkModel* model) {
cb_.Run();
}
void AnyBookmarkChangeObserver::OnWillRemoveAllUserBookmarks(
BookmarkModel* model) {
cb_.Run();
}
void AnyBookmarkChangeObserver::BookmarkAllUserNodesRemoved(
BookmarkModel* model,
const std::set<GURL>& removed_urls) {
cb_.Run();
}
void AnyBookmarkChangeObserver::GroupedBookmarkChangesBeginning(
BookmarkModel* model) {
cb_.Run();
}
void AnyBookmarkChangeObserver::GroupedBookmarkChangesEnded(
BookmarkModel* model) {
cb_.Run();
}
BookmarkModelStatusChangeChecker::BookmarkModelStatusChangeChecker() = default;
BookmarkModelStatusChangeChecker::~BookmarkModelStatusChangeChecker() {
for (const auto& model_and_observer : observers_) {
model_and_observer.first->RemoveObserver(model_and_observer.second.get());
}
}
void BookmarkModelStatusChangeChecker::Observe(
bookmarks::BookmarkModel* model) {
auto observer =
std::make_unique<AnyBookmarkChangeObserver>(base::BindRepeating(
&SingleBookmarkModelStatusChangeChecker::PostCheckExitCondition,
weak_ptr_factory_.GetWeakPtr()));
model->AddObserver(observer.get());
observers_.emplace_back(model, std::move(observer));
}
void BookmarkModelStatusChangeChecker::CheckExitCondition() {
pending_check_exit_condition_ = false;
StatusChangeChecker::CheckExitCondition();
}
void BookmarkModelStatusChangeChecker::PostCheckExitCondition() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (pending_check_exit_condition_) {
// Already posted.
return;
}
pending_check_exit_condition_ = true;
// Use base::PostTask() instead of CheckExitCondition() directly to make sure
// that the checker doesn't immediately kick in while bookmarks are modified.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&BookmarkModelStatusChangeChecker::CheckExitCondition,
weak_ptr_factory_.GetWeakPtr()));
}
BookmarksMatchChecker::BookmarksMatchChecker() {
for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
Observe(GetBookmarkModel(i));
}
}
bool BookmarksMatchChecker::IsExitConditionSatisfied(std::ostream* os) {
*os << "Waiting for matching models";
return AllModelsMatch();
}
bool BookmarksMatchChecker::Wait() {
for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) {
TriggerAllFaviconLoading(GetBookmarkModel(i));
}
return BookmarkModelStatusChangeChecker::Wait();
}
SingleBookmarkModelStatusChangeChecker::SingleBookmarkModelStatusChangeChecker(
int profile_index)
: profile_index_(profile_index),
bookmark_model_(GetBookmarkModel(profile_index)) {
Observe(bookmark_model_);
}
SingleBookmarkModelStatusChangeChecker::
~SingleBookmarkModelStatusChangeChecker() = default;
int SingleBookmarkModelStatusChangeChecker::profile_index() const {
return profile_index_;
}
BookmarkModel* SingleBookmarkModelStatusChangeChecker::bookmark_model() const {
return bookmark_model_;
}
SingleBookmarksModelMatcherChecker::SingleBookmarksModelMatcherChecker(
int profile_index,
const Matcher& matcher)
: SingleBookmarkModelStatusChangeChecker(profile_index),
matcher_(matcher) {}
SingleBookmarksModelMatcherChecker::~SingleBookmarksModelMatcherChecker() {}
bool SingleBookmarksModelMatcherChecker::IsExitConditionSatisfied(
std::ostream* os) {
const std::vector<const BookmarkNode*> all_bookmark_nodes =
GetAllBookmarkNodes(bookmark_model());
testing::StringMatchResultListener result_listener;
const bool matches = testing::ExplainMatchResult(matcher_, all_bookmark_nodes,
&result_listener);
if (TimedOut() && !matches && result_listener.str().empty()) {
// Some matchers don't provide details via ExplainMatchResult().
*os << "Expected: ";
matcher_.DescribeTo(os);
*os << " Actual: " << testing::PrintToString(all_bookmark_nodes);
} else {
*os << result_listener.str();
}
return matches;
}
BookmarksTitleChecker::BookmarksTitleChecker(int profile_index,
const std::string& title,
int expected_count)
: SingleBookmarkModelStatusChangeChecker(profile_index),
profile_index_(profile_index),
title_(title),
expected_count_(expected_count) {
DCHECK_GE(expected_count, 0) << "expected_count must be non-negative.";
}
bool BookmarksTitleChecker::IsExitConditionSatisfied(std::ostream* os) {
*os << "Waiting for bookmark count to match";
int actual_count = CountBookmarksWithTitlesMatching(profile_index_, title_);
return expected_count_ == actual_count;
}
BookmarkFaviconLoadedChecker::BookmarkFaviconLoadedChecker(int profile_index,
const GURL& page_url)
: SingleBookmarkModelStatusChangeChecker(profile_index),
bookmark_node_(GetUniqueNodeByURL(profile_index, page_url)) {
DCHECK_NE(nullptr, bookmark_node_);
}
bool BookmarkFaviconLoadedChecker::IsExitConditionSatisfied(std::ostream* os) {
*os << "Waiting for the favicon to be loaded for " << bookmark_node_->url();
return bookmark_node_->is_favicon_loaded();
}
ServerBookmarksEqualityChecker::ServerBookmarksEqualityChecker(
syncer::ProfileSyncService* service,
fake_server::FakeServer* fake_server,
std::vector<ExpectedBookmark> expected_bookmarks,
syncer::Cryptographer* cryptographer)
: SingleClientStatusChangeChecker(service),
fake_server_(fake_server),
cryptographer_(cryptographer),
expected_bookmarks_(std::move(expected_bookmarks)) {}
bool ServerBookmarksEqualityChecker::IsExitConditionSatisfied(
std::ostream* os) {
*os << "Waiting for server-side bookmarks to match expected.";
std::vector<sync_pb::SyncEntity> entities =
fake_server_->GetSyncEntitiesByModelType(syncer::BOOKMARKS);
if (expected_bookmarks_.size() != entities.size()) {
return false;
}
// Make a copy so we can remove bookmarks that were found.
std::vector<ExpectedBookmark> expected = expected_bookmarks_;
for (const sync_pb::SyncEntity& entity : entities) {
sync_pb::BookmarkSpecifics actual_specifics;
if (entity.specifics().has_encrypted()) {
// If no cryptographer was provided, we expect the specifics to have
// unencrypted data.
if (!cryptographer_) {
return false;
}
sync_pb::EntitySpecifics entity_specifics;
EXPECT_TRUE(cryptographer_->Decrypt(entity.specifics().encrypted(),
&entity_specifics));
actual_specifics = entity_specifics.bookmark();
} else {
// If the cryptographer was provided, we expect the specifics to have
// encrypted data.
if (cryptographer_) {
return false;
}
actual_specifics = entity.specifics().bookmark();
}
auto it =
std::find_if(expected.begin(), expected.end(),
[actual_specifics](const ExpectedBookmark& bookmark) {
return actual_specifics.legacy_canonicalized_title() ==
bookmark.title &&
actual_specifics.full_title() == bookmark.title &&
actual_specifics.url() == bookmark.url;
});
if (it != expected.end()) {
expected.erase(it);
} else {
*os << "Could not find expected bookmark with title '"
<< actual_specifics.legacy_canonicalized_title() << "' and URL '"
<< actual_specifics.url() << "'";
return false;
}
}
return true;
}
ServerBookmarksEqualityChecker::~ServerBookmarksEqualityChecker() {}
BookmarksUrlChecker::BookmarksUrlChecker(int profile,
const GURL& url,
int expected_count)
: SingleBookmarkModelStatusChangeChecker(profile),
url_(url),
expected_count_(expected_count) {}
bool BookmarksUrlChecker::IsExitConditionSatisfied(std::ostream* os) {
int actual_count = CountBookmarksWithUrlsMatching(profile_index(), url_);
*os << "Expected " << expected_count_ << " bookmarks with URL " << url_
<< " but found " << actual_count;
if (TimedOut()) {
*os << " in "
<< testing::PrintToString(
GetAllBookmarkNodes(GetBookmarkModel(profile_index())));
}
return expected_count_ == actual_count;
}
BookmarksGUIDChecker::BookmarksGUIDChecker(int profile, const base::GUID& guid)
: SingleBookmarksModelMatcherChecker(profile,
testing::Contains(HasGuid(guid))) {}
BookmarksGUIDChecker::~BookmarksGUIDChecker() {}
BookmarkModelMatchesFakeServerChecker::BookmarkModelMatchesFakeServerChecker(
int profile,
syncer::ProfileSyncService* service,
fake_server::FakeServer* fake_server)
: SingleClientStatusChangeChecker(service),
fake_server_(fake_server),
profile_index_(profile) {}
bool BookmarkModelMatchesFakeServerChecker::IsExitConditionSatisfied(
std::ostream* os) {
*os << "Waiting for server-side bookmarks to match bookmark model.";
std::map<base::GUID, sync_pb::SyncEntity> server_bookmarks_by_guid;
if (!GetServerBookmarksByUniqueGUID(&server_bookmarks_by_guid)) {
*os << "The server has duplicate entities having the same GUID.";
return false;
}
const std::map<std::string, std::vector<base::GUID>>
server_guids_by_parent_id =
GetServerGuidsGroupedByParentSyncId(server_bookmarks_by_guid);
// |bookmarks_count| is used to check that the bookmark model doesn't have
// less nodes than the fake server.
size_t bookmarks_count = 0;
const bookmarks::BookmarkNode* root_node =
GetBookmarkModel(profile_index_)->root_node();
ui::TreeNodeIterator<const bookmarks::BookmarkNode> iterator(root_node);
while (iterator.has_next()) {
const BookmarkNode* node = iterator.Next();
// Do not check permanent nodes.
if (node->is_permanent_node()) {
continue;
}
auto iter = server_bookmarks_by_guid.find(node->guid());
if (iter == server_bookmarks_by_guid.end()) {
*os << "Missing a node from on the server for GUID: " << node->guid();
return false;
}
const sync_pb::SyncEntity& server_entity = iter->second;
bookmarks_count++;
// Check that the server node has the same parent as the local |node|.
if (!CheckParentNode(node, server_bookmarks_by_guid, os)) {
return false;
}
// Check that the local |node| and the server entity have the same position.
auto parent_iter =
server_guids_by_parent_id.find(server_entity.parent_id_string());
DCHECK(parent_iter != server_guids_by_parent_id.end());
auto server_position_iter = std::find(
parent_iter->second.begin(), parent_iter->second.end(), node->guid());
DCHECK(server_position_iter != parent_iter->second.end());
const size_t server_position =
server_position_iter - parent_iter->second.begin();
const size_t local_position = node->parent()->GetIndexOf(node);
if (server_position != local_position) {
*os << "Different positions on the server and in the local model for "
"node: "
<< node->GetTitle() << ", server position: " << server_position
<< ", local position: " << local_position;
return false;
}
// Check titles and URLs.
if (base::UTF16ToUTF8(node->GetTitle()) !=
server_entity.specifics().bookmark().full_title()) {
*os << " Title mismatch for node: " << node->GetTitle();
return false;
}
if (node->is_folder() != server_entity.folder()) {
*os << " Node type mismatch for node: " << node->GetTitle();
return false;
}
if (!node->is_folder() &&
node->url() != server_entity.specifics().bookmark().url()) {
*os << " Node URL mismatch for node: " << node->GetTitle();
return false;
}
}
if (server_bookmarks_by_guid.size() != bookmarks_count) {
// An iteration over the local bookmark model has been finished at the
// moment. So the server can have only more entities than the local model if
// their sizes differ.
*os << " The fake server has more nodes than the bookmark model";
return false;
}
return true;
}
bool BookmarkModelMatchesFakeServerChecker::CheckPermanentParentNode(
const bookmarks::BookmarkNode* node,
const sync_pb::SyncEntity& server_entity,
std::ostream* os) const {
// Parent node must be a permanent node.
const BookmarkNode* parent_node = node->parent();
DCHECK(parent_node->is_permanent_node());
// Matching server entity must exist.
DCHECK_EQ(node->guid().AsLowercaseString(),
server_entity.specifics().bookmark().guid());
const std::map<std::string, sync_pb::SyncEntity>
permanent_nodes_by_server_id =
GetServerPermanentBookmarksGroupedBySyncId();
auto permanent_parent_iter =
permanent_nodes_by_server_id.find(server_entity.parent_id_string());
if (permanent_parent_iter == permanent_nodes_by_server_id.end()) {
*os << " A permanent parent node is missing on the server for node: "
<< node->GetTitle();
return false;
}
if (parent_node !=
GetPermanentNodeForServerTag(
profile_index_,
permanent_parent_iter->second.server_defined_unique_tag())) {
*os << " Permanent parent node mismatch for node: " << node->GetTitle();
return false;
}
return true;
}
bool BookmarkModelMatchesFakeServerChecker::CheckParentNode(
const bookmarks::BookmarkNode* node,
const std::map<base::GUID, sync_pb::SyncEntity>& server_bookmarks_by_guid,
std::ostream* os) const {
// Only one matching server entity must exist.
DCHECK_EQ(1u, server_bookmarks_by_guid.count(node->guid()));
auto iter = server_bookmarks_by_guid.find(node->guid());
const sync_pb::SyncEntity& server_entity = iter->second;
const BookmarkNode* parent_node = node->parent();
if (parent_node->is_permanent_node()) {
return CheckPermanentParentNode(node, server_entity, os);
}
auto parent_iter = server_bookmarks_by_guid.find(parent_node->guid());
if (parent_iter == server_bookmarks_by_guid.end()) {
*os << " Missing a parent node on the server for node: "
<< node->GetTitle();
return false;
}
if (parent_iter->second.id_string() != iter->second.parent_id_string()) {
*os << " Parent mismatch found for node: " << node->GetTitle();
return false;
}
return true;
}
std::map<std::string, sync_pb::SyncEntity>
BookmarkModelMatchesFakeServerChecker::
GetServerPermanentBookmarksGroupedBySyncId() const {
const std::vector<sync_pb::SyncEntity> server_permanent_bookmarks =
fake_server_->GetPermanentSyncEntitiesByModelType(syncer::BOOKMARKS);
std::map<std::string, sync_pb::SyncEntity> permanent_nodes_by_server_id;
for (const sync_pb::SyncEntity& entity : server_permanent_bookmarks) {
DCHECK(!entity.server_defined_unique_tag().empty());
permanent_nodes_by_server_id[entity.id_string()] = entity;
}
return permanent_nodes_by_server_id;
}
bool BookmarkModelMatchesFakeServerChecker::GetServerBookmarksByUniqueGUID(
std::map<base::GUID, sync_pb::SyncEntity>* server_bookmarks_by_guid) const {
const std::vector<sync_pb::SyncEntity> server_bookmarks =
fake_server_->GetSyncEntitiesByModelType(syncer::BOOKMARKS);
for (const sync_pb::SyncEntity& entity : server_bookmarks) {
// Skip permanent nodes.
if (!entity.server_defined_unique_tag().empty()) {
continue;
}
if (!server_bookmarks_by_guid
->emplace(base::GUID::ParseLowercase(
entity.specifics().bookmark().guid()),
entity)
.second) {
return false;
}
}
return true;
}
std::map<std::string, std::vector<base::GUID>>
BookmarkModelMatchesFakeServerChecker::GetServerGuidsGroupedByParentSyncId(
const std::map<base::GUID, sync_pb::SyncEntity>& server_bookmarks_by_guid)
const {
std::map<std::string, std::vector<base::GUID>> guids_grouped_by_parent_id;
for (const auto& guid_and_entity : server_bookmarks_by_guid) {
const sync_pb::SyncEntity& entity = guid_and_entity.second;
guids_grouped_by_parent_id[entity.parent_id_string()].push_back(
guid_and_entity.first);
}
auto sort_by_position_fn = [&server_bookmarks_by_guid](
const base::GUID& left,
const base::GUID& right) {
const sync_pb::UniquePosition& left_position =
server_bookmarks_by_guid.at(left).unique_position();
const sync_pb::UniquePosition& right_position =
server_bookmarks_by_guid.at(right).unique_position();
return syncer::UniquePosition::FromProto(left_position)
.LessThan(syncer::UniquePosition::FromProto(right_position));
};
for (auto& parent_id_and_children_guids : guids_grouped_by_parent_id) {
std::vector<base::GUID>& children = parent_id_and_children_guids.second;
std::sort(children.begin(), children.end(), sort_by_position_fn);
}
return guids_grouped_by_parent_id;
}
} // namespace bookmarks_helper