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.
4
5 #include "chrome/browser/bookmarks/bookmark_model.h"
6
7 #include <algorithm>
8 #include <functional>
9
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"
36
37 using base::Time;
38
39 namespace {
40
41 // Helper to get a mutable bookmark node.
AsMutable(const BookmarkNode * node)42 BookmarkNode* AsMutable(const BookmarkNode* node) {
43 return const_cast<BookmarkNode*>(node);
44 }
45
46 // Whitespace characters to strip from bookmark titles.
47 const char16 kInvalidChars[] = {
48 '\n', '\r', '\t',
49 0x2028, // Line separator
50 0x2029, // Paragraph separator
51 0
52 };
53
54 } // namespace
55
56 // BookmarkNode ---------------------------------------------------------------
57
58 const int64 BookmarkNode::kInvalidSyncTransactionVersion = -1;
59
BookmarkNode(const GURL & url)60 BookmarkNode::BookmarkNode(const GURL& url)
61 : url_(url) {
62 Initialize(0);
63 }
64
BookmarkNode(int64 id,const GURL & url)65 BookmarkNode::BookmarkNode(int64 id, const GURL& url)
66 : url_(url) {
67 Initialize(id);
68 }
69
~BookmarkNode()70 BookmarkNode::~BookmarkNode() {
71 }
72
SetTitle(const base::string16 & title)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, ASCIIToUTF16(" "), &trimmed_title);
78 ui::TreeNode<BookmarkNode>::SetTitle(trimmed_title);
79 }
80
IsVisible() const81 bool BookmarkNode::IsVisible() const {
82 return true;
83 }
84
GetMetaInfo(const std::string & key,std::string * value) const85 bool BookmarkNode::GetMetaInfo(const std::string& key,
86 std::string* value) const {
87 if (!meta_info_map_)
88 return false;
89
90 MetaInfoMap::const_iterator it = meta_info_map_->find(key);
91 if (it == meta_info_map_->end())
92 return false;
93
94 *value = it->second;
95 return true;
96 }
97
SetMetaInfo(const std::string & key,const std::string & value)98 bool BookmarkNode::SetMetaInfo(const std::string& key,
99 const std::string& value) {
100 if (!meta_info_map_)
101 meta_info_map_.reset(new MetaInfoMap);
102
103 MetaInfoMap::iterator it = meta_info_map_->find(key);
104 if (it == meta_info_map_->end()) {
105 (*meta_info_map_)[key] = value;
106 return true;
107 }
108 // Key already in map, check if the value has changed.
109 if (it->second == value)
110 return false;
111 it->second = value;
112 return true;
113 }
114
DeleteMetaInfo(const std::string & key)115 bool BookmarkNode::DeleteMetaInfo(const std::string& key) {
116 if (!meta_info_map_)
117 return false;
118 bool erased = meta_info_map_->erase(key) != 0;
119 if (meta_info_map_->empty())
120 meta_info_map_.reset();
121 return erased;
122 }
123
SetMetaInfoMap(const MetaInfoMap & meta_info_map)124 void BookmarkNode::SetMetaInfoMap(const MetaInfoMap& meta_info_map) {
125 if (meta_info_map.empty())
126 meta_info_map_.reset();
127 else
128 meta_info_map_.reset(new MetaInfoMap(meta_info_map));
129 }
130
GetMetaInfoMap() const131 const BookmarkNode::MetaInfoMap* BookmarkNode::GetMetaInfoMap() const {
132 return meta_info_map_.get();
133 }
134
Initialize(int64 id)135 void BookmarkNode::Initialize(int64 id) {
136 id_ = id;
137 type_ = url_.is_empty() ? FOLDER : URL;
138 date_added_ = Time::Now();
139 favicon_state_ = INVALID_FAVICON;
140 favicon_load_task_id_ = CancelableTaskTracker::kBadTaskId;
141 meta_info_map_.reset();
142 sync_transaction_version_ = kInvalidSyncTransactionVersion;
143 }
144
InvalidateFavicon()145 void BookmarkNode::InvalidateFavicon() {
146 icon_url_ = GURL();
147 favicon_ = gfx::Image();
148 favicon_state_ = INVALID_FAVICON;
149 }
150
151 namespace {
152
153 // Comparator used when sorting bookmarks. Folders are sorted first, then
154 // bookmarks.
155 class SortComparator : public std::binary_function<const BookmarkNode*,
156 const BookmarkNode*,
157 bool> {
158 public:
SortComparator(icu::Collator * collator)159 explicit SortComparator(icu::Collator* collator) : collator_(collator) {}
160
161 // Returns true if |n1| preceeds |n2|.
operator ()(const BookmarkNode * n1,const BookmarkNode * n2)162 bool operator()(const BookmarkNode* n1, const BookmarkNode* n2) {
163 if (n1->type() == n2->type()) {
164 // Types are the same, compare the names.
165 if (!collator_)
166 return n1->GetTitle() < n2->GetTitle();
167 return base::i18n::CompareString16WithCollator(
168 collator_, n1->GetTitle(), n2->GetTitle()) == UCOL_LESS;
169 }
170 // Types differ, sort such that folders come first.
171 return n1->is_folder();
172 }
173
174 private:
175 icu::Collator* collator_;
176 };
177
178 } // namespace
179
180 // BookmarkPermanentNode -------------------------------------------------------
181
BookmarkPermanentNode(int64 id)182 BookmarkPermanentNode::BookmarkPermanentNode(int64 id)
183 : BookmarkNode(id, GURL()),
184 visible_(true) {
185 }
186
~BookmarkPermanentNode()187 BookmarkPermanentNode::~BookmarkPermanentNode() {
188 }
189
IsVisible() const190 bool BookmarkPermanentNode::IsVisible() const {
191 return visible_ || !empty();
192 }
193
194 // BookmarkModel --------------------------------------------------------------
195
BookmarkModel(Profile * profile)196 BookmarkModel::BookmarkModel(Profile* profile)
197 : profile_(profile),
198 loaded_(false),
199 root_(GURL()),
200 bookmark_bar_node_(NULL),
201 other_node_(NULL),
202 mobile_node_(NULL),
203 next_node_id_(1),
204 observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY),
205 loaded_signal_(true, false),
206 extensive_changes_(0) {
207 if (!profile_) {
208 // Profile is null during testing.
209 DoneLoading(CreateLoadDetails());
210 }
211 }
212
~BookmarkModel()213 BookmarkModel::~BookmarkModel() {
214 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
215 BookmarkModelBeingDeleted(this));
216
217 if (store_.get()) {
218 // The store maintains a reference back to us. We need to tell it we're gone
219 // so that it doesn't try and invoke a method back on us again.
220 store_->BookmarkModelDeleted();
221 }
222 }
223
Shutdown()224 void BookmarkModel::Shutdown() {
225 if (loaded_)
226 return;
227
228 // See comment in HistoryService::ShutdownOnUIThread where this is invoked for
229 // details. It is also called when the BookmarkModel is deleted.
230 loaded_signal_.Signal();
231 }
232
Load(const scoped_refptr<base::SequencedTaskRunner> & task_runner)233 void BookmarkModel::Load(
234 const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
235 if (store_.get()) {
236 // If the store is non-null, it means Load was already invoked. Load should
237 // only be invoked once.
238 NOTREACHED();
239 return;
240 }
241
242 expanded_state_tracker_.reset(
243 new BookmarkExpandedStateTracker(this, profile_->GetPrefs()));
244
245 // Listen for changes to favicons so that we can update the favicon of the
246 // node appropriately.
247 registrar_.Add(this, chrome::NOTIFICATION_FAVICON_CHANGED,
248 content::Source<Profile>(profile_));
249
250 // Load the bookmarks. BookmarkStorage notifies us when done.
251 store_ = new BookmarkStorage(profile_, this, task_runner.get());
252 store_->LoadBookmarks(CreateLoadDetails());
253 }
254
GetParentForNewNodes()255 const BookmarkNode* BookmarkModel::GetParentForNewNodes() {
256 std::vector<const BookmarkNode*> nodes =
257 bookmark_utils::GetMostRecentlyModifiedFolders(this, 1);
258 DCHECK(!nodes.empty()); // This list is always padded with default folders.
259 return nodes[0];
260 }
261
AddObserver(BookmarkModelObserver * observer)262 void BookmarkModel::AddObserver(BookmarkModelObserver* observer) {
263 observers_.AddObserver(observer);
264 }
265
RemoveObserver(BookmarkModelObserver * observer)266 void BookmarkModel::RemoveObserver(BookmarkModelObserver* observer) {
267 observers_.RemoveObserver(observer);
268 }
269
BeginExtensiveChanges()270 void BookmarkModel::BeginExtensiveChanges() {
271 if (++extensive_changes_ == 1) {
272 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
273 ExtensiveBookmarkChangesBeginning(this));
274 }
275 }
276
EndExtensiveChanges()277 void BookmarkModel::EndExtensiveChanges() {
278 --extensive_changes_;
279 DCHECK_GE(extensive_changes_, 0);
280 if (extensive_changes_ == 0) {
281 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
282 ExtensiveBookmarkChangesEnded(this));
283 }
284 }
285
Remove(const BookmarkNode * parent,int index)286 void BookmarkModel::Remove(const BookmarkNode* parent, int index) {
287 if (!loaded_ || !IsValidIndex(parent, index, false) || is_root_node(parent)) {
288 NOTREACHED();
289 return;
290 }
291 RemoveAndDeleteNode(AsMutable(parent->GetChild(index)));
292 }
293
RemoveAll()294 void BookmarkModel::RemoveAll() {
295 std::set<GURL> removed_urls;
296 ScopedVector<BookmarkNode> removed_nodes;
297
298 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
299 OnWillRemoveAllBookmarks(this));
300
301 BeginExtensiveChanges();
302 // Skip deleting permanent nodes. Permanent bookmark nodes are the root and
303 // its immediate children. For removing all non permanent nodes just remove
304 // all children of non-root permanent nodes.
305 {
306 base::AutoLock url_lock(url_lock_);
307 for (int i = 0; i < root_.child_count(); ++i) {
308 const BookmarkNode* permanent_node = root_.GetChild(i);
309 for (int j = permanent_node->child_count() - 1; j >= 0; --j) {
310 BookmarkNode* child_node = AsMutable(permanent_node->GetChild(j));
311 removed_nodes.push_back(child_node);
312 RemoveNodeAndGetRemovedUrls(child_node, &removed_urls);
313 }
314 }
315 }
316 EndExtensiveChanges();
317 if (store_.get())
318 store_->ScheduleSave();
319
320 NotifyHistoryAboutRemovedBookmarks(removed_urls);
321
322 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
323 BookmarkAllNodesRemoved(this));
324 }
325
Move(const BookmarkNode * node,const BookmarkNode * new_parent,int index)326 void BookmarkModel::Move(const BookmarkNode* node,
327 const BookmarkNode* new_parent,
328 int index) {
329 if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
330 is_root_node(new_parent) || is_permanent_node(node)) {
331 NOTREACHED();
332 return;
333 }
334
335 if (new_parent->HasAncestor(node)) {
336 // Can't make an ancestor of the node be a child of the node.
337 NOTREACHED();
338 return;
339 }
340
341 const BookmarkNode* old_parent = node->parent();
342 int old_index = old_parent->GetIndexOf(node);
343
344 if (old_parent == new_parent &&
345 (index == old_index || index == old_index + 1)) {
346 // Node is already in this position, nothing to do.
347 return;
348 }
349
350 SetDateFolderModified(new_parent, Time::Now());
351
352 if (old_parent == new_parent && index > old_index)
353 index--;
354 BookmarkNode* mutable_new_parent = AsMutable(new_parent);
355 mutable_new_parent->Add(AsMutable(node), index);
356
357 if (store_.get())
358 store_->ScheduleSave();
359
360 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
361 BookmarkNodeMoved(this, old_parent, old_index,
362 new_parent, index));
363 }
364
Copy(const BookmarkNode * node,const BookmarkNode * new_parent,int index)365 void BookmarkModel::Copy(const BookmarkNode* node,
366 const BookmarkNode* new_parent,
367 int index) {
368 if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
369 is_root_node(new_parent) || is_permanent_node(node)) {
370 NOTREACHED();
371 return;
372 }
373
374 if (new_parent->HasAncestor(node)) {
375 // Can't make an ancestor of the node be a child of the node.
376 NOTREACHED();
377 return;
378 }
379
380 SetDateFolderModified(new_parent, Time::Now());
381 BookmarkNodeData drag_data(node);
382 std::vector<BookmarkNodeData::Element> elements(drag_data.elements);
383 // CloneBookmarkNode will use BookmarkModel methods to do the job, so we
384 // don't need to send notifications here.
385 bookmark_utils::CloneBookmarkNode(this, elements, new_parent, index, true);
386
387 if (store_.get())
388 store_->ScheduleSave();
389 }
390
GetFavicon(const BookmarkNode * node)391 const gfx::Image& BookmarkModel::GetFavicon(const BookmarkNode* node) {
392 DCHECK(node);
393 if (node->favicon_state() == BookmarkNode::INVALID_FAVICON) {
394 BookmarkNode* mutable_node = AsMutable(node);
395 mutable_node->set_favicon_state(BookmarkNode::LOADING_FAVICON);
396 LoadFavicon(mutable_node);
397 }
398 return node->favicon();
399 }
400
SetTitle(const BookmarkNode * node,const base::string16 & title)401 void BookmarkModel::SetTitle(const BookmarkNode* node, const base::string16& title) {
402 if (!node) {
403 NOTREACHED();
404 return;
405 }
406 if (node->GetTitle() == title)
407 return;
408
409 if (is_permanent_node(node)) {
410 NOTREACHED();
411 return;
412 }
413
414 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
415 OnWillChangeBookmarkNode(this, node));
416
417 // The title index doesn't support changing the title, instead we remove then
418 // add it back.
419 index_->Remove(node);
420 AsMutable(node)->SetTitle(title);
421 index_->Add(node);
422
423 if (store_.get())
424 store_->ScheduleSave();
425
426 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
427 BookmarkNodeChanged(this, node));
428 }
429
SetURL(const BookmarkNode * node,const GURL & url)430 void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
431 if (!node) {
432 NOTREACHED();
433 return;
434 }
435
436 // We cannot change the URL of a folder.
437 if (node->is_folder()) {
438 NOTREACHED();
439 return;
440 }
441
442 if (node->url() == url)
443 return;
444
445 BookmarkNode* mutable_node = AsMutable(node);
446 mutable_node->InvalidateFavicon();
447 CancelPendingFaviconLoadRequests(mutable_node);
448
449 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
450 OnWillChangeBookmarkNode(this, node));
451
452 {
453 base::AutoLock url_lock(url_lock_);
454 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(
455 mutable_node);
456 DCHECK(i != nodes_ordered_by_url_set_.end());
457 // i points to the first node with the URL, advance until we find the
458 // node we're removing.
459 while (*i != node)
460 ++i;
461 nodes_ordered_by_url_set_.erase(i);
462
463 mutable_node->set_url(url);
464 nodes_ordered_by_url_set_.insert(mutable_node);
465 }
466
467 if (store_.get())
468 store_->ScheduleSave();
469
470 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
471 BookmarkNodeChanged(this, node));
472 }
473
SetNodeMetaInfo(const BookmarkNode * node,const std::string & key,const std::string & value)474 void BookmarkModel::SetNodeMetaInfo(const BookmarkNode* node,
475 const std::string& key,
476 const std::string& value) {
477 std::string old_value;
478 if (node->GetMetaInfo(key, &old_value) && old_value == value)
479 return;
480
481 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
482 OnWillChangeBookmarkMetaInfo(this, node));
483
484 if (AsMutable(node)->SetMetaInfo(key, value) && store_.get())
485 store_->ScheduleSave();
486
487 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
488 BookmarkMetaInfoChanged(this, node));
489 }
490
SetNodeMetaInfoMap(const BookmarkNode * node,const BookmarkNode::MetaInfoMap & meta_info_map)491 void BookmarkModel::SetNodeMetaInfoMap(
492 const BookmarkNode* node,
493 const BookmarkNode::MetaInfoMap& meta_info_map) {
494 const BookmarkNode::MetaInfoMap* old_meta_info_map = node->GetMetaInfoMap();
495 if ((!old_meta_info_map && meta_info_map.empty()) ||
496 (old_meta_info_map && meta_info_map == *old_meta_info_map))
497 return;
498
499 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
500 OnWillChangeBookmarkMetaInfo(this, node));
501
502 AsMutable(node)->SetMetaInfoMap(meta_info_map);
503 if (store_.get())
504 store_->ScheduleSave();
505
506 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
507 BookmarkMetaInfoChanged(this, node));
508 }
509
DeleteNodeMetaInfo(const BookmarkNode * node,const std::string & key)510 void BookmarkModel::DeleteNodeMetaInfo(const BookmarkNode* node,
511 const std::string& key) {
512 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
513 if (!meta_info_map || meta_info_map->find(key) == meta_info_map->end())
514 return;
515
516 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
517 OnWillChangeBookmarkMetaInfo(this, node));
518
519 if (AsMutable(node)->DeleteMetaInfo(key) && store_.get())
520 store_->ScheduleSave();
521
522 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
523 BookmarkMetaInfoChanged(this, node));
524 }
525
SetNodeSyncTransactionVersion(const BookmarkNode * node,int64 sync_transaction_version)526 void BookmarkModel::SetNodeSyncTransactionVersion(
527 const BookmarkNode* node,
528 int64 sync_transaction_version) {
529 if (sync_transaction_version == node->sync_transaction_version())
530 return;
531
532 AsMutable(node)->set_sync_transaction_version(sync_transaction_version);
533 if (store_.get())
534 store_->ScheduleSave();
535 }
536
SetDateAdded(const BookmarkNode * node,base::Time date_added)537 void BookmarkModel::SetDateAdded(const BookmarkNode* node,
538 base::Time date_added) {
539 if (!node) {
540 NOTREACHED();
541 return;
542 }
543
544 if (node->date_added() == date_added)
545 return;
546
547 if (is_permanent_node(node)) {
548 NOTREACHED();
549 return;
550 }
551
552 AsMutable(node)->set_date_added(date_added);
553
554 // Syncing might result in dates newer than the folder's last modified date.
555 if (date_added > node->parent()->date_folder_modified()) {
556 // Will trigger store_->ScheduleSave().
557 SetDateFolderModified(node->parent(), date_added);
558 } else if (store_.get()) {
559 store_->ScheduleSave();
560 }
561 }
562
GetNodesByURL(const GURL & url,std::vector<const BookmarkNode * > * nodes)563 void BookmarkModel::GetNodesByURL(const GURL& url,
564 std::vector<const BookmarkNode*>* nodes) {
565 base::AutoLock url_lock(url_lock_);
566 BookmarkNode tmp_node(url);
567 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
568 while (i != nodes_ordered_by_url_set_.end() && (*i)->url() == url) {
569 nodes->push_back(*i);
570 ++i;
571 }
572 }
573
GetMostRecentlyAddedNodeForURL(const GURL & url)574 const BookmarkNode* BookmarkModel::GetMostRecentlyAddedNodeForURL(
575 const GURL& url) {
576 std::vector<const BookmarkNode*> nodes;
577 GetNodesByURL(url, &nodes);
578 if (nodes.empty())
579 return NULL;
580
581 std::sort(nodes.begin(), nodes.end(), &bookmark_utils::MoreRecentlyAdded);
582 return nodes.front();
583 }
584
HasBookmarks()585 bool BookmarkModel::HasBookmarks() {
586 base::AutoLock url_lock(url_lock_);
587 return !nodes_ordered_by_url_set_.empty();
588 }
589
IsBookmarked(const GURL & url)590 bool BookmarkModel::IsBookmarked(const GURL& url) {
591 base::AutoLock url_lock(url_lock_);
592 return IsBookmarkedNoLock(url);
593 }
594
GetBookmarks(std::vector<BookmarkService::URLAndTitle> * bookmarks)595 void BookmarkModel::GetBookmarks(
596 std::vector<BookmarkService::URLAndTitle>* bookmarks) {
597 base::AutoLock url_lock(url_lock_);
598 const GURL* last_url = NULL;
599 for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
600 i != nodes_ordered_by_url_set_.end(); ++i) {
601 const GURL* url = &((*i)->url());
602 // Only add unique URLs.
603 if (!last_url || *url != *last_url) {
604 BookmarkService::URLAndTitle bookmark;
605 bookmark.url = *url;
606 bookmark.title = (*i)->GetTitle();
607 bookmarks->push_back(bookmark);
608 }
609 last_url = url;
610 }
611 }
612
BlockTillLoaded()613 void BookmarkModel::BlockTillLoaded() {
614 loaded_signal_.Wait();
615 }
616
GetNodeByID(int64 id) const617 const BookmarkNode* BookmarkModel::GetNodeByID(int64 id) const {
618 // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
619 return GetNodeByID(&root_, id);
620 }
621
AddFolder(const BookmarkNode * parent,int index,const base::string16 & title)622 const BookmarkNode* BookmarkModel::AddFolder(const BookmarkNode* parent,
623 int index,
624 const base::string16& title) {
625 if (!loaded_ || is_root_node(parent) || !IsValidIndex(parent, index, true)) {
626 // Can't add to the root.
627 NOTREACHED();
628 return NULL;
629 }
630
631 BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), GURL());
632 new_node->set_date_folder_modified(Time::Now());
633 // Folders shouldn't have line breaks in their titles.
634 new_node->SetTitle(title);
635 new_node->set_type(BookmarkNode::FOLDER);
636
637 return AddNode(AsMutable(parent), index, new_node);
638 }
639
AddURL(const BookmarkNode * parent,int index,const base::string16 & title,const GURL & url)640 const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
641 int index,
642 const base::string16& title,
643 const GURL& url) {
644 return AddURLWithCreationTime(parent, index,
645 CollapseWhitespace(title, false),
646 url, Time::Now());
647 }
648
AddURLWithCreationTime(const BookmarkNode * parent,int index,const base::string16 & title,const GURL & url,const Time & creation_time)649 const BookmarkNode* BookmarkModel::AddURLWithCreationTime(
650 const BookmarkNode* parent,
651 int index,
652 const base::string16& title,
653 const GURL& url,
654 const Time& creation_time) {
655 if (!loaded_ || !url.is_valid() || is_root_node(parent) ||
656 !IsValidIndex(parent, index, true)) {
657 NOTREACHED();
658 return NULL;
659 }
660
661 // Syncing may result in dates newer than the last modified date.
662 if (creation_time > parent->date_folder_modified())
663 SetDateFolderModified(parent, creation_time);
664
665 BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url);
666 new_node->SetTitle(title);
667 new_node->set_date_added(creation_time);
668 new_node->set_type(BookmarkNode::URL);
669
670 {
671 // Only hold the lock for the duration of the insert.
672 base::AutoLock url_lock(url_lock_);
673 nodes_ordered_by_url_set_.insert(new_node);
674 }
675
676 return AddNode(AsMutable(parent), index, new_node);
677 }
678
SortChildren(const BookmarkNode * parent)679 void BookmarkModel::SortChildren(const BookmarkNode* parent) {
680 if (!parent || !parent->is_folder() || is_root_node(parent) ||
681 parent->child_count() <= 1) {
682 return;
683 }
684
685 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
686 OnWillReorderBookmarkNode(this, parent));
687
688 UErrorCode error = U_ZERO_ERROR;
689 scoped_ptr<icu::Collator> collator(icu::Collator::createInstance(error));
690 if (U_FAILURE(error))
691 collator.reset(NULL);
692 BookmarkNode* mutable_parent = AsMutable(parent);
693 std::sort(mutable_parent->children().begin(),
694 mutable_parent->children().end(),
695 SortComparator(collator.get()));
696
697 if (store_.get())
698 store_->ScheduleSave();
699
700 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
701 BookmarkNodeChildrenReordered(this, parent));
702 }
703
ReorderChildren(const BookmarkNode * parent,const std::vector<const BookmarkNode * > & ordered_nodes)704 void BookmarkModel::ReorderChildren(
705 const BookmarkNode* parent,
706 const std::vector<const BookmarkNode*>& ordered_nodes) {
707 // Ensure that all children in |parent| are in |ordered_nodes|.
708 DCHECK_EQ(static_cast<size_t>(parent->child_count()), ordered_nodes.size());
709 for (size_t i = 0; i < ordered_nodes.size(); ++i)
710 DCHECK_EQ(parent, ordered_nodes[i]->parent());
711
712 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
713 OnWillReorderBookmarkNode(this, parent));
714
715 AsMutable(parent)->SetChildren(
716 *(reinterpret_cast<const std::vector<BookmarkNode*>*>(&ordered_nodes)));
717
718 if (store_.get())
719 store_->ScheduleSave();
720
721 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
722 BookmarkNodeChildrenReordered(this, parent));
723 }
724
SetDateFolderModified(const BookmarkNode * parent,const Time time)725 void BookmarkModel::SetDateFolderModified(const BookmarkNode* parent,
726 const Time time) {
727 DCHECK(parent);
728 AsMutable(parent)->set_date_folder_modified(time);
729
730 if (store_.get())
731 store_->ScheduleSave();
732 }
733
ResetDateFolderModified(const BookmarkNode * node)734 void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) {
735 SetDateFolderModified(node, Time());
736 }
737
GetBookmarksWithTitlesMatching(const base::string16 & text,size_t max_count,std::vector<BookmarkTitleMatch> * matches)738 void BookmarkModel::GetBookmarksWithTitlesMatching(
739 const base::string16& text,
740 size_t max_count,
741 std::vector<BookmarkTitleMatch>* matches) {
742 if (!loaded_)
743 return;
744
745 index_->GetBookmarksWithTitlesMatching(text, max_count, matches);
746 }
747
ClearStore()748 void BookmarkModel::ClearStore() {
749 registrar_.RemoveAll();
750 store_ = NULL;
751 }
752
SetPermanentNodeVisible(BookmarkNode::Type type,bool value)753 void BookmarkModel::SetPermanentNodeVisible(BookmarkNode::Type type,
754 bool value) {
755 DCHECK(loaded_);
756 switch (type) {
757 case BookmarkNode::BOOKMARK_BAR:
758 bookmark_bar_node_->set_visible(value);
759 break;
760 case BookmarkNode::OTHER_NODE:
761 other_node_->set_visible(value);
762 break;
763 case BookmarkNode::MOBILE:
764 mobile_node_->set_visible(value);
765 break;
766 default:
767 NOTREACHED();
768 }
769 }
770
IsBookmarkedNoLock(const GURL & url)771 bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) {
772 BookmarkNode tmp_node(url);
773 return (nodes_ordered_by_url_set_.find(&tmp_node) !=
774 nodes_ordered_by_url_set_.end());
775 }
776
RemoveNode(BookmarkNode * node,std::set<GURL> * removed_urls)777 void BookmarkModel::RemoveNode(BookmarkNode* node,
778 std::set<GURL>* removed_urls) {
779 if (!loaded_ || !node || is_permanent_node(node)) {
780 NOTREACHED();
781 return;
782 }
783
784 url_lock_.AssertAcquired();
785 if (node->is_url()) {
786 // NOTE: this is called in such a way that url_lock_ is already held. As
787 // such, this doesn't explicitly grab the lock.
788 NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
789 DCHECK(i != nodes_ordered_by_url_set_.end());
790 // i points to the first node with the URL, advance until we find the
791 // node we're removing.
792 while (*i != node)
793 ++i;
794 nodes_ordered_by_url_set_.erase(i);
795 removed_urls->insert(node->url());
796
797 index_->Remove(node);
798 }
799
800 CancelPendingFaviconLoadRequests(node);
801
802 // Recurse through children.
803 for (int i = node->child_count() - 1; i >= 0; --i)
804 RemoveNode(node->GetChild(i), removed_urls);
805 }
806
DoneLoading(BookmarkLoadDetails * details_delete_me)807 void BookmarkModel::DoneLoading(BookmarkLoadDetails* details_delete_me) {
808 DCHECK(details_delete_me);
809 scoped_ptr<BookmarkLoadDetails> details(details_delete_me);
810 if (loaded_) {
811 // We should only ever be loaded once.
812 NOTREACHED();
813 return;
814 }
815
816 next_node_id_ = details->max_id();
817 if (details->computed_checksum() != details->stored_checksum() ||
818 details->ids_reassigned()) {
819 // If bookmarks file changed externally, the IDs may have changed
820 // externally. In that case, the decoder may have reassigned IDs to make
821 // them unique. So when the file has changed externally, we should save the
822 // bookmarks file to persist new IDs.
823 if (store_.get())
824 store_->ScheduleSave();
825 }
826 bookmark_bar_node_ = details->release_bb_node();
827 other_node_ = details->release_other_folder_node();
828 mobile_node_ = details->release_mobile_folder_node();
829 index_.reset(details->release_index());
830
831 // WARNING: order is important here, various places assume the order is
832 // constant.
833 root_.Add(bookmark_bar_node_, 0);
834 root_.Add(other_node_, 1);
835 root_.Add(mobile_node_, 2);
836
837 root_.SetMetaInfoMap(details->model_meta_info_map());
838 root_.set_sync_transaction_version(details->model_sync_transaction_version());
839
840 {
841 base::AutoLock url_lock(url_lock_);
842 // Update nodes_ordered_by_url_set_ from the nodes.
843 PopulateNodesByURL(&root_);
844 }
845
846 loaded_ = true;
847
848 loaded_signal_.Signal();
849
850 // Notify our direct observers.
851 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
852 Loaded(this, details->ids_reassigned()));
853 }
854
RemoveAndDeleteNode(BookmarkNode * delete_me)855 void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) {
856 scoped_ptr<BookmarkNode> node(delete_me);
857
858 const BookmarkNode* parent = node->parent();
859 DCHECK(parent);
860 int index = parent->GetIndexOf(node.get());
861
862 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
863 OnWillRemoveBookmarks(this, parent, index, node.get()));
864
865 std::set<GURL> removed_urls;
866 {
867 base::AutoLock url_lock(url_lock_);
868 RemoveNodeAndGetRemovedUrls(node.get(), &removed_urls);
869 }
870
871 if (store_.get())
872 store_->ScheduleSave();
873
874 NotifyHistoryAboutRemovedBookmarks(removed_urls);
875
876 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
877 BookmarkNodeRemoved(this, parent, index, node.get()));
878 }
879
RemoveNodeAndGetRemovedUrls(BookmarkNode * node,std::set<GURL> * removed_urls)880 void BookmarkModel::RemoveNodeAndGetRemovedUrls(BookmarkNode* node,
881 std::set<GURL>* removed_urls) {
882 // NOTE: this method should be always called with |url_lock_| held.
883 // This method does not explicitly acquires a lock.
884 url_lock_.AssertAcquired();
885 DCHECK(removed_urls);
886 BookmarkNode* parent = AsMutable(node->parent());
887 DCHECK(parent);
888 parent->Remove(node);
889 RemoveNode(node, removed_urls);
890 // RemoveNode adds an entry to removed_urls for each node of type URL. As we
891 // allow duplicates we need to remove any entries that are still bookmarked.
892 for (std::set<GURL>::iterator i = removed_urls->begin();
893 i != removed_urls->end();) {
894 if (IsBookmarkedNoLock(*i)) {
895 // When we erase the iterator pointing at the erasee is
896 // invalidated, so using i++ here within the "erase" call is
897 // important as it advances the iterator before passing the
898 // old value through to erase.
899 removed_urls->erase(i++);
900 } else {
901 ++i;
902 }
903 }
904 }
905
NotifyHistoryAboutRemovedBookmarks(const std::set<GURL> & removed_bookmark_urls) const906 void BookmarkModel::NotifyHistoryAboutRemovedBookmarks(
907 const std::set<GURL>& removed_bookmark_urls) const {
908 if (removed_bookmark_urls.empty()) {
909 // No point in sending out notification if the starred state didn't change.
910 return;
911 }
912
913 if (profile_) {
914 HistoryService* history =
915 HistoryServiceFactory::GetForProfile(profile_,
916 Profile::EXPLICIT_ACCESS);
917 if (history)
918 history->URLsNoLongerBookmarked(removed_bookmark_urls);
919 }
920 }
921
AddNode(BookmarkNode * parent,int index,BookmarkNode * node)922 BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
923 int index,
924 BookmarkNode* node) {
925 parent->Add(node, index);
926
927 if (store_.get())
928 store_->ScheduleSave();
929
930 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
931 BookmarkNodeAdded(this, parent, index));
932
933 index_->Add(node);
934
935 return node;
936 }
937
GetNodeByID(const BookmarkNode * node,int64 id) const938 const BookmarkNode* BookmarkModel::GetNodeByID(const BookmarkNode* node,
939 int64 id) const {
940 if (node->id() == id)
941 return node;
942
943 for (int i = 0, child_count = node->child_count(); i < child_count; ++i) {
944 const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
945 if (result)
946 return result;
947 }
948 return NULL;
949 }
950
IsValidIndex(const BookmarkNode * parent,int index,bool allow_end)951 bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
952 int index,
953 bool allow_end) {
954 return (parent && parent->is_folder() &&
955 (index >= 0 && (index < parent->child_count() ||
956 (allow_end && index == parent->child_count()))));
957 }
958
CreatePermanentNode(BookmarkNode::Type type)959 BookmarkPermanentNode* BookmarkModel::CreatePermanentNode(
960 BookmarkNode::Type type) {
961 DCHECK(type == BookmarkNode::BOOKMARK_BAR ||
962 type == BookmarkNode::OTHER_NODE ||
963 type == BookmarkNode::MOBILE);
964 BookmarkPermanentNode* node =
965 new BookmarkPermanentNode(generate_next_node_id());
966 if (type == BookmarkNode::MOBILE)
967 node->set_visible(false); // Mobile node is initially hidden.
968
969 int title_id;
970 switch (type) {
971 case BookmarkNode::BOOKMARK_BAR:
972 title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
973 break;
974 case BookmarkNode::OTHER_NODE:
975 title_id = IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME;
976 break;
977 case BookmarkNode::MOBILE:
978 title_id = IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME;
979 break;
980 default:
981 NOTREACHED();
982 title_id = IDS_BOOKMARK_BAR_FOLDER_NAME;
983 break;
984 }
985 node->SetTitle(l10n_util::GetStringUTF16(title_id));
986 node->set_type(type);
987 return node;
988 }
989
OnFaviconDataAvailable(BookmarkNode * node,const chrome::FaviconImageResult & image_result)990 void BookmarkModel::OnFaviconDataAvailable(
991 BookmarkNode* node,
992 const chrome::FaviconImageResult& image_result) {
993 DCHECK(node);
994 node->set_favicon_load_task_id(CancelableTaskTracker::kBadTaskId);
995 node->set_favicon_state(BookmarkNode::LOADED_FAVICON);
996 if (!image_result.image.IsEmpty()) {
997 node->set_favicon(image_result.image);
998 node->set_icon_url(image_result.icon_url);
999 FaviconLoaded(node);
1000 }
1001 }
1002
LoadFavicon(BookmarkNode * node)1003 void BookmarkModel::LoadFavicon(BookmarkNode* node) {
1004 if (node->is_folder())
1005 return;
1006
1007 DCHECK(node->url().is_valid());
1008 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
1009 profile_, Profile::EXPLICIT_ACCESS);
1010 if (!favicon_service)
1011 return;
1012 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
1013 FaviconService::FaviconForURLParams(node->url(),
1014 chrome::FAVICON,
1015 gfx::kFaviconSize),
1016 base::Bind(&BookmarkModel::OnFaviconDataAvailable,
1017 base::Unretained(this), node),
1018 &cancelable_task_tracker_);
1019 node->set_favicon_load_task_id(handle);
1020 }
1021
FaviconLoaded(const BookmarkNode * node)1022 void BookmarkModel::FaviconLoaded(const BookmarkNode* node) {
1023 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
1024 BookmarkNodeFaviconChanged(this, node));
1025 }
1026
CancelPendingFaviconLoadRequests(BookmarkNode * node)1027 void BookmarkModel::CancelPendingFaviconLoadRequests(BookmarkNode* node) {
1028 if (node->favicon_load_task_id() != CancelableTaskTracker::kBadTaskId) {
1029 cancelable_task_tracker_.TryCancel(node->favicon_load_task_id());
1030 node->set_favicon_load_task_id(CancelableTaskTracker::kBadTaskId);
1031 }
1032 }
1033
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)1034 void BookmarkModel::Observe(int type,
1035 const content::NotificationSource& source,
1036 const content::NotificationDetails& details) {
1037 switch (type) {
1038 case chrome::NOTIFICATION_FAVICON_CHANGED: {
1039 // Prevent the observers from getting confused for multiple favicon loads.
1040 content::Details<FaviconChangedDetails> favicon_details(details);
1041 for (std::set<GURL>::const_iterator i = favicon_details->urls.begin();
1042 i != favicon_details->urls.end(); ++i) {
1043 std::vector<const BookmarkNode*> nodes;
1044 GetNodesByURL(*i, &nodes);
1045 for (size_t i = 0; i < nodes.size(); ++i) {
1046 // Got an updated favicon, for a URL, do a new request.
1047 BookmarkNode* node = AsMutable(nodes[i]);
1048 node->InvalidateFavicon();
1049 CancelPendingFaviconLoadRequests(node);
1050 FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
1051 BookmarkNodeFaviconChanged(this, node));
1052 }
1053 }
1054 break;
1055 }
1056
1057 default:
1058 NOTREACHED();
1059 break;
1060 }
1061 }
1062
PopulateNodesByURL(BookmarkNode * node)1063 void BookmarkModel::PopulateNodesByURL(BookmarkNode* node) {
1064 // NOTE: this is called with url_lock_ already held. As such, this doesn't
1065 // explicitly grab the lock.
1066 if (node->is_url())
1067 nodes_ordered_by_url_set_.insert(node);
1068 for (int i = 0; i < node->child_count(); ++i)
1069 PopulateNodesByURL(node->GetChild(i));
1070 }
1071
generate_next_node_id()1072 int64 BookmarkModel::generate_next_node_id() {
1073 return next_node_id_++;
1074 }
1075
CreateLoadDetails()1076 BookmarkLoadDetails* BookmarkModel::CreateLoadDetails() {
1077 BookmarkPermanentNode* bb_node =
1078 CreatePermanentNode(BookmarkNode::BOOKMARK_BAR);
1079 BookmarkPermanentNode* other_node =
1080 CreatePermanentNode(BookmarkNode::OTHER_NODE);
1081 BookmarkPermanentNode* mobile_node =
1082 CreatePermanentNode(BookmarkNode::MOBILE);
1083 return new BookmarkLoadDetails(bb_node, other_node, mobile_node,
1084 new BookmarkIndex(profile_),
1085 next_node_id_);
1086 }
1087