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 52 using base::Time; 53 54 namespace { 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. 59 class 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 93 void 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. 109 int 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. 125 void 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 159 bool 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. 175 bool 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. 181 bool 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. 192 bool 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 207 namespace bookmark_utils { 208 209 int num_urls_before_prompting = 15; 210 211 int 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 224 int 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) 235 int 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 257 int 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 280 bool 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 313 void 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 327 void 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 362 void 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 390 void 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 400 void 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 417 void 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 433 bool CanPasteFromClipboard(const BookmarkNode* node) { 434 if (!node) 435 return false; 436 return BookmarkNodeData::ClipboardContainsBookmarks(); 437 } 438 439 string16 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 447 std::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 486 void 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 505 TitleMatch::TitleMatch() 506 : node(NULL) { 507 } 508 509 TitleMatch::~TitleMatch() {} 510 511 bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2) { 512 return n1->date_added() > n2->date_added(); 513 } 514 515 void 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 537 bool 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 549 static 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 570 const 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 588 const 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 609 void 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 625 void RegisterUserPrefs(PrefService* prefs) { 626 prefs->RegisterBooleanPref(prefs::kShowBookmarkBar, false); 627 prefs->RegisterBooleanPref(prefs::kEditBookmarksEnabled, true); 628 } 629 630 void GetURLAndTitleToBookmark(TabContents* tab_contents, 631 GURL* url, 632 string16* title) { 633 *url = tab_contents->GetURL(); 634 *title = tab_contents->GetTitle(); 635 } 636 637 void 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 647 const 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 672 bool 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