bookmark_model.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2010 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 "app/l10n_util.h"
8#include "app/l10n_util_collator.h"
9#include "base/callback.h"
10#include "base/scoped_vector.h"
11#include "build/build_config.h"
12#include "chrome/browser/bookmarks/bookmark_index.h"
13#include "chrome/browser/bookmarks/bookmark_utils.h"
14#include "chrome/browser/bookmarks/bookmark_storage.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/history/history_notifications.h"
17#include "chrome/browser/prefs/pref_service.h"
18#include "chrome/browser/profile.h"
19#include "chrome/common/notification_service.h"
20#include "gfx/codec/png_codec.h"
21#include "grit/generated_resources.h"
22
23using base::Time;
24
25namespace {
26
27// Helper to get a mutable bookmark node.
28static BookmarkNode* AsMutable(const BookmarkNode* node) {
29  return const_cast<BookmarkNode*>(node);
30}
31
32}  // anonymous namespace
33
34// BookmarkNode ---------------------------------------------------------------
35
36BookmarkNode::BookmarkNode(const GURL& url)
37    : url_(url) {
38  Initialize(0);
39}
40
41BookmarkNode::BookmarkNode(int64 id, const GURL& url)
42    : url_(url) {
43  Initialize(id);
44}
45
46BookmarkNode::~BookmarkNode() {
47}
48
49void BookmarkNode::Initialize(int64 id) {
50  id_ = id;
51  loaded_favicon_ = false;
52  favicon_load_handle_ = 0;
53  type_ = !url_.is_empty() ? URL : BOOKMARK_BAR;
54  date_added_ = Time::Now();
55}
56
57void BookmarkNode::InvalidateFavicon() {
58  loaded_favicon_ = false;
59  favicon_ = SkBitmap();
60}
61
62void BookmarkNode::Reset(const history::StarredEntry& entry) {
63  DCHECK(entry.type != history::StarredEntry::URL || entry.url == url_);
64
65  favicon_ = SkBitmap();
66  switch (entry.type) {
67    case history::StarredEntry::URL:
68      type_ = BookmarkNode::URL;
69      break;
70    case history::StarredEntry::USER_GROUP:
71      type_ = BookmarkNode::FOLDER;
72      break;
73    case history::StarredEntry::BOOKMARK_BAR:
74      type_ = BookmarkNode::BOOKMARK_BAR;
75      break;
76    case history::StarredEntry::OTHER:
77      type_ = BookmarkNode::OTHER_NODE;
78      break;
79    default:
80      NOTREACHED();
81  }
82  date_added_ = entry.date_added;
83  date_group_modified_ = entry.date_group_modified;
84  SetTitle(entry.title);
85}
86
87// BookmarkModel --------------------------------------------------------------
88
89namespace {
90
91// Comparator used when sorting bookmarks. Folders are sorted first, then
92// bookmarks.
93class SortComparator : public std::binary_function<const BookmarkNode*,
94                                                   const BookmarkNode*,
95                                                   bool> {
96 public:
97  explicit SortComparator(icu::Collator* collator) : collator_(collator) { }
98
99  // Returns true if lhs preceeds rhs.
100  bool operator() (const BookmarkNode* n1, const BookmarkNode* n2) {
101    if (n1->type() == n2->type()) {
102      // Types are the same, compare the names.
103      if (!collator_)
104        return n1->GetTitle() < n2->GetTitle();
105      return l10n_util::CompareStringWithCollator(collator_,
106          UTF16ToWideHack(n1->GetTitle()), UTF16ToWideHack(n2->GetTitle())) ==
107          UCOL_LESS;
108    }
109    // Types differ, sort such that folders come first.
110    return n1->is_folder();
111  }
112
113 private:
114  icu::Collator* collator_;
115};
116
117}  // namespace
118
119BookmarkModel::BookmarkModel(Profile* profile)
120    : profile_(profile),
121      loaded_(false),
122      file_changed_(false),
123      root_(GURL()),
124      bookmark_bar_node_(NULL),
125      other_node_(NULL),
126      next_node_id_(1),
127      observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY),
128      loaded_signal_(TRUE, FALSE) {
129  if (!profile_) {
130    // Profile is null during testing.
131    DoneLoading(CreateLoadDetails());
132  }
133}
134
135BookmarkModel::~BookmarkModel() {
136  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
137                    BookmarkModelBeingDeleted(this));
138
139  if (store_) {
140    // The store maintains a reference back to us. We need to tell it we're gone
141    // so that it doesn't try and invoke a method back on us again.
142    store_->BookmarkModelDeleted();
143  }
144}
145
146void BookmarkModel::Load() {
147  if (store_.get()) {
148    // If the store is non-null, it means Load was already invoked. Load should
149    // only be invoked once.
150    NOTREACHED();
151    return;
152  }
153
154  // Listen for changes to favicons so that we can update the favicon of the
155  // node appropriately.
156  registrar_.Add(this, NotificationType::FAVICON_CHANGED,
157                 Source<Profile>(profile_));
158
159  // Load the bookmarks. BookmarkStorage notifies us when done.
160  store_ = new BookmarkStorage(profile_, this);
161  store_->LoadBookmarks(CreateLoadDetails());
162}
163
164const BookmarkNode* BookmarkModel::GetParentForNewNodes() {
165  std::vector<const BookmarkNode*> nodes =
166      bookmark_utils::GetMostRecentlyModifiedGroups(this, 1);
167  return nodes.empty() ? bookmark_bar_node_ : nodes[0];
168}
169
170void BookmarkModel::Remove(const BookmarkNode* parent, int index) {
171  if (!loaded_ || !IsValidIndex(parent, index, false) || is_root(parent)) {
172    NOTREACHED();
173    return;
174  }
175  RemoveAndDeleteNode(AsMutable(parent->GetChild(index)));
176}
177
178void BookmarkModel::Move(const BookmarkNode* node,
179                         const BookmarkNode* new_parent,
180                         int index) {
181  if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
182      is_root(new_parent) || is_permanent_node(node)) {
183    NOTREACHED();
184    return;
185  }
186
187  if (new_parent->HasAncestor(node)) {
188    // Can't make an ancestor of the node be a child of the node.
189    NOTREACHED();
190    return;
191  }
192
193  SetDateGroupModified(new_parent, Time::Now());
194
195  const BookmarkNode* old_parent = node->GetParent();
196  int old_index = old_parent->IndexOfChild(node);
197
198  if (old_parent == new_parent &&
199      (index == old_index || index == old_index + 1)) {
200    // Node is already in this position, nothing to do.
201    return;
202  }
203
204  if (old_parent == new_parent && index > old_index)
205    index--;
206  BookmarkNode* mutable_new_parent = AsMutable(new_parent);
207  mutable_new_parent->Add(index, AsMutable(node));
208
209  if (store_.get())
210    store_->ScheduleSave();
211
212  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
213                    BookmarkNodeMoved(this, old_parent, old_index,
214                                      new_parent, index));
215}
216
217void BookmarkModel::Copy(const BookmarkNode* node,
218                         const BookmarkNode* new_parent,
219                         int index) {
220  if (!loaded_ || !node || !IsValidIndex(new_parent, index, true) ||
221      is_root(new_parent) || is_permanent_node(node)) {
222    NOTREACHED();
223    return;
224  }
225
226  if (new_parent->HasAncestor(node)) {
227    // Can't make an ancestor of the node be a child of the node.
228    NOTREACHED();
229    return;
230  }
231
232  SetDateGroupModified(new_parent, Time::Now());
233  BookmarkNodeData drag_data_(node);
234  std::vector<BookmarkNodeData::Element> elements(drag_data_.elements);
235  // CloneBookmarkNode will use BookmarkModel methods to do the job, so we
236  // don't need to send notifications here.
237  bookmark_utils::CloneBookmarkNode(this, elements, new_parent, index);
238
239  if (store_.get())
240    store_->ScheduleSave();
241}
242
243const SkBitmap& BookmarkModel::GetFavIcon(const BookmarkNode* node) {
244  DCHECK(node);
245  if (!node->is_favicon_loaded()) {
246    BookmarkNode* mutable_node = AsMutable(node);
247    mutable_node->set_favicon_loaded(true);
248    LoadFavIcon(mutable_node);
249  }
250  return node->favicon();
251}
252
253void BookmarkModel::SetTitle(const BookmarkNode* node, const string16& title) {
254  if (!node) {
255    NOTREACHED();
256    return;
257  }
258  if (node->GetTitle() == title)
259    return;
260
261  if (node == bookmark_bar_node_ || node == other_node_) {
262    NOTREACHED();
263    return;
264  }
265
266  // The title index doesn't support changing the title, instead we remove then
267  // add it back.
268  index_->Remove(node);
269  AsMutable(node)->SetTitle(title);
270  index_->Add(node);
271
272  if (store_.get())
273    store_->ScheduleSave();
274
275  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
276                    BookmarkNodeChanged(this, node));
277}
278
279void BookmarkModel::SetURL(const BookmarkNode* node, const GURL& url) {
280  if (!node) {
281    NOTREACHED();
282    return;
283  }
284
285  // We cannot change the URL of a folder.
286  if (node->is_folder()) {
287    NOTREACHED();
288    return;
289  }
290
291  if (url == node->GetURL())
292    return;
293
294  AsMutable(node)->InvalidateFavicon();
295  CancelPendingFavIconLoadRequests(AsMutable(node));
296
297  {
298    AutoLock url_lock(url_lock_);
299    NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(
300        AsMutable(node));
301    DCHECK(i != nodes_ordered_by_url_set_.end());
302    // i points to the first node with the URL, advance until we find the
303    // node we're removing.
304    while (*i != node)
305      ++i;
306    nodes_ordered_by_url_set_.erase(i);
307
308    AsMutable(node)->SetURL(url);
309    nodes_ordered_by_url_set_.insert(AsMutable(node));
310  }
311
312  if (store_.get())
313    store_->ScheduleSave();
314
315  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
316                    BookmarkNodeChanged(this, node));
317}
318
319bool BookmarkModel::IsLoaded() {
320  return loaded_;
321}
322
323void BookmarkModel::GetNodesByURL(const GURL& url,
324                                  std::vector<const BookmarkNode*>* nodes) {
325  AutoLock url_lock(url_lock_);
326  BookmarkNode tmp_node(url);
327  NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(&tmp_node);
328  while (i != nodes_ordered_by_url_set_.end() && (*i)->GetURL() == url) {
329    nodes->push_back(*i);
330    ++i;
331  }
332}
333
334const BookmarkNode* BookmarkModel::GetMostRecentlyAddedNodeForURL(
335    const GURL& url) {
336  std::vector<const BookmarkNode*> nodes;
337  GetNodesByURL(url, &nodes);
338  if (nodes.empty())
339    return NULL;
340
341  std::sort(nodes.begin(), nodes.end(), &bookmark_utils::MoreRecentlyAdded);
342  return nodes.front();
343}
344
345void BookmarkModel::GetBookmarks(std::vector<GURL>* urls) {
346  AutoLock url_lock(url_lock_);
347  const GURL* last_url = NULL;
348  for (NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.begin();
349       i != nodes_ordered_by_url_set_.end(); ++i) {
350    const GURL* url = &((*i)->GetURL());
351    // Only add unique URLs.
352    if (!last_url || *url != *last_url)
353      urls->push_back(*url);
354    last_url = url;
355  }
356}
357
358bool BookmarkModel::HasBookmarks() {
359  AutoLock url_lock(url_lock_);
360  return !nodes_ordered_by_url_set_.empty();
361}
362
363bool BookmarkModel::IsBookmarked(const GURL& url) {
364  AutoLock url_lock(url_lock_);
365  return IsBookmarkedNoLock(url);
366}
367
368const BookmarkNode* BookmarkModel::GetNodeByID(int64 id) {
369  // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
370  return GetNodeByID(&root_, id);
371}
372
373const BookmarkNode* BookmarkModel::AddGroup(const BookmarkNode* parent,
374                                            int index,
375                                            const string16& title) {
376  if (!loaded_ || parent == &root_ || !IsValidIndex(parent, index, true)) {
377    // Can't add to the root.
378    NOTREACHED();
379    return NULL;
380  }
381
382  BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(),
383                                            GURL());
384  new_node->set_date_group_modified(Time::Now());
385  new_node->SetTitle(title);
386  new_node->set_type(BookmarkNode::FOLDER);
387
388  return AddNode(AsMutable(parent), index, new_node, false);
389}
390
391const BookmarkNode* BookmarkModel::AddURL(const BookmarkNode* parent,
392                                          int index,
393                                          const string16& title,
394                                          const GURL& url) {
395  return AddURLWithCreationTime(parent, index, title, url, Time::Now());
396}
397
398const BookmarkNode* BookmarkModel::AddURLWithCreationTime(
399    const BookmarkNode* parent,
400    int index,
401    const string16& title,
402    const GURL& url,
403    const Time& creation_time) {
404  if (!loaded_ || !url.is_valid() || is_root(parent) ||
405      !IsValidIndex(parent, index, true)) {
406    NOTREACHED();
407    return NULL;
408  }
409
410  bool was_bookmarked = IsBookmarked(url);
411
412  SetDateGroupModified(parent, creation_time);
413
414  BookmarkNode* new_node = new BookmarkNode(generate_next_node_id(), url);
415  new_node->SetTitle(title);
416  new_node->set_date_added(creation_time);
417  new_node->set_type(BookmarkNode::URL);
418
419  {
420    // Only hold the lock for the duration of the insert.
421    AutoLock url_lock(url_lock_);
422    nodes_ordered_by_url_set_.insert(new_node);
423  }
424
425  return AddNode(AsMutable(parent), index, new_node, was_bookmarked);
426}
427
428void BookmarkModel::SortChildren(const BookmarkNode* parent) {
429  if (!parent || !parent->is_folder() || is_root(parent) ||
430      parent->GetChildCount() <= 1) {
431    return;
432  }
433
434  UErrorCode error = U_ZERO_ERROR;
435  scoped_ptr<icu::Collator> collator(
436      icu::Collator::createInstance(
437          icu::Locale(g_browser_process->GetApplicationLocale().c_str()),
438          error));
439  if (U_FAILURE(error))
440    collator.reset(NULL);
441  BookmarkNode* mutable_parent = AsMutable(parent);
442  std::sort(mutable_parent->children().begin(),
443            mutable_parent->children().end(),
444            SortComparator(collator.get()));
445
446  if (store_.get())
447    store_->ScheduleSave();
448
449  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
450                    BookmarkNodeChildrenReordered(this, parent));
451}
452
453void BookmarkModel::SetURLStarred(const GURL& url,
454                                  const string16& title,
455                                  bool is_starred) {
456  std::vector<const BookmarkNode*> bookmarks;
457  GetNodesByURL(url, &bookmarks);
458  bool bookmarks_exist = !bookmarks.empty();
459  if (is_starred == bookmarks_exist)
460    return;  // Nothing to do, state already matches.
461
462  if (is_starred) {
463    // Create a bookmark.
464    const BookmarkNode* parent = GetParentForNewNodes();
465    AddURL(parent, parent->GetChildCount(), title, url);
466  } else {
467    // Remove all the bookmarks.
468    for (size_t i = 0; i < bookmarks.size(); ++i) {
469      const BookmarkNode* node = bookmarks[i];
470      Remove(node->GetParent(), node->GetParent()->IndexOfChild(node));
471    }
472  }
473}
474
475void BookmarkModel::SetDateGroupModified(const BookmarkNode* parent,
476                                         const Time time) {
477  DCHECK(parent);
478  AsMutable(parent)->set_date_group_modified(time);
479
480  if (store_.get())
481    store_->ScheduleSave();
482}
483
484void BookmarkModel::ResetDateGroupModified(const BookmarkNode* node) {
485  SetDateGroupModified(node, Time());
486}
487
488void BookmarkModel::GetBookmarksWithTitlesMatching(
489    const string16& text,
490    size_t max_count,
491    std::vector<bookmark_utils::TitleMatch>* matches) {
492  if (!loaded_)
493    return;
494
495  index_->GetBookmarksWithTitlesMatching(text, max_count, matches);
496}
497
498void BookmarkModel::ClearStore() {
499  registrar_.RemoveAll();
500  store_ = NULL;
501}
502
503bool BookmarkModel::IsBookmarkedNoLock(const GURL& url) {
504  BookmarkNode tmp_node(url);
505  return (nodes_ordered_by_url_set_.find(&tmp_node) !=
506          nodes_ordered_by_url_set_.end());
507}
508
509void BookmarkModel::FavIconLoaded(const BookmarkNode* node) {
510  // Send out notification to the observer.
511  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
512                    BookmarkNodeFavIconLoaded(this, node));
513}
514
515void BookmarkModel::RemoveNode(BookmarkNode* node,
516                               std::set<GURL>* removed_urls) {
517  if (!loaded_ || !node || is_permanent_node(node)) {
518    NOTREACHED();
519    return;
520  }
521
522  if (node->type() == BookmarkNode::URL) {
523    // NOTE: this is called in such a way that url_lock_ is already held. As
524    // such, this doesn't explicitly grab the lock.
525    NodesOrderedByURLSet::iterator i = nodes_ordered_by_url_set_.find(node);
526    DCHECK(i != nodes_ordered_by_url_set_.end());
527    // i points to the first node with the URL, advance until we find the
528    // node we're removing.
529    while (*i != node)
530      ++i;
531    nodes_ordered_by_url_set_.erase(i);
532    removed_urls->insert(node->GetURL());
533
534    index_->Remove(node);
535  }
536
537  CancelPendingFavIconLoadRequests(node);
538
539  // Recurse through children.
540  for (int i = node->GetChildCount() - 1; i >= 0; --i)
541    RemoveNode(node->GetChild(i), removed_urls);
542}
543
544void BookmarkModel::DoneLoading(
545    BookmarkLoadDetails* details_delete_me) {
546  DCHECK(details_delete_me);
547  scoped_ptr<BookmarkLoadDetails> details(details_delete_me);
548  if (loaded_) {
549    // We should only ever be loaded once.
550    NOTREACHED();
551    return;
552  }
553
554  next_node_id_ = details->max_id();
555  if (details->computed_checksum() != details->stored_checksum())
556    SetFileChanged();
557  if (details->computed_checksum() != details->stored_checksum() ||
558      details->ids_reassigned()) {
559    // If bookmarks file changed externally, the IDs may have changed
560    // externally. In that case, the decoder may have reassigned IDs to make
561    // them unique. So when the file has changed externally, we should save the
562    // bookmarks file to persist new IDs.
563    if (store_.get())
564      store_->ScheduleSave();
565  }
566  bookmark_bar_node_ = details->release_bb_node();
567  other_node_ = details->release_other_folder_node();
568  index_.reset(details->release_index());
569
570  // WARNING: order is important here, various places assume bookmark bar then
571  // other node.
572  root_.Add(0, bookmark_bar_node_);
573  root_.Add(1, other_node_);
574
575  {
576    AutoLock url_lock(url_lock_);
577    // Update nodes_ordered_by_url_set_ from the nodes.
578    PopulateNodesByURL(&root_);
579  }
580
581  loaded_ = true;
582
583  loaded_signal_.Signal();
584
585  // Notify our direct observers.
586  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_, Loaded(this));
587
588  // And generic notification.
589  NotificationService::current()->Notify(
590      NotificationType::BOOKMARK_MODEL_LOADED,
591      Source<Profile>(profile_),
592      NotificationService::NoDetails());
593}
594
595void BookmarkModel::RemoveAndDeleteNode(BookmarkNode* delete_me) {
596  scoped_ptr<BookmarkNode> node(delete_me);
597
598  BookmarkNode* parent = AsMutable(node->GetParent());
599  DCHECK(parent);
600  int index = parent->IndexOfChild(node.get());
601  parent->Remove(index);
602  history::URLsStarredDetails details(false);
603  {
604    AutoLock url_lock(url_lock_);
605    RemoveNode(node.get(), &details.changed_urls);
606
607    // RemoveNode adds an entry to changed_urls for each node of type URL. As we
608    // allow duplicates we need to remove any entries that are still bookmarked.
609    for (std::set<GURL>::iterator i = details.changed_urls.begin();
610         i != details.changed_urls.end(); ) {
611      if (IsBookmarkedNoLock(*i)) {
612        // When we erase the iterator pointing at the erasee is
613        // invalidated, so using i++ here within the "erase" call is
614        // important as it advances the iterator before passing the
615        // old value through to erase.
616        details.changed_urls.erase(i++);
617      } else {
618        ++i;
619      }
620    }
621  }
622
623  if (store_.get())
624    store_->ScheduleSave();
625
626  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
627                    BookmarkNodeRemoved(this, parent, index, node.get()));
628
629  if (details.changed_urls.empty()) {
630    // No point in sending out notification if the starred state didn't change.
631    return;
632  }
633
634  if (profile_) {
635    HistoryService* history =
636        profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
637    if (history)
638      history->URLsNoLongerBookmarked(details.changed_urls);
639  }
640
641  NotificationService::current()->Notify(
642      NotificationType::URLS_STARRED,
643      Source<Profile>(profile_),
644      Details<history::URLsStarredDetails>(&details));
645}
646
647void BookmarkModel::BeginImportMode() {
648  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
649                    BookmarkImportBeginning(this));
650}
651
652void BookmarkModel::EndImportMode() {
653  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
654                    BookmarkImportEnding(this));
655}
656
657BookmarkNode* BookmarkModel::AddNode(BookmarkNode* parent,
658                                     int index,
659                                     BookmarkNode* node,
660                                     bool was_bookmarked) {
661  parent->Add(index, node);
662
663  if (store_.get())
664    store_->ScheduleSave();
665
666  FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
667                    BookmarkNodeAdded(this, parent, index));
668
669  index_->Add(node);
670
671  if (node->type() == BookmarkNode::URL && !was_bookmarked) {
672    history::URLsStarredDetails details(true);
673    details.changed_urls.insert(node->GetURL());
674    NotificationService::current()->Notify(
675        NotificationType::URLS_STARRED,
676        Source<Profile>(profile_),
677        Details<history::URLsStarredDetails>(&details));
678  }
679  return node;
680}
681
682void BookmarkModel::BlockTillLoaded() {
683  loaded_signal_.Wait();
684}
685
686const BookmarkNode* BookmarkModel::GetNodeByID(const BookmarkNode* node,
687                                               int64 id) {
688  if (node->id() == id)
689    return node;
690
691  for (int i = 0, child_count = node->GetChildCount(); i < child_count; ++i) {
692    const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
693    if (result)
694      return result;
695  }
696  return NULL;
697}
698
699bool BookmarkModel::IsValidIndex(const BookmarkNode* parent,
700                                 int index,
701                                 bool allow_end) {
702  return (parent && parent->is_folder() &&
703          (index >= 0 && (index < parent->GetChildCount() ||
704                          (allow_end && index == parent->GetChildCount()))));
705}
706
707BookmarkNode* BookmarkModel::CreateBookmarkNode() {
708  history::StarredEntry entry;
709  entry.type = history::StarredEntry::BOOKMARK_BAR;
710  return CreateRootNodeFromStarredEntry(entry);
711}
712
713BookmarkNode* BookmarkModel::CreateOtherBookmarksNode() {
714  history::StarredEntry entry;
715  entry.type = history::StarredEntry::OTHER;
716  return CreateRootNodeFromStarredEntry(entry);
717}
718
719BookmarkNode* BookmarkModel::CreateRootNodeFromStarredEntry(
720    const history::StarredEntry& entry) {
721  DCHECK(entry.type == history::StarredEntry::BOOKMARK_BAR ||
722         entry.type == history::StarredEntry::OTHER);
723  BookmarkNode* node = new BookmarkNode(generate_next_node_id(), GURL());
724  node->Reset(entry);
725  if (entry.type == history::StarredEntry::BOOKMARK_BAR) {
726    node->SetTitle(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_FOLDER_NAME));
727  } else {
728    node->SetTitle(
729        l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
730  }
731  return node;
732}
733
734void BookmarkModel::OnFavIconDataAvailable(
735    FaviconService::Handle handle,
736    bool know_favicon,
737    scoped_refptr<RefCountedMemory> data,
738    bool expired,
739    GURL icon_url) {
740  SkBitmap fav_icon;
741  BookmarkNode* node =
742      load_consumer_.GetClientData(
743          profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), handle);
744  DCHECK(node);
745  node->set_favicon_load_handle(0);
746  if (know_favicon && data.get() && data->size() &&
747      gfx::PNGCodec::Decode(data->front(), data->size(), &fav_icon)) {
748    node->set_favicon(fav_icon);
749    FavIconLoaded(node);
750  }
751}
752
753void BookmarkModel::LoadFavIcon(BookmarkNode* node) {
754  if (node->type() != BookmarkNode::URL)
755    return;
756
757  DCHECK(node->GetURL().is_valid());
758  FaviconService* favicon_service =
759      profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
760  if (!favicon_service)
761    return;
762  FaviconService::Handle handle = favicon_service->GetFaviconForURL(
763      node->GetURL(), &load_consumer_,
764      NewCallback(this, &BookmarkModel::OnFavIconDataAvailable));
765  load_consumer_.SetClientData(favicon_service, handle, node);
766  node->set_favicon_load_handle(handle);
767}
768
769void BookmarkModel::CancelPendingFavIconLoadRequests(BookmarkNode* node) {
770  if (node->favicon_load_handle()) {
771    FaviconService* favicon_service =
772        profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
773    if (favicon_service)
774      favicon_service->CancelRequest(node->favicon_load_handle());
775    node->set_favicon_load_handle(0);
776  }
777}
778
779void BookmarkModel::Observe(NotificationType type,
780                            const NotificationSource& source,
781                            const NotificationDetails& details) {
782  switch (type.value) {
783    case NotificationType::FAVICON_CHANGED: {
784      // Prevent the observers from getting confused for multiple favicon loads.
785      Details<history::FavIconChangeDetails> favicon_details(details);
786      for (std::set<GURL>::const_iterator i = favicon_details->urls.begin();
787           i != favicon_details->urls.end(); ++i) {
788        std::vector<const BookmarkNode*> nodes;
789        GetNodesByURL(*i, &nodes);
790        for (size_t i = 0; i < nodes.size(); ++i) {
791          // Got an updated favicon, for a URL, do a new request.
792          BookmarkNode* node = AsMutable(nodes[i]);
793          node->InvalidateFavicon();
794          CancelPendingFavIconLoadRequests(node);
795          FOR_EACH_OBSERVER(BookmarkModelObserver, observers_,
796                            BookmarkNodeChanged(this, node));
797        }
798      }
799      break;
800    }
801
802    default:
803      NOTREACHED();
804      break;
805  }
806}
807
808void BookmarkModel::PopulateNodesByURL(BookmarkNode* node) {
809  // NOTE: this is called with url_lock_ already held. As such, this doesn't
810  // explicitly grab the lock.
811  if (node->is_url())
812    nodes_ordered_by_url_set_.insert(node);
813  for (int i = 0; i < node->GetChildCount(); ++i)
814    PopulateNodesByURL(node->GetChild(i));
815}
816
817int64 BookmarkModel::generate_next_node_id() {
818  return next_node_id_++;
819}
820
821void BookmarkModel::SetFileChanged() {
822  file_changed_ = true;
823}
824
825BookmarkLoadDetails* BookmarkModel::CreateLoadDetails() {
826  BookmarkNode* bb_node = CreateBookmarkNode();
827  BookmarkNode* other_folder_node = CreateOtherBookmarksNode();
828  return new BookmarkLoadDetails(
829      bb_node, other_folder_node, new BookmarkIndex(profile()), next_node_id_);
830}
831