blob: 48ea81b84243572234ded456ad633450b44eef4b [file] [log] [blame]
// Copyright (c) 2012 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/views/bookmarks/bookmark_bar_view.h"
#include <stdint.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/macros.h"
#include "base/metrics/user_metrics.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/favicon/favicon_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils_desktop.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view_observer.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_context_menu.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/event_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_background.h"
#include "chrome/browser/ui/views/read_later/read_later_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_metrics.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/bookmarks/managed/managed_bookmark_service.h"
#include "components/metrics/metrics_service.h"
#include "components/prefs/pref_service.h"
#include "components/profile_metrics/browser_profile_type.h"
#include "components/reading_list/features/reading_list_switches.h"
#include "components/url_formatter/elide_url.h"
#include "components/url_formatter/url_formatter.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/theme_provider.h"
#include "ui/base/window_open_disposition.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/events/event_constants.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/button_drag_utils.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/menu_button_controller.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/drag_utils.h"
#include "ui/views/metrics.h"
#include "ui/views/view_constants.h"
#include "ui/views/widget/tooltip_manager.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/non_client_view.h"
namespace {
using ::bookmarks::BookmarkModel;
using ::bookmarks::BookmarkNode;
using ::ui::mojom::DragOperation;
using ::views::LabelButtonBorder;
using ::views::MenuButton;
// Margin around the content.
constexpr int kBookmarkBarHorizontalMargin = 8;
// Used to globally disable rich animations.
bool animations_enabled = true;
gfx::ImageSkia* GetImageSkiaNamed(int id) {
return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(id);
}
std::unique_ptr<LabelButtonBorder> CreateBookmarkButtonBorder() {
auto border = std::make_unique<LabelButtonBorder>();
border->set_insets(ChromeLayoutProvider::Get()->GetInsetsMetric(
INSETS_BOOKMARKS_BAR_BUTTON));
return border;
}
// BookmarkButtonBase -----------------------------------------------
// Base class for non-menu hosting buttons used on the bookmark bar.
class BookmarkButtonBase : public views::LabelButton {
public:
METADATA_HEADER(BookmarkButtonBase);
BookmarkButtonBase(PressedCallback callback, const std::u16string& title)
: LabelButton(std::move(callback), title) {
ConfigureInkDropForToolbar(this);
SetImageLabelSpacing(ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_LABEL_HORIZONTAL_LIST));
views::InstallPillHighlightPathGenerator(this);
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
show_animation_ = std::make_unique<gfx::SlideAnimation>(this);
if (!animations_enabled) {
// For some reason during testing the events generated by animating
// throw off the test. So, don't animate while testing.
show_animation_->Reset(1);
} else {
show_animation_->Show();
}
}
BookmarkButtonBase(const BookmarkButtonBase&) = delete;
BookmarkButtonBase& operator=(const BookmarkButtonBase&) = delete;
View* GetTooltipHandlerForPoint(const gfx::Point& point) override {
return HitTestPoint(point) && GetCanProcessEventsWithinSubtree() ? this
: nullptr;
}
bool IsTriggerableEvent(const ui::Event& e) override {
return e.type() == ui::ET_GESTURE_TAP ||
e.type() == ui::ET_GESTURE_TAP_DOWN ||
event_utils::IsPossibleDispositionEvent(e);
}
// LabelButton:
std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override {
return CreateBookmarkButtonBorder();
}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
views::LabelButton::GetAccessibleNodeData(node_data);
node_data->AddStringAttribute(
ax::mojom::StringAttribute::kRoleDescription,
l10n_util::GetStringUTF8(IDS_ACCNAME_BOOKMARK_BUTTON_ROLE_DESCRIPTION));
}
private:
std::unique_ptr<gfx::SlideAnimation> show_animation_;
};
BEGIN_METADATA(BookmarkButtonBase, views::LabelButton)
END_METADATA
// BookmarkButton -------------------------------------------------------------
// Buttons used for the bookmarks on the bookmark bar.
class BookmarkButton : public BookmarkButtonBase {
public:
METADATA_HEADER(BookmarkButton);
BookmarkButton(PressedCallback callback,
const GURL& url,
const std::u16string& title)
: BookmarkButtonBase(std::move(callback), title), url_(url) {}
BookmarkButton(const BookmarkButton&) = delete;
BookmarkButton& operator=(const BookmarkButton&) = delete;
// views::View:
std::u16string GetTooltipText(const gfx::Point& p) const override {
const views::TooltipManager* tooltip_manager =
GetWidget()->GetTooltipManager();
gfx::Point location(p);
ConvertPointToScreen(this, &location);
// Also update when the maximum width for tooltip has changed because the
// it may be elided differently.
int max_tooltip_width = tooltip_manager->GetMaxWidth(location);
if (tooltip_text_.empty() || max_tooltip_width != max_tooltip_width_) {
max_tooltip_width_ = max_tooltip_width;
tooltip_text_ = BookmarkBarView::CreateToolTipForURLAndTitle(
max_tooltip_width_, tooltip_manager->GetFontList(), url_, GetText());
}
return tooltip_text_;
}
void SetText(const std::u16string& text) override {
BookmarkButtonBase::SetText(text);
tooltip_text_.clear();
}
private:
// A cached value of maximum width for tooltip to skip generating
// new tooltip text.
mutable int max_tooltip_width_ = 0;
mutable std::u16string tooltip_text_;
const GURL& url_;
};
BEGIN_METADATA(BookmarkButton, BookmarkButtonBase)
END_METADATA
// ShortcutButton -------------------------------------------------------------
// Buttons used for the shortcuts on the bookmark bar.
class ShortcutButton : public BookmarkButtonBase {
public:
METADATA_HEADER(ShortcutButton);
ShortcutButton(PressedCallback callback, const std::u16string& title)
: BookmarkButtonBase(std::move(callback), title) {}
ShortcutButton(const ShortcutButton&) = delete;
ShortcutButton& operator=(const ShortcutButton&) = delete;
};
BEGIN_METADATA(ShortcutButton, BookmarkButtonBase)
END_METADATA
// BookmarkMenuButtonBase -----------------------------------------------------
// Base class for menu hosting buttons used on the bookmark bar.
class BookmarkMenuButtonBase : public MenuButton {
public:
METADATA_HEADER(BookmarkMenuButtonBase);
explicit BookmarkMenuButtonBase(
PressedCallback callback,
const std::u16string& title = std::u16string())
: MenuButton(std::move(callback), title) {
ConfigureInkDropForToolbar(this);
SetImageLabelSpacing(ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_LABEL_HORIZONTAL_LIST));
views::InstallPillHighlightPathGenerator(this);
}
BookmarkMenuButtonBase(const BookmarkMenuButtonBase&) = delete;
BookmarkMenuButtonBase& operator=(const BookmarkMenuButtonBase&) = delete;
// MenuButton:
std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override {
return CreateBookmarkButtonBorder();
}
};
BEGIN_METADATA(BookmarkMenuButtonBase, MenuButton)
END_METADATA
// BookmarkFolderButton -------------------------------------------------------
// Buttons used for folders on the bookmark bar, including the 'other folders'
// button.
class BookmarkFolderButton : public BookmarkMenuButtonBase {
public:
METADATA_HEADER(BookmarkFolderButton);
explicit BookmarkFolderButton(PressedCallback callback,
const std::u16string& title = std::u16string())
: BookmarkMenuButtonBase(std::move(callback), title) {
show_animation_ = std::make_unique<gfx::SlideAnimation>(this);
if (!animations_enabled) {
// For some reason during testing the events generated by animating
// throw off the test. So, don't animate while testing.
show_animation_->Reset(1);
} else {
show_animation_->Show();
}
// ui::EF_MIDDLE_MOUSE_BUTTON opens all bookmarked links in separate tabs.
SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON |
ui::EF_MIDDLE_MOUSE_BUTTON);
}
BookmarkFolderButton(const BookmarkFolderButton&) = delete;
BookmarkFolderButton& operator=(const BookmarkFolderButton&) = delete;
std::u16string GetTooltipText(const gfx::Point& p) const override {
return label()->GetPreferredSize().width() > label()->size().width()
? GetText()
: std::u16string();
}
bool OnMousePressed(const ui::MouseEvent& event) override {
if (event.IsOnlyLeftMouseButton()) {
// TODO(bruthig): The ACTION_PENDING triggering logic should be in
// MenuButton::OnPressed() however there is a bug with the pressed state
// logic in MenuButton. See http://crbug.com/567252.
views::InkDrop::Get(this)->AnimateToState(
views::InkDropState::ACTION_PENDING, &event);
}
return BookmarkMenuButtonBase::OnMousePressed(event);
}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
BookmarkMenuButtonBase::GetAccessibleNodeData(node_data);
node_data->AddStringAttribute(
ax::mojom::StringAttribute::kRoleDescription,
l10n_util::GetStringUTF8(
IDS_ACCNAME_BOOKMARK_FOLDER_BUTTON_ROLE_DESCRIPTION));
}
private:
std::unique_ptr<gfx::SlideAnimation> show_animation_;
};
BEGIN_METADATA(BookmarkFolderButton, BookmarkMenuButtonBase)
END_METADATA
// OverflowButton (chevron) --------------------------------------------------
class OverflowButton : public BookmarkMenuButtonBase {
public:
METADATA_HEADER(OverflowButton);
OverflowButton(PressedCallback callback, BookmarkBarView* owner)
: BookmarkMenuButtonBase(std::move(callback)), owner_(owner) {}
OverflowButton(const OverflowButton&) = delete;
OverflowButton& operator=(const OverflowButton&) = delete;
bool OnMousePressed(const ui::MouseEvent& e) override {
owner_->StopThrobbing(true);
return BookmarkMenuButtonBase::OnMousePressed(e);
}
private:
BookmarkBarView* owner_;
};
void RecordAppLaunch(Profile* profile, const GURL& url) {
const extensions::Extension* extension =
extensions::ExtensionRegistry::Get(profile)
->enabled_extensions()
.GetAppByURL(url);
if (!extension)
return;
extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_BOOKMARK_BAR,
extension->GetType());
}
BEGIN_METADATA(OverflowButton, BookmarkMenuButtonBase)
END_METADATA
} // namespace
// DropLocation ---------------------------------------------------------------
struct BookmarkBarView::DropLocation {
bool Equals(const DropLocation& other) {
return ((other.index == index) && (other.on == on) &&
(other.button_type == button_type));
}
// Index into the model the drop is over. This is relative to the root node.
absl::optional<size_t> index;
// Drop constants.
DragOperation operation = DragOperation::kNone;
// If true, the user is dropping on a folder.
bool on = false;
// Type of button.
DropButtonType button_type = DROP_BOOKMARK;
};
// DropInfo -------------------------------------------------------------------
// Tracks drops on the BookmarkBarView.
struct BookmarkBarView::DropInfo {
// Whether the data is valid.
bool valid = false;
// If true, the menu is being shown.
bool is_menu_showing = false;
// Coordinates of the drag (in terms of the BookmarkBarView).
int x = 0;
int y = 0;
// DropData for the drop.
bookmarks::BookmarkNodeData data;
DropLocation location;
};
// ButtonSeparatorView --------------------------------------------------------
class BookmarkBarView::ButtonSeparatorView : public views::Separator {
public:
METADATA_HEADER(ButtonSeparatorView);
ButtonSeparatorView() {
// Total width of the separator and surrounding padding.
constexpr int kSeparatorWidth = 9;
constexpr int kPaddingWidth = kSeparatorWidth - kThickness;
constexpr int kLeadingPadding = (kPaddingWidth + 1) / 2;
SetBorder(views::CreateEmptyBorder(0, kLeadingPadding, 0,
kPaddingWidth - kLeadingPadding));
SetPreferredHeight(gfx::kFaviconSize);
}
ButtonSeparatorView(const ButtonSeparatorView&) = delete;
ButtonSeparatorView& operator=(const ButtonSeparatorView&) = delete;
~ButtonSeparatorView() override = default;
void OnThemeChanged() override {
views::Separator::OnThemeChanged();
SetColor(GetThemeProvider()->GetColor(
ThemeProperties::COLOR_TOOLBAR_VERTICAL_SEPARATOR));
}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
node_data->SetName(l10n_util::GetStringUTF8(IDS_ACCNAME_SEPARATOR));
node_data->role = ax::mojom::Role::kSplitter;
}
};
using ButtonSeparatorView = BookmarkBarView::ButtonSeparatorView;
BEGIN_METADATA(ButtonSeparatorView, views::Separator)
END_METADATA
// BookmarkBarView ------------------------------------------------------------
BookmarkBarView::BookmarkBarView(Browser* browser, BrowserView* browser_view)
: AnimationDelegateViews(this),
browser_(browser),
browser_view_(browser_view) {
SetID(VIEW_ID_BOOKMARK_BAR);
Init();
// TODO(lgrey): This layer was introduced to support clipping the bookmark
// bar to bounds to prevent it from drawing over the toolbar while animating.
// This is no longer necessary, so the masking was removed; however removing
// the layer now makes the animation jerky (or jerkier). The animation should
// be fixed and, if the layer is no longer necessary, itshould be removed.
// See https://crbug.com/844037.
SetPaintToLayer();
size_animation_.Reset(1);
if (!gfx::Animation::ShouldRenderRichAnimation())
animations_enabled = false;
// May be null for tests.
if (browser_view)
SetBackground(std::make_unique<TopContainerBackground>(browser_view));
views::FocusRing::SetBackgroundColorIdForSubtree(
this, ThemeProperties::COLOR_TOOLBAR);
}
BookmarkBarView::~BookmarkBarView() {
if (model_)
model_->RemoveObserver(this);
// It's possible for the menu to outlive us, reset the observer to make sure
// it doesn't have a reference to us.
if (bookmark_menu_) {
bookmark_menu_->set_observer(nullptr);
bookmark_menu_->clear_bookmark_bar();
}
StopShowFolderDropMenuTimer();
}
// static
void BookmarkBarView::DisableAnimationsForTesting(bool disabled) {
animations_enabled = !disabled;
}
void BookmarkBarView::AddObserver(BookmarkBarViewObserver* observer) {
observers_.AddObserver(observer);
}
void BookmarkBarView::RemoveObserver(BookmarkBarViewObserver* observer) {
observers_.RemoveObserver(observer);
}
void BookmarkBarView::SetPageNavigator(content::PageNavigator* navigator) {
page_navigator_ = navigator;
}
void BookmarkBarView::SetInfoBarVisible(bool infobar_visible) {
if (infobar_visible == infobar_visible_)
return;
infobar_visible_ = infobar_visible;
OnPropertyChanged(&infobar_visible_, views::kPropertyEffectsLayout);
}
bool BookmarkBarView::GetInfoBarVisible() const {
return infobar_visible_;
}
void BookmarkBarView::SetBookmarkBarState(
BookmarkBar::State state,
BookmarkBar::AnimateChangeType animate_type) {
if (animate_type == BookmarkBar::ANIMATE_STATE_CHANGE && animations_enabled) {
if (state == BookmarkBar::SHOW) {
size_animation_.Show();
} else {
if (read_later_button_)
read_later_button_->CloseBubble();
size_animation_.Hide();
}
} else {
size_animation_.Reset(state == BookmarkBar::SHOW ? 1 : 0);
if (!animations_enabled)
AnimationEnded(&size_animation_);
}
bookmark_bar_state_ = state;
}
const BookmarkNode* BookmarkBarView::GetNodeForButtonAtModelIndex(
const gfx::Point& loc,
size_t* model_start_index) {
*model_start_index = 0;
if (loc.x() < 0 || loc.x() >= width() || loc.y() < 0 || loc.y() >= height())
return nullptr;
gfx::Point adjusted_loc(GetMirroredXInView(loc.x()), loc.y());
// Check the managed button first.
if (managed_bookmarks_button_->GetVisible() &&
managed_bookmarks_button_->bounds().Contains(adjusted_loc)) {
return managed_->managed_node();
}
// Then check the bookmark buttons.
for (size_t i = 0; i < bookmark_buttons_.size(); ++i) {
views::View* child = bookmark_buttons_[i];
if (!child->GetVisible())
break;
if (child->bounds().Contains(adjusted_loc))
return model_->bookmark_bar_node()->children()[i].get();
}
// Then the overflow button.
if (overflow_button_->GetVisible() &&
overflow_button_->bounds().Contains(adjusted_loc)) {
*model_start_index = GetFirstHiddenNodeIndex();
return model_->bookmark_bar_node();
}
// And finally the other folder.
if (other_bookmarks_button_->GetVisible() &&
other_bookmarks_button_->bounds().Contains(adjusted_loc)) {
return model_->other_node();
}
return nullptr;
}
MenuButton* BookmarkBarView::GetMenuButtonForNode(const BookmarkNode* node) {
if (node == managed_->managed_node())
return managed_bookmarks_button_;
if (node == model_->other_node())
return other_bookmarks_button_;
if (node == model_->bookmark_bar_node())
return overflow_button_;
int index = model_->bookmark_bar_node()->GetIndexOf(node);
if (index == -1 || !node->is_folder())
return nullptr;
return static_cast<MenuButton*>(
bookmark_buttons_[static_cast<size_t>(index)]);
}
void BookmarkBarView::GetAnchorPositionForButton(
MenuButton* button,
views::MenuAnchorPosition* anchor) {
using Position = views::MenuAnchorPosition;
if (button == other_bookmarks_button_ || button == overflow_button_)
*anchor = Position::kTopRight;
else
*anchor = Position::kTopLeft;
}
views::MenuItemView* BookmarkBarView::GetMenu() {
return bookmark_menu_ ? bookmark_menu_->menu() : nullptr;
}
views::MenuItemView* BookmarkBarView::GetContextMenu() {
return bookmark_menu_ ? bookmark_menu_->context_menu() : nullptr;
}
views::MenuItemView* BookmarkBarView::GetDropMenu() {
return bookmark_drop_menu_ ? bookmark_drop_menu_->menu() : nullptr;
}
void BookmarkBarView::StopThrobbing(bool immediate) {
if (!throbbing_view_)
return;
// If not immediate, cycle through 2 more complete cycles.
throbbing_view_->StartThrobbing(immediate ? 0 : 4);
throbbing_view_ = nullptr;
}
// static
std::u16string BookmarkBarView::CreateToolTipForURLAndTitle(
int max_width,
const gfx::FontList& tt_fonts,
const GURL& url,
const std::u16string& title) {
std::u16string result;
// First the title.
if (!title.empty()) {
std::u16string localized_title = title;
base::i18n::AdjustStringForLocaleDirection(&localized_title);
result.append(
gfx::ElideText(localized_title, tt_fonts, max_width, gfx::ELIDE_TAIL));
}
// Only show the URL if the url and title differ.
if (title != base::UTF8ToUTF16(url.spec())) {
if (!result.empty())
result.push_back('\n');
// We need to explicitly specify the directionality of the URL's text to
// make sure it is treated as an LTR string when the context is RTL. For
// example, the URL "http://www.yahoo.com/" appears as
// "/http://www.yahoo.com" when rendered, as is, in an RTL context since
// the Unicode BiDi algorithm puts certain characters on the left by
// default.
std::u16string elided_url(
url_formatter::ElideUrl(url, tt_fonts, max_width));
elided_url = base::i18n::GetDisplayStringInLTRDirectionality(elided_url);
result.append(elided_url);
}
return result;
}
gfx::Size BookmarkBarView::CalculatePreferredSize() const {
gfx::Size prefsize;
int preferred_height = GetLayoutConstant(BOOKMARK_BAR_HEIGHT);
prefsize.set_height(
static_cast<int>(preferred_height * size_animation_.GetCurrentValue()));
return prefsize;
}
gfx::Size BookmarkBarView::GetMinimumSize() const {
// The minimum width of the bookmark bar should at least contain the overflow
// button, by which one can access all the Bookmark Bar items, and the "Other
// Bookmarks" folder, along with appropriate margins and button padding.
// It should also contain the Managed Bookmarks folder, if it is visible.
int width = kBookmarkBarHorizontalMargin;
int height = GetLayoutConstant(BOOKMARK_BAR_HEIGHT);
const int bookmark_bar_button_padding =
GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
if (managed_bookmarks_button_->GetVisible()) {
gfx::Size size = managed_bookmarks_button_->GetPreferredSize();
width += size.width() + bookmark_bar_button_padding;
}
if (other_bookmarks_button_->GetVisible()) {
gfx::Size size = other_bookmarks_button_->GetPreferredSize();
width += size.width() + bookmark_bar_button_padding;
}
if (overflow_button_->GetVisible()) {
gfx::Size size = overflow_button_->GetPreferredSize();
width += size.width() + bookmark_bar_button_padding;
}
if (bookmarks_separator_view_->GetVisible()) {
gfx::Size size = bookmarks_separator_view_->GetPreferredSize();
width += size.width();
}
if (apps_page_shortcut_->GetVisible()) {
gfx::Size size = apps_page_shortcut_->GetPreferredSize();
width += size.width() + bookmark_bar_button_padding;
}
if (read_later_button_ && read_later_button_->GetVisible()) {
gfx::Size separator_size = read_later_separator_view_->GetPreferredSize();
gfx::Size size = read_later_button_->GetPreferredSize();
width +=
separator_size.width() + size.width() + bookmark_bar_button_padding;
}
return gfx::Size(width, height);
}
void BookmarkBarView::Layout() {
// Skip layout during destruction, when no model exists.
if (!model_)
return;
int x = kBookmarkBarHorizontalMargin;
int width = View::width() - 2 * kBookmarkBarHorizontalMargin;
const int button_height = GetLayoutConstant(BOOKMARK_BAR_BUTTON_HEIGHT);
// Bookmark bar buttons should be centered between the bottom of the location
// bar and the bottom of the bookmarks bar, which requires factoring in the
// bottom margin of the toolbar into the button position.
int toolbar_bottom_margin = 0;
// Note: |browser_view_| may be null during tests.
if (browser_view_ && !browser_view_->IsFullscreen()) {
toolbar_bottom_margin =
browser_view_->toolbar()->height() -
browser_view_->GetLocationBarView()->bounds().bottom();
}
// Center the buttons in the total available space.
const int total_height = GetContentsBounds().height() + toolbar_bottom_margin;
const auto center_y = [total_height, toolbar_bottom_margin](int height) {
const int top_margin = (total_height - height) / 2;
// Calculate the top inset in the bookmarks bar itself (not counting the
// space in the toolbar) but do not allow the buttons to leave the bookmarks
// bar.
return std::max(0, top_margin - toolbar_bottom_margin);
};
const int y = center_y(button_height);
gfx::Size other_bookmarks_pref =
other_bookmarks_button_->GetVisible()
? other_bookmarks_button_->GetPreferredSize()
: gfx::Size();
gfx::Size overflow_pref = overflow_button_->GetPreferredSize();
gfx::Size bookmarks_separator_pref =
bookmarks_separator_view_->GetPreferredSize();
gfx::Size apps_page_shortcut_pref =
apps_page_shortcut_->GetVisible()
? apps_page_shortcut_->GetPreferredSize()
: gfx::Size();
const int bookmark_bar_button_padding =
GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
int max_x = kBookmarkBarHorizontalMargin + width - overflow_pref.width() -
bookmarks_separator_pref.width();
if (other_bookmarks_button_->GetVisible()) {
max_x -= other_bookmarks_pref.width();
// Additional spacing is only needed for this button if it is the last
// button in the bookmark bar. When the read later button exists this is no
// longer the last button.
if (!read_later_button_ || !read_later_button_->GetVisible())
max_x -= bookmark_bar_button_padding;
}
if (read_later_button_ && read_later_button_->GetVisible()) {
if (bookmarks_separator_view_->GetVisible())
max_x -= bookmarks_separator_pref.width();
max_x -= read_later_button_->GetPreferredSize().width() +
bookmark_bar_button_padding;
}
// Start with the apps page shortcut button.
if (apps_page_shortcut_->GetVisible()) {
apps_page_shortcut_->SetBounds(x, y, apps_page_shortcut_pref.width(),
button_height);
x += apps_page_shortcut_pref.width() + bookmark_bar_button_padding;
}
// Then comes the managed bookmarks folder, if visible.
if (managed_bookmarks_button_->GetVisible()) {
gfx::Size managed_bookmarks_pref =
managed_bookmarks_button_->GetPreferredSize();
managed_bookmarks_button_->SetBounds(x, y, managed_bookmarks_pref.width(),
button_height);
x += managed_bookmarks_pref.width() + bookmark_bar_button_padding;
}
if (model_->loaded() && !model_->bookmark_bar_node()->children().empty()) {
bool last_visible = x < max_x;
size_t button_count = bookmark_buttons_.size();
for (size_t i = 0; i <= button_count; ++i) {
if (i == button_count) {
// Add another button if there is room for it (and there is another
// button to load).
if (!last_visible || !model_->loaded() ||
model_->bookmark_bar_node()->children().size() <= button_count)
break;
InsertBookmarkButtonAtIndex(
CreateBookmarkButton(
model_->bookmark_bar_node()->children()[i].get()),
i);
button_count = bookmark_buttons_.size();
}
views::View* child = bookmark_buttons_[i];
gfx::Size pref = child->GetPreferredSize();
int next_x = x + pref.width() + bookmark_bar_button_padding;
last_visible = next_x < max_x;
child->SetVisible(last_visible);
// Only need to set bounds if the view is actually visible.
if (last_visible)
child->SetBounds(x, y, pref.width(), button_height);
x = next_x;
}
}
// Layout the right side buttons.
x = max_x + bookmark_bar_button_padding;
// The overflow button.
overflow_button_->SetBounds(x, y, overflow_pref.width(), button_height);
const bool show_overflow =
model_->loaded() &&
(model_->bookmark_bar_node()->children().size() >
bookmark_buttons_.size() ||
(!bookmark_buttons_.empty() && !bookmark_buttons_.back()->GetVisible()));
overflow_button_->SetVisible(show_overflow);
x += overflow_pref.width();
// Separator.
if (bookmarks_separator_view_->GetVisible()) {
bookmarks_separator_view_->SetBounds(
x, center_y(bookmarks_separator_pref.height()),
bookmarks_separator_pref.width(), bookmarks_separator_pref.height());
x += bookmarks_separator_pref.width();
}
// The "Other Bookmarks" button.
if (other_bookmarks_button_->GetVisible()) {
other_bookmarks_button_->SetBounds(x, y, other_bookmarks_pref.width(),
button_height);
x += other_bookmarks_pref.width();
// Additional spacing is only needed for the last button in the bookmark
// bar. When the read later button exists this is no longer the last button.
if (!read_later_button_ || !read_later_button_->GetVisible())
x += bookmark_bar_button_padding;
}
// Read-later button and separator.
if (read_later_button_ && read_later_button_->GetVisible()) {
gfx::Size read_later_separator_pref =
read_later_separator_view_->GetPreferredSize();
gfx::Size read_later_pref = read_later_button_->GetPreferredSize();
read_later_separator_view_->SetBounds(
x, center_y(read_later_separator_pref.height()),
read_later_separator_pref.width(), read_later_separator_pref.height());
x += read_later_separator_pref.width();
read_later_button_->SetBounds(x, y, read_later_pref.width(), button_height);
x += read_later_pref.width() + bookmark_bar_button_padding;
}
}
void BookmarkBarView::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
if (details.is_add && details.child == this) {
// We may get inserted into a hierarchy with a profile - this typically
// occurs when the bar's contents get populated fast enough that the
// buttons are created before the bar is attached to a frame.
UpdateAppearanceForTheme();
if (height() > 0) {
// We only layout while parented. When we become parented, if our bounds
// haven't changed, OnBoundsChanged() won't get invoked and we won't
// layout. Therefore we always force a layout when added.
Layout();
}
}
}
void BookmarkBarView::PaintChildren(const views::PaintInfo& paint_info) {
View::PaintChildren(paint_info);
if (drop_info_ && drop_info_->valid &&
drop_info_->location.operation != DragOperation::kNone &&
drop_info_->location.index.has_value() &&
drop_info_->location.button_type != DROP_OVERFLOW &&
!drop_info_->location.on) {
size_t index = drop_info_->location.index.value();
DCHECK_LE(index, bookmark_buttons_.size());
int x = 0;
int y = 0;
int h = height();
if (index == bookmark_buttons_.size()) {
if (index != 0)
x = bookmark_buttons_[index - 1]->bounds().right();
else if (managed_bookmarks_button_->GetVisible())
x = managed_bookmarks_button_->bounds().right();
else if (apps_page_shortcut_->GetVisible())
x = apps_page_shortcut_->bounds().right();
else
x = kBookmarkBarHorizontalMargin;
} else {
x = bookmark_buttons_[index]->x();
}
if (!bookmark_buttons_.empty() && bookmark_buttons_.front()->GetVisible()) {
y = bookmark_buttons_.front()->y();
h = bookmark_buttons_.front()->height();
}
// Since the drop indicator is painted directly onto the canvas, we must
// make sure it is painted in the right location if the locale is RTL.
constexpr int kDropIndicatorWidth = 2;
gfx::Rect indicator_bounds = GetMirroredRect(
gfx::Rect(x - kDropIndicatorWidth / 2, y, kDropIndicatorWidth, h));
ui::PaintRecorder recorder(paint_info.context(), size());
// TODO(sky/glen): make me pretty!
recorder.canvas()->FillRect(
indicator_bounds,
GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
}
}
bool BookmarkBarView::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
if (!model_ || !model_->loaded())
return false;
*formats = ui::OSExchangeData::URL;
format_types->insert(bookmarks::BookmarkNodeData::GetBookmarkFormatType());
return true;
}
bool BookmarkBarView::AreDropTypesRequired() {
return true;
}
bool BookmarkBarView::CanDrop(const ui::OSExchangeData& data) {
if (!model_ || !model_->loaded() ||
!browser_->profile()->GetPrefs()->GetBoolean(
bookmarks::prefs::kEditBookmarksEnabled))
return false;
if (!drop_info_.get())
drop_info_ = std::make_unique<DropInfo>();
// Only accept drops of 1 node, which is the case for all data dragged from
// bookmark bar and menus.
return drop_info_->data.Read(data) && drop_info_->data.size() == 1;
}
void BookmarkBarView::OnDragEntered(const ui::DropTargetEvent& event) {}
int BookmarkBarView::OnDragUpdated(const ui::DropTargetEvent& event) {
if (!drop_info_.get())
return 0;
if (drop_info_->valid &&
(drop_info_->x == event.x() && drop_info_->y == event.y())) {
// The location of the mouse didn't change, return the last operation.
return static_cast<int>(drop_info_->location.operation);
}
drop_info_->x = event.x();
drop_info_->y = event.y();
DropLocation location;
CalculateDropLocation(event, drop_info_->data, &location);
if (drop_info_->valid && drop_info_->location.Equals(location)) {
// The position we're going to drop didn't change, return the last drag
// operation we calculated. Copy of the operation in case it changed.
drop_info_->location.operation = location.operation;
return static_cast<int>(drop_info_->location.operation);
}
StopShowFolderDropMenuTimer();
// TODO(sky): Optimize paint region.
SchedulePaint();
drop_info_->location = location;
drop_info_->valid = true;
if (drop_info_->is_menu_showing) {
if (bookmark_drop_menu_)
bookmark_drop_menu_->Cancel();
drop_info_->is_menu_showing = false;
}
if (location.on || location.button_type == DROP_OVERFLOW ||
location.button_type == DROP_OTHER_FOLDER) {
const BookmarkNode* node;
if (location.button_type == DROP_OTHER_FOLDER) {
node = model_->other_node();
} else if (location.button_type == DROP_OVERFLOW) {
node = model_->bookmark_bar_node();
} else {
node =
model_->bookmark_bar_node()->children()[location.index.value()].get();
}
StartShowFolderDropMenuTimer(node);
}
return static_cast<int>(drop_info_->location.operation);
}
void BookmarkBarView::OnDragExited() {
StopShowFolderDropMenuTimer();
// NOTE: we don't hide the menu on exit as it's possible the user moved the
// mouse over the menu, which triggers an exit on us.
if (drop_info_->location.index.has_value()) {
// TODO(sky): optimize the paint region.
SchedulePaint();
}
drop_info_.reset();
}
DragOperation BookmarkBarView::OnPerformDrop(const ui::DropTargetEvent& event) {
StopShowFolderDropMenuTimer();
if (bookmark_drop_menu_)
bookmark_drop_menu_->Cancel();
if (!drop_info_ || !drop_info_->valid ||
drop_info_->location.operation == DragOperation::kNone)
return DragOperation::kNone;
size_t index = -1;
const bookmarks::BookmarkNode* parent_node =
GetParentNodeAndIndexForDrop(index);
bool copy = drop_info_->location.operation == DragOperation::kCopy;
DragOperation output_drag_op = DragOperation::kNone;
bookmarks::BookmarkNodeData drop_data = drop_info_->data;
drop_info_.reset();
PerformDrop(std::move(drop_data), parent_node, index, copy, event,
output_drag_op);
return output_drag_op;
}
views::View::DropCallback BookmarkBarView::GetDropCallback(
const ui::DropTargetEvent& event) {
StopShowFolderDropMenuTimer();
if (bookmark_drop_menu_)
bookmark_drop_menu_->Cancel();
if (!drop_info_ || !drop_info_->valid ||
drop_info_->location.operation == DragOperation::kNone)
return base::NullCallback();
size_t index = -1;
const bookmarks::BookmarkNode* parent_node =
GetParentNodeAndIndexForDrop(index);
bool copy = drop_info_->location.operation == DragOperation::kCopy;
bookmarks::BookmarkNodeData drop_data = drop_info_->data;
drop_info_.reset();
return base::BindOnce(&BookmarkBarView::PerformDrop,
drop_weak_ptr_factory_.GetWeakPtr(),
std::move(drop_data), parent_node, index, copy);
}
void BookmarkBarView::OnThemeChanged() {
views::AccessiblePaneView::OnThemeChanged();
UpdateAppearanceForTheme();
}
void BookmarkBarView::VisibilityChanged(View* starting_from, bool is_visible) {
AccessiblePaneView::VisibilityChanged(starting_from, is_visible);
if (starting_from == this) {
for (BookmarkBarViewObserver& observer : observers_)
observer.OnBookmarkBarVisibilityChanged();
}
}
void BookmarkBarView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kToolbar;
node_data->SetName(l10n_util::GetStringUTF8(IDS_ACCNAME_BOOKMARKS));
}
void BookmarkBarView::AnimationProgressed(const gfx::Animation* animation) {
// |browser_view_| can be null during tests.
if (browser_view_)
browser_view_->ToolbarSizeChanged(true);
}
void BookmarkBarView::AnimationEnded(const gfx::Animation* animation) {
// |browser_view_| can be null during tests.
if (browser_view_) {
browser_view_->ToolbarSizeChanged(false);
SchedulePaint();
}
}
void BookmarkBarView::BookmarkMenuControllerDeleted(
BookmarkMenuController* controller) {
if (controller == bookmark_menu_)
bookmark_menu_ = nullptr;
else if (controller == bookmark_drop_menu_)
bookmark_drop_menu_ = nullptr;
}
void BookmarkBarView::OnBookmarkBubbleShown(const BookmarkNode* node) {
StopThrobbing(true);
if (!node)
return; // Generally shouldn't happen.
StartThrobbing(node, false);
}
void BookmarkBarView::OnBookmarkBubbleHidden() {
StopThrobbing(false);
}
void BookmarkBarView::BookmarkModelLoaded(BookmarkModel* model,
bool ids_reassigned) {
// There should be no buttons. If non-zero it means Load was invoked more than
// once, or we didn't properly clear things. Either of which shouldn't happen.
// The actual bookmark buttons are added from Layout().
DCHECK(bookmark_buttons_.empty());
DCHECK(model->other_node());
other_bookmarks_button_->SetAccessibleName(model->other_node()->GetTitle());
other_bookmarks_button_->SetText(model->other_node()->GetTitle());
managed_bookmarks_button_->SetAccessibleName(
managed_->managed_node()->GetTitle());
managed_bookmarks_button_->SetText(managed_->managed_node()->GetTitle());
UpdateAppearanceForTheme();
UpdateOtherAndManagedButtonsVisibility();
other_bookmarks_button_->SetEnabled(true);
managed_bookmarks_button_->SetEnabled(true);
LayoutAndPaint();
}
void BookmarkBarView::BookmarkModelBeingDeleted(BookmarkModel* model) {
NOTREACHED();
// Do minimal cleanup, presumably we'll be deleted shortly.
model_->RemoveObserver(this);
model_ = nullptr;
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkNodeMoved(BookmarkModel* model,
const BookmarkNode* old_parent,
size_t old_index,
const BookmarkNode* new_parent,
size_t new_index) {
// It is extremely rare for the model to mutate during a drop. Rather than
// trying to validate the location (which may no longer be valid), this takes
// the simple route of marking the drop as invalid. If the user moves the
// mouse/touch-device, the location will update accordingly.
InvalidateDrop();
bool was_throbbing =
throbbing_view_ &&
throbbing_view_ == DetermineViewToThrobFromRemove(old_parent, old_index);
if (was_throbbing)
throbbing_view_->StopThrobbing();
bool needs_layout_and_paint =
BookmarkNodeRemovedImpl(model, old_parent, old_index);
if (BookmarkNodeAddedImpl(model, new_parent, new_index))
needs_layout_and_paint = true;
if (was_throbbing && new_index < bookmark_buttons_.size())
StartThrobbing(new_parent->children()[new_index].get(), false);
if (needs_layout_and_paint)
LayoutAndPaint();
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkNodeAdded(BookmarkModel* model,
const BookmarkNode* parent,
size_t index) {
// See comment in BookmarkNodeMoved() for details on this.
InvalidateDrop();
if (BookmarkNodeAddedImpl(model, parent, index))
LayoutAndPaint();
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkNodeRemoved(BookmarkModel* model,
const BookmarkNode* parent,
size_t old_index,
const BookmarkNode* node,
const std::set<GURL>& removed_urls) {
// See comment in BookmarkNodeMoved() for details on this.
InvalidateDrop();
// Close the menu if the menu is showing for the deleted node.
if (bookmark_menu_ && bookmark_menu_->node() == node)
bookmark_menu_->Cancel();
if (BookmarkNodeRemovedImpl(model, parent, old_index))
LayoutAndPaint();
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkAllUserNodesRemoved(
BookmarkModel* model,
const std::set<GURL>& removed_urls) {
// See comment in BookmarkNodeMoved() for details on this.
InvalidateDrop();
UpdateOtherAndManagedButtonsVisibility();
StopThrobbing(true);
// Remove the existing buttons.
for (views::LabelButton* button : bookmark_buttons_) {
delete button;
}
bookmark_buttons_.clear();
LayoutAndPaint();
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkNodeChanged(BookmarkModel* model,
const BookmarkNode* node) {
BookmarkNodeChangedImpl(model, node);
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkNodeChildrenReordered(BookmarkModel* model,
const BookmarkNode* node) {
// See comment in BookmarkNodeMoved() for details on this.
InvalidateDrop();
if (node != model->bookmark_bar_node())
return; // We only care about reordering of the bookmark bar node.
// Remove the existing buttons.
for (views::LabelButton* button : bookmark_buttons_) {
delete button;
}
bookmark_buttons_.clear();
// Create the new buttons.
for (size_t i = 0; i < node->children().size(); ++i) {
InsertBookmarkButtonAtIndex(CreateBookmarkButton(node->children()[i].get()),
i);
}
LayoutAndPaint();
drop_weak_ptr_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::BookmarkNodeFaviconChanged(BookmarkModel* model,
const BookmarkNode* node) {
BookmarkNodeChangedImpl(model, node);
}
void BookmarkBarView::WriteDragDataForView(View* sender,
const gfx::Point& press_pt,
ui::OSExchangeData* data) {
base::RecordAction(base::UserMetricsAction("BookmarkBar_DragButton"));
const auto* node = GetNodeForSender(sender);
ui::ImageModel icon;
views::Widget* widget = sender->GetWidget();
if (node->is_url()) {
const gfx::Image& image = model_->GetFavicon(node);
icon = image.IsEmpty()
? ui::ImageModel::FromImage(favicon::GetDefaultFavicon())
: ui::ImageModel::FromImage(image);
} else {
icon =
chrome::GetBookmarkFolderIcon(widget->GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelEnabledColor));
}
button_drag_utils::SetDragImage(node->url(), node->GetTitle(),
*icon.GetImage().ToImageSkia(), &press_pt,
*widget, data);
WriteBookmarkDragData(node, data);
}
int BookmarkBarView::GetDragOperationsForView(View* sender,
const gfx::Point& p) {
if (size_animation_.is_animating() ||
size_animation_.GetCurrentValue() == 0) {
// Don't let the user drag while animating open or we're closed. This
// typically is only hit if the user does something to inadvertently trigger
// DnD such as pressing the mouse and hitting control-b.
return ui::DragDropTypes::DRAG_NONE;
}
return chrome::GetBookmarkDragOperation(browser_->profile(),
GetNodeForSender(sender));
}
bool BookmarkBarView::CanStartDragForView(views::View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
// Check if we have not moved enough horizontally but we have moved downward
// vertically - downward drag.
gfx::Vector2d move_offset = p - press_pt;
gfx::Vector2d horizontal_offset(move_offset.x(), 0);
if (!View::ExceededDragThreshold(horizontal_offset) && move_offset.y() > 0) {
// If the folder button was dragged, show the menu instead.
const auto* node = GetNodeForSender(sender);
if (node->is_folder()) {
static_cast<MenuButton*>(sender)->Activate(nullptr);
return false;
}
}
return true;
}
void BookmarkBarView::AppsPageShortcutPressed(const ui::Event& event) {
content::OpenURLParams params(GURL(chrome::kChromeUIAppsURL),
content::Referrer(),
ui::DispositionFromEventFlags(event.flags()),
ui::PAGE_TRANSITION_AUTO_BOOKMARK, false);
page_navigator_->OpenURL(params);
RecordBookmarkAppsPageOpen(BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR);
}
void BookmarkBarView::OnButtonPressed(const bookmarks::BookmarkNode* node,
const ui::Event& event) {
DCHECK(page_navigator_);
// Only URL nodes have regular buttons on the bookmarks bar; folder clicks
// are directed to ::OnMenuButtonPressed().
DCHECK(node->is_url());
RecordAppLaunch(browser_->profile(), node->url());
content::OpenURLParams params(node->url(), content::Referrer(),
ui::DispositionFromEventFlags(event.flags()),
ui::PAGE_TRANSITION_AUTO_BOOKMARK, false);
page_navigator_->OpenURL(params);
RecordBookmarkLaunch(
BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR,
profile_metrics::GetBrowserProfileType(browser_->profile()));
}
void BookmarkBarView::OnMenuButtonPressed(const bookmarks::BookmarkNode* node,
const ui::Event& event) {
// Clicking the middle mouse button or clicking with Control/Command key down
// opens all bookmarks in the folder in new tabs.
if ((event.flags() & ui::EF_MIDDLE_MOUSE_BUTTON) ||
(event.flags() & ui::EF_PLATFORM_ACCELERATOR)) {
RecordBookmarkFolderLaunch(BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR);
chrome::OpenAllIfAllowed(browser_, GetPageNavigatorGetter(), {node},
ui::DispositionFromEventFlags(event.flags()));
} else {
RecordBookmarkFolderOpen(BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR);
const size_t start_index =
(node == model_->bookmark_bar_node()) ? GetFirstHiddenNodeIndex() : 0;
bookmark_menu_ =
new BookmarkMenuController(browser_, GetPageNavigatorGetter(),
GetWidget(), node, start_index, false);
bookmark_menu_->set_observer(this);
bookmark_menu_->RunMenuAt(this);
}
}
void BookmarkBarView::ShowContextMenuForViewImpl(
views::View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) {
if (!model_->loaded()) {
// Don't do anything if the model isn't loaded.
return;
}
const BookmarkNode* parent = nullptr;
std::vector<const BookmarkNode*> nodes;
if (source == other_bookmarks_button_) {
parent = model_->other_node();
// Do this so the user can open all bookmarks. BookmarkContextMenu makes
// sure the user can't edit/delete the node in this case.
nodes.push_back(parent);
} else if (source == managed_bookmarks_button_) {
parent = managed_->managed_node();
nodes.push_back(parent);
} else if (source == read_later_button_) {
// Do nothing here for now.
} else if (source != this && source != apps_page_shortcut_) {
// User clicked on one of the bookmark buttons, find which one they
// clicked on, except for the apps page shortcut, which must behave as if
// the user clicked on the bookmark bar background.
size_t bookmark_button_index = GetIndexForButton(source);
DCHECK_NE(static_cast<size_t>(-1), bookmark_button_index);
DCHECK_LT(bookmark_button_index, bookmark_buttons_.size());
const BookmarkNode* node =
model_->bookmark_bar_node()->children()[bookmark_button_index].get();
nodes.push_back(node);
parent = node->parent();
} else {
parent = model_->bookmark_bar_node();
nodes.push_back(parent);
}
// |close_on_remove| only matters for nested menus. We're not nested at this
// point, so this value has no effect.
const bool close_on_remove = true;
context_menu_ = std::make_unique<BookmarkContextMenu>(
GetWidget(), browser_, browser_->profile(), GetPageNavigatorGetter(),
BOOKMARK_LAUNCH_LOCATION_ATTACHED_BAR, parent, nodes, close_on_remove);
context_menu_->RunMenuAt(point, source_type);
}
void BookmarkBarView::Init() {
// Note that at this point we're not in a hierarchy so GetThemeProvider() will
// return nullptr. When we're inserted into a hierarchy, we'll call
// UpdateAppearanceForTheme(), which will set the appropriate colors for all
// the objects added in this function.
// Child views are traversed in the order they are added. Make sure the order
// they are added matches the visual order.
apps_page_shortcut_ = AddChildView(CreateAppsPageShortcutButton());
managed_bookmarks_button_ = AddChildView(CreateManagedBookmarksButton());
// Also re-enabled when the model is loaded.
managed_bookmarks_button_->SetEnabled(false);
overflow_button_ = AddChildView(CreateOverflowButton());
other_bookmarks_button_ = AddChildView(CreateOtherBookmarksButton());
// We'll re-enable when the model is loaded.
other_bookmarks_button_->SetEnabled(false);
if (base::FeatureList::IsEnabled(reading_list::switches::kReadLater) &&
!base::FeatureList::IsEnabled(features::kSidePanel)) {
read_later_separator_view_ =
AddChildView(std::make_unique<ButtonSeparatorView>());
read_later_button_ =
AddChildView(std::make_unique<ReadLaterButton>(browser_));
read_later_button_->set_context_menu_controller(this);
}
profile_pref_registrar_.Init(browser_->profile()->GetPrefs());
profile_pref_registrar_.Add(
bookmarks::prefs::kShowAppsShortcutInBookmarkBar,
base::BindRepeating(
&BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged,
base::Unretained(this)));
if (read_later_button_) {
profile_pref_registrar_.Add(
bookmarks::prefs::kShowReadingListInBookmarkBar,
base::BindRepeating(
&BookmarkBarView::OnReadingListVisibilityPrefChanged,
base::Unretained(this)));
}
profile_pref_registrar_.Add(
bookmarks::prefs::kShowManagedBookmarksInBookmarkBar,
base::BindRepeating(&BookmarkBarView::OnShowManagedBookmarksPrefChanged,
base::Unretained(this)));
apps_page_shortcut_->SetVisible(
chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile()));
if (read_later_button_) {
read_later_button_->SetVisible(
chrome::ShouldShowReadingListInBookmarkBar(browser_->profile()));
}
bookmarks_separator_view_ =
AddChildView(std::make_unique<ButtonSeparatorView>());
UpdateBookmarksSeparatorVisibility();
set_context_menu_controller(this);
model_ = BookmarkModelFactory::GetForBrowserContext(browser_->profile());
managed_ = ManagedBookmarkServiceFactory::GetForProfile(browser_->profile());
if (model_) {
model_->AddObserver(this);
if (model_->loaded())
BookmarkModelLoaded(model_, false);
// else case: we'll receive notification back from the BookmarkModel when
// done loading, then we'll populate the bar.
}
}
size_t BookmarkBarView::GetFirstHiddenNodeIndex() const {
const auto i =
std::find_if(bookmark_buttons_.cbegin(), bookmark_buttons_.cend(),
[](const auto* button) { return !button->GetVisible(); });
return i - bookmark_buttons_.cbegin();
}
std::unique_ptr<MenuButton> BookmarkBarView::CreateOtherBookmarksButton() {
// Title is set in Loaded.
auto button = std::make_unique<BookmarkFolderButton>(base::BindRepeating(
[](BookmarkBarView* bar, const ui::Event& event) {
bar->OnMenuButtonPressed(bar->model_->other_node(), event);
},
base::Unretained(this)));
button->SetID(VIEW_ID_OTHER_BOOKMARKS);
button->set_context_menu_controller(this);
return button;
}
std::unique_ptr<MenuButton> BookmarkBarView::CreateManagedBookmarksButton() {
// Title is set in Loaded.
auto button = std::make_unique<BookmarkFolderButton>(base::BindRepeating(
[](BookmarkBarView* bar, const ui::Event& event) {
bar->OnMenuButtonPressed(bar->managed_->managed_node(), event);
},
base::Unretained(this)));
button->SetID(VIEW_ID_MANAGED_BOOKMARKS);
button->set_context_menu_controller(this);
return button;
}
std::unique_ptr<MenuButton> BookmarkBarView::CreateOverflowButton() {
auto button = std::make_unique<OverflowButton>(
base::BindRepeating(
[](BookmarkBarView* bar, const ui::Event& event) {
bar->OnMenuButtonPressed(bar->model_->bookmark_bar_node(), event);
},
base::Unretained(this)),
this);
// The overflow button's image contains an arrow and therefore it is a
// direction sensitive image and we need to flip it if the UI layout is
// right-to-left.
//
// By default, menu buttons are not flipped because they generally contain
// text and flipping the gfx::Canvas object will break text rendering. Since
// the overflow button does not contain text, we can safely flip it.
button->SetFlipCanvasOnPaintForRTLUI(true);
// Make visible as necessary.
button->SetVisible(false);
// Set accessibility name.
button->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_ACCNAME_BOOKMARKS_CHEVRON));
button->SetTooltipText(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OVERFLOW_BUTTON_TOOLTIP));
return button;
}
std::unique_ptr<views::View> BookmarkBarView::CreateBookmarkButton(
const BookmarkNode* node) {
int index = node->parent()->GetIndexOf(node);
std::unique_ptr<views::LabelButton> button;
if (node->is_url()) {
button = std::make_unique<BookmarkButton>(
base::BindRepeating(&BookmarkBarView::OnButtonPressed,
base::Unretained(this), node),
node->url(), node->GetTitle());
button->GetViewAccessibility().OverrideDescription(url_formatter::FormatUrl(
node->url(), url_formatter::kFormatUrlOmitDefaults,
net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
} else {
button = std::make_unique<BookmarkFolderButton>(
base::BindRepeating(&BookmarkBarView::OnMenuButtonPressed,
base::Unretained(this), node),
node->GetTitle());
button->GetViewAccessibility().OverrideDescription("");
}
ConfigureButton(node, button.get());
bookmark_buttons_.insert(bookmark_buttons_.cbegin() + index, button.get());
return button;
}
std::unique_ptr<views::LabelButton>
BookmarkBarView::CreateAppsPageShortcutButton() {
auto button = std::make_unique<ShortcutButton>(
base::BindRepeating(&BookmarkBarView::AppsPageShortcutPressed,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_NAME));
button->SetTooltipText(
l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_APPS_SHORTCUT_TOOLTIP));
button->SetID(VIEW_ID_BOOKMARK_BAR_ELEMENT);
button->SetImageModel(views::Button::STATE_NORMAL,
ui::ImageModel::FromImageSkia(*GetImageSkiaNamed(
IDR_BOOKMARK_BAR_APPS_SHORTCUT)));
button->set_context_menu_controller(this);
return button;
}
void BookmarkBarView::ConfigureButton(const BookmarkNode* node,
views::LabelButton* button) {
button->SetText(node->GetTitle());
button->SetAccessibleName(node->GetTitle());
button->SetID(VIEW_ID_BOOKMARK_BAR_ELEMENT);
// We don't always have a theme provider (ui tests, for example).
SkColor text_color = gfx::kPlaceholderColor;
const ui::ThemeProvider* const tp = GetThemeProvider();
if (tp) {
text_color = tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
button->SetEnabledTextColors(text_color);
if (node->is_folder()) {
button->SetImageModel(views::Button::STATE_NORMAL,
chrome::GetBookmarkFolderIcon(text_color));
}
}
button->set_context_menu_controller(this);
button->set_drag_controller(this);
if (node->is_url()) {
// Themify chrome:// favicons and the default one. This is similar to
// code in the tabstrip.
bool themify_icon = node->url().SchemeIs(content::kChromeUIScheme);
gfx::ImageSkia favicon = model_->GetFavicon(node).AsImageSkia();
if (favicon.isNull()) {
if (ui::TouchUiController::Get()->touch_ui() && GetThemeProvider()) {
// This favicon currently does not match the default favicon icon used
// elsewhere in the codebase.
// See https://crbug/814447
const gfx::ImageSkia icon =
gfx::CreateVectorIcon(kDefaultTouchFaviconIcon, text_color);
const gfx::ImageSkia mask =
gfx::CreateVectorIcon(kDefaultTouchFaviconMaskIcon, SK_ColorBLACK);
favicon = gfx::ImageSkiaOperations::CreateMaskedImage(icon, mask);
} else {
favicon = favicon::GetDefaultFavicon().AsImageSkia();
}
themify_icon = true;
}
if (themify_icon && tp &&
tp->HasCustomColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON)) {
favicon = gfx::ImageSkiaOperations::CreateColorMask(
favicon, tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON));
}
button->SetImageModel(views::Button::STATE_NORMAL,
ui::ImageModel::FromImageSkia(favicon));
}
constexpr int kMaxButtonWidth = 150;
button->SetMaxSize(gfx::Size(kMaxButtonWidth, 0));
}
bool BookmarkBarView::BookmarkNodeAddedImpl(BookmarkModel* model,
const BookmarkNode* parent,
size_t index) {
const bool needs_layout_and_paint = UpdateOtherAndManagedButtonsVisibility();
if (parent != model->bookmark_bar_node())
return needs_layout_and_paint;
if (index < bookmark_buttons_.size()) {
const BookmarkNode* node = parent->children()[index].get();
InsertBookmarkButtonAtIndex(CreateBookmarkButton(node), index);
return true;
}
// If the new node was added after the last button we've created we may be
// able to fit it. Assume we can by returning true, which forces a Layout()
// and creation of the button (if it fits).
return index == bookmark_buttons_.size();
}
bool BookmarkBarView::BookmarkNodeRemovedImpl(BookmarkModel* model,
const BookmarkNode* parent,
size_t index) {
const bool needs_layout = UpdateOtherAndManagedButtonsVisibility();
StopThrobbing(true);
// No need to start throbbing again as the bookmark bubble can't be up at
// the same time as the user reorders.
if (parent != model->bookmark_bar_node()) {
// Only children of the bookmark_bar_node get buttons.
return needs_layout;
}
if (index >= bookmark_buttons_.size())
return needs_layout;
views::LabelButton* button = bookmark_buttons_[index];
bookmark_buttons_.erase(bookmark_buttons_.cbegin() + index);
// Set not visible before removing to advance focus if needed. See
// crbug.com/1183980. TODO(crbug.com/1189729): remove this workaround if
// FocusManager behavior is changed.
button->SetVisible(false);
RemoveChildViewT(button);
return true;
}
void BookmarkBarView::BookmarkNodeChangedImpl(BookmarkModel* model,
const BookmarkNode* node) {
if (node == managed_->managed_node()) {
// The managed node may have its title updated.
managed_bookmarks_button_->SetAccessibleName(
managed_->managed_node()->GetTitle());
managed_bookmarks_button_->SetText(managed_->managed_node()->GetTitle());
return;
}
if (node->parent() != model->bookmark_bar_node()) {
// We only care about nodes on the bookmark bar.
return;
}
int index = model->bookmark_bar_node()->GetIndexOf(node);
DCHECK_NE(-1, index);
if (static_cast<size_t>(index) >= bookmark_buttons_.size())
return; // Buttons are created as needed.
views::LabelButton* button = bookmark_buttons_[static_cast<size_t>(index)];
const int old_pref_width = button->GetPreferredSize().width();
ConfigureButton(node, button);
if (old_pref_width != button->GetPreferredSize().width())
LayoutAndPaint();
}
void BookmarkBarView::ShowDropFolderForNode(const BookmarkNode* node) {
if (bookmark_drop_menu_) {
if (bookmark_drop_menu_->node() == node) {
// Already showing for the specified node.
return;
}
bookmark_drop_menu_->Cancel();
}
MenuButton* menu_button = GetMenuButtonForNode(node);
if (!menu_button)
return;
size_t start_index = 0;
if (node == model_->bookmark_bar_node())
start_index = GetFirstHiddenNodeIndex();
drop_info_->is_menu_showing = true;
bookmark_drop_menu_ = new BookmarkMenuController(
browser_, GetPageNavigatorGetter(), GetWidget(), node, start_index, true);
bookmark_drop_menu_->set_observer(this);
bookmark_drop_menu_->RunMenuAt(this);
for (BookmarkBarViewObserver& observer : observers_)
observer.OnDropMenuShown();
}
void BookmarkBarView::StopShowFolderDropMenuTimer() {
show_folder_method_factory_.InvalidateWeakPtrs();
}
void BookmarkBarView::StartShowFolderDropMenuTimer(const BookmarkNode* node) {
if (!animations_enabled) {
// So that tests can run as fast as possible disable the delay during
// testing.
ShowDropFolderForNode(node);
return;
}
show_folder_method_factory_.InvalidateWeakPtrs();
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BookmarkBarView::ShowDropFolderForNode,
show_folder_method_factory_.GetWeakPtr(), node),
base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay()));
}
void BookmarkBarView::CalculateDropLocation(
const ui::DropTargetEvent& event,
const bookmarks::BookmarkNodeData& data,
DropLocation* location) {
DCHECK(model_);
DCHECK(model_->loaded());
DCHECK(data.is_valid());
*location = DropLocation();
// The drop event uses the screen coordinates while the child Views are
// always laid out from left to right (even though they are rendered from
// right-to-left on RTL locales). Thus, in order to make sure the drop
// coordinates calculation works, we mirror the event's X coordinate if the
// locale is RTL.
int mirrored_x = GetMirroredXInView(event.x());
bool found = false;
const int other_delta_x = mirrored_x - other_bookmarks_button_->x();
Profile* profile = browser_->profile();
if (other_bookmarks_button_->GetVisible() && other_delta_x >= 0 &&
other_delta_x < other_bookmarks_button_->width()) {
// Mouse is over 'other' folder.
location->button_type = DROP_OTHER_FOLDER;
location->on = true;
found = true;
} else if (bookmark_buttons_.empty()) {
// No bookmarks, accept the drop.
location->index = 0;
const BookmarkNode* node = data.GetFirstNode(model_, profile->GetPath());
int ops = node && managed_->CanBeEditedByUser(node)
? ui::DragDropTypes::DRAG_MOVE
: ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
location->operation = chrome::GetPreferredBookmarkDropOperation(
event.source_operations(), ops);
return;
}
for (size_t i = 0; i < bookmark_buttons_.size() &&
bookmark_buttons_[i]->GetVisible() && !found;
i++) {
views::LabelButton* button = bookmark_buttons_[i];
int button_x = mirrored_x - button->x();
int button_w = button->width();
if (button_x < button_w) {
found = true;
const BookmarkNode* node =
model_->bookmark_bar_node()->children()[i].get();
if (node->is_folder()) {
if (button_x <= views::kDropBetweenPixels) {
location->index = i;
} else if (button_x < button_w - views::kDropBetweenPixels) {
location->index = i;
location->on = true;
} else {
location->index = i + 1;
}
} else if (button_x < button_w / 2) {
location->index = i;
} else {
location->index = i + 1;
}
break;
}
}
if (!found) {
if (overflow_button_->GetVisible()) {
// Are we over the overflow button?
int overflow_delta_x = mirrored_x - overflow_button_->x();
if (overflow_delta_x >= 0 &&
overflow_delta_x < overflow_button_->width()) {
// Mouse is over overflow button.
location->index = GetFirstHiddenNodeIndex();
location->button_type = DROP_OVERFLOW;
} else if (overflow_delta_x < 0) {
// Mouse is after the last visible button but before overflow button;
// use the last visible index.
location->index = GetFirstHiddenNodeIndex();
} else {
return;
}
} else if (!other_bookmarks_button_->GetVisible() ||
mirrored_x < other_bookmarks_button_->x()) {
// Mouse is after the last visible button but before more recently
// bookmarked; use the last visible index.
location->index = GetFirstHiddenNodeIndex();
} else {
return;
}
}
if (location->on) {
const BookmarkNode* parent = (location->button_type == DROP_OTHER_FOLDER)
? model_->other_node()
: model_->bookmark_bar_node()
->children()[location->index.value()]
.get();
location->operation = chrome::GetBookmarkDropOperation(
profile, event, data, parent, parent->children().size());
if (location->operation != DragOperation::kNone && !data.has_single_url() &&
data.GetFirstNode(model_, profile->GetPath()) == parent) {
// Don't open a menu if the node being dragged is the menu to open.
location->on = false;
}
} else {
location->operation = chrome::GetBookmarkDropOperation(
profile, event, data, model_->bookmark_bar_node(),
location->index.value());
}
}
void BookmarkBarView::InvalidateDrop() {
if (drop_info_ && drop_info_->valid) {
drop_info_->valid = false;
SchedulePaint();
}
if (bookmark_drop_menu_)
bookmark_drop_menu_->Cancel();
StopShowFolderDropMenuTimer();
}
const BookmarkNode* BookmarkBarView::GetNodeForSender(View* sender) const {
const auto i =
std::find(bookmark_buttons_.cbegin(), bookmark_buttons_.cend(), sender);
DCHECK(i != bookmark_buttons_.cend());
size_t child = i - bookmark_buttons_.cbegin();
return model_->bookmark_bar_node()->children()[child].get();
}
void BookmarkBarView::WriteBookmarkDragData(const BookmarkNode* node,
ui::OSExchangeData* data) {
DCHECK(node && data);
bookmarks::BookmarkNodeData drag_data(node);
drag_data.Write(browser_->profile()->GetPath(), data);
}
void BookmarkBarView::StartThrobbing(const BookmarkNode* node,
bool overflow_only) {
DCHECK(!throbbing_view_);
// Determine which visible button is showing the bookmark (or is an ancestor
// of the bookmark).
const BookmarkNode* bbn = model_->bookmark_bar_node();
const BookmarkNode* parent_on_bb = node;
while (parent_on_bb) {
const BookmarkNode* parent = parent_on_bb->parent();
if (parent == bbn)
break;
parent_on_bb = parent;
}
if (parent_on_bb) {
size_t index = static_cast<size_t>(bbn->GetIndexOf(parent_on_bb));
if (index >= GetFirstHiddenNodeIndex()) {
// Node is hidden, animate the overflow button.
throbbing_view_ = overflow_button_;
} else if (!overflow_only) {
throbbing_view_ = static_cast<views::Button*>(bookmark_buttons_[index]);
}
} else if (bookmarks::IsDescendantOf(node, managed_->managed_node())) {
throbbing_view_ = managed_bookmarks_button_;
} else if (!overflow_only) {
throbbing_view_ = other_bookmarks_button_;
}
// Use a large number so that the button continues to throb.
if (throbbing_view_)
throbbing_view_->StartThrobbing(std::numeric_limits<int>::max());
}
views::Button* BookmarkBarView::DetermineViewToThrobFromRemove(
const BookmarkNode* parent,
size_t old_index) {
const BookmarkNode* bbn = model_->bookmark_bar_node();
const BookmarkNode* old_node = parent;
size_t old_index_on_bb = old_index;
while (old_node && old_node != bbn) {
const BookmarkNode* parent = old_node->parent();
if (parent == bbn) {
old_index_on_bb = static_cast<size_t>(bbn->GetIndexOf(old_node));
break;
}
old_node = parent;
}
if (old_node) {
if (old_index_on_bb >= GetFirstHiddenNodeIndex()) {
// Node is hidden, animate the overflow button.
return overflow_button_;
}
return static_cast<views::Button*>(bookmark_buttons_[old_index_on_bb]);
}
if (bookmarks::IsDescendantOf(parent, managed_->managed_node()))
return managed_bookmarks_button_;
// Node wasn't on the bookmark bar, use the "Other Bookmarks" button.
return other_bookmarks_button_;
}
void BookmarkBarView::UpdateAppearanceForTheme() {
// We don't always have a theme provider (ui tests, for example).
const ui::ThemeProvider* theme_provider = GetThemeProvider();
if (!theme_provider)
return;
for (size_t i = 0; i < bookmark_buttons_.size(); ++i) {
ConfigureButton(model_->bookmark_bar_node()->children()[i].get(),
bookmark_buttons_[i]);
}
const SkColor color =
theme_provider->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
other_bookmarks_button_->SetEnabledTextColors(color);
other_bookmarks_button_->SetImageModel(views::Button::STATE_NORMAL,
chrome::GetBookmarkFolderIcon(color));
managed_bookmarks_button_->SetEnabledTextColors(color);
managed_bookmarks_button_->SetImageModel(
views::Button::STATE_NORMAL, chrome::GetBookmarkManagedFolderIcon(color));
if (apps_page_shortcut_->GetVisible())
apps_page_shortcut_->SetEnabledTextColors(color);
const SkColor overflow_color =
theme_provider->GetColor(ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
const bool touch_ui = ui::TouchUiController::Get()->touch_ui();
overflow_button_->SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(
touch_ui ? kBookmarkbarTouchOverflowIcon : kOverflowChevronIcon,
overflow_color));
// Redraw the background.
SchedulePaint();
}
bool BookmarkBarView::UpdateOtherAndManagedButtonsVisibility() {
bool has_other_children = !model_->other_node()->children().empty();
bool update_other =
has_other_children != other_bookmarks_button_->GetVisible();
if (update_other) {
other_bookmarks_button_->SetVisible(has_other_children);
UpdateBookmarksSeparatorVisibility();
}
bool show_managed = !managed_->managed_node()->children().empty() &&
browser_->profile()->GetPrefs()->GetBoolean(
bookmarks::prefs::kShowManagedBookmarksInBookmarkBar);
bool update_managed = show_managed != managed_bookmarks_button_->GetVisible();
if (update_managed)
managed_bookmarks_button_->SetVisible(show_managed);
return update_other || update_managed;
}
void BookmarkBarView::UpdateBookmarksSeparatorVisibility() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Ash does not paint the bookmarks separator line because it looks odd on
// the flat background. We keep it present for layout, but don't draw it.
bookmarks_separator_view_->SetVisible(false);
#else
bookmarks_separator_view_->SetVisible(other_bookmarks_button_->GetVisible());
#endif
}
void BookmarkBarView::OnAppsPageShortcutVisibilityPrefChanged() {
DCHECK(apps_page_shortcut_);
// Only perform layout if required.
bool visible =
chrome::ShouldShowAppsShortcutInBookmarkBar(browser_->profile());
if (apps_page_shortcut_->GetVisible() == visible)
return;
apps_page_shortcut_->SetVisible(visible);
UpdateBookmarksSeparatorVisibility();
LayoutAndPaint();
}
void BookmarkBarView::OnReadingListVisibilityPrefChanged() {
DCHECK(read_later_button_);
bool visible =
chrome::ShouldShowReadingListInBookmarkBar(browser_->profile());
if (read_later_button_->GetVisible() == visible)
return;
read_later_button_->CloseBubble();
read_later_button_->SetVisible(visible);
read_later_separator_view_->SetVisible(visible);
LayoutAndPaint();
}
void BookmarkBarView::OnShowManagedBookmarksPrefChanged() {
if (UpdateOtherAndManagedButtonsVisibility())
LayoutAndPaint();
}
void BookmarkBarView::InsertBookmarkButtonAtIndex(
std::unique_ptr<views::View> button,
size_t index) {
// All of the secondary buttons are always in the view hierarchy, even if
// they're not visible. The order should be: [Apps shortcut] [Managed bookmark
// button] ..bookmark buttons.. [Overflow chevron] [Other bookmarks]
#if DCHECK_IS_ON()
auto i = children().cbegin();
DCHECK_EQ(*i++, apps_page_shortcut_);
DCHECK_EQ(*i++, managed_bookmarks_button_);
const auto is_bookmark_button = [this](const auto* v) {
const char* class_name = v->GetClassName();
return (class_name == BookmarkButton::kViewClassName ||
class_name == BookmarkFolderButton::kViewClassName) &&
v != overflow_button_ && v != other_bookmarks_button_;
};
i = std::find_if_not(i, children().cend(), is_bookmark_button);
DCHECK_EQ(*i++, overflow_button_);
DCHECK_EQ(*i++, other_bookmarks_button_);
#endif
AddChildViewAt(std::move(button), GetIndexOf(managed_bookmarks_button_) + 1 +
static_cast<int>(index));
}
size_t BookmarkBarView::GetIndexForButton(views::View* button) {
auto it =
std::find(bookmark_buttons_.cbegin(), bookmark_buttons_.cend(), button);
if (it == bookmark_buttons_.cend())
return static_cast<size_t>(-1);
return static_cast<size_t>(it - bookmark_buttons_.cbegin());
}
base::RepeatingCallback<content::PageNavigator*()>
BookmarkBarView::GetPageNavigatorGetter() {
auto getter = [](base::WeakPtr<BookmarkBarView> bookmark_bar)
-> content::PageNavigator* {
if (!bookmark_bar)
return nullptr;
return bookmark_bar->page_navigator_;
};
return base::BindRepeating(getter, weak_ptr_factory_.GetWeakPtr());
}
const BookmarkNode* BookmarkBarView::GetParentNodeAndIndexForDrop(
size_t& index) {
const BookmarkNode* root =
(drop_info_->location.button_type == DROP_OTHER_FOLDER)
? model_->other_node()
: model_->bookmark_bar_node();
if (drop_info_->location.index.has_value()) {
// TODO(sky): optimize the SchedulePaint region.
SchedulePaint();
}
const BookmarkNode* parent_node;
if (drop_info_->location.button_type == DROP_OTHER_FOLDER) {
parent_node = root;
index = parent_node->children().size();
} else if (drop_info_->location.on) {
parent_node = root->children()[drop_info_->location.index.value()].get();
index = parent_node->children().size();
} else {
parent_node = root;
index = drop_info_->location.index.value();
}
return parent_node;
}
void BookmarkBarView::PerformDrop(const bookmarks::BookmarkNodeData data,
const BookmarkNode* parent_node,
const size_t index,
const bool copy,
const ui::DropTargetEvent& event,
ui::mojom::DragOperation& output_drag_op) {
DCHECK(data.is_valid());
DCHECK(parent_node);
DCHECK_NE(index, static_cast<size_t>(-1));
base::RecordAction(base::UserMetricsAction("BookmarkBar_DragEnd"));
output_drag_op = chrome::DropBookmarks(browser_->profile(), data, parent_node,
index, copy);
}
int BookmarkBarView::GetDropLocationModelIndexForTesting() const {
return (drop_info_ && drop_info_->valid &&
drop_info_->location.index.has_value())
? *(drop_info_->location.index)
: -1;
}
BEGIN_METADATA(BookmarkBarView, views::AccessiblePaneView)
ADD_PROPERTY_METADATA(bool, InfoBarVisible)
ADD_READONLY_PROPERTY_METADATA(size_t, FirstHiddenNodeIndex)
END_METADATA