// Copyright (c) 2011 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/bookmarks/bookmark_model.h"
#include <algorithm>
#include <functional>
#include "base/callback.h"
#include "base/memory/scoped_vector.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_index.h"
#include "chrome/browser/bookmarks/bookmark_storage.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/history/history_notifications.h"
#include "chrome/browser/profiles/profile.h"
#include "content/common/notification_service.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_collator.h"
#include "ui/gfx/codec/png_codec.h"
using base::Time;
namespace {
// Helper to get a mutable bookmark node.
static BookmarkNode* AsMutable(const BookmarkNode* node) {
return const_cast<BookmarkNode*>(node);
}
} // anonymous namespace
// BookmarkNode ---------------------------------------------------------------
BookmarkNode::BookmarkNode(const GURL& url)
: url_(url) {
Initialize(0);
}
BookmarkNode::BookmarkNode(int64 id, const GURL& url)
: url_(url) {
Initialize(id);
}
BookmarkNode::~BookmarkNode() {
}
void BookmarkNode::Initialize(int64 id) {
id_ = id;
loaded_favicon_ = false;
favicon_load_handle_ = 0;
type_ = !url_.is_empty() ? URL : BOOKMARK_BAR;
date_added_ = Time::Now();
}
void BookmarkNode::InvalidateFavicon() {
loaded_favicon_ = false;
favicon_ = SkBitmap();
}
void BookmarkNode::Reset(const history::StarredEntry& entry) {
DCHECK(entry.type != history::StarredEntry::URL || entry.url == url_);
favicon_ = SkBitmap();
switch (entry.type) {
case history::StarredEntry::URL:
type_ = BookmarkNode::URL;
break;
case history::StarredEntry::USER_FOLDER:
type_ = BookmarkNode::FOLDER;
break;
case history::StarredEntry::BOOKMARK_BAR:
type_ = BookmarkNode::BOOKMARK_BAR;
break;
case history::StarredEntry::OTHER:
type_ = BookmarkNode::OTHER_NODE;
break;
default:
NOTREACHED();
}
date_added_ = entry.date_added;
date_folder_modified_ = entry.date_folder_modified;
set_title(entry.title);
}
// BookmarkModel --------------------------------------------------------------
namespace {
// Comparator used when sorting bookmarks. Folders are sorted first, then
// bookmarks.
class SortComparator : public std::binary_function<const BookmarkNode*,
const BookmarkNode*,
bool> {
public:
explicit SortComparator(icu::Collator* collator) : collator_(collator) { }
// Returns true if lhs preceeds rhs.
bool operator() (const BookmarkNode* n1, const BookmarkNode* n2) {
if (n1->type() == n2->type()) {
// Types are the same, compare the names.
if (!collator_)
return n1->GetTitle() < n2->GetTitle();
return l10n_util::CompareString16WithCollator(
collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
}
// Types differ, sort such that folders come first.
return n1->is_folder();
}
private:
icu::Collator* collator_;
};
} // namespace
BookmarkModel::BookmarkModel(Profile* profile)
: profile_(profile),
loaded_(false),
file_changed_(false),
root_(GURL()),
bookmark_bar_node_(NULL),
other_node_(NULL),
next_node_id_(1),
observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY),
loaded_signal_(TRUE, FALSE) {
if (!profile_) {
// Profile is null during testing.
DoneLoading(CreateLoadDetails());
}
}
BookmarkModel::~BookmarkModel() {
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkModelBeingDeleted(this));
if (store_) {
// 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::Load() {
if (store_.get()) {
// If the store is non-null, it means Load was already invoked. Load should
// only be invoked once.
NOTREACHED();
return;
}
// Listen for changes to favicons so that we can update the favicon of the
// node appropriately.
registrar_.Add(this, NotificationType::FAVICON_CHANGED,
Source<Profile>(profile_));
// Load the bookmarks. BookmarkStorage notifies us when done.
store_ = new BookmarkStorage(profile_, this);
store_->LoadBookmarks(CreateLoadDetails());
}
const BookmarkNode* BookmarkModel::GetParentForNewNodes() {
std::vector<const BookmarkNode*> nodes =
bookmark_utils::GetMostRecentlyModifiedFolders(this, 1);
return nodes.empty() ? bookmark_bar_node_ : nodes[0];
}
void BookmarkModel::Remove(const BookmarkNode* parent, int index) {
if (!loaded_ || !IsValidIndex(parent, index, false) || is_root(parent)) {
NOTREACHED();
return;
}
RemoveAndDeleteNode(AsMutable(parent->GetChild(index)));
}
void BookmarkModel::Move(const BookmarkNode* node,
const BookmarkNode* new_parent,
int index) {
if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
is_root(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());
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;
}
if (old_parent == new_parent && index > old_index)
index--;
BookmarkNode* mutable_new_parent = AsMutable(new_parent);
mutable_new_parent->Add(AsMutable(node), index);
if (store_.get())
store_->ScheduleSave();
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
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(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);
std::vector<BookmarkNodeData::Element> elements(drag_data_.elements);
// CloneBookmarkNode will use BookmarkModel methods to do the job, so we
// don't need to send notifications here.
bookmark_utils::CloneBookmarkNode(this, elements, new_parent, index);
if (store_.get())
store_->ScheduleSave();
}
const SkBitmap& BookmarkModel::GetFavicon(const BookmarkNode* node) {
DCHECK(node);
if (!node->is_favicon_loaded()) {
BookmarkNode* mutable_node = AsMutable(node);
mutable_node->set_favicon_loaded(true);
LoadFavicon(mutable_node);
}
return node->favicon();
}
void BookmarkModel::SetTitle(const BookmarkNode* node, const string16& title) {
if (!node) {
NOTREACHED();
return;
}
if (node->GetTitle() == title)
return;
if (node == bookmark_bar_node_ || node == other_node_) {
NOTREACHED();
return;
}
// The title index doesn't support changing the title, instead we remove then
// add it back.
index_->Remove(node);
AsMutable(node)->set_title(title);
index_->Add(node);
if (store_.get())
store_->ScheduleSave();
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeChanged(this, node));
}
void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
if (!node) {
NOTREACHED();
return;
}
// We cannot change the URL of a folder.
if (node->is_folder()) {
NOTREACHED();
return;
}
if (url == node->GetURL())
return;
AsMutable(node)->InvalidateFavicon();
CancelPendingFaviconLoadRequests(AsMutable(node));
{
base::AutoLock url_lock(url_lock_);
NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(
AsMutable(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);
AsMutable(node)->SetURL(url);
nodes_ordered_by_url_set_.insert(AsMutable(node));
}
if (store_.get())
store_->ScheduleSave();
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeChanged(this, node));
}
bool BookmarkModel::IsLoaded() {
return loaded_;
}
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)->GetURL() == url) {
nodes->push_back(*i);
++i;
}
}
const BookmarkNode* BookmarkModel::GetMostRecentlyAddedNodeForURL(
const GURL& url) {
std::vector<const BookmarkNode*> nodes;
GetNodesByURL(url, &nodes);
if (nodes.empty())
return NULL;
std::sort(nodes.begin(), nodes.end(), &bookmark_utils::MoreRecentlyAdded);
return nodes.front();
}
void BookmarkModel::GetBookmarks(std::vector<GURL>* urls) {
base::AutoLock url_lock(url_lock_);
const GURL* last_url = NULL;
for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
i != nodes_ordered_by_url_set_.end(); ++i) {
const GURL* url = &((*i)->GetURL());
// Only add unique URLs.
if (!last_url || *url != *last_url)
urls->push_back(*url);
last_url = url;
}
}
bool BookmarkModel::HasBookmarks() {
base::AutoLock url_lock(url_lock_);
return !nodes_ordered_by_url_set_.empty();
}
bool BookmarkModel::IsBookmarked(const GURL& url) {
base::AutoLock url_lock(url_lock_);
return IsBookmarkedNoLock(url);
}
const BookmarkNode* BookmarkModel::GetNodeByID(int64 id) {
// TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
return GetNodeByID(&root_, id);
}
const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent,
int index,
const string16& title) {
if (!loaded_ || parent == &root_ || !IsValidIndex(parent, index, true)) {
// Can't add to the root.
NOTREACHED();
return NULL;
}
BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(),
GURL());
new_node->set_date_folder_modified(Time::Now());
new_node->set_title(title);
new_node->set_type(BookmarkNode::FOLDER);
return AddNode(AsMutable(parent), index, new_node, false);
}
const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
int index,
const string16& title,
const GURL& url) {
return AddURLWithCreationTime(parent, index, title, url, Time::Now());
}
const BookmarkNode* BookmarkModel::AddURLWithCreationTime(
const BookmarkNode* parent,
int index,
const string16& title,
const GURL& url,
const Time& creation_time) {
if (!loaded_ || !url.is_valid() || is_root(parent) ||
!IsValidIndex(parent, index, true)) {
NOTREACHED();
return NULL;
}
bool was_bookmarked = IsBookmarked(url);
SetDateFolderModified(parent, creation_time);
BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url);
new_node->set_title(title);
new_node->set_date_added(creation_time);
new_node->set_type(BookmarkNode::URL);
{
// Only hold the lock for the duration of the insert.
base::AutoLock url_lock(url_lock_);
nodes_ordered_by_url_set_.insert(new_node);
}
return AddNode(AsMutable(parent), index, new_node, was_bookmarked);
}
void BookmarkModel::SortChildren(const BookmarkNode* parent) {
if (!parent || !parent->is_folder() || is_root(parent) ||
parent->child_count() <= 1) {
return;
}
UErrorCode error = U_ZERO_ERROR;
scoped_ptr<icu::Collator> collator(
icu::Collator::createInstance(
icu::Locale(g_browser_process->GetApplicationLocale().c_str()),
error));
if (U_FAILURE(error))
collator.reset(NULL);
BookmarkNode* mutable_parent = AsMutable(parent);
std::sort(mutable_parent->children().begin(),
mutable_parent->children().end(),
SortComparator(collator.get()));
if (store_.get())
store_->ScheduleSave();
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeChildrenReordered(this, parent));
}
void BookmarkModel::SetURLStarred(const GURL& url,
const string16& title,
bool is_starred) {
std::vector<const BookmarkNode*> bookmarks;
GetNodesByURL(url, &bookmarks);
bool bookmarks_exist = !bookmarks.empty();
if (is_starred == bookmarks_exist)
return; // Nothing to do, state already matches.
if (is_starred) {
// Create a bookmark.
const BookmarkNode* parent = GetParentForNewNodes();
AddURL(parent, parent->child_count(), title, url);
} else {
// Remove all the bookmarks.
for (size_t i = 0; i < bookmarks.size(); ++i) {
const BookmarkNode* node = bookmarks[i];
int index = node->parent()->GetIndexOf(node);
if (index > -1)
Remove(node->parent(), index);
}
}
}
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::GetBookmarksWithTitlesMatching(
const string16& text,
size_t max_count,
std::vector<bookmark_utils::TitleMatch>* matches) {
if (!loaded_)
return;
index_->GetBookmarksWithTitlesMatching(text, max_count, matches);
}
void BookmarkModel::ClearStore() {
registrar_.RemoveAll();
store_ = NULL;
}
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::FaviconLoaded(const BookmarkNode* node) {
// Send out notification to the observer.
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeFaviconLoaded(this, node));
}
void BookmarkModel::RemoveNode(BookmarkNode* node,
std::set<GURL>* removed_urls) {
if (!loaded_ || !node || is_permanent_node(node)) {
NOTREACHED();
return;
}
if (node->type() == BookmarkNode::URL) {
// NOTE: this is called in such a way that url_lock_ is already held. As
// such, this doesn't explicitly grab the lock.
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);
removed_urls->insert(node->GetURL());
index_->Remove(node);
}
CancelPendingFaviconLoadRequests(node);
// Recurse through children.
for (int i = node->child_count() - 1; i >= 0; --i)
RemoveNode(node->GetChild(i), removed_urls);
}
void BookmarkModel::DoneLoading(
BookmarkLoadDetails* details_delete_me) {
DCHECK(details_delete_me);
scoped_ptr<BookmarkLoadDetails> details(details_delete_me);
if (loaded_) {
// We should only ever be loaded once.
NOTREACHED();
return;
}
next_node_id_ = details->max_id();
if (details->computed_checksum() != details->stored_checksum())
SetFileChanged();
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();
}
bookmark_bar_node_ = details->release_bb_node();
other_node_ = details->release_other_folder_node();
index_.reset(details->release_index());
// WARNING: order is important here, various places assume bookmark bar then
// other node.
root_.Add(bookmark_bar_node_, 0);
root_.Add(other_node_, 1);
{
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_EACH_OBSERVER(BookmarkModelObserver, observers_, Loaded(this));
// And generic notification.
NotificationService::current()->Notify(
NotificationType::BOOKMARK_MODEL_LOADED,
Source<Profile>(profile_),
NotificationService::NoDetails());
}
void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) {
scoped_ptr<BookmarkNode> node(delete_me);
BookmarkNode* parent = AsMutable(node->parent());
DCHECK(parent);
int index = parent->GetIndexOf(node.get());
parent->Remove(node.get());
history::URLsStarredDetails details(false);
{
base::AutoLock url_lock(url_lock_);
RemoveNode(node.get(), &details.changed_urls);
// RemoveNode adds an entry to changed_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 = details.changed_urls.begin();
i != details.changed_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.
details.changed_urls.erase(i++);
} else {
++i;
}
}
}
if (store_.get())
store_->ScheduleSave();
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeRemoved(this, parent, index, node.get()));
if (details.changed_urls.empty()) {
// No point in sending out notification if the starred state didn't change.
return;
}
if (profile_) {
HistoryService* history =
profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
if (history)
history->URLsNoLongerBookmarked(details.changed_urls);
}
NotificationService::current()->Notify(
NotificationType::URLS_STARRED,
Source<Profile>(profile_),
Details<history::URLsStarredDetails>(&details));
}
void BookmarkModel::BeginImportMode() {
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkImportBeginning(this));
}
void BookmarkModel::EndImportMode() {
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkImportEnding(this));
}
BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
int index,
BookmarkNode* node,
bool was_bookmarked) {
parent->Add(node, index);
if (store_.get())
store_->ScheduleSave();
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeAdded(this, parent, index));
index_->Add(node);
if (node->type() == BookmarkNode::URL && !was_bookmarked) {
history::URLsStarredDetails details(true);
details.changed_urls.insert(node->GetURL());
NotificationService::current()->Notify(
NotificationType::URLS_STARRED,
Source<Profile>(profile_),
Details<history::URLsStarredDetails>(&details));
}
return node;
}
void BookmarkModel::BlockTillLoaded() {
loaded_signal_.Wait();
}
const BookmarkNode* BookmarkModel::GetNodeByID(const BookmarkNode* node,
int64 id) {
if (node->id() == id)
return node;
for (int i = 0, child_count = node->child_count(); i < child_count; ++i) {
const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
if (result)
return result;
}
return NULL;
}
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()))));
}
BookmarkNode* BookmarkModel::CreateBookmarkNode() {
history::StarredEntry entry;
entry.type = history::StarredEntry::BOOKMARK_BAR;
return CreateRootNodeFromStarredEntry(entry);
}
BookmarkNode* BookmarkModel::CreateOtherBookmarksNode() {
history::StarredEntry entry;
entry.type = history::StarredEntry::OTHER;
return CreateRootNodeFromStarredEntry(entry);
}
BookmarkNode* BookmarkModel::CreateRootNodeFromStarredEntry(
const history::StarredEntry& entry) {
DCHECK(entry.type == history::StarredEntry::BOOKMARK_BAR ||
entry.type == history::StarredEntry::OTHER);
BookmarkNode* node = new BookmarkNode(generate_next_node_id(), GURL());
node->Reset(entry);
if (entry.type == history::StarredEntry::BOOKMARK_BAR) {
node->set_title(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_FOLDER_NAME));
} else {
node->set_title(
l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
}
return node;
}
void BookmarkModel::OnFaviconDataAvailable(
FaviconService::Handle handle,
history::FaviconData favicon) {
SkBitmap favicon_bitmap;
BookmarkNode* node =
load_consumer_.GetClientData(
profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), handle);
DCHECK(node);
node->set_favicon_load_handle(0);
if (favicon.is_valid() && gfx::PNGCodec::Decode(favicon.image_data->front(),
favicon.image_data->size(),
&favicon_bitmap)) {
node->set_favicon(favicon_bitmap);
FaviconLoaded(node);
}
}
void BookmarkModel::LoadFavicon(BookmarkNode* node) {
if (node->type() != BookmarkNode::URL)
return;
DCHECK(node->GetURL().is_valid());
FaviconService* favicon_service =
profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
if (!favicon_service)
return;
FaviconService::Handle handle = favicon_service->GetFaviconForURL(
node->GetURL(), history::FAVICON, &load_consumer_,
NewCallback(this, &BookmarkModel::OnFaviconDataAvailable));
load_consumer_.SetClientData(favicon_service, handle, node);
node->set_favicon_load_handle(handle);
}
void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
if (node->favicon_load_handle()) {
FaviconService* favicon_service =
profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
if (favicon_service)
favicon_service->CancelRequest(node->favicon_load_handle());
node->set_favicon_load_handle(0);
}
}
void BookmarkModel::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::FAVICON_CHANGED: {
// Prevent the observers from getting confused for multiple favicon loads.
Details<history::FaviconChangeDetails> favicon_details(details);
for (std::set<GURL>::const_iterator i = favicon_details->urls.begin();
i != favicon_details->urls.end(); ++i) {
std::vector<const BookmarkNode*> nodes;
GetNodesByURL(*i, &nodes);
for (size_t i = 0; i < nodes.size(); ++i) {
// Got an updated favicon, for a URL, do a new request.
BookmarkNode* node = AsMutable(nodes[i]);
node->InvalidateFavicon();
CancelPendingFaviconLoadRequests(node);
FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
BookmarkNodeChanged(this, node));
}
}
break;
}
default:
NOTREACHED();
break;
}
}
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 BookmarkModel::generate_next_node_id() {
return next_node_id_++;
}
void BookmarkModel::SetFileChanged() {
file_changed_ = true;
}
BookmarkLoadDetails* BookmarkModel::CreateLoadDetails() {
BookmarkNode* bb_node = CreateBookmarkNode();
BookmarkNode* other_folder_node = CreateOtherBookmarksNode();
return new BookmarkLoadDetails(
bb_node, other_folder_node, new BookmarkIndex(profile()), next_node_id_);
}