#include "chrome/browser/bookmarks/permanent_folder_ordering_tracker.h"
#include <cstddef>
#include <cstdint>
#include <unordered_set>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/notreached.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
namespace {
bool IsValidTrackedType(BookmarkNode::Type type) {
switch (type) {
case bookmarks::BookmarkNode::URL:
case bookmarks::BookmarkNode::FOLDER:
NOTREACHED();
case bookmarks::BookmarkNode::BOOKMARK_BAR:
case bookmarks::BookmarkNode::OTHER_NODE:
case bookmarks::BookmarkNode::MOBILE:
return true;
}
NOTREACHED();
}
void AddNextVisitedNotAddedNodes(
const BookmarkNode* parent,
size_t& position,
std::vector<raw_ptr<const bookmarks::BookmarkNode>>& ordered_nodes,
std::unordered_set<int64_t>& visited_not_added) {
while (position < parent->children().size()) {
const BookmarkNode* const next_child_node =
parent->children()[position].get();
if (!visited_not_added.contains(next_child_node->id())) {
break;
}
visited_not_added.erase(next_child_node->id());
ordered_nodes.push_back(next_child_node);
++position;
}
}
void AddNewNodes(const std::unordered_set<int64_t>& existing_nodes,
std::unordered_set<int64_t>& added_nodes,
const BookmarkNode* parent,
size_t& position,
std::vector<raw_ptr<const bookmarks::BookmarkNode>>& nodes) {
while (position < parent->children().size()) {
const BookmarkNode* node = parent->children()[position].get();
if (added_nodes.contains(node->id())) {
++position;
continue;
}
if (existing_nodes.contains(node->id())) {
return;
}
nodes.push_back(parent->children()[position].get());
added_nodes.insert(node->id());
++position;
}
}
}
PermanentFolderOrderingTracker::PermanentFolderOrderingTracker(
BookmarkModel* model,
BookmarkNode::Type tracked_type,
Delegate* delegate)
: model_(model), tracked_type_(tracked_type), delegate_(delegate) {
CHECK(model);
CHECK(IsValidTrackedType(tracked_type))
<< "Invalid tracked type : " << tracked_type;
CHECK(delegate);
}
PermanentFolderOrderingTracker::~PermanentFolderOrderingTracker() = default;
void PermanentFolderOrderingTracker::Init(
std::vector<int64_t> in_order_node_ids) {
CHECK(!initialized_);
initialized_ = true;
loaded_node_ids_during_model_load_ = std::move(in_order_node_ids);
model_observation_.Observe(model_);
if (model_->loaded()) {
BookmarkModelLoaded(false);
}
}
std::vector<const bookmarks::BookmarkNode*>
PermanentFolderOrderingTracker::GetUnderlyingPermanentNodes() const {
std::vector<const bookmarks::BookmarkNode*> nodes;
if (account_node_) {
nodes.push_back(account_node_);
}
if (local_or_syncable_node_) {
nodes.push_back(local_or_syncable_node_);
}
return nodes;
}
const BookmarkNode*
PermanentFolderOrderingTracker::GetDefaultParentForNewNodes() const {
CHECK(model_->loaded());
return account_node_ ? account_node_ : local_or_syncable_node_;
}
size_t PermanentFolderOrderingTracker::GetIndexOf(
const bookmarks::BookmarkNode* node) const {
CHECK(node);
CHECK(node->parent());
CHECK_EQ(node->parent()->type(), tracked_type_);
CHECK(node->parent() == account_node_ ||
node->parent() == local_or_syncable_node_);
if (ordering_.empty()) {
return *node->parent()->GetIndexOf(node);
}
for (size_t i = 0; i < ordering_.size(); i++) {
if (ordering_[i] == node) {
return i;
}
}
NOTREACHED();
}
const bookmarks::BookmarkNode* PermanentFolderOrderingTracker::GetNodeAtIndex(
size_t index) const {
CHECK_LT(index, GetChildrenCount());
if (ShouldTrackOrdering()) {
return ordering_[index];
}
CHECK(ordering_.empty());
if (!account_node_) {
return local_or_syncable_node_->children()[index].get();
}
if (index < account_node_->children().size()) {
return account_node_->children()[index].get();
}
index -= account_node_->children().size();
CHECK_LT(index, local_or_syncable_node_->children().size());
return local_or_syncable_node_->children()[index].get();
}
size_t PermanentFolderOrderingTracker::GetChildrenCount() const {
size_t count = 0;
if (local_or_syncable_node_) {
count += local_or_syncable_node_->children().size();
}
if (account_node_) {
count += account_node_->children().size();
}
if (ShouldTrackOrdering()) {
CHECK_EQ(ordering_.size(), count);
}
return count;
}
std::optional<size_t> PermanentFolderOrderingTracker::MoveToIndex(
const bookmarks::BookmarkNode* node,
size_t index) {
CHECK(node);
std::optional<size_t> old_index_if_tracked =
IsTrackedPermanentNode(node->parent())
? std::optional<size_t>(GetIndexOf(node))
: std::nullopt;
if (old_index_if_tracked && (index == old_index_if_tracked.value() ||
index == old_index_if_tracked.value() + 1)) {
return std::nullopt;
}
bool is_account_node = account_node_ && !model_->IsLocalOnlyNode(*node);
size_t in_storage_index =
GetInStorageBookmarkCountBeforeIndex(is_account_node, index);
model_->Move(node, is_account_node ? account_node_ : local_or_syncable_node_,
in_storage_index);
if (old_index_if_tracked && index > old_index_if_tracked.value()) {
index--;
}
if (!ShouldTrackOrdering()) {
CHECK(ordering_.empty());
CHECK_LE(index, GetChildrenCount());
CHECK_EQ(GetNodeAtIndex(index), node);
return index;
}
if (GetNodeAtIndex(index) == node) {
return index;
}
std::erase(ordering_, node);
CHECK_LE(index, ordering_.size());
ordering_.insert(ordering_.cbegin() + index, node);
CHECK_EQ(ordering_.size(), GetExpectedOrderingSize());
NotifyTrackedOrderingChanged();
return index;
}
void PermanentFolderOrderingTracker::AddNodesAsCopiesOfNodeData(
const std::vector<bookmarks::BookmarkNodeData::Element>& elements,
size_t index) {
CHECK(!elements.empty());
const BookmarkNode* parent = GetDefaultParentForNewNodes();
const size_t in_storage_index =
GetInStorageBookmarkCountBeforeIndex(parent == account_node_, index);
const size_t elements_size = elements.size();
bookmarks::CloneBookmarkNode(model_, elements, parent, in_storage_index,
true);
CHECK_EQ(ordering_.size(), GetExpectedOrderingSize());
const BookmarkNode* new_node = parent->children()[in_storage_index].get();
CHECK_EQ(new_node->parent()->type(), tracked_type_);
CHECK_LT(index, GetChildrenCount());
if (GetNodeAtIndex(index) == new_node) {
return;
}
CHECK(ShouldTrackOrdering());
const size_t current_start_index = GetIndexOf(new_node);
CHECK_GE(ordering_.size(), current_start_index + elements_size);
std::vector<const BookmarkNode*> new_nodes(
ordering_.cbegin() + current_start_index,
ordering_.cbegin() + current_start_index + elements_size);
ordering_.erase(ordering_.cbegin() + current_start_index,
ordering_.cbegin() + current_start_index + elements_size);
CHECK_LE(index, ordering_.size());
ordering_.insert(ordering_.cbegin() + index, new_nodes.cbegin(),
new_nodes.cend());
CHECK_EQ(ordering_.size(), GetExpectedOrderingSize());
NotifyTrackedOrderingChanged();
}
size_t PermanentFolderOrderingTracker::GetIndexAcrossStorage(
const bookmarks::BookmarkNode* node,
size_t in_storage_index) const {
if (!ShouldTrackOrdering()) {
CHECK(node);
CHECK(node->parent());
CHECK_EQ(node->parent()->type(), tracked_type_);
CHECK(ordering_.empty());
DCHECK_EQ(GetIndexOf(node), in_storage_index);
return in_storage_index;
}
return GetIndexOf(node);
}
bool PermanentFolderOrderingTracker::IsNonDefaultOrderingTracked() const {
CHECK_EQ(ordering_.size(), GetExpectedOrderingSize());
return ordering_ != GetDefaultOrderIfTracked();
}
void PermanentFolderOrderingTracker::NotifyTrackedOrderingChanged() {
CHECK(initialized_);
delegate_->TrackedOrderingChanged();
}
void PermanentFolderOrderingTracker::SetTrackedPermanentNodes() {
switch (tracked_type_) {
case bookmarks::BookmarkNode::URL:
case bookmarks::BookmarkNode::FOLDER:
NOTREACHED();
case bookmarks::BookmarkNode::BOOKMARK_BAR:
local_or_syncable_node_ = model_->bookmark_bar_node();
account_node_ = model_->account_bookmark_bar_node();
return;
case bookmarks::BookmarkNode::OTHER_NODE:
local_or_syncable_node_ = model_->other_node();
account_node_ = model_->account_other_node();
return;
case bookmarks::BookmarkNode::MOBILE:
local_or_syncable_node_ = model_->mobile_node();
account_node_ = model_->account_mobile_node();
return;
}
NOTREACHED();
}
bool PermanentFolderOrderingTracker::IsTrackedPermanentNode(
const bookmarks::BookmarkNode* node) const {
return node->type() == tracked_type_;
}
void PermanentFolderOrderingTracker::ResetOrderingToDefault() {
ordering_ = GetDefaultOrderIfTracked();
CHECK_EQ(GetExpectedOrderingSize(), ordering_.size());
NotifyTrackedOrderingChanged();
}
void PermanentFolderOrderingTracker::BookmarkModelLoaded(bool ids_reassigned) {
SetTrackedPermanentNodes();
CHECK(ordering_.empty());
if (ids_reassigned || loaded_node_ids_during_model_load_.empty() ||
!ShouldTrackOrdering()) {
loaded_node_ids_during_model_load_.clear();
ResetOrderingToDefault();
return;
}
ReconcileLoadedNodeIds();
}
void PermanentFolderOrderingTracker::ReconcileLoadedNodeIds() {
std::map<int64_t, const BookmarkNode*> id_to_node;
for (const BookmarkNode* parent : {local_or_syncable_node_, account_node_}) {
for (const auto& node : parent->children()) {
id_to_node[node->id()] = node.get();
}
}
std::vector<raw_ptr<const BookmarkNode>> nodes;
std::unordered_set<int64_t> loaded_node_ids_set(
loaded_node_ids_during_model_load_.cbegin(),
loaded_node_ids_during_model_load_.cend());
std::unordered_set<int64_t> added_nodes;
size_t local_index = 0;
size_t account_index = 0;
for (int64_t node_id : loaded_node_ids_during_model_load_) {
auto node_it = id_to_node.find(node_id);
if (node_it == id_to_node.end()) {
continue;
}
const BookmarkNode* node = node_it->second;
size_t& index =
node->parent() == account_node_ ? account_index : local_index;
AddNewNodes(loaded_node_ids_set, added_nodes, node->parent(), index, nodes);
nodes.push_back(node);
added_nodes.insert(node->id());
AddNewNodes(loaded_node_ids_set, added_nodes, node->parent(), index, nodes);
}
if (nodes.size() != (local_or_syncable_node_->children().size() +
account_node_->children().size())) {
ResetOrderingToDefault();
return;
}
ordering_ = std::move(nodes);
for (const BookmarkNode* parent : {local_or_syncable_node_, account_node_}) {
size_t index = 0;
for (const BookmarkNode* node : ordering_) {
if (node->parent() != parent) {
continue;
}
CHECK_LE(index, parent->children().size());
if (node == parent->children()[index].get()) {
++index;
continue;
}
BookmarkNodeChildrenReordered(parent);
break;
}
}
NotifyTrackedOrderingChanged();
}
void PermanentFolderOrderingTracker::BookmarkNodeMoved(
const bookmarks::BookmarkNode* old_parent,
size_t old_index,
const bookmarks::BookmarkNode* new_parent,
size_t new_index) {
RemoveBookmarkNodeIfTracked(old_parent,
new_parent->children()[new_index].get());
AddBookmarkNodeIfTracked(new_parent, new_index);
CHECK_EQ(GetExpectedOrderingSize(), ordering_.size());
}
void PermanentFolderOrderingTracker::BookmarkNodeAdded(
const bookmarks::BookmarkNode* parent,
size_t index,
bool added_by_user) {
AddBookmarkNodeIfTracked(parent, index);
CHECK_EQ(GetExpectedOrderingSize(), ordering_.size());
}
void PermanentFolderOrderingTracker::BookmarkNodeRemoved(
const bookmarks::BookmarkNode* parent,
size_t old_index,
const bookmarks::BookmarkNode* node,
const std::set<GURL>& removed_urls,
const base::Location& location) {
RemoveBookmarkNodeIfTracked(parent, node);
CHECK_EQ(GetExpectedOrderingSize(), ordering_.size());
}
void PermanentFolderOrderingTracker::OnWillRemoveAllUserBookmarks(
const base::Location& location) {
all_user_bookmarks_remove_in_progress_ = true;
if (!ordering_.empty()) {
ordering_.clear();
NotifyTrackedOrderingChanged();
}
}
void PermanentFolderOrderingTracker::BookmarkAllUserNodesRemoved(
const std::set<GURL>& removed_urls,
const base::Location& location) {
all_user_bookmarks_remove_in_progress_ = false;
CHECK(ordering_.empty());
}
void PermanentFolderOrderingTracker::BookmarkNodeChildrenReordered(
const bookmarks::BookmarkNode* node) {
if (!IsTrackedPermanentNode(node) || !ShouldTrackOrdering()) {
return;
}
std::unordered_map<const BookmarkNode*, size_t> original_positions;
size_t storage_index = 0;
for (const BookmarkNode* child_node : ordering_) {
if (child_node->parent() == node) {
original_positions[child_node] = storage_index;
++storage_index;
}
}
CHECK_EQ(storage_index, node->children().size());
std::vector<raw_ptr<const bookmarks::BookmarkNode>> new_ordering;
std::unordered_set<int64_t> visited_not_added;
std::unordered_set<int64_t> added_not_visited_child_nodes;
size_t current_child_node_index = 0;
for (const BookmarkNode* current_node : ordering_) {
if (current_node->parent() != node) {
new_ordering.push_back(current_node);
continue;
}
if (added_not_visited_child_nodes.contains(current_node->id())) {
continue;
}
CHECK_LT(current_child_node_index, node->children().size());
const BookmarkNode* const in_order_child_node =
node->children()[current_child_node_index].get();
if (current_node == in_order_child_node) {
new_ordering.push_back(current_node);
++current_child_node_index;
AddNextVisitedNotAddedNodes(node, current_child_node_index, new_ordering,
visited_not_added);
continue;
}
visited_not_added.insert(current_node->id());
int position_difference =
original_positions[in_order_child_node] - current_child_node_index;
if (position_difference <= 1) {
continue;
}
new_ordering.push_back(in_order_child_node);
added_not_visited_child_nodes.insert(in_order_child_node->id());
++current_child_node_index;
AddNextVisitedNotAddedNodes(node, current_child_node_index, new_ordering,
visited_not_added);
}
ordering_ = std::move(new_ordering);
CHECK_EQ(ordering_.size(), GetExpectedOrderingSize());
NotifyTrackedOrderingChanged();
}
bool PermanentFolderOrderingTracker::ShouldTrackOrdering() const {
bool has_local_children =
local_or_syncable_node_ && !local_or_syncable_node_->children().empty();
bool has_account_children =
account_node_ && !account_node_->children().empty();
return has_local_children && has_account_children;
}
size_t PermanentFolderOrderingTracker::GetExpectedOrderingSize() const {
return ShouldTrackOrdering() ? GetChildrenCount() : 0u;
}
std::vector<raw_ptr<const bookmarks::BookmarkNode>>
PermanentFolderOrderingTracker::GetDefaultOrderIfTracked() const {
if (!ShouldTrackOrdering()) {
return {};
}
std::vector<raw_ptr<const bookmarks::BookmarkNode>> default_order;
for (const auto& node : account_node_->children()) {
default_order.push_back(node.get());
}
for (const auto& node : local_or_syncable_node_->children()) {
default_order.push_back(node.get());
}
return default_order;
}
void PermanentFolderOrderingTracker::RemoveBookmarkNodeIfTracked(
const bookmarks::BookmarkNode* parent,
const bookmarks::BookmarkNode* node) {
if (IsTrackedPermanentNode(node)) {
SetTrackedPermanentNodes();
ResetOrderingToDefault();
return;
}
if (!IsTrackedPermanentNode(parent)) {
return;
}
if (!ShouldTrackOrdering()) {
if (!ordering_.empty()) {
ordering_.clear();
NotifyTrackedOrderingChanged();
}
return;
}
if (all_user_bookmarks_remove_in_progress_) {
CHECK(ordering_.empty());
return;
}
size_t erase_count = std::erase(ordering_, node);
if (erase_count) {
NotifyTrackedOrderingChanged();
}
}
void PermanentFolderOrderingTracker::AddBookmarkNodeIfTracked(
const bookmarks::BookmarkNode* parent,
size_t index) {
const BookmarkNode* new_node = parent->children()[index].get();
if (IsTrackedPermanentNode(new_node)) {
SetTrackedPermanentNodes();
ResetOrderingToDefault();
return;
}
if (!IsTrackedPermanentNode(parent)) {
return;
}
if (!ShouldTrackOrdering()) {
CHECK(ordering_.empty());
return;
}
if (ordering_.empty()) {
CHECK_EQ(parent->children().size(), 1u);
ResetOrderingToDefault();
return;
}
CHECK_GT(parent->children().size(), 1u);
if (index == 0) {
size_t next_node_index = GetIndexOf(parent->children()[1].get());
ordering_.insert(ordering_.cbegin() + next_node_index, new_node);
} else {
size_t previous_node_index =
GetIndexOf(parent->children()[index - 1].get());
ordering_.insert(ordering_.cbegin() + previous_node_index + 1, new_node);
}
NotifyTrackedOrderingChanged();
}
size_t PermanentFolderOrderingTracker::GetInStorageBookmarkCountBeforeIndex(
bool account_node,
size_t index) const {
const BookmarkNode* parent =
account_node ? account_node_ : local_or_syncable_node_;
if (!ShouldTrackOrdering()) {
return parent->children().empty() ? 0u : index;
}
return std::count_if(
ordering_.begin(), ordering_.begin() + index,
[parent](const BookmarkNode* node) { return node->parent() == parent; });
;
}