blob: 01ea4198a2a74a3887ceaa2d94ada340f47034b8 [file] [log] [blame]
// Copyright 2014 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 "components/bookmarks/browser/bookmark_model.h"
#include <algorithm>
#include <functional>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/i18n/string_compare.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "components/bookmarks/browser/bookmark_expanded_state_tracker.h"
#include "components/bookmarks/browser/bookmark_model_observer.h"
#include "components/bookmarks/browser/bookmark_node_data.h"
#include "components/bookmarks/browser/bookmark_storage.h"
#include "components/bookmarks/browser/bookmark_undo_delegate.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/bookmarks/browser/titled_url_index.h"
#include "components/bookmarks/browser/titled_url_match.h"
#include "components/bookmarks/browser/typed_count_sorter.h"
#include "components/favicon_base/favicon_types.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/favicon_size.h"
using base::Time;
namespace bookmarks {
namespace {
// Helper to get a mutable bookmark node.
BookmarkNode* AsMutable(const BookmarkNode* node) {
return const_cast<BookmarkNode*>(node);
}
// Helper to get a mutable permanent bookmark node.
BookmarkPermanentNode* AsMutable(const BookmarkPermanentNode* node) {
return const_cast<BookmarkPermanentNode*>(node);
}
// Comparator used when sorting permanent nodes. Nodes that are initially
// visible are sorted before nodes that are initially hidden.
class VisibilityComparator {
public:
explicit VisibilityComparator(BookmarkClient* client) : client_(client) {}
// Returns true if |n1| precedes |n2|.
bool operator()(const std::unique_ptr<BookmarkPermanentNode>& n1,
const std::unique_ptr<BookmarkPermanentNode>& n2) {
bool n1_visible = client_->IsPermanentNodeVisible(n1.get());
bool n2_visible = client_->IsPermanentNodeVisible(n2.get());
return n1_visible != n2_visible && n1_visible;
}
private:
BookmarkClient* client_;
};
// Comparator used when sorting bookmarks. Folders are sorted first, then
// bookmarks.
class SortComparator {
public:
explicit SortComparator(icu::Collator* collator) : collator_(collator) {}
// Returns true if |n1| precedes |n2|.
bool operator()(const std::unique_ptr<BookmarkNode>& n1,
const std::unique_ptr<BookmarkNode>& n2) {
if (n1->type() == n2->type()) {
// Types are the same, compare the names.
if (!collator_)
return n1->GetTitle() < n2->GetTitle();
return base::i18n::CompareString16WithCollator(
*collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
}
// Types differ, sort such that folders come first.
return n1->is_folder();
}
private:
icu::Collator* collator_;
};
// Delegate that does nothing.
class EmptyUndoDelegate : public BookmarkUndoDelegate {
public:
EmptyUndoDelegate() {}
~EmptyUndoDelegate() override {}
private:
// BookmarkUndoDelegate:
void SetUndoProvider(BookmarkUndoProvider* provider) override {}
void OnBookmarkNodeRemoved(BookmarkModel* model,
const BookmarkNode* parent,
int index,
std::unique_ptr<BookmarkNode> node) override {}
DISALLOW_COPY_AND_ASSIGN(EmptyUndoDelegate);
};
} // namespace
// BookmarkModel --------------------------------------------------------------
BookmarkModel::BookmarkModel(std::unique_ptr<BookmarkClient> client)
: client_(std::move(client)),
loaded_(false),
root_(GURL()),
bookmark_bar_node_(nullptr),
other_node_(nullptr),
mobile_node_(nullptr),
next_node_id_(1),
observers_(base::ObserverListPolicy::EXISTING_ONLY),
loaded_signal_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
extensive_changes_(0),
undo_delegate_(nullptr),
empty_undo_delegate_(new EmptyUndoDelegate) {
DCHECK(client_);
client_->Init(this);
}
BookmarkModel::~BookmarkModel() {
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkModelBeingDeleted(this);
if (store_.get()) {
// The store maintains a reference back to us. We need to tell it we're gone
// so that it doesn't try and invoke a method back on us again.
store_->BookmarkModelDeleted();
}
}
void BookmarkModel::Shutdown() {
if (loaded_)
return;
// See comment in HistoryService::ShutdownOnUIThread where this is invoked for
// details. It is also called when the BookmarkModel is deleted.
loaded_signal_.Signal();
}
void BookmarkModel::Load(
PrefService* pref_service,
const base::FilePath& profile_path,
const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& ui_task_runner) {
if (store_.get()) {
// If the store is non-null, it means Load was already invoked. Load should
// only be invoked once.
NOTREACHED();
return;
}
expanded_state_tracker_.reset(
new BookmarkExpandedStateTracker(this, pref_service));
// Load the bookmarks. BookmarkStorage notifies us when done.
store_.reset(new BookmarkStorage(this, profile_path, io_task_runner.get()));
store_->LoadBookmarks(CreateLoadDetails(), ui_task_runner);
}
void BookmarkModel::AddObserver(BookmarkModelObserver* observer) {
observers_.AddObserver(observer);
}
void BookmarkModel::RemoveObserver(BookmarkModelObserver* observer) {
observers_.RemoveObserver(observer);
}
void BookmarkModel::BeginExtensiveChanges() {
if (++extensive_changes_ == 1) {
for (BookmarkModelObserver& observer : observers_)
observer.ExtensiveBookmarkChangesBeginning(this);
}
}
void BookmarkModel::EndExtensiveChanges() {
--extensive_changes_;
DCHECK_GE(extensive_changes_, 0);
if (extensive_changes_ == 0) {
for (BookmarkModelObserver& observer : observers_)
observer.ExtensiveBookmarkChangesEnded(this);
}
}
void BookmarkModel::BeginGroupedChanges() {
for (BookmarkModelObserver& observer : observers_)
observer.GroupedBookmarkChangesBeginning(this);
}
void BookmarkModel::EndGroupedChanges() {
for (BookmarkModelObserver& observer : observers_)
observer.GroupedBookmarkChangesEnded(this);
}
void BookmarkModel::Remove(const BookmarkNode* node) {
DCHECK(loaded_);
DCHECK(node);
DCHECK(!is_root_node(node));
RemoveAndDeleteNode(AsMutable(node));
}
void BookmarkModel::RemoveAllUserBookmarks() {
std::set<GURL> removed_urls;
struct RemoveNodeData {
const BookmarkNode* parent;
int index;
std::unique_ptr<BookmarkNode> node;
};
std::vector<RemoveNodeData> removed_node_data_list;
for (BookmarkModelObserver& observer : observers_)
observer.OnWillRemoveAllUserBookmarks(this);
BeginExtensiveChanges();
// Skip deleting permanent nodes. Permanent bookmark nodes are the root and
// its immediate children. For removing all non permanent nodes just remove
// all children of non-root permanent nodes.
{
base::AutoLock url_lock(url_lock_);
for (int i = 0; i < root_.child_count(); ++i) {
const BookmarkNode* permanent_node = root_.GetChild(i);
if (!client_->CanBeEditedByUser(permanent_node))
continue;
for (int j = permanent_node->child_count() - 1; j >= 0; --j) {
std::unique_ptr<BookmarkNode> node = RemoveNodeAndGetRemovedUrls(
AsMutable(permanent_node->GetChild(j)), &removed_urls);
removed_node_data_list.push_back({permanent_node, j, std::move(node)});
}
}
}
EndExtensiveChanges();
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkAllUserNodesRemoved(this, removed_urls);
BeginGroupedChanges();
for (auto& removed_node_data : removed_node_data_list) {
undo_delegate()->OnBookmarkNodeRemoved(this, removed_node_data.parent,
removed_node_data.index,
std::move(removed_node_data.node));
}
EndGroupedChanges();
}
void BookmarkModel::Move(const BookmarkNode* node,
const BookmarkNode* new_parent,
int index) {
if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
is_root_node(new_parent) || is_permanent_node(node)) {
NOTREACHED();
return;
}
if (new_parent->HasAncestor(node)) {
// Can't make an ancestor of the node be a child of the node.
NOTREACHED();
return;
}
const BookmarkNode* old_parent = node->parent();
int old_index = old_parent->GetIndexOf(node);
if (old_parent == new_parent &&
(index == old_index || index == old_index + 1)) {
// Node is already in this position, nothing to do.
return;
}
SetDateFolderModified(new_parent, Time::Now());
if (old_parent == new_parent && index > old_index)
index--;
BookmarkNode* mutable_old_parent = AsMutable(old_parent);
std::unique_ptr<BookmarkNode> owned_node =
mutable_old_parent->Remove(AsMutable(node));
BookmarkNode* mutable_new_parent = AsMutable(new_parent);
mutable_new_parent->Add(std::move(owned_node), index);
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeMoved(this, old_parent, old_index, new_parent, index);
}
void BookmarkModel::Copy(const BookmarkNode* node,
const BookmarkNode* new_parent,
int index) {
if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
is_root_node(new_parent) || is_permanent_node(node)) {
NOTREACHED();
return;
}
if (new_parent->HasAncestor(node)) {
// Can't make an ancestor of the node be a child of the node.
NOTREACHED();
return;
}
SetDateFolderModified(new_parent, Time::Now());
BookmarkNodeData drag_data(node);
// CloneBookmarkNode will use BookmarkModel methods to do the job, so we
// don't need to send notifications here.
CloneBookmarkNode(this, drag_data.elements, new_parent, index, true);
if (store_.get())
store_->ScheduleSave();
}
const gfx::Image& BookmarkModel::GetFavicon(const BookmarkNode* node) {
DCHECK(node);
if (node->favicon_state() == BookmarkNode::INVALID_FAVICON) {
BookmarkNode* mutable_node = AsMutable(node);
LoadFavicon(mutable_node, client_->PreferTouchIcon()
? favicon_base::IconType::kTouchIcon
: favicon_base::IconType::kFavicon);
}
return node->favicon();
}
favicon_base::IconType BookmarkModel::GetFaviconType(const BookmarkNode* node) {
DCHECK(node);
return node->favicon_type();
}
void BookmarkModel::SetTitle(const BookmarkNode* node,
const base::string16& title) {
DCHECK(node);
if (node->GetTitle() == title)
return;
if (is_permanent_node(node) && !client_->CanSetPermanentNodeTitle(node)) {
NOTREACHED();
return;
}
for (BookmarkModelObserver& observer : observers_)
observer.OnWillChangeBookmarkNode(this, node);
// The title index doesn't support changing the title, instead we remove then
// add it back. Only do this for URL nodes. A directory node can have its
// title changed but should be excluded from the index.
if (node->is_url())
index_->Remove(node);
AsMutable(node)->SetTitle(title);
if (node->is_url())
index_->Add(node);
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeChanged(this, node);
}
void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
DCHECK(node && !node->is_folder());
if (node->url() == url)
return;
BookmarkNode* mutable_node = AsMutable(node);
mutable_node->InvalidateFavicon();
CancelPendingFaviconLoadRequests(mutable_node);
for (BookmarkModelObserver& observer : observers_)
observer.OnWillChangeBookmarkNode(this, node);
{
base::AutoLock url_lock(url_lock_);
RemoveNodeFromInternalMaps(mutable_node);
mutable_node->set_url(url);
AddNodeToInternalMaps(mutable_node);
}
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeChanged(this, node);
}
void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node,
const std::string& key,
const std::string& value) {
std::string old_value;
if (node->GetMetaInfo(key, &old_value) && old_value == value)
return;
for (BookmarkModelObserver& observer : observers_)
observer.OnWillChangeBookmarkMetaInfo(this, node);
if (AsMutable(node)->SetMetaInfo(key, value) && store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkMetaInfoChanged(this, node);
}
void BookmarkModel::SetNodeMetaInfoMap(
const BookmarkNode* node,
const BookmarkNode::MetaInfoMap& meta_info_map) {
const BookmarkNode::MetaInfoMap* old_meta_info_map = node->GetMetaInfoMap();
if ((!old_meta_info_map && meta_info_map.empty()) ||
(old_meta_info_map && meta_info_map == *old_meta_info_map))
return;
for (BookmarkModelObserver& observer : observers_)
observer.OnWillChangeBookmarkMetaInfo(this, node);
AsMutable(node)->SetMetaInfoMap(meta_info_map);
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkMetaInfoChanged(this, node);
}
void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node,
const std::string& key) {
const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end())
return;
for (BookmarkModelObserver& observer : observers_)
observer.OnWillChangeBookmarkMetaInfo(this, node);
if (AsMutable(node)->DeleteMetaInfo(key) && store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkMetaInfoChanged(this, node);
}
void BookmarkModel::AddNonClonedKey(const std::string& key) {
non_cloned_keys_.insert(key);
}
void BookmarkModel::SetNodeSyncTransactionVersion(
const BookmarkNode* node,
int64_t sync_transaction_version) {
DCHECK(client_->CanSyncNode(node));
if (sync_transaction_version == node->sync_transaction_version())
return;
AsMutable(node)->set_sync_transaction_version(sync_transaction_version);
if (store_.get())
store_->ScheduleSave();
}
void BookmarkModel::OnFaviconsChanged(const std::set<GURL>& page_urls,
const GURL& icon_url) {
std::set<const BookmarkNode*> to_update;
for (const GURL& page_url : page_urls) {
std::vector<const BookmarkNode*> nodes;
GetNodesByURL(page_url, &nodes);
to_update.insert(nodes.begin(), nodes.end());
}
if (!icon_url.is_empty()) {
// Log Histogram to determine how often |icon_url| is non empty in
// practice.
// TODO(pkotwicz): Do something more efficient if |icon_url| is non-empty
// many times a day for each user.
UMA_HISTOGRAM_BOOLEAN("Bookmarks.OnFaviconsChangedIconURL", true);
base::AutoLock url_lock(url_lock_);
for (const BookmarkNode* node : nodes_ordered_by_url_set_) {
if (node->icon_url() && icon_url == *node->icon_url())
to_update.insert(node);
}
}
for (const BookmarkNode* node : to_update) {
// Rerequest the favicon.
BookmarkNode* mutable_node = AsMutable(node);
mutable_node->InvalidateFavicon();
CancelPendingFaviconLoadRequests(mutable_node);
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeFaviconChanged(this, node);
}
}
void BookmarkModel::SetDateAdded(const BookmarkNode* node, Time date_added) {
DCHECK(node && !is_permanent_node(node));
if (node->date_added() == date_added)
return;
AsMutable(node)->set_date_added(date_added);
// Syncing might result in dates newer than the folder's last modified date.
if (date_added > node->parent()->date_folder_modified()) {
// Will trigger store_->ScheduleSave().
SetDateFolderModified(node->parent(), date_added);
} else if (store_.get()) {
store_->ScheduleSave();
}
}
void BookmarkModel::GetNodesByURL(const GURL& url,
std::vector<const BookmarkNode*>* nodes) {
base::AutoLock url_lock(url_lock_);
BookmarkNode tmp_node(url);
NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
while (i != nodes_ordered_by_url_set_.end() && (*i)->url() == url) {
nodes->push_back(*i);
++i;
}
}
const BookmarkNode* BookmarkModel::GetMostRecentlyAddedUserNodeForURL(
const GURL& url) {
std::vector<const BookmarkNode*> nodes;
GetNodesByURL(url, &nodes);
std::sort(nodes.begin(), nodes.end(), &MoreRecentlyAdded);
// Look for the first node that the user can edit.
for (size_t i = 0; i < nodes.size(); ++i) {
if (client_->CanBeEditedByUser(nodes[i]))
return nodes[i];
}
return nullptr;
}
bool BookmarkModel::HasBookmarks() {
base::AutoLock url_lock(url_lock_);
return !nodes_ordered_by_url_set_.empty();
}
bool BookmarkModel::HasNoUserCreatedBookmarksOrFolders() {
return bookmark_bar_node_->empty() && other_node_->empty() &&
mobile_node_->empty();
}
bool BookmarkModel::IsBookmarked(const GURL& url) {
base::AutoLock url_lock(url_lock_);
return IsBookmarkedNoLock(url);
}
void BookmarkModel::GetBookmarks(
std::vector<BookmarkModel::URLAndTitle>* bookmarks) {
base::AutoLock url_lock(url_lock_);
const GURL* last_url = nullptr;
for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
i != nodes_ordered_by_url_set_.end(); ++i) {
const GURL* url = &((*i)->url());
// Only add unique URLs.
if (!last_url || *url != *last_url) {
BookmarkModel::URLAndTitle bookmark;
bookmark.url = *url;
bookmark.title = (*i)->GetTitle();
bookmarks->push_back(bookmark);
}
last_url = url;
}
}
void BookmarkModel::BlockTillLoaded() {
loaded_signal_.Wait();
}
const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent,
int index,
const base::string16& title) {
return AddFolderWithMetaInfo(parent, index, title, nullptr);
}
const BookmarkNode* BookmarkModel::AddFolderWithMetaInfo(
const BookmarkNode* parent,
int index,
const base::string16& title,
const BookmarkNode::MetaInfoMap* meta_info) {
if (!loaded_ || is_root_node(parent) || !IsValidIndex(parent, index, true)) {
// Can't add to the root.
NOTREACHED();
return nullptr;
}
std::unique_ptr<BookmarkNode> new_node =
base::MakeUnique<BookmarkNode>(generate_next_node_id(), GURL());
new_node->set_date_folder_modified(Time::Now());
// Folders shouldn't have line breaks in their titles.
new_node->SetTitle(title);
new_node->set_type(BookmarkNode::FOLDER);
if (meta_info)
new_node->SetMetaInfoMap(*meta_info);
return AddNode(AsMutable(parent), index, std::move(new_node));
}
const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
int index,
const base::string16& title,
const GURL& url) {
return AddURLWithCreationTimeAndMetaInfo(parent, index, title, url,
Time::Now(), nullptr);
}
const BookmarkNode* BookmarkModel::AddURLWithCreationTimeAndMetaInfo(
const BookmarkNode* parent,
int index,
const base::string16& title,
const GURL& url,
const Time& creation_time,
const BookmarkNode::MetaInfoMap* meta_info) {
if (!loaded_ || !url.is_valid() || is_root_node(parent) ||
!IsValidIndex(parent, index, true)) {
NOTREACHED();
return nullptr;
}
// Syncing may result in dates newer than the last modified date.
if (creation_time > parent->date_folder_modified())
SetDateFolderModified(parent, creation_time);
std::unique_ptr<BookmarkNode> new_node =
base::MakeUnique<BookmarkNode>(generate_next_node_id(), url);
new_node->SetTitle(title);
new_node->set_date_added(creation_time);
new_node->set_type(BookmarkNode::URL);
if (meta_info)
new_node->SetMetaInfoMap(*meta_info);
return AddNode(AsMutable(parent), index, std::move(new_node));
}
void BookmarkModel::SortChildren(const BookmarkNode* parent) {
DCHECK(client_->CanBeEditedByUser(parent));
if (!parent || !parent->is_folder() || is_root_node(parent) ||
parent->child_count() <= 1) {
return;
}
for (BookmarkModelObserver& observer : observers_)
observer.OnWillReorderBookmarkNode(this, parent);
UErrorCode error = U_ZERO_ERROR;
std::unique_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
if (U_FAILURE(error))
collator.reset(nullptr);
BookmarkNode* mutable_parent = AsMutable(parent);
std::sort(mutable_parent->children().begin(),
mutable_parent->children().end(),
SortComparator(collator.get()));
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeChildrenReordered(this, parent);
}
void BookmarkModel::ReorderChildren(
const BookmarkNode* parent,
const std::vector<const BookmarkNode*>& ordered_nodes) {
DCHECK(client_->CanBeEditedByUser(parent));
// Ensure that all children in |parent| are in |ordered_nodes|.
DCHECK_EQ(static_cast<size_t>(parent->child_count()), ordered_nodes.size());
for (const BookmarkNode* node : ordered_nodes)
DCHECK_EQ(parent, node->parent());
for (BookmarkModelObserver& observer : observers_)
observer.OnWillReorderBookmarkNode(this, parent);
if (ordered_nodes.size() > 1) {
std::map<const BookmarkNode*, int> order;
for (size_t i = 0; i < ordered_nodes.size(); ++i)
order[ordered_nodes[i]] = i;
std::vector<std::unique_ptr<BookmarkNode>> new_children(
ordered_nodes.size());
BookmarkNode* mutable_parent = AsMutable(parent);
for (auto& child : mutable_parent->children()) {
size_t new_location = order[child.get()];
new_children[new_location] = std::move(child);
}
mutable_parent->children().swap(new_children);
if (store_.get())
store_->ScheduleSave();
}
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeChildrenReordered(this, parent);
}
void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
const Time time) {
DCHECK(parent);
AsMutable(parent)->set_date_folder_modified(time);
if (store_.get())
store_->ScheduleSave();
}
void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) {
SetDateFolderModified(node, Time());
}
void BookmarkModel::GetBookmarksMatching(const base::string16& text,
size_t max_count,
std::vector<TitledUrlMatch>* matches) {
GetBookmarksMatching(text, max_count,
query_parser::MatchingAlgorithm::DEFAULT, matches);
}
void BookmarkModel::GetBookmarksMatching(
const base::string16& text,
size_t max_count,
query_parser::MatchingAlgorithm matching_algorithm,
std::vector<TitledUrlMatch>* matches) {
if (!loaded_)
return;
index_->GetResultsMatching(text, max_count, matching_algorithm, matches);
}
void BookmarkModel::ClearStore() {
store_.reset();
}
void BookmarkModel::SetPermanentNodeVisible(BookmarkNode::Type type,
bool value) {
BookmarkPermanentNode* node = AsMutable(PermanentNode(type));
node->set_visible(value || client_->IsPermanentNodeVisible(node));
}
const BookmarkPermanentNode* BookmarkModel::PermanentNode(
BookmarkNode::Type type) {
DCHECK(loaded_);
switch (type) {
case BookmarkNode::BOOKMARK_BAR:
return bookmark_bar_node_;
case BookmarkNode::OTHER_NODE:
return other_node_;
case BookmarkNode::MOBILE:
return mobile_node_;
default:
NOTREACHED();
return nullptr;
}
}
void BookmarkModel::RestoreRemovedNode(const BookmarkNode* parent,
int index,
std::unique_ptr<BookmarkNode> node) {
BookmarkNode* node_ptr = node.get();
AddNode(AsMutable(parent), index, std::move(node));
// We might be restoring a folder node that have already contained a set of
// child nodes. We need to notify all of them.
NotifyNodeAddedForAllDescendents(node_ptr);
}
void BookmarkModel::NotifyNodeAddedForAllDescendents(const BookmarkNode* node) {
for (int i = 0; i < node->child_count(); ++i) {
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeAdded(this, node, i);
NotifyNodeAddedForAllDescendents(node->GetChild(i));
}
}
bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) {
BookmarkNode tmp_node(url);
return (nodes_ordered_by_url_set_.find(&tmp_node) !=
nodes_ordered_by_url_set_.end());
}
void BookmarkModel::RemoveNode(BookmarkNode* node,
std::set<GURL>* removed_urls) {
if (!loaded_ || !node || is_permanent_node(node)) {
NOTREACHED();
return;
}
url_lock_.AssertAcquired();
if (node->is_url()) {
RemoveNodeFromInternalMaps(node);
removed_urls->insert(node->url());
}
CancelPendingFaviconLoadRequests(node);
// Recurse through children.
for (int i = node->child_count() - 1; i >= 0; --i)
RemoveNode(node->GetChild(i), removed_urls);
}
void BookmarkModel::DoneLoading(std::unique_ptr<BookmarkLoadDetails> details) {
DCHECK(details);
if (loaded_) {
// We should only ever be loaded once.
NOTREACHED();
return;
}
next_node_id_ = details->max_id();
if (details->computed_checksum() != details->stored_checksum() ||
details->ids_reassigned()) {
// If bookmarks file changed externally, the IDs may have changed
// externally. In that case, the decoder may have reassigned IDs to make
// them unique. So when the file has changed externally, we should save the
// bookmarks file to persist new IDs.
if (store_.get())
store_->ScheduleSave();
}
std::unique_ptr<BookmarkPermanentNode> owned_bb_node =
details->owned_bb_node();
std::unique_ptr<BookmarkPermanentNode> owned_other_folder_node =
details->owned_other_folder_node();
std::unique_ptr<BookmarkPermanentNode> owned_mobile_folder_node =
details->owned_mobile_folder_node();
index_ = details->owned_index();
bookmark_bar_node_ = owned_bb_node.get();
other_node_ = owned_other_folder_node.get();
mobile_node_ = owned_mobile_folder_node.get();
// Get any extra nodes and take ownership of them at the |root_|.
std::vector<std::unique_ptr<BookmarkPermanentNode>> extra_nodes =
details->owned_extra_nodes();
// WARNING: order is important here, various places assume the order is
// constant (but can vary between embedders with the initial visibility
// of permanent nodes).
std::vector<std::unique_ptr<BookmarkPermanentNode>> root_children;
root_children.push_back(std::move(owned_bb_node));
root_children.push_back(std::move(owned_other_folder_node));
root_children.push_back(std::move(owned_mobile_folder_node));
std::move(extra_nodes.begin(), extra_nodes.end(),
std::back_inserter(root_children));
std::stable_sort(root_children.begin(),
root_children.end(),
VisibilityComparator(client_.get()));
for (size_t i = 0; i < root_children.size(); ++i)
root_.Add(std::move(root_children[i]), static_cast<int>(i));
root_.SetMetaInfoMap(details->model_meta_info_map());
root_.set_sync_transaction_version(details->model_sync_transaction_version());
{
base::AutoLock url_lock(url_lock_);
// Update nodes_ordered_by_url_set_ from the nodes.
PopulateNodesByURL(&root_);
}
loaded_ = true;
loaded_signal_.Signal();
// Notify our direct observers.
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkModelLoaded(this, details->ids_reassigned());
}
void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* node_ptr) {
std::unique_ptr<BookmarkNode> node;
const BookmarkNode* parent = node_ptr->parent();
DCHECK(parent);
int index = parent->GetIndexOf(node_ptr);
DCHECK_NE(-1, index);
for (BookmarkModelObserver& observer : observers_)
observer.OnWillRemoveBookmarks(this, parent, index, node_ptr);
std::set<GURL> removed_urls;
{
base::AutoLock url_lock(url_lock_);
node = RemoveNodeAndGetRemovedUrls(node_ptr, &removed_urls);
}
if (store_.get())
store_->ScheduleSave();
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeRemoved(this, parent, index, node.get(), removed_urls);
undo_delegate()->OnBookmarkNodeRemoved(this, parent, index, std::move(node));
}
void BookmarkModel::RemoveNodeFromInternalMaps(BookmarkNode* node) {
index_->Remove(node);
// NOTE: this is called in such a way that url_lock_ is already held. As
// such, this doesn't explicitly grab the lock.
url_lock_.AssertAcquired();
NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
DCHECK(i != nodes_ordered_by_url_set_.end());
// i points to the first node with the URL, advance until we find the
// node we're removing.
while (*i != node)
++i;
nodes_ordered_by_url_set_.erase(i);
}
std::unique_ptr<BookmarkNode> BookmarkModel::RemoveNodeAndGetRemovedUrls(
BookmarkNode* node_ptr,
std::set<GURL>* removed_urls) {
// NOTE: this method should be always called with |url_lock_| held.
// This method does not explicitly acquires a lock.
url_lock_.AssertAcquired();
DCHECK(removed_urls);
BookmarkNode* parent = node_ptr->parent();
DCHECK(parent);
std::unique_ptr<BookmarkNode> node = parent->Remove(node_ptr);
RemoveNode(node_ptr, removed_urls);
// RemoveNode adds an entry to removed_urls for each node of type URL. As we
// allow duplicates we need to remove any entries that are still bookmarked.
for (std::set<GURL>::iterator i = removed_urls->begin();
i != removed_urls->end();) {
if (IsBookmarkedNoLock(*i)) {
// When we erase the iterator pointing at the erasee is
// invalidated, so using i++ here within the "erase" call is
// important as it advances the iterator before passing the
// old value through to erase.
removed_urls->erase(i++);
} else {
++i;
}
}
return node;
}
BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
int index,
std::unique_ptr<BookmarkNode> node) {
BookmarkNode* node_ptr = node.get();
parent->Add(std::move(node), index);
if (store_.get())
store_->ScheduleSave();
{
base::AutoLock url_lock(url_lock_);
AddNodeToInternalMaps(node_ptr);
}
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeAdded(this, parent, index);
return node_ptr;
}
void BookmarkModel::AddNodeToInternalMaps(BookmarkNode* node) {
url_lock_.AssertAcquired();
if (node->is_url()) {
index_->Add(node);
nodes_ordered_by_url_set_.insert(node);
}
for (int i = 0; i < node->child_count(); ++i)
AddNodeToInternalMaps(node->GetChild(i));
}
bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
int index,
bool allow_end) {
return (parent && parent->is_folder() &&
(index >= 0 && (index < parent->child_count() ||
(allow_end && index == parent->child_count()))));
}
BookmarkPermanentNode* BookmarkModel::CreatePermanentNode(
BookmarkNode::Type type) {
DCHECK(type == BookmarkNode::BOOKMARK_BAR ||
type == BookmarkNode::OTHER_NODE ||
type == BookmarkNode::MOBILE);
BookmarkPermanentNode* node =
new BookmarkPermanentNode(generate_next_node_id());
node->set_type(type);
node->set_visible(client_->IsPermanentNodeVisible(node));
int title_id;
switch (type) {
case BookmarkNode::BOOKMARK_BAR:
title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
break;
case BookmarkNode::OTHER_NODE:
title_id = IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME;
break;
case BookmarkNode::MOBILE:
title_id = IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME;
break;
default:
NOTREACHED();
title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
break;
}
node->SetTitle(l10n_util::GetStringUTF16(title_id));
return node;
}
void BookmarkModel::OnFaviconDataAvailable(
BookmarkNode* node,
favicon_base::IconType icon_type,
const favicon_base::FaviconImageResult& image_result) {
DCHECK(node);
node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId);
node->set_favicon_state(BookmarkNode::LOADED_FAVICON);
if (!image_result.image.IsEmpty()) {
node->set_favicon_type(icon_type);
node->set_favicon(image_result.image);
node->set_icon_url(image_result.icon_url);
FaviconLoaded(node);
} else if (icon_type == favicon_base::IconType::kTouchIcon) {
// Couldn't load the touch icon, fallback to the regular favicon.
DCHECK(client_->PreferTouchIcon());
LoadFavicon(node, favicon_base::IconType::kFavicon);
} else {
// No favicon available, but we still notify observers.
FaviconLoaded(node);
}
}
void BookmarkModel::LoadFavicon(BookmarkNode* node,
favicon_base::IconType icon_type) {
if (node->is_folder())
return;
DCHECK(node->url().is_valid());
node->set_favicon_state(BookmarkNode::LOADING_FAVICON);
base::CancelableTaskTracker::TaskId taskId =
client_->GetFaviconImageForPageURL(
node->url(),
icon_type,
base::Bind(
&BookmarkModel::OnFaviconDataAvailable,
base::Unretained(this),
node,
icon_type),
&cancelable_task_tracker_);
if (taskId != base::CancelableTaskTracker::kBadTaskId)
node->set_favicon_load_task_id(taskId);
}
void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
for (BookmarkModelObserver& observer : observers_)
observer.BookmarkNodeFaviconChanged(this, node);
}
void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
if (node->favicon_load_task_id() != base::CancelableTaskTracker::kBadTaskId) {
cancelable_task_tracker_.TryCancel(node->favicon_load_task_id());
node->set_favicon_load_task_id(base::CancelableTaskTracker::kBadTaskId);
}
}
void BookmarkModel::PopulateNodesByURL(BookmarkNode* node) {
// NOTE: this is called with url_lock_ already held. As such, this doesn't
// explicitly grab the lock.
if (node->is_url())
nodes_ordered_by_url_set_.insert(node);
for (int i = 0; i < node->child_count(); ++i)
PopulateNodesByURL(node->GetChild(i));
}
int64_t BookmarkModel::generate_next_node_id() {
return next_node_id_++;
}
std::unique_ptr<BookmarkLoadDetails> BookmarkModel::CreateLoadDetails() {
BookmarkPermanentNode* bb_node =
CreatePermanentNode(BookmarkNode::BOOKMARK_BAR);
BookmarkPermanentNode* other_node =
CreatePermanentNode(BookmarkNode::OTHER_NODE);
BookmarkPermanentNode* mobile_node =
CreatePermanentNode(BookmarkNode::MOBILE);
std::unique_ptr<TitledUrlNodeSorter> node_sorter =
base::MakeUnique<TypedCountSorter>(client_.get());
return std::unique_ptr<BookmarkLoadDetails>(new BookmarkLoadDetails(
bb_node, other_node, mobile_node, client_->GetLoadExtraNodesCallback(),
new TitledUrlIndex(std::move(node_sorter)), next_node_id_));
}
void BookmarkModel::SetUndoDelegate(BookmarkUndoDelegate* undo_delegate) {
undo_delegate_ = undo_delegate;
if (undo_delegate_)
undo_delegate_->SetUndoProvider(this);
}
BookmarkUndoDelegate* BookmarkModel::undo_delegate() const {
return undo_delegate_ ? undo_delegate_ : empty_undo_delegate_.get();
}
} // namespace bookmarks