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