| // 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_); |
| } |