blob: 394979e1df14200e73eda0680bc1279940a98c8c [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/bookmarks/bookmark_utils_desktop.h"
#include <numeric>
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
namespace chrome {
size_t kNumBookmarkUrlsBeforePrompting = 15;
namespace {
// Returns a vector of all URLs in |nodes| and their immediate children. Only
// recurses one level deep, not infinitely. TODO(pkasting): It's not clear why
// this shouldn't just recurse infinitely.
std::vector<GURL> GetURLsToOpen(
const std::vector<const BookmarkNode*>& nodes,
content::BrowserContext* browser_context = nullptr,
bool incognito_urls_only = false) {
std::vector<GURL> urls;
const auto AddUrlIfLegal = [&](const GURL url) {
if (!incognito_urls_only || IsURLAllowedInIncognito(url, browser_context))
urls.push_back(url);
};
for (const BookmarkNode* node : nodes) {
if (node->is_url()) {
AddUrlIfLegal(node->url());
} else {
// If the node is not a URL, it is a folder. We want to add those of its
// children which are URLs.
for (const auto& child : node->children()) {
if (child->is_url())
AddUrlIfLegal(child->url());
}
}
}
return urls;
}
// Returns the total number of descendants nodes.
int ChildURLCountTotal(const BookmarkNode* node) {
const auto count_children = [](int total, const auto& child) {
if (child->is_folder())
total += ChildURLCountTotal(child.get());
return total + 1;
};
return std::accumulate(node->children().cbegin(), node->children().cend(), 0,
count_children);
}
#if !BUILDFLAG(IS_ANDROID)
// Returns in |urls|, the url and title pairs for each open tab in browser.
void GetURLsAndFoldersForOpenTabs(
Browser* browser,
std::vector<BookmarkEditor::EditDetails::BookmarkData>* folder_data) {
std::vector<std::pair<GURL, std::u16string>> tab_entries;
base::flat_map<int, TabGroupData> groups_by_index;
for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
std::pair<GURL, std::u16string> entry;
auto* contents = browser->tab_strip_model()->GetWebContentsAt(i);
GetURLAndTitleToBookmark(contents, &(entry.first), &(entry.second));
tab_entries.push_back(entry);
auto tab_group_id = browser->tab_strip_model()->GetTabGroupForTab(i);
std::u16string title;
if (tab_group_id.has_value()) {
title = browser->tab_strip_model()
->group_model()
->GetTabGroup(tab_group_id.value())
->visual_data()
->title();
}
groups_by_index.emplace(i, std::make_pair(tab_group_id, title));
}
GetURLsAndFoldersForTabEntries(folder_data, tab_entries, groups_by_index);
}
#endif
// Represents a reference set of web contents opened by OpenAllHelper() so that
// the actual web contents and what browsers they are located in can be
// determined (if necessary).
using OpenedWebContentsSet = base::flat_set<const content::WebContents*>;
// Opens all of the URLs in `bookmark_urls` using `navigator` and
// `initial_disposition` as a starting point. Returns a reference set of the
// WebContents created; see OpenedWebContentsSet.
OpenedWebContentsSet OpenAllHelper(content::PageNavigator* navigator,
std::vector<GURL> bookmark_urls,
WindowOpenDisposition initial_disposition) {
OpenedWebContentsSet::container_type opened_tabs;
WindowOpenDisposition disposition = initial_disposition;
for (std::vector<GURL>::const_iterator url_it = bookmark_urls.begin();
url_it != bookmark_urls.end(); ++url_it) {
content::WebContents* opened_tab = navigator->OpenURL(
content::OpenURLParams(*url_it, content::Referrer(), disposition,
ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
if (url_it == bookmark_urls.begin()) {
// We opened the first URL which may have opened a new window or clobbered
// the current page, reset the navigator just to be sure. |opened_tab| may
// be null in tests.
if (opened_tab)
navigator = opened_tab;
disposition = WindowOpenDisposition::NEW_BACKGROUND_TAB;
}
if (opened_tab)
opened_tabs.push_back(opened_tab);
}
// Constructing the return value in this way is significantly more efficient.
return OpenedWebContentsSet(std::move(opened_tabs));
}
} // namespace
#if !BUILDFLAG(IS_ANDROID)
void OpenAllIfAllowed(
Browser* browser,
base::OnceCallback<content::PageNavigator*()> get_navigator,
const std::vector<const bookmarks::BookmarkNode*>& nodes,
WindowOpenDisposition initial_disposition,
bool add_to_group) {
std::vector<GURL> urls = GetURLsToOpen(
nodes, browser->profile(),
initial_disposition == WindowOpenDisposition::OFF_THE_RECORD);
auto do_open = [](Browser* browser,
base::OnceCallback<content::PageNavigator*()> get_navigator,
std::vector<GURL> urls,
WindowOpenDisposition initial_disposition,
absl::optional<std::u16string> folder_title,
chrome::MessageBoxResult result) {
if (result != chrome::MESSAGE_BOX_RESULT_YES)
return;
if (!get_navigator)
return;
content::PageNavigator* navigator = std::move(get_navigator).Run();
if (!navigator)
return;
const auto opened_web_contents =
OpenAllHelper(navigator, std::move(urls), initial_disposition);
if (folder_title.has_value()) {
TabStripModel* model = browser->tab_strip_model();
// Figure out which tabs we actually opened in this browser that aren't
// already in groups.
std::vector<int> tab_indices;
for (int i = 0; i < model->count(); ++i) {
if (base::Contains(opened_web_contents, model->GetWebContentsAt(i)) &&
!model->GetTabGroupForTab(i).has_value()) {
tab_indices.push_back(i);
}
}
if (!tab_indices.empty()) {
tab_groups::TabGroupId new_group_id = model->AddToNewGroup(tab_indices);
// Use the bookmark folder's title as the group's title.
TabGroup* group = model->group_model()->GetTabGroup(new_group_id);
const tab_groups::TabGroupVisualData* current_visual_data =
group->visual_data();
tab_groups::TabGroupVisualData new_visual_data(
folder_title.value(), current_visual_data->color(),
current_visual_data->is_collapsed());
group->SetVisualData(new_visual_data);
model->OpenTabGroupEditor(new_group_id);
}
}
};
// Skip the prompt if there are few bookmarks.
size_t child_count = urls.size();
if (child_count < kNumBookmarkUrlsBeforePrompting) {
do_open(
browser, std::move(get_navigator), std::move(urls), initial_disposition,
add_to_group
? absl::optional<std::u16string>(nodes[0]->GetTitledUrlNodeTitle())
: absl::nullopt,
chrome::MESSAGE_BOX_RESULT_YES);
return;
}
// The callback passed contains the pointer |browser|. This is safe
// since if |browser| is closed, the message box will be destroyed
// before the user can answer "Yes".
ShowQuestionMessageBox(
browser->window()->GetNativeWindow(),
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
l10n_util::GetStringFUTF16(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL,
base::NumberToString16(child_count)),
base::BindOnce(do_open, browser, std::move(get_navigator),
std::move(urls), initial_disposition,
add_to_group ? absl::optional<std::u16string>(
nodes[0]->GetTitledUrlNodeTitle())
: absl::nullopt));
}
void OpenAllNow(content::PageNavigator* navigator,
const std::vector<const BookmarkNode*>& nodes,
WindowOpenDisposition initial_disposition,
content::BrowserContext* browser_context) {
// Opens all |nodes| of type URL and any children of |nodes| that are of type
// URL. |navigator| is the PageNavigator used to open URLs. After the first
// url is opened |navigator| is set to the PageNavigator of the last active
// tab. This is done to handle a window disposition of new window, in which
// case we want subsequent tabs to open in that window.
std::vector<GURL> urls = GetURLsToOpen(
nodes, browser_context,
initial_disposition == WindowOpenDisposition::OFF_THE_RECORD);
OpenAllHelper(navigator, std::move(urls), initial_disposition);
}
int OpenCount(gfx::NativeWindow parent,
const std::vector<const bookmarks::BookmarkNode*>& nodes,
content::BrowserContext* incognito_context) {
return GetURLsToOpen(nodes, incognito_context, incognito_context != nullptr)
.size();
}
int OpenCount(gfx::NativeWindow parent,
const BookmarkNode* node,
content::BrowserContext* incognito_context) {
std::vector<const BookmarkNode*> nodes;
nodes.push_back(node);
return OpenCount(parent, std::vector<const bookmarks::BookmarkNode*>{node},
incognito_context);
}
bool ConfirmDeleteBookmarkNode(gfx::NativeWindow window,
const BookmarkNode* node) {
DCHECK(node && node->is_folder() && !node->children().empty());
return ShowQuestionMessageBoxSync(
window, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
l10n_util::GetPluralStringFUTF16(
IDS_BOOKMARK_EDITOR_CONFIRM_DELETE,
ChildURLCountTotal(node))) == MESSAGE_BOX_RESULT_YES;
}
void ShowBookmarkAllTabsDialog(Browser* browser) {
Profile* profile = browser->profile();
BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile);
DCHECK(model && model->loaded());
const BookmarkNode* parent = GetParentForNewNodes(model);
BookmarkEditor::EditDetails details =
BookmarkEditor::EditDetails::AddFolder(parent, parent->children().size());
GetURLsAndFoldersForOpenTabs(browser, &(details.bookmark_data.children));
DCHECK(!details.bookmark_data.children.empty());
BookmarkEditor::Show(browser->window()->GetNativeWindow(), profile, details,
BookmarkEditor::SHOW_TREE);
}
bool HasBookmarkURLs(const std::vector<const BookmarkNode*>& selection) {
return !GetURLsToOpen(selection).empty();
}
bool HasBookmarkURLsAllowedInIncognitoMode(
const std::vector<const BookmarkNode*>& selection,
content::BrowserContext* browser_context) {
return !GetURLsToOpen(selection, browser_context, true).empty();
}
void GetURLsAndFoldersForTabEntries(
std::vector<BookmarkEditor::EditDetails::BookmarkData>* folder_data,
std::vector<std::pair<GURL, std::u16string>> tab_entries,
base::flat_map<int, TabGroupData> groups_by_index) {
absl::optional<tab_groups::TabGroupId> current_group_id;
for (size_t i = 0; i < tab_entries.size(); ++i) {
std::pair<GURL, std::u16string> entry = tab_entries.at(i);
if (entry.first.is_empty()) {
continue;
}
BookmarkEditor::EditDetails::BookmarkData child;
child.url = entry.first;
child.title = entry.second;
if (groups_by_index.at(i).first.has_value()) {
if (current_group_id != groups_by_index.at(i).first.value()) {
BookmarkEditor::EditDetails::BookmarkData tab_group;
tab_group.title = groups_by_index.at(i).second;
folder_data->push_back(tab_group);
current_group_id = groups_by_index.at(i).first;
}
folder_data->back().children.push_back(child);
} else {
folder_data->push_back(child);
}
}
}
#endif // !BUILDFLAG(IS_ANDROID)
} // namespace chrome