blob: 6df5654f9d8c6184e868e3bcc90cf669ad966574 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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.h"
#include <stddef.h>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service.h"
#include "chrome/browser/bookmarks/bookmark_merged_surface_service_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node_data.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/bookmarks/managed/managed_bookmark_service.h"
#include "components/dom_distiller/core/url_constants.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/prefs/pref_service.h"
#include "components/search/search.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/url_formatter.h"
#include "components/user_prefs/user_prefs.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drop_target_event.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_variant.h"
#if defined(TOOLKIT_VIEWS)
#include "chrome/grit/theme_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/themed_vector_icon.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_source.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/resources/grit/ui_resources.h"
#endif
namespace chrome {
namespace {
using ::bookmarks::BookmarkModel;
using ::bookmarks::BookmarkNode;
using ::ui::mojom::DragOperation;
using PermanentFolderType = BookmarkParentFolder::PermanentFolderType;
#if defined(TOOLKIT_VIEWS)
// Image source that flips the supplied source image in RTL.
class RTLFlipSource : public gfx::ImageSkiaSource {
public:
explicit RTLFlipSource(gfx::ImageSkia source) : source_(std::move(source)) {}
~RTLFlipSource() override = default;
// gfx::ImageSkiaSource:
gfx::ImageSkiaRep GetImageForScale(float scale) override {
gfx::Canvas canvas(source_.size(), scale, false);
gfx::ScopedCanvas scoped_canvas(&canvas);
scoped_canvas.FlipIfRTL(source_.width());
canvas.DrawImageInt(source_, 0, 0);
return gfx::ImageSkiaRep(canvas.GetBitmap(), scale);
}
private:
const gfx::ImageSkia source_;
};
#endif // defined(TOOLKIT_VIEWS)
// Returns true if the `dragged_node` can be dropped on `drop_parent` at
// `index`. A drop is not allowed on a managed node. A drop from a separate
// profile or a URL is allowed, where as a drop from the same profile is only
// allowed if:
// - The `dragged_node` is not an ancestor of `drop_parent`
// - The `dragged _node` isn't already a child of `drop_parent` at `index`.
bool IsValidBookmarkDropLocation(
const BookmarkMergedSurfaceService* bookmark_merged_service,
const bookmarks::BookmarkNode* dragged_node,
bool dragged_from_same_profile,
const BookmarkParentFolder& drop_parent,
size_t index) {
if (bookmark_merged_service->IsParentFolderManaged(drop_parent)) {
// Drop on a managed node is not allowed.
return false;
}
if (!dragged_from_same_profile) {
// If the drop is not on a managed node, always accept if node from another
// profile or the user is dragging a URL.
// Note: It is expected if the user is dragging a URL
// `dragged_from_same_profile` is false as a URL is not associated with a
// profile.
return true;
}
// `dragged_node` is null if the node is from another profile or the user is
// dragging a url. In both cases, `dragged_from_same_profile` is expected to
// be false. For dragging a node within the same profile, the `dragged_node`
// must be not null.
CHECK(dragged_node);
CHECK(!dragged_node->is_root());
CHECK(!dragged_node->is_permanent_node());
// Don't allow the drop if the user is attempting to drop on the node being
// dragged.
if (drop_parent.HasDirectChildNode(dragged_node)) {
// Reordering.
size_t node_index = bookmark_merged_service->GetIndexOf(dragged_node);
if (index == node_index || index == node_index + 1) {
return false;
}
}
// drop_parent can't accept a child that is an ancestor.
if (drop_parent.HoldsNonPermanentFolder() &&
drop_parent.as_non_permanent_folder()->HasAncestor(dragged_node)) {
return false;
}
return true;
}
} // namespace
GURL GetURLToBookmark(content::WebContents* web_contents) {
DCHECK(web_contents);
if (search::IsInstantNTP(web_contents)) {
return GURL(kChromeUINewTabURL);
}
// Users cannot bookmark Reader Mode pages directly, so the bookmark
// interaction is as if it were with the original page.
if (dom_distiller::url_utils::IsDistilledPage(
web_contents->GetVisibleURL())) {
return dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
web_contents->GetVisibleURL());
}
return web_contents->GetVisibleURL();
}
bool GetURLAndTitleToBookmark(content::WebContents* web_contents,
GURL* url,
std::u16string* title) {
GURL u = GetURLToBookmark(web_contents);
if (!u.is_valid()) {
return false;
}
*url = u;
if (dom_distiller::url_utils::IsDistilledPage(
web_contents->GetVisibleURL())) {
// Users cannot bookmark Reader Mode pages directly. Instead, a bookmark
// is added for the original page and original title.
*title =
base::UTF8ToUTF16(dom_distiller::url_utils::GetTitleFromDistillerUrl(
web_contents->GetVisibleURL()));
} else {
*title = web_contents->GetTitle();
}
// Use "New tab" as title if the current page is NTP even in incognito mode.
if (u == GURL(chrome::kChromeUINewTabURL)) {
*title = l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
}
return true;
}
void ToggleBookmarkBarWhenVisible(content::BrowserContext* browser_context) {
PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
const bool always_show =
!prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar);
// The user changed when the bookmark bar is shown, update the preferences.
prefs->SetBoolean(bookmarks::prefs::kShowBookmarkBar, always_show);
}
std::u16string FormatBookmarkURLForDisplay(const GURL& url) {
// Because this gets re-parsed by FixupURL(), it's safe to omit the scheme
// and trailing slash, and unescape most characters. However, it's
// important not to drop any username/password, or unescape anything that
// changes the URL's meaning.
url_formatter::FormatUrlTypes format_types =
url_formatter::kFormatUrlOmitDefaults &
~url_formatter::kFormatUrlOmitUsernamePassword;
// If username is present, we must not omit the scheme because FixupURL() will
// subsequently interpret the username as a scheme. crbug.com/639126
if (url.has_username()) {
format_types &= ~url_formatter::kFormatUrlOmitHTTP;
}
return url_formatter::FormatUrl(url, format_types, base::UnescapeRule::SPACES,
nullptr, nullptr, nullptr);
}
bool IsAppsShortcutEnabled(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
// Chrome OS uses the app list / app launcher.
return false;
#else
return search::IsInstantExtendedAPIEnabled() && !profile->IsOffTheRecord();
#endif
}
bool ShouldShowAppsShortcutInBookmarkBar(Profile* profile) {
return IsAppsShortcutEnabled(profile) &&
profile->GetPrefs()->GetBoolean(
bookmarks::prefs::kShowAppsShortcutInBookmarkBar);
}
bool ShouldShowTabGroupsInBookmarkBar(Profile* profile) {
return profile->GetPrefs()->GetBoolean(
bookmarks::prefs::kShowTabGroupsInBookmarkBar);
}
int GetBookmarkDragOperation(content::BrowserContext* browser_context,
const BookmarkNode* node) {
PrefService* prefs = user_prefs::UserPrefs::Get(browser_context);
BookmarkModel* model =
BookmarkModelFactory::GetForBrowserContext(browser_context);
int move = ui::DragDropTypes::DRAG_MOVE;
if (!prefs->GetBoolean(bookmarks::prefs::kEditBookmarksEnabled) ||
model->client()->IsNodeManaged(node)) {
move = ui::DragDropTypes::DRAG_NONE;
}
if (node->is_url()) {
return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK | move;
}
return ui::DragDropTypes::DRAG_COPY | move;
}
DragOperation GetPreferredBookmarkDropOperation(int source_operations,
int operations) {
int common_ops = (source_operations & operations);
if (!common_ops) {
return DragOperation::kNone;
}
if (ui::DragDropTypes::DRAG_COPY & common_ops) {
return DragOperation::kCopy;
}
if (ui::DragDropTypes::DRAG_LINK & common_ops) {
return DragOperation::kLink;
}
if (ui::DragDropTypes::DRAG_MOVE & common_ops) {
return DragOperation::kMove;
}
return DragOperation::kNone;
}
DragOperation GetBookmarkDropOperation(Profile* profile,
const ui::DropTargetEvent& event,
const bookmarks::BookmarkNodeData& data,
const BookmarkParentFolder& parent,
size_t index) {
if (data.size() != 1) {
// Currently only accept one dragged element (bookmark node/url) at a time.
return DragOperation::kNone;
}
BookmarkMergedSurfaceService* const bookmark_merged_service =
BookmarkMergedSurfaceServiceFactory::GetForProfile(profile);
const BookmarkNode* const dragged_node = data.GetFirstNode(
bookmark_merged_service->bookmark_model(), profile->GetPath());
if (!IsValidBookmarkDropLocation(bookmark_merged_service, dragged_node,
data.IsFromProfilePath(profile->GetPath()),
parent, index)) {
return DragOperation::kNone;
}
if (dragged_node) {
// User is dragging from this profile.
if (bookmark_merged_service->IsNodeManaged(dragged_node)) {
// Do a copy instead of a move when dragging bookmarks that the user can't
// modify.
return DragOperation::kCopy;
}
return DragOperation::kMove;
}
// User is dragging from another app, copy.
return GetPreferredBookmarkDropOperation(
event.source_operations(),
ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK);
}
bool CanAllBeEditedByUser(
bookmarks::ManagedBookmarkService* managed_bookmark_service,
const std::vector<
raw_ptr<const bookmarks::BookmarkNode, VectorExperimental>>& nodes) {
if (!managed_bookmark_service) {
return true;
}
for (const bookmarks::BookmarkNode* node : nodes) {
if (managed_bookmark_service->IsNodeManaged(node)) {
return false;
}
}
return true;
}
#if defined(TOOLKIT_VIEWS)
gfx::ImageSkia GetBookmarkFolderImageFromVectorIcon(
BookmarkFolderIconType icon_type,
ui::ColorVariant color,
const ui::ColorProvider* color_provider) {
const gfx::VectorIcon* id;
gfx::ImageSkia folder;
if (icon_type == BookmarkFolderIconType::kNormal) {
id = &vector_icons::kFolderChromeRefreshIcon;
} else {
id = &vector_icons::kFolderManagedRefreshIcon;
}
const ui::ThemedVectorIcon icon(id, color);
folder = icon.GetImageSkia(color_provider);
return folder;
}
ui::ImageModel GetBookmarkFolderIcon(BookmarkFolderIconType icon_type,
ui::ColorVariant color) {
int default_id = IDR_FOLDER_CLOSED;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
// This block must be #ifdefed because only these platforms actually have this
// resource ID.
if (icon_type == BookmarkFolderIconType::kManaged) {
default_id = IDR_BOOKMARK_BAR_FOLDER_MANAGED;
}
#endif
const auto generator = [](int default_id, BookmarkFolderIconType icon_type,
ui::ColorVariant color,
const ui::ColorProvider* color_provider) {
gfx::ImageSkia folder;
folder =
GetBookmarkFolderImageFromVectorIcon(icon_type, color, color_provider);
return gfx::ImageSkia(std::make_unique<RTLFlipSource>(folder),
folder.size());
};
const gfx::Size size =
ui::ResourceBundle::GetSharedInstance().GetImageNamed(default_id).Size();
return ui::ImageModel::FromImageGenerator(
base::BindRepeating(generator, default_id, icon_type, std::move(color)),
size);
}
#endif
} // namespace chrome