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