bookmark_utils.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_utils.h"
6
7#include "app/drag_drop_types.h"
8#include "app/l10n_util.h"
9#include "app/tree_node_iterator.h"
10#include "base/basictypes.h"
11#include "base/file_path.h"
12#include "base/string_number_conversions.h"
13#include "base/string16.h"
14#include "base/time.h"
15#include "base/utf_string_conversions.h"
16#include "chrome/browser/bookmarks/bookmark_node_data.h"
17#include "chrome/browser/bookmarks/bookmark_model.h"
18#if defined(OS_MACOSX)
19#include "chrome/browser/bookmarks/bookmark_pasteboard_helper_mac.h"
20#endif
21#include "chrome/browser/browser_list.h"
22#include "chrome/browser/browser_process.h"
23#include "chrome/browser/browser_window.h"
24#include "chrome/browser/history/query_parser.h"
25#include "chrome/browser/platform_util.h"
26#include "chrome/browser/prefs/pref_service.h"
27#include "chrome/browser/profile.h"
28#include "chrome/browser/tab_contents/page_navigator.h"
29#include "chrome/browser/tab_contents/tab_contents.h"
30#include "chrome/browser/ui/browser.h"
31#include "chrome/common/notification_service.h"
32#include "chrome/common/pref_names.h"
33#include "grit/app_strings.h"
34#include "grit/chromium_strings.h"
35#include "grit/generated_resources.h"
36#include "net/base/net_util.h"
37#include "views/event.h"
38
39#if defined(TOOLKIT_VIEWS)
40#include "app/os_exchange_data.h"
41#include "views/drag_utils.h"
42#include "views/widget/root_view.h"
43#include "views/widget/widget.h"
44#elif defined(TOOLKIT_GTK)
45#include "chrome/browser/gtk/custom_drag.h"
46#endif
47
48using base::Time;
49
50namespace {
51
52// A PageNavigator implementation that creates a new Browser. This is used when
53// opening a url and there is no Browser open. The Browser is created the first
54// time the PageNavigator method is invoked.
55class NewBrowserPageNavigator : public PageNavigator {
56 public:
57  explicit NewBrowserPageNavigator(Profile* profile)
58      : profile_(profile),
59        browser_(NULL) {}
60
61  virtual ~NewBrowserPageNavigator() {
62    if (browser_)
63      browser_->window()->Show();
64  }
65
66  Browser* browser() const { return browser_; }
67
68  virtual void OpenURL(const GURL& url,
69                       const GURL& referrer,
70                       WindowOpenDisposition disposition,
71                       PageTransition::Type transition) {
72    if (!browser_) {
73      Profile* profile = (disposition == OFF_THE_RECORD) ?
74          profile_->GetOffTheRecordProfile() : profile_;
75      browser_ = Browser::Create(profile);
76      // Always open the first tab in the foreground.
77      disposition = NEW_FOREGROUND_TAB;
78    }
79    browser_->OpenURL(url, referrer, NEW_FOREGROUND_TAB, transition);
80  }
81
82 private:
83  Profile* profile_;
84  Browser* browser_;
85
86  DISALLOW_COPY_AND_ASSIGN(NewBrowserPageNavigator);
87};
88
89void CloneBookmarkNodeImpl(BookmarkModel* model,
90                           const BookmarkNodeData::Element& element,
91                           const BookmarkNode* parent,
92                           int index_to_add_at) {
93  if (element.is_url) {
94    model->AddURL(parent, index_to_add_at, element.title, element.url);
95  } else {
96    const BookmarkNode* new_folder = model->AddGroup(parent,
97                                                     index_to_add_at,
98                                                     element.title);
99    for (int i = 0; i < static_cast<int>(element.children.size()); ++i)
100      CloneBookmarkNodeImpl(model, element.children[i], new_folder, i);
101  }
102}
103
104// Returns the number of descendants of node that are of type url.
105int DescendantURLCount(const BookmarkNode* node) {
106  int result = 0;
107  for (int i = 0; i < node->GetChildCount(); ++i) {
108    const BookmarkNode* child = node->GetChild(i);
109    if (child->is_url())
110      result++;
111    else
112      result += DescendantURLCount(child);
113  }
114  return result;
115}
116
117// Implementation of OpenAll. Opens all nodes of type URL and recurses for
118// groups. |navigator| is the PageNavigator used to open URLs. After the first
119// url is opened |opened_url| is set to true and |navigator| is set to the
120// PageNavigator of the last active tab. This is done to handle a window
121// disposition of new window, in which case we want subsequent tabs to open in
122// that window.
123void OpenAllImpl(const BookmarkNode* node,
124                 WindowOpenDisposition initial_disposition,
125                 PageNavigator** navigator,
126                 bool* opened_url) {
127  if (node->is_url()) {
128    WindowOpenDisposition disposition;
129    if (*opened_url)
130      disposition = NEW_BACKGROUND_TAB;
131    else
132      disposition = initial_disposition;
133    (*navigator)->OpenURL(node->GetURL(), GURL(), disposition,
134                          PageTransition::AUTO_BOOKMARK);
135    if (!*opened_url) {
136      *opened_url = true;
137      // We opened the first URL which may have opened a new window or clobbered
138      // the current page, reset the navigator just to be sure.
139      Browser* new_browser = BrowserList::GetLastActive();
140      if (new_browser) {
141        TabContents* current_tab = new_browser->GetSelectedTabContents();
142        DCHECK(new_browser && current_tab);
143        if (new_browser && current_tab)
144          *navigator = current_tab;
145      }  // else, new_browser == NULL, which happens during testing.
146    }
147  } else {
148    // Group, recurse through children.
149    for (int i = 0; i < node->GetChildCount(); ++i) {
150      OpenAllImpl(node->GetChild(i), initial_disposition, navigator,
151                  opened_url);
152    }
153  }
154}
155
156bool ShouldOpenAll(gfx::NativeWindow parent,
157                   const std::vector<const BookmarkNode*>& nodes) {
158  int descendant_count = 0;
159  for (size_t i = 0; i < nodes.size(); ++i)
160    descendant_count += DescendantURLCount(nodes[i]);
161  if (descendant_count < bookmark_utils::num_urls_before_prompting)
162    return true;
163
164  string16 message = l10n_util::GetStringFUTF16(
165      IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL,
166      base::IntToString16(descendant_count));
167  string16 title = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
168  return platform_util::SimpleYesNoBox(parent, title, message);
169}
170
171// Comparison function that compares based on date modified of the two nodes.
172bool MoreRecentlyModified(const BookmarkNode* n1, const BookmarkNode* n2) {
173  return n1->date_group_modified() > n2->date_group_modified();
174}
175
176// Returns true if |text| contains each string in |words|. This is used when
177// searching for bookmarks.
178bool DoesBookmarkTextContainWords(const string16& text,
179                                  const std::vector<string16>& words) {
180  for (size_t i = 0; i < words.size(); ++i) {
181    if (text.find(words[i]) == string16::npos)
182      return false;
183  }
184  return true;
185}
186
187// Returns true if |node|s title or url contains the strings in |words|.
188// |languages| argument is user's accept-language setting to decode IDN.
189bool DoesBookmarkContainWords(const BookmarkNode* node,
190                              const std::vector<string16>& words,
191                              const std::string& languages) {
192  return
193      DoesBookmarkTextContainWords(
194          l10n_util::ToLower(node->GetTitle()), words) ||
195      DoesBookmarkTextContainWords(
196          l10n_util::ToLower(UTF8ToUTF16(node->GetURL().spec())), words) ||
197      DoesBookmarkTextContainWords(l10n_util::ToLower(
198          net::FormatUrl(node->GetURL(), languages, net::kFormatUrlOmitNothing,
199                         UnescapeRule::NORMAL, NULL, NULL, NULL)), words);
200}
201
202}  // namespace
203
204namespace bookmark_utils {
205
206int num_urls_before_prompting = 15;
207
208int PreferredDropOperation(int source_operations, int operations) {
209  int common_ops = (source_operations & operations);
210  if (!common_ops)
211    return 0;
212  if (DragDropTypes::DRAG_COPY & common_ops)
213    return DragDropTypes::DRAG_COPY;
214  if (DragDropTypes::DRAG_LINK & common_ops)
215    return DragDropTypes::DRAG_LINK;
216  if (DragDropTypes::DRAG_MOVE & common_ops)
217    return DragDropTypes::DRAG_MOVE;
218  return DragDropTypes::DRAG_NONE;
219}
220
221int BookmarkDragOperation(const BookmarkNode* node) {
222  if (node->is_url()) {
223    return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE |
224           DragDropTypes::DRAG_LINK;
225  }
226  return DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE;
227}
228
229int BookmarkDropOperation(Profile* profile,
230                          const views::DropTargetEvent& event,
231                          const BookmarkNodeData& data,
232                          const BookmarkNode* parent,
233                          int index) {
234  if (data.IsFromProfile(profile) && data.size() > 1)
235    // Currently only accept one dragged node at a time.
236    return DragDropTypes::DRAG_NONE;
237
238  if (!bookmark_utils::IsValidDropLocation(profile, data, parent, index))
239    return DragDropTypes::DRAG_NONE;
240
241  if (data.GetFirstNode(profile)) {
242    // User is dragging from this profile: move.
243    return DragDropTypes::DRAG_MOVE;
244  }
245  // User is dragging from another app, copy.
246  return PreferredDropOperation(event.GetSourceOperations(),
247      DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK);
248}
249
250int PerformBookmarkDrop(Profile* profile,
251                        const BookmarkNodeData& data,
252                        const BookmarkNode* parent_node,
253                        int index) {
254  BookmarkModel* model = profile->GetBookmarkModel();
255  if (data.IsFromProfile(profile)) {
256    const std::vector<const BookmarkNode*> dragged_nodes =
257        data.GetNodes(profile);
258    if (!dragged_nodes.empty()) {
259      // Drag from same profile. Move nodes.
260      for (size_t i = 0; i < dragged_nodes.size(); ++i) {
261        model->Move(dragged_nodes[i], parent_node, index);
262        index = parent_node->IndexOfChild(dragged_nodes[i]) + 1;
263      }
264      return DragDropTypes::DRAG_MOVE;
265    }
266    return DragDropTypes::DRAG_NONE;
267  }
268  // Dropping a group from different profile. Always accept.
269  bookmark_utils::CloneBookmarkNode(model, data.elements, parent_node, index);
270  return DragDropTypes::DRAG_COPY;
271}
272
273bool IsValidDropLocation(Profile* profile,
274                         const BookmarkNodeData& data,
275                         const BookmarkNode* drop_parent,
276                         int index) {
277  if (!drop_parent->is_folder()) {
278    NOTREACHED();
279    return false;
280  }
281
282  if (!data.is_valid())
283    return false;
284
285  if (data.IsFromProfile(profile)) {
286    std::vector<const BookmarkNode*> nodes = data.GetNodes(profile);
287    for (size_t i = 0; i < nodes.size(); ++i) {
288      // Don't allow the drop if the user is attempting to drop on one of the
289      // nodes being dragged.
290      const BookmarkNode* node = nodes[i];
291      int node_index = (drop_parent == node->GetParent()) ?
292          drop_parent->IndexOfChild(nodes[i]) : -1;
293      if (node_index != -1 && (index == node_index || index == node_index + 1))
294        return false;
295
296      // drop_parent can't accept a child that is an ancestor.
297      if (drop_parent->HasAncestor(node))
298        return false;
299    }
300    return true;
301  }
302  // From the same profile, always accept.
303  return true;
304}
305
306void CloneBookmarkNode(BookmarkModel* model,
307                       const std::vector<BookmarkNodeData::Element>& elements,
308                       const BookmarkNode* parent,
309                       int index_to_add_at) {
310  if (!parent->is_folder() || !model) {
311    NOTREACHED();
312    return;
313  }
314  for (size_t i = 0; i < elements.size(); ++i)
315    CloneBookmarkNodeImpl(model, elements[i], parent, index_to_add_at + i);
316}
317
318
319// Bookmark dragging
320void DragBookmarks(Profile* profile,
321                   const std::vector<const BookmarkNode*>& nodes,
322                   gfx::NativeView view) {
323  DCHECK(!nodes.empty());
324
325#if defined(TOOLKIT_VIEWS)
326  // Set up our OLE machinery
327  OSExchangeData data;
328  BookmarkNodeData drag_data(nodes);
329  drag_data.Write(profile, &data);
330
331  views::RootView* root_view =
332      views::Widget::GetWidgetFromNativeView(view)->GetRootView();
333
334  // Allow nested message loop so we get DnD events as we drag this around.
335  bool was_nested = MessageLoop::current()->IsNested();
336  MessageLoop::current()->SetNestableTasksAllowed(true);
337
338  root_view->StartDragForViewFromMouseEvent(NULL, data,
339      DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE |
340      DragDropTypes::DRAG_LINK);
341
342  MessageLoop::current()->SetNestableTasksAllowed(was_nested);
343#elif defined(OS_MACOSX)
344  // Allow nested message loop so we get DnD events as we drag this around.
345  bool was_nested = MessageLoop::current()->IsNested();
346  MessageLoop::current()->SetNestableTasksAllowed(true);
347  bookmark_pasteboard_helper_mac::StartDrag(profile, nodes, view);
348  MessageLoop::current()->SetNestableTasksAllowed(was_nested);
349#elif defined(TOOLKIT_GTK)
350  BookmarkDrag::BeginDrag(profile, nodes);
351#endif
352}
353
354void OpenAll(gfx::NativeWindow parent,
355             Profile* profile,
356             PageNavigator* navigator,
357             const std::vector<const BookmarkNode*>& nodes,
358             WindowOpenDisposition initial_disposition) {
359  if (!ShouldOpenAll(parent, nodes))
360    return;
361
362  NewBrowserPageNavigator navigator_impl(profile);
363  if (!navigator) {
364    Browser* browser =
365        BrowserList::FindBrowserWithType(profile, Browser::TYPE_NORMAL, false);
366    if (!browser || !browser->GetSelectedTabContents()) {
367      navigator = &navigator_impl;
368    } else {
369      if (initial_disposition != NEW_WINDOW &&
370          initial_disposition != OFF_THE_RECORD) {
371        browser->window()->Activate();
372      }
373      navigator = browser->GetSelectedTabContents();
374    }
375  }
376
377  bool opened_url = false;
378  for (size_t i = 0; i < nodes.size(); ++i)
379    OpenAllImpl(nodes[i], initial_disposition, &navigator, &opened_url);
380}
381
382void OpenAll(gfx::NativeWindow parent,
383             Profile* profile,
384             PageNavigator* navigator,
385             const BookmarkNode* node,
386             WindowOpenDisposition initial_disposition) {
387  std::vector<const BookmarkNode*> nodes;
388  nodes.push_back(node);
389  OpenAll(parent, profile, navigator, nodes, initial_disposition);
390}
391
392void CopyToClipboard(BookmarkModel* model,
393                     const std::vector<const BookmarkNode*>& nodes,
394                     bool remove_nodes) {
395  if (nodes.empty())
396    return;
397
398  BookmarkNodeData(nodes).WriteToClipboard(NULL);
399
400  if (remove_nodes) {
401    for (size_t i = 0; i < nodes.size(); ++i) {
402      model->Remove(nodes[i]->GetParent(),
403                    nodes[i]->GetParent()->IndexOfChild(nodes[i]));
404    }
405  }
406}
407
408void PasteFromClipboard(BookmarkModel* model,
409                        const BookmarkNode* parent,
410                        int index) {
411  if (!parent)
412    return;
413
414  BookmarkNodeData bookmark_data;
415  if (!bookmark_data.ReadFromClipboard())
416    return;
417
418  if (index == -1)
419    index = parent->GetChildCount();
420  bookmark_utils::CloneBookmarkNode(
421      model, bookmark_data.elements, parent, index);
422}
423
424bool CanPasteFromClipboard(const BookmarkNode* node) {
425  if (!node)
426    return false;
427  return BookmarkNodeData::ClipboardContainsBookmarks();
428}
429
430std::string GetNameForURL(const GURL& url) {
431  if (url.is_valid()) {
432    return WideToUTF8(net::GetSuggestedFilename(
433        url, std::string(), std::string(), FilePath()).ToWStringHack());
434  } else {
435    return l10n_util::GetStringUTF8(IDS_APP_UNTITLED_SHORTCUT_FILE_NAME);
436  }
437}
438
439std::vector<const BookmarkNode*> GetMostRecentlyModifiedGroups(
440    BookmarkModel* model,
441    size_t max_count) {
442  std::vector<const BookmarkNode*> nodes;
443  TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
444  while (iterator.has_next()) {
445    const BookmarkNode* parent = iterator.Next();
446    if (parent->is_folder() && parent->date_group_modified() > base::Time()) {
447      if (max_count == 0) {
448        nodes.push_back(parent);
449      } else {
450        std::vector<const BookmarkNode*>::iterator i =
451            std::upper_bound(nodes.begin(), nodes.end(), parent,
452                             &MoreRecentlyModified);
453        if (nodes.size() < max_count || i != nodes.end()) {
454          nodes.insert(i, parent);
455          while (nodes.size() > max_count)
456            nodes.pop_back();
457        }
458      }
459    }  // else case, the root node, which we don't care about or imported nodes
460       // (which have a time of 0).
461  }
462
463  if (nodes.size() < max_count) {
464    // Add the bookmark bar and other nodes if there is space.
465    if (find(nodes.begin(), nodes.end(), model->GetBookmarkBarNode()) ==
466        nodes.end()) {
467      nodes.push_back(model->GetBookmarkBarNode());
468    }
469
470    if (nodes.size() < max_count &&
471        find(nodes.begin(), nodes.end(), model->other_node()) == nodes.end()) {
472      nodes.push_back(model->other_node());
473    }
474  }
475  return nodes;
476}
477
478void GetMostRecentlyAddedEntries(BookmarkModel* model,
479                                 size_t count,
480                                 std::vector<const BookmarkNode*>* nodes) {
481  TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
482  while (iterator.has_next()) {
483    const BookmarkNode* node = iterator.Next();
484    if (node->is_url()) {
485      std::vector<const BookmarkNode*>::iterator insert_position =
486          std::upper_bound(nodes->begin(), nodes->end(), node,
487                           &MoreRecentlyAdded);
488      if (nodes->size() < count || insert_position != nodes->end()) {
489        nodes->insert(insert_position, node);
490        while (nodes->size() > count)
491          nodes->pop_back();
492      }
493    }
494  }
495}
496
497bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2) {
498  return n1->date_added() > n2->date_added();
499}
500
501void GetBookmarksContainingText(BookmarkModel* model,
502                                const string16& text,
503                                size_t max_count,
504                                const std::string& languages,
505                                std::vector<const BookmarkNode*>* nodes) {
506  std::vector<string16> words;
507  QueryParser parser;
508  parser.ExtractQueryWords(l10n_util::ToLower(text), &words);
509  if (words.empty())
510    return;
511
512  TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
513  while (iterator.has_next()) {
514    const BookmarkNode* node = iterator.Next();
515    if (node->is_url() && DoesBookmarkContainWords(node, words, languages)) {
516      nodes->push_back(node);
517      if (nodes->size() == max_count)
518        return;
519    }
520  }
521}
522
523bool DoesBookmarkContainText(const BookmarkNode* node,
524                             const string16& text,
525                             const std::string& languages) {
526  std::vector<string16> words;
527  QueryParser parser;
528  parser.ExtractQueryWords(l10n_util::ToLower(text), &words);
529  if (words.empty())
530    return false;
531
532  return (node->is_url() && DoesBookmarkContainWords(node, words, languages));
533}
534
535static const BookmarkNode* CreateNewNode(BookmarkModel* model,
536    const BookmarkNode* parent, const BookmarkEditor::EditDetails& details,
537    const string16& new_title, const GURL& new_url) {
538  const BookmarkNode* node;
539  if (details.type == BookmarkEditor::EditDetails::NEW_URL) {
540    node = model->AddURL(parent, parent->GetChildCount(), new_title, new_url);
541  } else if (details.type == BookmarkEditor::EditDetails::NEW_FOLDER) {
542    node = model->AddGroup(parent, parent->GetChildCount(), new_title);
543    for (size_t i = 0; i < details.urls.size(); ++i) {
544      model->AddURL(node, node->GetChildCount(), details.urls[i].second,
545                    details.urls[i].first);
546    }
547    model->SetDateGroupModified(parent, Time::Now());
548  } else {
549    NOTREACHED();
550    return NULL;
551  }
552
553  return node;
554}
555
556const BookmarkNode* ApplyEditsWithNoGroupChange(BookmarkModel* model,
557    const BookmarkNode* parent, const BookmarkEditor::EditDetails& details,
558    const string16& new_title, const GURL& new_url) {
559  if (details.type == BookmarkEditor::EditDetails::NEW_URL ||
560      details.type == BookmarkEditor::EditDetails::NEW_FOLDER) {
561    return CreateNewNode(model, parent, details, new_title, new_url);
562  }
563
564  const BookmarkNode* node = details.existing_node;
565  DCHECK(node);
566
567  if (node->is_url())
568    model->SetURL(node, new_url);
569  model->SetTitle(node, new_title);
570
571  return node;
572}
573
574const BookmarkNode* ApplyEditsWithPossibleGroupChange(BookmarkModel* model,
575    const BookmarkNode* new_parent, const BookmarkEditor::EditDetails& details,
576    const string16& new_title, const GURL& new_url) {
577  if (details.type == BookmarkEditor::EditDetails::NEW_URL ||
578      details.type == BookmarkEditor::EditDetails::NEW_FOLDER) {
579    return CreateNewNode(model, new_parent, details, new_title, new_url);
580  }
581
582  const BookmarkNode* node = details.existing_node;
583  DCHECK(node);
584
585  if (new_parent != node->GetParent())
586    model->Move(node, new_parent, new_parent->GetChildCount());
587  if (node->is_url())
588    model->SetURL(node, new_url);
589  model->SetTitle(node, new_title);
590
591  return node;
592}
593
594// Formerly in BookmarkBarView
595void ToggleWhenVisible(Profile* profile) {
596  PrefService* prefs = profile->GetPrefs();
597  const bool always_show = !prefs->GetBoolean(prefs::kShowBookmarkBar);
598
599  // The user changed when the bookmark bar is shown, update the preferences.
600  prefs->SetBoolean(prefs::kShowBookmarkBar, always_show);
601  prefs->ScheduleSavePersistentPrefs();
602
603  // And notify the notification service.
604  Source<Profile> source(profile);
605  NotificationService::current()->Notify(
606      NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
607      source,
608      NotificationService::NoDetails());
609}
610
611void RegisterUserPrefs(PrefService* prefs) {
612  prefs->RegisterBooleanPref(prefs::kShowBookmarkBar, false);
613}
614
615void GetURLAndTitleToBookmark(TabContents* tab_contents,
616                              GURL* url,
617                              string16* title) {
618  *url = tab_contents->GetURL();
619  *title = tab_contents->GetTitle();
620}
621
622void GetURLsForOpenTabs(Browser* browser,
623    std::vector<std::pair<GURL, string16> >* urls) {
624  for (int i = 0; i < browser->tab_count(); ++i) {
625    std::pair<GURL, string16> entry;
626    GetURLAndTitleToBookmark(browser->GetTabContentsAt(i), &(entry.first),
627                             &(entry.second));
628    urls->push_back(entry);
629  }
630}
631
632const BookmarkNode* GetParentForNewNodes(
633    const BookmarkNode* parent,
634    const std::vector<const BookmarkNode*>& selection,
635    int* index) {
636  const BookmarkNode* real_parent = parent;
637
638  if (selection.size() == 1 && selection[0]->is_folder())
639    real_parent = selection[0];
640
641  if (index) {
642    if (selection.size() == 1 && selection[0]->is_url()) {
643      *index = real_parent->IndexOfChild(selection[0]) + 1;
644      if (*index == 0) {
645        // Node doesn't exist in parent, add to end.
646        NOTREACHED();
647        *index = real_parent->GetChildCount();
648      }
649    } else {
650      *index = real_parent->GetChildCount();
651    }
652  }
653
654  return real_parent;
655}
656
657bool NodeHasURLs(const BookmarkNode* node) {
658  DCHECK(node);
659
660  if (node->is_url())
661    return true;
662
663  for (int i = 0; i < node->GetChildCount(); ++i) {
664    if (NodeHasURLs(node->GetChild(i)))
665      return true;
666  }
667  return false;
668}
669
670}  // namespace bookmark_utils
671