Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / bookmarks / bookmark_model.cc
blob84b78a8b2cf7ce620a29a6cdf2762c8d7cd21b1f
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/bookmarks/bookmark_model.h"
7 #include <algorithm>
8 #include <functional>
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/i18n/string_compare.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/bookmarks/bookmark_expanded_state_tracker.h"
17 #include "chrome/browser/bookmarks/bookmark_index.h"
18 #include "chrome/browser/bookmarks/bookmark_model_observer.h"
19 #include "chrome/browser/bookmarks/bookmark_storage.h"
20 #include "chrome/browser/bookmarks/bookmark_title_match.h"
21 #include "chrome/browser/bookmarks/bookmark_utils.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/favicon/favicon_changed_details.h"
24 #include "chrome/browser/favicon/favicon_service.h"
25 #include "chrome/browser/favicon/favicon_service_factory.h"
26 #include "chrome/browser/history/history_service.h"
27 #include "chrome/browser/history/history_service_factory.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/common/favicon/favicon_types.h"
30 #include "content/public/browser/notification_details.h"
31 #include "content/public/browser/notification_source.h"
32 #include "grit/generated_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/gfx/favicon_size.h"
35 #include "ui/gfx/image/image_util.h"
37 using base::Time;
39 namespace {
41 // Helper to get a mutable bookmark node.
42 BookmarkNode* AsMutable(const BookmarkNode* node) {
43 return const_cast<BookmarkNode*>(node);
46 // Whitespace characters to strip from bookmark titles.
47 const base::char16 kInvalidChars[] = {
48 '\n', '\r', '\t',
49 0x2028, // Line separator
50 0x2029, // Paragraph separator
54 } // namespace
56 // BookmarkNode ---------------------------------------------------------------
58 const int64 BookmarkNode::kInvalidSyncTransactionVersion = -1;
60 BookmarkNode::BookmarkNode(const GURL& url)
61 : url_(url) {
62 Initialize(0);
65 BookmarkNode::BookmarkNode(int64 id, const GURL& url)
66 : url_(url) {
67 Initialize(id);
70 BookmarkNode::~BookmarkNode() {
73 void BookmarkNode::SetTitle(const base::string16& title) {
74 // Replace newlines and other problematic whitespace characters in
75 // folder/bookmark names with spaces.
76 base::string16 trimmed_title;
77 base::ReplaceChars(title, kInvalidChars, base::ASCIIToUTF16(" "),
78 &trimmed_title);
79 ui::TreeNode<BookmarkNode>::SetTitle(trimmed_title);
82 bool BookmarkNode::IsVisible() const {
83 return true;
86 bool BookmarkNode::GetMetaInfo(const std::string& key,
87 std::string* value) const {
88 if (!meta_info_map_)
89 return false;
91 MetaInfoMap::const_iterator it = meta_info_map_->find(key);
92 if (it == meta_info_map_->end())
93 return false;
95 *value = it->second;
96 return true;
99 bool BookmarkNode::SetMetaInfo(const std::string& key,
100 const std::string& value) {
101 if (!meta_info_map_)
102 meta_info_map_.reset(new MetaInfoMap);
104 MetaInfoMap::iterator it = meta_info_map_->find(key);
105 if (it == meta_info_map_->end()) {
106 (*meta_info_map_)[key] = value;
107 return true;
109 // Key already in map, check if the value has changed.
110 if (it->second == value)
111 return false;
112 it->second = value;
113 return true;
116 bool BookmarkNode::DeleteMetaInfo(const std::string& key) {
117 if (!meta_info_map_)
118 return false;
119 bool erased = meta_info_map_->erase(key) != 0;
120 if (meta_info_map_->empty())
121 meta_info_map_.reset();
122 return erased;
125 void BookmarkNode::SetMetaInfoMap(const MetaInfoMap& meta_info_map) {
126 if (meta_info_map.empty())
127 meta_info_map_.reset();
128 else
129 meta_info_map_.reset(new MetaInfoMap(meta_info_map));
132 const BookmarkNode::MetaInfoMap* BookmarkNode::GetMetaInfoMap() const {
133 return meta_info_map_.get();
136 void BookmarkNode::Initialize(int64 id) {
137 id_ = id;
138 type_ = url_.is_empty() ? FOLDER : URL;
139 date_added_ = Time::Now();
140 favicon_state_ = INVALID_FAVICON;
141 favicon_load_task_id_ = CancelableTaskTracker::kBadTaskId;
142 meta_info_map_.reset();
143 sync_transaction_version_ = kInvalidSyncTransactionVersion;
146 void BookmarkNode::InvalidateFavicon() {
147 icon_url_ = GURL();
148 favicon_ = gfx::Image();
149 favicon_state_ = INVALID_FAVICON;
152 namespace {
154 // Comparator used when sorting bookmarks. Folders are sorted first, then
155 // bookmarks.
156 class SortComparator : public std::binary_function<const BookmarkNode*,
157 const BookmarkNode*,
158 bool> {
159 public:
160 explicit SortComparator(icu::Collator* collator) : collator_(collator) {}
162 // Returns true if |n1| preceeds |n2|.
163 bool operator()(const BookmarkNode* n1, const BookmarkNode* n2) {
164 if (n1->type() == n2->type()) {
165 // Types are the same, compare the names.
166 if (!collator_)
167 return n1->GetTitle() < n2->GetTitle();
168 return base::i18n::CompareString16WithCollator(
169 collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
171 // Types differ, sort such that folders come first.
172 return n1->is_folder();
175 private:
176 icu::Collator* collator_;
179 } // namespace
181 // BookmarkPermanentNode -------------------------------------------------------
183 BookmarkPermanentNode::BookmarkPermanentNode(int64 id)
184 : BookmarkNode(id, GURL()),
185 visible_(true) {
188 BookmarkPermanentNode::~BookmarkPermanentNode() {
191 bool BookmarkPermanentNode::IsVisible() const {
192 return visible_ || !empty();
195 // BookmarkModel --------------------------------------------------------------
197 BookmarkModel::BookmarkModel(Profile* profile)
198 : profile_(profile),
199 loaded_(false),
200 root_(GURL()),
201 bookmark_bar_node_(NULL),
202 other_node_(NULL),
203 mobile_node_(NULL),
204 next_node_id_(1),
205 observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY),
206 loaded_signal_(true, false),
207 extensive_changes_(0) {
208 if (!profile_) {
209 // Profile is null during testing.
210 DoneLoading(CreateLoadDetails());
214 BookmarkModel::~BookmarkModel() {
215 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
216 BookmarkModelBeingDeleted(this));
218 if (store_.get()) {
219 // The store maintains a reference back to us. We need to tell it we're gone
220 // so that it doesn't try and invoke a method back on us again.
221 store_->BookmarkModelDeleted();
225 void BookmarkModel::Shutdown() {
226 if (loaded_)
227 return;
229 // See comment in HistoryService::ShutdownOnUIThread where this is invoked for
230 // details. It is also called when the BookmarkModel is deleted.
231 loaded_signal_.Signal();
234 void BookmarkModel::Load(
235 const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
236 if (store_.get()) {
237 // If the store is non-null, it means Load was already invoked. Load should
238 // only be invoked once.
239 NOTREACHED();
240 return;
243 expanded_state_tracker_.reset(
244 new BookmarkExpandedStateTracker(this, profile_->GetPrefs()));
246 // Listen for changes to favicons so that we can update the favicon of the
247 // node appropriately.
248 registrar_.Add(this, chrome::NOTIFICATION_FAVICON_CHANGED,
249 content::Source<Profile>(profile_));
251 // Load the bookmarks. BookmarkStorage notifies us when done.
252 store_ = new BookmarkStorage(profile_, this, task_runner.get());
253 store_->LoadBookmarks(CreateLoadDetails());
256 const BookmarkNode* BookmarkModel::GetParentForNewNodes() {
257 std::vector<const BookmarkNode*> nodes =
258 bookmark_utils::GetMostRecentlyModifiedFolders(this, 1);
259 DCHECK(!nodes.empty()); // This list is always padded with default folders.
260 return nodes[0];
263 void BookmarkModel::AddObserver(BookmarkModelObserver* observer) {
264 observers_.AddObserver(observer);
267 void BookmarkModel::RemoveObserver(BookmarkModelObserver* observer) {
268 observers_.RemoveObserver(observer);
271 void BookmarkModel::BeginExtensiveChanges() {
272 if (++extensive_changes_ == 1) {
273 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
274 ExtensiveBookmarkChangesBeginning(this));
278 void BookmarkModel::EndExtensiveChanges() {
279 --extensive_changes_;
280 DCHECK_GE(extensive_changes_, 0);
281 if (extensive_changes_ == 0) {
282 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
283 ExtensiveBookmarkChangesEnded(this));
287 void BookmarkModel::Remove(const BookmarkNode* parent, int index) {
288 if (!loaded_ || !IsValidIndex(parent, index, false) || is_root_node(parent)) {
289 NOTREACHED();
290 return;
292 RemoveAndDeleteNode(AsMutable(parent->GetChild(index)));
295 void BookmarkModel::RemoveAll() {
296 std::set<GURL> removed_urls;
297 ScopedVector<BookmarkNode> removed_nodes;
299 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
300 OnWillRemoveAllBookmarks(this));
302 BeginExtensiveChanges();
303 // Skip deleting permanent nodes. Permanent bookmark nodes are the root and
304 // its immediate children. For removing all non permanent nodes just remove
305 // all children of non-root permanent nodes.
307 base::AutoLock url_lock(url_lock_);
308 for (int i = 0; i < root_.child_count(); ++i) {
309 BookmarkNode* permanent_node = root_.GetChild(i);
310 for (int j = permanent_node->child_count() - 1; j >= 0; --j) {
311 BookmarkNode* child_node = permanent_node->GetChild(j);
312 removed_nodes.push_back(child_node);
313 RemoveNodeAndGetRemovedUrls(child_node, &removed_urls);
317 EndExtensiveChanges();
318 if (store_.get())
319 store_->ScheduleSave();
321 NotifyHistoryAboutRemovedBookmarks(removed_urls);
323 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
324 BookmarkAllNodesRemoved(this));
327 void BookmarkModel::Move(const BookmarkNode* node,
328 const BookmarkNode* new_parent,
329 int index) {
330 if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
331 is_root_node(new_parent) || is_permanent_node(node)) {
332 NOTREACHED();
333 return;
336 if (new_parent->HasAncestor(node)) {
337 // Can't make an ancestor of the node be a child of the node.
338 NOTREACHED();
339 return;
342 const BookmarkNode* old_parent = node->parent();
343 int old_index = old_parent->GetIndexOf(node);
345 if (old_parent == new_parent &&
346 (index == old_index || index == old_index + 1)) {
347 // Node is already in this position, nothing to do.
348 return;
351 SetDateFolderModified(new_parent, Time::Now());
353 if (old_parent == new_parent && index > old_index)
354 index--;
355 BookmarkNode* mutable_new_parent = AsMutable(new_parent);
356 mutable_new_parent->Add(AsMutable(node), index);
358 if (store_.get())
359 store_->ScheduleSave();
361 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
362 BookmarkNodeMoved(this, old_parent, old_index,
363 new_parent, index));
366 void BookmarkModel::Copy(const BookmarkNode* node,
367 const BookmarkNode* new_parent,
368 int index) {
369 if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
370 is_root_node(new_parent) || is_permanent_node(node)) {
371 NOTREACHED();
372 return;
375 if (new_parent->HasAncestor(node)) {
376 // Can't make an ancestor of the node be a child of the node.
377 NOTREACHED();
378 return;
381 SetDateFolderModified(new_parent, Time::Now());
382 BookmarkNodeData drag_data(node);
383 std::vector<BookmarkNodeData::Element> elements(drag_data.elements);
384 // CloneBookmarkNode will use BookmarkModel methods to do the job, so we
385 // don't need to send notifications here.
386 bookmark_utils::CloneBookmarkNode(this, elements, new_parent, index, true);
388 if (store_.get())
389 store_->ScheduleSave();
392 const gfx::Image& BookmarkModel::GetFavicon(const BookmarkNode* node) {
393 DCHECK(node);
394 if (node->favicon_state() == BookmarkNode::INVALID_FAVICON) {
395 BookmarkNode* mutable_node = AsMutable(node);
396 mutable_node->set_favicon_state(BookmarkNode::LOADING_FAVICON);
397 LoadFavicon(mutable_node);
399 return node->favicon();
402 void BookmarkModel::SetTitle(const BookmarkNode* node, const base::string16& title) {
403 if (!node) {
404 NOTREACHED();
405 return;
407 if (node->GetTitle() == title)
408 return;
410 if (is_permanent_node(node)) {
411 NOTREACHED();
412 return;
415 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
416 OnWillChangeBookmarkNode(this, node));
418 // The title index doesn't support changing the title, instead we remove then
419 // add it back.
420 index_->Remove(node);
421 AsMutable(node)->SetTitle(title);
422 index_->Add(node);
424 if (store_.get())
425 store_->ScheduleSave();
427 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
428 BookmarkNodeChanged(this, node));
431 void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
432 if (!node) {
433 NOTREACHED();
434 return;
437 // We cannot change the URL of a folder.
438 if (node->is_folder()) {
439 NOTREACHED();
440 return;
443 if (node->url() == url)
444 return;
446 BookmarkNode* mutable_node = AsMutable(node);
447 mutable_node->InvalidateFavicon();
448 CancelPendingFaviconLoadRequests(mutable_node);
450 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
451 OnWillChangeBookmarkNode(this, node));
454 base::AutoLock url_lock(url_lock_);
455 RemoveNodeFromURLSet(mutable_node);
456 mutable_node->set_url(url);
457 nodes_ordered_by_url_set_.insert(mutable_node);
460 if (store_.get())
461 store_->ScheduleSave();
463 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
464 BookmarkNodeChanged(this, node));
467 void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node,
468 const std::string& key,
469 const std::string& value) {
470 std::string old_value;
471 if (node->GetMetaInfo(key, &old_value) && old_value == value)
472 return;
474 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
475 OnWillChangeBookmarkMetaInfo(this, node));
477 if (AsMutable(node)->SetMetaInfo(key, value) && store_.get())
478 store_->ScheduleSave();
480 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
481 BookmarkMetaInfoChanged(this, node));
484 void BookmarkModel::SetNodeMetaInfoMap(
485 const BookmarkNode* node,
486 const BookmarkNode::MetaInfoMap& meta_info_map) {
487 const BookmarkNode::MetaInfoMap* old_meta_info_map = node->GetMetaInfoMap();
488 if ((!old_meta_info_map && meta_info_map.empty()) ||
489 (old_meta_info_map && meta_info_map == *old_meta_info_map))
490 return;
492 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
493 OnWillChangeBookmarkMetaInfo(this, node));
495 AsMutable(node)->SetMetaInfoMap(meta_info_map);
496 if (store_.get())
497 store_->ScheduleSave();
499 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
500 BookmarkMetaInfoChanged(this, node));
503 void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node,
504 const std::string& key) {
505 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
506 if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end())
507 return;
509 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
510 OnWillChangeBookmarkMetaInfo(this, node));
512 if (AsMutable(node)->DeleteMetaInfo(key) && store_.get())
513 store_->ScheduleSave();
515 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
516 BookmarkMetaInfoChanged(this, node));
519 void BookmarkModel::SetNodeSyncTransactionVersion(
520 const BookmarkNode* node,
521 int64 sync_transaction_version) {
522 if (sync_transaction_version == node->sync_transaction_version())
523 return;
525 AsMutable(node)->set_sync_transaction_version(sync_transaction_version);
526 if (store_.get())
527 store_->ScheduleSave();
530 void BookmarkModel::SetDateAdded(const BookmarkNode* node,
531 base::Time date_added) {
532 if (!node) {
533 NOTREACHED();
534 return;
537 if (node->date_added() == date_added)
538 return;
540 if (is_permanent_node(node)) {
541 NOTREACHED();
542 return;
545 AsMutable(node)->set_date_added(date_added);
547 // Syncing might result in dates newer than the folder's last modified date.
548 if (date_added > node->parent()->date_folder_modified()) {
549 // Will trigger store_->ScheduleSave().
550 SetDateFolderModified(node->parent(), date_added);
551 } else if (store_.get()) {
552 store_->ScheduleSave();
556 void BookmarkModel::GetNodesByURL(const GURL& url,
557 std::vector<const BookmarkNode*>* nodes) {
558 base::AutoLock url_lock(url_lock_);
559 BookmarkNode tmp_node(url);
560 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
561 while (i != nodes_ordered_by_url_set_.end() && (*i)->url() == url) {
562 nodes->push_back(*i);
563 ++i;
567 const BookmarkNode* BookmarkModel::GetMostRecentlyAddedNodeForURL(
568 const GURL& url) {
569 std::vector<const BookmarkNode*> nodes;
570 GetNodesByURL(url, &nodes);
571 if (nodes.empty())
572 return NULL;
574 std::sort(nodes.begin(), nodes.end(), &bookmark_utils::MoreRecentlyAdded);
575 return nodes.front();
578 bool BookmarkModel::HasBookmarks() {
579 base::AutoLock url_lock(url_lock_);
580 return !nodes_ordered_by_url_set_.empty();
583 bool BookmarkModel::IsBookmarked(const GURL& url) {
584 base::AutoLock url_lock(url_lock_);
585 return IsBookmarkedNoLock(url);
588 void BookmarkModel::GetBookmarks(
589 std::vector<BookmarkService::URLAndTitle>* bookmarks) {
590 base::AutoLock url_lock(url_lock_);
591 const GURL* last_url = NULL;
592 for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
593 i != nodes_ordered_by_url_set_.end(); ++i) {
594 const GURL* url = &((*i)->url());
595 // Only add unique URLs.
596 if (!last_url || *url != *last_url) {
597 BookmarkService::URLAndTitle bookmark;
598 bookmark.url = *url;
599 bookmark.title = (*i)->GetTitle();
600 bookmarks->push_back(bookmark);
602 last_url = url;
606 void BookmarkModel::BlockTillLoaded() {
607 loaded_signal_.Wait();
610 const BookmarkNode* BookmarkModel::GetNodeByID(int64 id) const {
611 // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
612 return GetNodeByID(&root_, id);
615 const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent,
616 int index,
617 const base::string16& title) {
618 if (!loaded_ || is_root_node(parent) || !IsValidIndex(parent, index, true)) {
619 // Can't add to the root.
620 NOTREACHED();
621 return NULL;
624 BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), GURL());
625 new_node->set_date_folder_modified(Time::Now());
626 // Folders shouldn't have line breaks in their titles.
627 new_node->SetTitle(title);
628 new_node->set_type(BookmarkNode::FOLDER);
630 return AddNode(AsMutable(parent), index, new_node);
633 const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
634 int index,
635 const base::string16& title,
636 const GURL& url) {
637 return AddURLWithCreationTime(parent, index,
638 CollapseWhitespace(title, false),
639 url, Time::Now());
642 const BookmarkNode* BookmarkModel::AddURLWithCreationTime(
643 const BookmarkNode* parent,
644 int index,
645 const base::string16& title,
646 const GURL& url,
647 const Time& creation_time) {
648 if (!loaded_ || !url.is_valid() || is_root_node(parent) ||
649 !IsValidIndex(parent, index, true)) {
650 NOTREACHED();
651 return NULL;
654 // Syncing may result in dates newer than the last modified date.
655 if (creation_time > parent->date_folder_modified())
656 SetDateFolderModified(parent, creation_time);
658 BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url);
659 new_node->SetTitle(title);
660 new_node->set_date_added(creation_time);
661 new_node->set_type(BookmarkNode::URL);
664 // Only hold the lock for the duration of the insert.
665 base::AutoLock url_lock(url_lock_);
666 nodes_ordered_by_url_set_.insert(new_node);
669 return AddNode(AsMutable(parent), index, new_node);
672 void BookmarkModel::SortChildren(const BookmarkNode* parent) {
673 if (!parent || !parent->is_folder() || is_root_node(parent) ||
674 parent->child_count() <= 1) {
675 return;
678 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
679 OnWillReorderBookmarkNode(this, parent));
681 UErrorCode error = U_ZERO_ERROR;
682 scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
683 if (U_FAILURE(error))
684 collator.reset(NULL);
685 BookmarkNode* mutable_parent = AsMutable(parent);
686 std::sort(mutable_parent->children().begin(),
687 mutable_parent->children().end(),
688 SortComparator(collator.get()));
690 if (store_.get())
691 store_->ScheduleSave();
693 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
694 BookmarkNodeChildrenReordered(this, parent));
697 void BookmarkModel::ReorderChildren(
698 const BookmarkNode* parent,
699 const std::vector<const BookmarkNode*>& ordered_nodes) {
700 // Ensure that all children in |parent| are in |ordered_nodes|.
701 DCHECK_EQ(static_cast<size_t>(parent->child_count()), ordered_nodes.size());
702 for (size_t i = 0; i < ordered_nodes.size(); ++i)
703 DCHECK_EQ(parent, ordered_nodes[i]->parent());
705 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
706 OnWillReorderBookmarkNode(this, parent));
708 AsMutable(parent)->SetChildren(
709 *(reinterpret_cast<const std::vector<BookmarkNode*>*>(&ordered_nodes)));
711 if (store_.get())
712 store_->ScheduleSave();
714 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
715 BookmarkNodeChildrenReordered(this, parent));
718 void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
719 const Time time) {
720 DCHECK(parent);
721 AsMutable(parent)->set_date_folder_modified(time);
723 if (store_.get())
724 store_->ScheduleSave();
727 void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) {
728 SetDateFolderModified(node, Time());
731 void BookmarkModel::GetBookmarksWithTitlesMatching(
732 const base::string16& text,
733 size_t max_count,
734 std::vector<BookmarkTitleMatch>* matches) {
735 if (!loaded_)
736 return;
738 index_->GetBookmarksWithTitlesMatching(text, max_count, matches);
741 void BookmarkModel::ClearStore() {
742 registrar_.RemoveAll();
743 store_ = NULL;
746 void BookmarkModel::SetPermanentNodeVisible(BookmarkNode::Type type,
747 bool value) {
748 DCHECK(loaded_);
749 switch (type) {
750 case BookmarkNode::BOOKMARK_BAR:
751 bookmark_bar_node_->set_visible(value);
752 break;
753 case BookmarkNode::OTHER_NODE:
754 other_node_->set_visible(value);
755 break;
756 case BookmarkNode::MOBILE:
757 mobile_node_->set_visible(value);
758 break;
759 default:
760 NOTREACHED();
764 bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) {
765 BookmarkNode tmp_node(url);
766 return (nodes_ordered_by_url_set_.find(&tmp_node) !=
767 nodes_ordered_by_url_set_.end());
770 void BookmarkModel::RemoveNode(BookmarkNode* node,
771 std::set<GURL>* removed_urls) {
772 if (!loaded_ || !node || is_permanent_node(node)) {
773 NOTREACHED();
774 return;
777 url_lock_.AssertAcquired();
778 if (node->is_url()) {
779 RemoveNodeFromURLSet(node);
780 removed_urls->insert(node->url());
781 index_->Remove(node);
784 CancelPendingFaviconLoadRequests(node);
786 // Recurse through children.
787 for (int i = node->child_count() - 1; i >= 0; --i)
788 RemoveNode(node->GetChild(i), removed_urls);
791 void BookmarkModel::DoneLoading(BookmarkLoadDetails* details_delete_me) {
792 DCHECK(details_delete_me);
793 scoped_ptr<BookmarkLoadDetails> details(details_delete_me);
794 if (loaded_) {
795 // We should only ever be loaded once.
796 NOTREACHED();
797 return;
800 next_node_id_ = details->max_id();
801 if (details->computed_checksum() != details->stored_checksum() ||
802 details->ids_reassigned()) {
803 // If bookmarks file changed externally, the IDs may have changed
804 // externally. In that case, the decoder may have reassigned IDs to make
805 // them unique. So when the file has changed externally, we should save the
806 // bookmarks file to persist new IDs.
807 if (store_.get())
808 store_->ScheduleSave();
810 bookmark_bar_node_ = details->release_bb_node();
811 other_node_ = details->release_other_folder_node();
812 mobile_node_ = details->release_mobile_folder_node();
813 index_.reset(details->release_index());
815 // WARNING: order is important here, various places assume the order is
816 // constant.
817 root_.Add(bookmark_bar_node_, 0);
818 root_.Add(other_node_, 1);
819 root_.Add(mobile_node_, 2);
821 root_.SetMetaInfoMap(details->model_meta_info_map());
822 root_.set_sync_transaction_version(details->model_sync_transaction_version());
825 base::AutoLock url_lock(url_lock_);
826 // Update nodes_ordered_by_url_set_ from the nodes.
827 PopulateNodesByURL(&root_);
830 loaded_ = true;
832 loaded_signal_.Signal();
834 // Notify our direct observers.
835 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
836 BookmarkModelLoaded(this, details->ids_reassigned()));
839 void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) {
840 scoped_ptr<BookmarkNode> node(delete_me);
842 const BookmarkNode* parent = node->parent();
843 DCHECK(parent);
844 int index = parent->GetIndexOf(node.get());
846 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
847 OnWillRemoveBookmarks(this, parent, index, node.get()));
849 std::set<GURL> removed_urls;
851 base::AutoLock url_lock(url_lock_);
852 RemoveNodeAndGetRemovedUrls(node.get(), &removed_urls);
855 if (store_.get())
856 store_->ScheduleSave();
858 NotifyHistoryAboutRemovedBookmarks(removed_urls);
860 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
861 BookmarkNodeRemoved(this, parent, index, node.get()));
864 void BookmarkModel::RemoveNodeFromURLSet(BookmarkNode* node) {
865 // NOTE: this is called in such a way that url_lock_ is already held. As
866 // such, this doesn't explicitly grab the lock.
867 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
868 DCHECK(i != nodes_ordered_by_url_set_.end());
869 // i points to the first node with the URL, advance until we find the
870 // node we're removing.
871 while (*i != node)
872 ++i;
873 nodes_ordered_by_url_set_.erase(i);
876 void BookmarkModel::RemoveNodeAndGetRemovedUrls(BookmarkNode* node,
877 std::set<GURL>* removed_urls) {
878 // NOTE: this method should be always called with |url_lock_| held.
879 // This method does not explicitly acquires a lock.
880 url_lock_.AssertAcquired();
881 DCHECK(removed_urls);
882 BookmarkNode* parent = AsMutable(node->parent());
883 DCHECK(parent);
884 parent->Remove(node);
885 RemoveNode(node, removed_urls);
886 // RemoveNode adds an entry to removed_urls for each node of type URL. As we
887 // allow duplicates we need to remove any entries that are still bookmarked.
888 for (std::set<GURL>::iterator i = removed_urls->begin();
889 i != removed_urls->end();) {
890 if (IsBookmarkedNoLock(*i)) {
891 // When we erase the iterator pointing at the erasee is
892 // invalidated, so using i++ here within the "erase" call is
893 // important as it advances the iterator before passing the
894 // old value through to erase.
895 removed_urls->erase(i++);
896 } else {
897 ++i;
902 void BookmarkModel::NotifyHistoryAboutRemovedBookmarks(
903 const std::set<GURL>& removed_bookmark_urls) const {
904 if (removed_bookmark_urls.empty()) {
905 // No point in sending out notification if the starred state didn't change.
906 return;
909 if (profile_) {
910 HistoryService* history =
911 HistoryServiceFactory::GetForProfile(profile_,
912 Profile::EXPLICIT_ACCESS);
913 if (history)
914 history->URLsNoLongerBookmarked(removed_bookmark_urls);
918 BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
919 int index,
920 BookmarkNode* node) {
921 parent->Add(node, index);
923 if (store_.get())
924 store_->ScheduleSave();
926 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
927 BookmarkNodeAdded(this, parent, index));
929 index_->Add(node);
931 return node;
934 const BookmarkNode* BookmarkModel::GetNodeByID(const BookmarkNode* node,
935 int64 id) const {
936 if (node->id() == id)
937 return node;
939 for (int i = 0, child_count = node->child_count(); i < child_count; ++i) {
940 const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
941 if (result)
942 return result;
944 return NULL;
947 bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
948 int index,
949 bool allow_end) {
950 return (parent && parent->is_folder() &&
951 (index >= 0 && (index < parent->child_count() ||
952 (allow_end && index == parent->child_count()))));
955 BookmarkPermanentNode* BookmarkModel::CreatePermanentNode(
956 BookmarkNode::Type type) {
957 DCHECK(type == BookmarkNode::BOOKMARK_BAR ||
958 type == BookmarkNode::OTHER_NODE ||
959 type == BookmarkNode::MOBILE);
960 BookmarkPermanentNode* node =
961 new BookmarkPermanentNode(generate_next_node_id());
962 if (type == BookmarkNode::MOBILE)
963 node->set_visible(false); // Mobile node is initially hidden.
965 int title_id;
966 switch (type) {
967 case BookmarkNode::BOOKMARK_BAR:
968 title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
969 break;
970 case BookmarkNode::OTHER_NODE:
971 title_id = IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME;
972 break;
973 case BookmarkNode::MOBILE:
974 title_id = IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME;
975 break;
976 default:
977 NOTREACHED();
978 title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
979 break;
981 node->SetTitle(l10n_util::GetStringUTF16(title_id));
982 node->set_type(type);
983 return node;
986 void BookmarkModel::OnFaviconDataAvailable(
987 BookmarkNode* node,
988 const chrome::FaviconImageResult& image_result) {
989 DCHECK(node);
990 node->set_favicon_load_task_id(CancelableTaskTracker::kBadTaskId);
991 node->set_favicon_state(BookmarkNode::LOADED_FAVICON);
992 if (!image_result.image.IsEmpty()) {
993 node->set_favicon(image_result.image);
994 node->set_icon_url(image_result.icon_url);
995 FaviconLoaded(node);
999 void BookmarkModel::LoadFavicon(BookmarkNode* node) {
1000 if (node->is_folder())
1001 return;
1003 DCHECK(node->url().is_valid());
1004 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
1005 profile_, Profile::EXPLICIT_ACCESS);
1006 if (!favicon_service)
1007 return;
1008 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
1009 FaviconService::FaviconForURLParams(node->url(),
1010 chrome::FAVICON,
1011 gfx::kFaviconSize),
1012 base::Bind(&BookmarkModel::OnFaviconDataAvailable,
1013 base::Unretained(this), node),
1014 &cancelable_task_tracker_);
1015 node->set_favicon_load_task_id(handle);
1018 void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
1019 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
1020 BookmarkNodeFaviconChanged(this, node));
1023 void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
1024 if (node->favicon_load_task_id() != CancelableTaskTracker::kBadTaskId) {
1025 cancelable_task_tracker_.TryCancel(node->favicon_load_task_id());
1026 node->set_favicon_load_task_id(CancelableTaskTracker::kBadTaskId);
1030 void BookmarkModel::Observe(int type,
1031 const content::NotificationSource& source,
1032 const content::NotificationDetails& details) {
1033 switch (type) {
1034 case chrome::NOTIFICATION_FAVICON_CHANGED: {
1035 // Prevent the observers from getting confused for multiple favicon loads.
1036 content::Details<FaviconChangedDetails> favicon_details(details);
1037 for (std::set<GURL>::const_iterator i = favicon_details->urls.begin();
1038 i != favicon_details->urls.end(); ++i) {
1039 std::vector<const BookmarkNode*> nodes;
1040 GetNodesByURL(*i, &nodes);
1041 for (size_t i = 0; i < nodes.size(); ++i) {
1042 // Got an updated favicon, for a URL, do a new request.
1043 BookmarkNode* node = AsMutable(nodes[i]);
1044 node->InvalidateFavicon();
1045 CancelPendingFaviconLoadRequests(node);
1046 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
1047 BookmarkNodeFaviconChanged(this, node));
1050 break;
1053 default:
1054 NOTREACHED();
1055 break;
1059 void BookmarkModel::PopulateNodesByURL(BookmarkNode* node) {
1060 // NOTE: this is called with url_lock_ already held. As such, this doesn't
1061 // explicitly grab the lock.
1062 if (node->is_url())
1063 nodes_ordered_by_url_set_.insert(node);
1064 for (int i = 0; i < node->child_count(); ++i)
1065 PopulateNodesByURL(node->GetChild(i));
1068 int64 BookmarkModel::generate_next_node_id() {
1069 return next_node_id_++;
1072 BookmarkLoadDetails* BookmarkModel::CreateLoadDetails() {
1073 BookmarkPermanentNode* bb_node =
1074 CreatePermanentNode(BookmarkNode::BOOKMARK_BAR);
1075 BookmarkPermanentNode* other_node =
1076 CreatePermanentNode(BookmarkNode::OTHER_NODE);
1077 BookmarkPermanentNode* mobile_node =
1078 CreatePermanentNode(BookmarkNode::MOBILE);
1079 return new BookmarkLoadDetails(bb_node, other_node, mobile_node,
1080 new BookmarkIndex(profile_),
1081 next_node_id_);