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