summaryrefslogtreecommitdiff
path: root/chromium/ui/views/controls
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-24 12:15:48 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-28 13:30:04 +0000
commitb014812705fc80bff0a5c120dfcef88f349816dc (patch)
tree25a2e2d9fa285f1add86aa333389a839f81a39ae /chromium/ui/views/controls
parent9f4560b1027ae06fdb497023cdcaf91b8511fa74 (diff)
downloadqtwebengine-chromium-b014812705fc80bff0a5c120dfcef88f349816dc.tar.gz
BASELINE: Update Chromium to 68.0.3440.125
Change-Id: I23f19369e01f688e496f5bf179abb521ad73874f Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/views/controls')
-rw-r--r--chromium/ui/views/controls/button/button.cc10
-rw-r--r--chromium/ui/views/controls/button/button.h5
-rw-r--r--chromium/ui/views/controls/button/checkbox.cc71
-rw-r--r--chromium/ui/views/controls/button/checkbox.h16
-rw-r--r--chromium/ui/views/controls/button/image_button.cc32
-rw-r--r--chromium/ui/views/controls/button/image_button.h14
-rw-r--r--chromium/ui/views/controls/button/image_button_factory_unittest.cc4
-rw-r--r--chromium/ui/views/controls/button/image_button_unittest.cc42
-rw-r--r--chromium/ui/views/controls/button/label_button.cc2
-rw-r--r--chromium/ui/views/controls/button/md_text_button.cc26
-rw-r--r--chromium/ui/views/controls/button/md_text_button.h5
-rw-r--r--chromium/ui/views/controls/button/menu_button.cc5
-rw-r--r--chromium/ui/views/controls/button/radio_button.cc13
-rw-r--r--chromium/ui/views/controls/button/radio_button.h5
-rw-r--r--chromium/ui/views/controls/combobox/combobox.cc18
-rw-r--r--chromium/ui/views/controls/combobox/combobox.h9
-rw-r--r--chromium/ui/views/controls/focus_ring.cc164
-rw-r--r--chromium/ui/views/controls/focus_ring.h94
-rw-r--r--chromium/ui/views/controls/glow_hover_controller.cc88
-rw-r--r--chromium/ui/views/controls/glow_hover_controller.h86
-rw-r--r--chromium/ui/views/controls/image_view.cc62
-rw-r--r--chromium/ui/views/controls/image_view.h4
-rw-r--r--chromium/ui/views/controls/menu/menu_config.cc47
-rw-r--r--chromium/ui/views/controls/menu/menu_config.h59
-rw-r--r--chromium/ui/views/controls/menu/menu_config_mac.mm13
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.cc358
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.h38
-rw-r--r--chromium/ui/views/controls/menu/menu_controller_unittest.cc119
-rw-r--r--chromium/ui/views/controls/menu/menu_host.cc4
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.cc152
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.h46
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view_unittest.cc27
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter.cc9
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc131
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm118
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl.cc10
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h5
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm7
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_unittest.cc5
-rw-r--r--chromium/ui/views/controls/menu/menu_scroll_view_container.cc21
-rw-r--r--chromium/ui/views/controls/menu/menu_separator.cc14
-rw-r--r--chromium/ui/views/controls/menu/menu_types.h3
-rw-r--r--chromium/ui/views/controls/menu/submenu_view.cc3
-rw-r--r--chromium/ui/views/controls/message_box_view.cc1
-rw-r--r--chromium/ui/views/controls/native/native_view_host_aura.cc3
-rw-r--r--chromium/ui/views/controls/native/native_view_host_mac.mm15
-rw-r--r--chromium/ui/views/controls/prefix_selector.cc12
-rw-r--r--chromium/ui/views/controls/prefix_selector.h2
-rw-r--r--chromium/ui/views/controls/scroll_view.cc76
-rw-r--r--chromium/ui/views/controls/scroll_view.h7
-rw-r--r--chromium/ui/views/controls/scroll_view_unittest.cc263
-rw-r--r--chromium/ui/views/controls/scrollbar/base_scroll_bar.cc1
-rw-r--r--chromium/ui/views/controls/slider.cc4
-rw-r--r--chromium/ui/views/controls/styled_label.cc6
-rw-r--r--chromium/ui/views/controls/styled_label.h2
-rw-r--r--chromium/ui/views/controls/styled_label_unittest.cc74
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc8
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc1
-rw-r--r--chromium/ui/views/controls/table/table_view.cc24
-rw-r--r--chromium/ui/views/controls/textfield/textfield.cc115
-rw-r--r--chromium/ui/views/controls/textfield/textfield.h17
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model.cc91
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model.h8
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model_unittest.cc55
-rw-r--r--chromium/ui/views/controls/textfield/textfield_unittest.cc192
-rw-r--r--chromium/ui/views/controls/tree/tree_view.cc13
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu.cc5
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.cc53
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.h41
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_mac.mm62
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view.cc23
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view.h2
-rw-r--r--chromium/ui/views/controls/webview/webview.cc37
-rw-r--r--chromium/ui/views/controls/webview/webview.h17
-rw-r--r--chromium/ui/views/controls/webview/webview_unittest.cc44
75 files changed, 2186 insertions, 1052 deletions
diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc
index 8a2e4dff278..5fa2f6f1ebb 100644
--- a/chromium/ui/views/controls/button/button.cc
+++ b/chromium/ui/views/controls/button/button.cc
@@ -22,6 +22,7 @@
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/button/toggle_button.h"
+#include "ui/views/controls/focus_ring.h"
#include "ui/views/painter.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/widget/widget.h"
@@ -145,6 +146,13 @@ void Button::SetAnimationDuration(int duration) {
hover_animation_.SetSlideDuration(duration);
}
+void Button::SetInstallFocusRingOnFocus(bool install) {
+ if (install)
+ focus_ring_ = FocusRing::Install(this);
+ else
+ focus_ring_.reset();
+}
+
void Button::SetHotTracked(bool is_hot_tracked) {
if (state_ != STATE_DISABLED)
SetState(is_hot_tracked ? STATE_HOVERED : STATE_NORMAL);
@@ -443,7 +451,7 @@ void Button::OnBlur() {
std::unique_ptr<InkDrop> Button::CreateInkDrop() {
std::unique_ptr<views::InkDropImpl> ink_drop = CreateDefaultInkDropImpl();
- ink_drop->SetShowHighlightOnFocus(true);
+ ink_drop->SetShowHighlightOnFocus(!focus_ring_);
ink_drop->SetAutoHighlightModeForPlatform();
return std::move(ink_drop);
}
diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h
index 3942d790bb4..6f0ce20627b 100644
--- a/chromium/ui/views/controls/button/button.h
+++ b/chromium/ui/views/controls/button/button.h
@@ -13,6 +13,7 @@
#include "ui/native_theme/native_theme.h"
#include "ui/views/animation/ink_drop_host_view.h"
#include "ui/views/animation/ink_drop_state.h"
+#include "ui/views/controls/focus_ring.h"
#include "ui/views/painter.h"
namespace views {
@@ -143,6 +144,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
void set_has_ink_drop_action_on_click(bool has_ink_drop_action_on_click) {
has_ink_drop_action_on_click_ = has_ink_drop_action_on_click;
}
+ void SetInstallFocusRingOnFocus(bool install_focus_ring_on_focus);
void SetHotTracked(bool is_hot_tracked);
bool IsHotTracked() const;
@@ -291,6 +293,9 @@ class VIEWS_EXPORT Button : public InkDropHostView,
// The color of the ripple and hover.
SkColor ink_drop_base_color_;
+ // The focus ring for this Button.
+ std::unique_ptr<FocusRing> focus_ring_;
+
std::unique_ptr<Painter> focus_painter_;
DISALLOW_COPY_AND_ASSIGN(Button);
diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc
index ecdcda3d503..cfe84643274 100644
--- a/chromium/ui/views/controls/button/checkbox.cc
+++ b/chromium/ui/views/controls/button/checkbox.cc
@@ -30,47 +30,6 @@
namespace views {
-constexpr int kFocusRingThicknessDip = 2;
-
-// View used to paint the focus ring around the Checkbox icon.
-// The icon is painted separately.
-class IconFocusRing : public View {
- public:
- explicit IconFocusRing(Checkbox* checkbox);
- ~IconFocusRing() override = default;
-
- private:
- // View:
- void Layout() override;
- void OnPaint(gfx::Canvas* canvas) override;
-
- Checkbox* checkbox_;
-
- DISALLOW_COPY_AND_ASSIGN(IconFocusRing);
-};
-
-IconFocusRing::IconFocusRing(Checkbox* checkbox) : checkbox_(checkbox) {
- FocusRing::InitFocusRing(this);
-}
-
-void IconFocusRing::Layout() {
- gfx::Rect focus_bounds = checkbox_->image()->bounds();
- focus_bounds.Inset(-kFocusRingThicknessDip, -kFocusRingThicknessDip);
- SetBoundsRect(focus_bounds);
-}
-
-void IconFocusRing::OnPaint(gfx::Canvas* canvas) {
- cc::PaintFlags focus_flags;
- focus_flags.setAntiAlias(true);
- focus_flags.setColor(
- SkColorSetA(GetNativeTheme()->GetSystemColor(
- ui::NativeTheme::kColorId_FocusedBorderColor),
- 0x66));
- focus_flags.setStyle(cc::PaintFlags::kStroke_Style);
- focus_flags.setStrokeWidth(2);
- checkbox_->PaintFocusRing(this, canvas, focus_flags);
-}
-
// static
const char Checkbox::kViewClassName[] = "Checkbox";
@@ -88,9 +47,7 @@ Checkbox::Checkbox(const base::string16& label, bool force_md)
set_request_focus_on_press(false);
SetInkDropMode(InkDropMode::ON);
set_has_ink_drop_action_on_click(true);
- focus_ring_ = new IconFocusRing(this);
- focus_ring_->SetVisible(false);
- AddChildView(focus_ring_);
+ focus_ring_ = FocusRing::Install(this);
} else {
std::unique_ptr<LabelButtonBorder> button_border(new LabelButtonBorder());
// Inset the trailing side by a couple pixels for the focus border.
@@ -200,16 +157,12 @@ void Checkbox::OnFocus() {
LabelButton::OnFocus();
if (!UseMd())
UpdateImage();
- else
- focus_ring_->SetVisible(true);
}
void Checkbox::OnBlur() {
LabelButton::OnBlur();
if (!UseMd())
UpdateImage();
- else
- focus_ring_->SetVisible(false);
}
void Checkbox::OnNativeThemeChanged(const ui::NativeTheme* theme) {
@@ -262,6 +215,20 @@ std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const {
return border;
}
+void Checkbox::Layout() {
+ LabelButton::Layout();
+ if (focus_ring_ && !image()->bounds().IsEmpty())
+ focus_ring_->SetPath(GetFocusRingPath());
+}
+
+SkPath Checkbox::GetFocusRingPath() const {
+ SkPath path;
+ gfx::Rect bounds = image()->bounds();
+ bounds.Inset(1, 1);
+ path.addRect(RectToSkRect(bounds));
+ return path;
+}
+
void Checkbox::SetCustomImage(bool checked,
bool focused,
ButtonState for_state,
@@ -272,14 +239,6 @@ void Checkbox::SetCustomImage(bool checked,
UpdateImage();
}
-void Checkbox::PaintFocusRing(View* view,
- gfx::Canvas* canvas,
- const cc::PaintFlags& flags) {
- gfx::RectF bounds(view->GetLocalBounds());
- bounds.Inset(kFocusRingThicknessDip, kFocusRingThicknessDip);
- canvas->DrawRoundRect(bounds, kFocusRingThicknessDip, flags);
-}
-
const gfx::VectorIcon& Checkbox::GetVectorIcon() const {
return checked() ? kCheckboxActiveIcon : kCheckboxNormalIcon;
}
diff --git a/chromium/ui/views/controls/button/checkbox.h b/chromium/ui/views/controls/button/checkbox.h
index ca8da3cb2af..de681d69271 100644
--- a/chromium/ui/views/controls/button/checkbox.h
+++ b/chromium/ui/views/controls/button/checkbox.h
@@ -12,6 +12,7 @@
#include "base/strings/string16.h"
#include "cc/paint/paint_flags.h"
#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/focus_ring.h"
namespace gfx {
struct VectorIcon;
@@ -63,6 +64,7 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
SkColor GetInkDropBaseColor() const override;
gfx::ImageSkia GetImage(ButtonState for_state) const override;
std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override;
+ void Layout() override;
// Set the image shown for each button state depending on whether it is
// [checked] or [focused].
@@ -71,14 +73,12 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
ButtonState for_state,
const gfx::ImageSkia& image);
- // Paints a focus indicator for the view. Overridden in RadioButton.
- virtual void PaintFocusRing(View* view,
- gfx::Canvas* canvas,
- const cc::PaintFlags& flags);
-
// Gets the vector icon to use based on the current state of |checked_|.
virtual const gfx::VectorIcon& GetVectorIcon() const;
+ // Returns the path to draw the focus ring around for this Checkbox.
+ virtual SkPath GetFocusRingPath() const;
+
private:
friend class IconFocusRing;
@@ -97,15 +97,15 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
// True if the checkbox is checked.
bool checked_;
- // FocusRing used in MD mode
- View* focus_ring_ = nullptr;
-
// The images for each button node_data.
gfx::ImageSkia images_[2][2][STATE_COUNT];
// The unique id for the associated label's accessible object.
int32_t label_ax_id_;
+ // The focus ring to use for this Checkbox.
+ std::unique_ptr<FocusRing> focus_ring_;
+
bool use_md_;
DISALLOW_COPY_AND_ASSIGN(Checkbox);
diff --git a/chromium/ui/views/controls/button/image_button.cc b/chromium/ui/views/controls/button/image_button.cc
index 6d945e4579c..677d4dc3d84 100644
--- a/chromium/ui/views/controls/button/image_button.cc
+++ b/chromium/ui/views/controls/button/image_button.cc
@@ -83,6 +83,13 @@ void ImageButton::SetImageAlignment(HorizontalAlignment h_align,
SchedulePaint();
}
+void ImageButton::SetBackgroundImageAlignment(HorizontalAlignment h_align,
+ VerticalAlignment v_align) {
+ h_background_alignment_ = h_align;
+ v_background_alignment_ = v_align;
+ SchedulePaint();
+}
+
void ImageButton::SetMinimumImageSize(const gfx::Size& size) {
if (minimum_image_size_ == size)
return;
@@ -138,10 +145,21 @@ void ImageButton::PaintButtonContents(gfx::Canvas* canvas) {
canvas->Scale(-1, 1);
}
- gfx::Point position = ComputeImagePaintPosition(img);
- if (!background_image_.isNull())
- canvas->DrawImageInt(background_image_, position.x(), position.y());
+ if (!background_image_.isNull()) {
+ // If the background image alignment was not set, use the image
+ // alignment.
+ HorizontalAlignment h_alignment =
+ h_background_alignment_.value_or(h_alignment_);
+ VerticalAlignment v_alignment =
+ v_background_alignment_.value_or(v_alignment_);
+ gfx::Point background_position = ComputeImagePaintPosition(
+ background_image_, h_alignment, v_alignment);
+ canvas->DrawImageInt(background_image_, background_position.x(),
+ background_position.y());
+ }
+ gfx::Point position =
+ ComputeImagePaintPosition(img, h_alignment_, v_alignment_);
canvas->DrawImageInt(img, position.x(), position.y());
}
}
@@ -166,11 +184,13 @@ gfx::ImageSkia ImageButton::GetImageToPaint() {
////////////////////////////////////////////////////////////////////////////////
// ImageButton, private:
-gfx::Point ImageButton::ComputeImagePaintPosition(const gfx::ImageSkia& image) {
+const gfx::Point ImageButton::ComputeImagePaintPosition(
+ const gfx::ImageSkia& image,
+ HorizontalAlignment h_alignment,
+ VerticalAlignment v_alignment) {
int x = 0, y = 0;
gfx::Rect rect = GetContentsBounds();
- HorizontalAlignment h_alignment = h_alignment_;
if (draw_image_mirrored_) {
if (h_alignment == ALIGN_RIGHT)
h_alignment = ALIGN_LEFT;
@@ -185,7 +205,7 @@ gfx::Point ImageButton::ComputeImagePaintPosition(const gfx::ImageSkia& image) {
if (v_alignment_ == ALIGN_MIDDLE)
y = (rect.height() - image.height()) / 2;
- else if (v_alignment_ == ALIGN_BOTTOM)
+ else if (v_alignment == ALIGN_BOTTOM)
y = rect.height() - image.height();
x += rect.x();
diff --git a/chromium/ui/views/controls/button/image_button.h b/chromium/ui/views/controls/button/image_button.h
index fb73e6c1e06..bc69e6e350b 100644
--- a/chromium/ui/views/controls/button/image_button.h
+++ b/chromium/ui/views/controls/button/image_button.h
@@ -60,6 +60,10 @@ class VIEWS_EXPORT ImageButton : public Button {
void SetImageAlignment(HorizontalAlignment h_align,
VerticalAlignment v_align);
+ // Sets how the background is laid out within the button's bounds.
+ void SetBackgroundImageAlignment(HorizontalAlignment h_align,
+ VerticalAlignment v_align);
+
// The minimum size of the contents (not including the border). The contents
// will be at least this size, but may be larger if the image itself is
// larger.
@@ -97,17 +101,25 @@ class VIEWS_EXPORT ImageButton : public Button {
FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, ImagePositionWithBorder);
FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, LeftAlignedMirrored);
FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, RightAlignedMirrored);
+ FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, ImagePositionWithBackground);
FRIEND_TEST_ALL_PREFIXES(ImageButtonFactoryTest, CreateVectorImageButton);
// Returns the correct position of the image for painting.
- gfx::Point ComputeImagePaintPosition(const gfx::ImageSkia& image);
+ const gfx::Point ComputeImagePaintPosition(const gfx::ImageSkia& image,
+ HorizontalAlignment h_alignment,
+ VerticalAlignment v_alignment);
// Image alignment.
HorizontalAlignment h_alignment_;
VerticalAlignment v_alignment_;
gfx::Size minimum_image_size_;
+ // Background alignment. If these are not set, the background image uses the
+ // image alignment.
+ base::Optional<HorizontalAlignment> h_background_alignment_;
+ base::Optional<VerticalAlignment> v_background_alignment_;
+
// Whether we draw our resources horizontally flipped. This can happen in the
// linux titlebar, where image resources were designed to be flipped so a
// small curved corner in the close button designed to fit into the frame
diff --git a/chromium/ui/views/controls/button/image_button_factory_unittest.cc b/chromium/ui/views/controls/button/image_button_factory_unittest.cc
index 52728ff3516..6a2e776d07d 100644
--- a/chromium/ui/views/controls/button/image_button_factory_unittest.cc
+++ b/chromium/ui/views/controls/button/image_button_factory_unittest.cc
@@ -26,14 +26,14 @@ TEST_F(ImageButtonFactoryTest, CreateVectorImageButton) {
TEST_F(ImageButtonFactoryTest, SetImageFromVectorIcon) {
ImageButton* button = CreateVectorImageButton(nullptr);
- SetImageFromVectorIcon(button, vector_icons::kClose16Icon, SK_ColorRED);
+ SetImageFromVectorIcon(button, vector_icons::kCloseRoundedIcon, SK_ColorRED);
EXPECT_FALSE(button->GetImage(Button::STATE_NORMAL).isNull());
EXPECT_FALSE(button->GetImage(Button::STATE_DISABLED).isNull());
EXPECT_EQ(color_utils::DeriveDefaultIconColor(SK_ColorRED),
button->GetInkDropBaseColor());
// Default to GoogleGrey900.
- SetImageFromVectorIcon(button, vector_icons::kClose16Icon);
+ SetImageFromVectorIcon(button, vector_icons::kCloseRoundedIcon);
EXPECT_EQ(color_utils::DeriveDefaultIconColor(gfx::kGoogleGrey900),
button->GetInkDropBaseColor());
delete button;
diff --git a/chromium/ui/views/controls/button/image_button_unittest.cc b/chromium/ui/views/controls/button/image_button_unittest.cc
index baf9a75d18b..4c595a5d7ef 100644
--- a/chromium/ui/views/controls/button/image_button_unittest.cc
+++ b/chromium/ui/views/controls/button/image_button_unittest.cc
@@ -39,6 +39,13 @@ class Parent : public views::View {
namespace views {
+namespace {
+const ImageButton::HorizontalAlignment kDefaultHorizontalAlignment =
+ ImageButton::ALIGN_LEFT;
+const ImageButton::VerticalAlignment kDefaultVerticalAlignment =
+ ImageButton::ALIGN_TOP;
+} // namespace
+
typedef ViewsTestBase ImageButtonTest;
TEST_F(ImageButtonTest, Basics) {
@@ -122,24 +129,39 @@ TEST_F(ImageButtonTest, ImagePositionWithBorder) {
// The image should be painted at the top-left corner.
EXPECT_EQ(gfx::Point().ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, kDefaultHorizontalAlignment,
+ kDefaultVerticalAlignment)
+ .ToString());
button.SetBorder(views::CreateEmptyBorder(10, 5, 0, 0));
EXPECT_EQ(gfx::Point(5, 10).ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, kDefaultHorizontalAlignment,
+ kDefaultVerticalAlignment)
+ .ToString());
button.SetBorder(NullBorder());
button.SetBounds(0, 0, 50, 50);
EXPECT_EQ(gfx::Point().ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, kDefaultHorizontalAlignment,
+ kDefaultVerticalAlignment)
+ .ToString());
button.SetImageAlignment(ImageButton::ALIGN_CENTER,
ImageButton::ALIGN_MIDDLE);
EXPECT_EQ(gfx::Point(15, 10).ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, ImageButton::ALIGN_CENTER,
+ ImageButton::ALIGN_MIDDLE)
+ .ToString());
button.SetBorder(views::CreateEmptyBorder(10, 10, 0, 0));
EXPECT_EQ(gfx::Point(20, 15).ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, ImageButton::ALIGN_CENTER,
+ ImageButton::ALIGN_MIDDLE)
+ .ToString());
// The entire button's size should take the border into account.
EXPECT_EQ(gfx::Size(30, 40).ToString(), button.GetPreferredSize().ToString());
@@ -161,7 +183,10 @@ TEST_F(ImageButtonTest, LeftAlignedMirrored) {
// Because the coordinates are flipped, we should expect this to draw as if
// it were ALIGN_RIGHT.
EXPECT_EQ(gfx::Point(30, 0).ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, ImageButton::ALIGN_LEFT,
+ ImageButton::ALIGN_BOTTOM)
+ .ToString());
}
TEST_F(ImageButtonTest, RightAlignedMirrored) {
@@ -176,7 +201,10 @@ TEST_F(ImageButtonTest, RightAlignedMirrored) {
// Because the coordinates are flipped, we should expect this to draw as if
// it were ALIGN_LEFT.
EXPECT_EQ(gfx::Point(0, 0).ToString(),
- button.ComputeImagePaintPosition(image).ToString());
+ button
+ .ComputeImagePaintPosition(image, ImageButton::ALIGN_RIGHT,
+ ImageButton::ALIGN_BOTTOM)
+ .ToString());
}
TEST_F(ImageButtonTest, PreferredSizeInvalidation) {
diff --git a/chromium/ui/views/controls/button/label_button.cc b/chromium/ui/views/controls/button/label_button.cc
index d56f4cb2f73..60907bd19cd 100644
--- a/chromium/ui/views/controls/button/label_button.cc
+++ b/chromium/ui/views/controls/button/label_button.cc
@@ -398,7 +398,7 @@ std::unique_ptr<views::InkDropHighlight> LabelButton::CreateInkDropHighlight()
const {
return ShouldUseFloodFillInkDrop()
? std::make_unique<views::InkDropHighlight>(
- size(), kInkDropSmallCornerRadius,
+ size(), ink_drop_small_corner_radius(),
gfx::RectF(GetLocalBounds()).CenterPoint(),
GetInkDropBaseColor())
: CreateDefaultInkDropHighlight(
diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc
index 60f6ca0ffb3..e3c0f6e9dc2 100644
--- a/chromium/ui/views/controls/button/md_text_button.cc
+++ b/chromium/ui/views/controls/button/md_text_button.cc
@@ -77,6 +77,7 @@ MdTextButton* MdTextButton::Create(ButtonListener* listener,
MdTextButton* button = new MdTextButton(listener, button_context);
button->SetText(text);
button->SetFocusForPlatform();
+
return button;
}
@@ -95,6 +96,11 @@ void MdTextButton::SetBgColorOverride(const base::Optional<SkColor>& color) {
UpdateColors();
}
+void MdTextButton::set_corner_radius(float radius) {
+ corner_radius_ = radius;
+ set_ink_drop_corner_radii(corner_radius_, corner_radius_);
+}
+
void MdTextButton::OnPaintBackground(gfx::Canvas* canvas) {
LabelButton::OnPaintBackground(canvas);
if (hover_animation().is_animating() || state() == STATE_HOVERED) {
@@ -104,16 +110,6 @@ void MdTextButton::OnPaintBackground(gfx::Canvas* canvas) {
}
}
-void MdTextButton::OnFocus() {
- LabelButton::OnFocus();
- FocusRing::Install(this);
-}
-
-void MdTextButton::OnBlur() {
- LabelButton::OnBlur();
- FocusRing::Uninstall(this);
-}
-
void MdTextButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
LabelButton::OnNativeThemeChanged(theme);
UpdateColors();
@@ -179,16 +175,17 @@ void MdTextButton::UpdateStyleToIndicateDefaultStatus() {
MdTextButton::MdTextButton(ButtonListener* listener, int button_context)
: LabelButton(listener, base::string16(), button_context),
- is_prominent_(false),
- corner_radius_(kInkDropSmallCornerRadius) {
+ is_prominent_(false) {
SetInkDropMode(InkDropMode::ON);
set_has_ink_drop_action_on_click(true);
+ set_corner_radius(LayoutProvider::Get()->GetCornerRadiusMetric(EMPHASIS_LOW));
SetHorizontalAlignment(gfx::ALIGN_CENTER);
SetFocusForPlatform();
const int minimum_width = LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH);
SetMinSize(gfx::Size(minimum_width, 0));
SetFocusPainter(nullptr);
+ SetInstallFocusRingOnFocus(true);
label()->SetAutoColorReadabilityEnabled(false);
set_request_focus_on_press(false);
@@ -226,8 +223,9 @@ void MdTextButton::UpdatePadding() {
// GetControlHeightForFont(). It can't because that only returns a correct
// result with --secondary-ui-md, and MdTextButtons appear in top chrome
// without that.
- const int kBaseHeight = 28;
- int target_height = std::max(kBaseHeight + size_delta * 2,
+ const int base_height =
+ ui::MaterialDesignController::IsNewerMaterialUi() ? 32 : 28;
+ int target_height = std::max(base_height + size_delta * 2,
label()->font_list().GetFontSize() * 2);
int label_height = label()->GetPreferredSize().height();
diff --git a/chromium/ui/views/controls/button/md_text_button.h b/chromium/ui/views/controls/button/md_text_button.h
index 25f65ce5e87..eafa8a817ba 100644
--- a/chromium/ui/views/controls/button/md_text_button.h
+++ b/chromium/ui/views/controls/button/md_text_button.h
@@ -9,6 +9,7 @@
#include "base/optional.h"
#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/focus_ring.h"
#include "ui/views/style/typography.h"
namespace views {
@@ -36,14 +37,12 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
// Override the default corner radius of the round rect used for the
// background and ink drop effects.
- void set_corner_radius(float radius) { corner_radius_ = radius; }
+ void set_corner_radius(float radius);
// View:
void OnPaintBackground(gfx::Canvas* canvas) override;
// LabelButton:
- void OnFocus() override;
- void OnBlur() override;
void OnNativeThemeChanged(const ui::NativeTheme* theme) override;
std::unique_ptr<InkDrop> CreateInkDrop() override;
std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
diff --git a/chromium/ui/views/controls/button/menu_button.cc b/chromium/ui/views/controls/button/menu_button.cc
index b6996aab742..7f38894e57c 100644
--- a/chromium/ui/views/controls/button/menu_button.cc
+++ b/chromium/ui/views/controls/button/menu_button.cc
@@ -287,7 +287,7 @@ bool MenuButton::OnKeyReleased(const ui::KeyEvent& event) {
void MenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
Button::GetAccessibleNodeData(node_data);
node_data->role = ax::mojom::Role::kPopUpButton;
- node_data->AddState(ax::mojom::State::kHaspopup);
+ node_data->SetHasPopup(ax::mojom::HasPopup::kMenu);
if (enabled())
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen);
}
@@ -385,7 +385,8 @@ void MenuButton::DecrementPressedLocked() {
if (should_disable_after_press_) {
desired_state = STATE_DISABLED;
should_disable_after_press_ = false;
- } else if (ShouldEnterHoveredState()) {
+ } else if (GetWidget() && !GetWidget()->dragged_view() &&
+ ShouldEnterHoveredState()) {
desired_state = STATE_HOVERED;
GetInkDrop()->SetHovered(true);
}
diff --git a/chromium/ui/views/controls/button/radio_button.cc b/chromium/ui/views/controls/button/radio_button.cc
index e8499db3311..efadd78b3e9 100644
--- a/chromium/ui/views/controls/button/radio_button.cc
+++ b/chromium/ui/views/controls/button/radio_button.cc
@@ -157,15 +157,14 @@ void RadioButton::SetChecked(bool checked) {
Checkbox::SetChecked(checked);
}
-void RadioButton::PaintFocusRing(View* view,
- gfx::Canvas* canvas,
- const cc::PaintFlags& flags) {
- canvas->DrawCircle(gfx::RectF(view->GetLocalBounds()).CenterPoint(),
- image()->width() / 2, flags);
-}
-
const gfx::VectorIcon& RadioButton::GetVectorIcon() const {
return checked() ? kRadioButtonActiveIcon : kRadioButtonNormalIcon;
}
+SkPath RadioButton::GetFocusRingPath() const {
+ SkPath path;
+ path.addOval(gfx::RectToSkRect(image()->bounds()));
+ return path;
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/button/radio_button.h b/chromium/ui/views/controls/button/radio_button.h
index b91ed088182..02cf91dcf8d 100644
--- a/chromium/ui/views/controls/button/radio_button.h
+++ b/chromium/ui/views/controls/button/radio_button.h
@@ -8,6 +8,7 @@
#include "base/macros.h"
#include "base/strings/string16.h"
#include "ui/views/controls/button/checkbox.h"
+#include "ui/views/controls/focus_ring.h"
namespace views {
@@ -38,10 +39,8 @@ class VIEWS_EXPORT RadioButton : public Checkbox {
// Overridden from Checkbox:
void SetChecked(bool checked) override;
- void PaintFocusRing(View* view,
- gfx::Canvas* canvas,
- const cc::PaintFlags& flags) override;
const gfx::VectorIcon& GetVectorIcon() const override;
+ SkPath GetFocusRingPath() const override;
private:
DISALLOW_COPY_AND_ASSIGN(RadioButton);
diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc
index 67f9af2eaee..2cb22fea6dc 100644
--- a/chromium/ui/views/controls/combobox/combobox.cc
+++ b/chromium/ui/views/controls/combobox/combobox.cc
@@ -462,6 +462,9 @@ Combobox::Combobox(ui::ComboboxModel* model, Style style)
arrow_image_ = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_MENU_DROPARROW);
}
+
+ if (UseMd())
+ focus_ring_ = FocusRing::Install(this);
}
Combobox::~Combobox() {
@@ -533,11 +536,9 @@ void Combobox::SetInvalid(bool invalid) {
invalid_ = invalid;
- if (HasFocus() && UseMd()) {
- FocusRing::Install(this, invalid_
- ? ui::NativeTheme::kColorId_AlertSeverityHigh
- : ui::NativeTheme::kColorId_NumColors);
- }
+ if (focus_ring_)
+ focus_ring_->SetInvalid(invalid);
+
UpdateBorder();
SchedulePaint();
}
@@ -746,11 +747,6 @@ void Combobox::OnFocus() {
View::OnFocus();
// Border renders differently when focused.
SchedulePaint();
- if (UseMd()) {
- FocusRing::Install(this, invalid_
- ? ui::NativeTheme::kColorId_AlertSeverityHigh
- : ui::NativeTheme::kColorId_NumColors);
- }
}
void Combobox::OnBlur() {
@@ -761,8 +757,6 @@ void Combobox::OnBlur() {
selector_->OnViewBlur();
// Border renders differently when focused.
SchedulePaint();
- if (UseMd())
- FocusRing::Uninstall(this);
}
void Combobox::GetAccessibleNodeData(ui::AXNodeData* node_data) {
diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h
index 7977c36feda..d6132543edd 100644
--- a/chromium/ui/views/controls/combobox/combobox.h
+++ b/chromium/ui/views/controls/combobox/combobox.h
@@ -11,6 +11,7 @@
#include "base/time/time.h"
#include "ui/base/models/combobox_model.h"
#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/prefix_delegate.h"
namespace gfx {
@@ -113,7 +114,7 @@ class VIEWS_EXPORT Combobox : public View,
void SetSelectedRow(int row) override;
base::string16 GetTextForRow(int row) override;
- // Overriden from ButtonListener:
+ // Overridden from ButtonListener:
void ButtonPressed(Button* sender, const ui::Event& event) override;
protected:
@@ -165,6 +166,9 @@ class VIEWS_EXPORT Combobox : public View,
// Returns the width of the combobox's arrow container.
int GetArrowContainerWidth() const;
+ // Returns the color to use for the combobox's focus ring.
+ SkColor GetFocusRingColor() const;
+
// Optionally used to tie the lifetime of the model to this combobox. See
// constructor.
std::unique_ptr<ui::ComboboxModel> owned_model_;
@@ -232,6 +236,9 @@ class VIEWS_EXPORT Combobox : public View,
// true, the parent view must relayout in ChildPreferredSizeChanged().
bool size_to_largest_label_;
+ // The focus ring for this Combobox.
+ std::unique_ptr<FocusRing> focus_ring_;
+
// Used for making calbacks.
base::WeakPtrFactory<Combobox> weak_ptr_factory_;
diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc
index 1cf8348a308..c17bc868e2f 100644
--- a/chromium/ui/views/controls/focus_ring.cc
+++ b/chromium/ui/views/controls/focus_ring.cc
@@ -5,70 +5,52 @@
#include "ui/views/controls/focus_ring.h"
#include "ui/gfx/canvas.h"
-
-namespace views {
+#include "ui/views/controls/focusable_border.h"
+#include "ui/views/style/platform_style.h"
namespace {
-// The default stroke width of the focus border in dp.
-constexpr float kFocusHaloThicknessDp = 2.f;
-
-FocusRing* GetFocusRing(View* parent) {
- for (int i = 0; i < parent->child_count(); ++i) {
- if (parent->child_at(i)->GetClassName() == FocusRing::kViewClassName)
- return static_cast<FocusRing*>(parent->child_at(i));
- }
- return nullptr;
+ui::NativeTheme::ColorId ColorIdForValidity(bool valid) {
+ return valid ? ui::NativeTheme::kColorId_FocusedBorderColor
+ : ui::NativeTheme::kColorId_AlertSeverityHigh;
}
} // namespace
+namespace views {
+
const char FocusRing::kViewClassName[] = "FocusRing";
// static
-FocusRing* FocusRing::Install(View* parent,
- SkColor color,
- float corner_radius) {
- FocusRing* ring = GetFocusRing(parent);
- if (!ring) {
- ring = new FocusRing(color, corner_radius);
- parent->AddChildView(ring);
- } else {
- ring->color_ = color;
- ring->corner_radius_ = corner_radius;
- }
+std::unique_ptr<FocusRing> FocusRing::Install(View* parent) {
+ auto ring = base::WrapUnique<FocusRing>(new FocusRing(parent));
+ ring->set_owned_by_client();
+ parent->AddChildView(ring.get());
+ parent->AddObserver(ring.get());
ring->Layout();
ring->SchedulePaint();
return ring;
}
// static
-FocusRing* FocusRing::Install(View* parent,
- ui::NativeTheme::ColorId override_color_id) {
- SkColor ring_color = parent->GetNativeTheme()->GetSystemColor(
- override_color_id == ui::NativeTheme::kColorId_NumColors
- ? ui::NativeTheme::kColorId_FocusedBorderColor
- : override_color_id);
- FocusRing* ring = Install(parent, ring_color);
- DCHECK(ring);
- ring->override_color_id_ = override_color_id;
- if (!ring->view_observer_.IsObserving(parent))
- ring->view_observer_.Add(parent);
- return ring;
+bool FocusRing::IsPathUseable(const SkPath& path) {
+ return path.isRect(nullptr) || path.isOval(nullptr) || path.isRRect(nullptr);
}
-// static
-void FocusRing::Uninstall(View* parent) {
- delete GetFocusRing(parent);
+void FocusRing::SetPath(const SkPath& path) {
+ DCHECK(IsPathUseable(path));
+ path_ = path;
+ SchedulePaint();
}
-// static
-void FocusRing::InitFocusRing(View* view) {
- // A layer is necessary to paint beyond the parent's bounds.
- view->SetPaintToLayer();
- view->layer()->SetFillsBoundsOpaquely(false);
- // Don't allow the view to process events.
- view->set_can_process_events_within_subtree(false);
+void FocusRing::SetInvalid(bool invalid) {
+ invalid_ = invalid;
+ SchedulePaint();
+}
+
+void FocusRing::SetHasFocusPredicate(const ViewPredicate& predicate) {
+ has_focus_predicate_ = predicate;
+ SchedulePaint();
}
const char* FocusRing::GetClassName() const {
@@ -79,37 +61,87 @@ void FocusRing::Layout() {
// The focus ring handles its own sizing, which is simply to fill the parent
// and extend a little beyond its borders.
gfx::Rect focus_bounds = parent()->GetLocalBounds();
- focus_bounds.Inset(gfx::Insets(-kFocusHaloThicknessDp));
+ focus_bounds.Inset(gfx::Insets(PlatformStyle::kFocusHaloInset));
SetBoundsRect(focus_bounds);
}
void FocusRing::OnPaint(gfx::Canvas* canvas) {
- cc::PaintFlags flags;
- flags.setAntiAlias(true);
- flags.setColor(SkColorSetA(color_, 0x66));
- flags.setStyle(cc::PaintFlags::kStroke_Style);
- flags.setStrokeWidth(kFocusHaloThicknessDp);
- gfx::RectF rect(GetLocalBounds());
- rect.Inset(gfx::InsetsF(kFocusHaloThicknessDp / 2.f));
- // The focus indicator should hug the normal border, when present (as in the
- // case of text buttons). Since it's drawn outside the parent view, increase
- // the rounding slightly by adding half the ring thickness.
- canvas->DrawRoundRect(rect, corner_radius_ + kFocusHaloThicknessDp / 2.f,
- flags);
+ if (!has_focus_predicate_(parent()))
+ return;
+
+ SkColor base_color =
+ GetNativeTheme()->GetSystemColor(ColorIdForValidity(!invalid_));
+
+ cc::PaintFlags paint;
+ paint.setAntiAlias(true);
+ paint.setColor(SkColorSetA(base_color, 0x66));
+ paint.setStyle(cc::PaintFlags::kStroke_Style);
+ paint.setStrokeWidth(PlatformStyle::kFocusHaloThickness);
+
+ SkPath path = path_;
+ if (path.isEmpty())
+ path.addRect(RectToSkRect(parent()->GetLocalBounds()));
+
+ DCHECK(IsPathUseable(path));
+ SkRect bounds;
+ SkRRect rbounds;
+ if (path.isRect(&bounds)) {
+ canvas->sk_canvas()->drawRRect(RingRectFromPathRect(bounds), paint);
+ } else if (path.isOval(&bounds)) {
+ gfx::RectF rect = gfx::SkRectToRectF(bounds);
+ View::ConvertRectToTarget(view_, this, &rect);
+ canvas->sk_canvas()->drawRRect(SkRRect::MakeOval(gfx::RectFToSkRect(rect)),
+ paint);
+ } else if (path.isRRect(&rbounds)) {
+ canvas->sk_canvas()->drawRRect(RingRectFromPathRect(rbounds), paint);
+ }
}
-void FocusRing::OnViewNativeThemeChanged(View* observed_view) {
- if (override_color_id_) {
- color_ = observed_view->GetNativeTheme()->GetSystemColor(
- override_color_id_.value());
- }
+void FocusRing::OnViewFocused(View* view) {
+ SchedulePaint();
}
-FocusRing::FocusRing(SkColor color, float corner_radius)
- : color_(color), corner_radius_(corner_radius), view_observer_(this) {
- InitFocusRing(this);
+void FocusRing::OnViewBlurred(View* view) {
+ SchedulePaint();
}
-FocusRing::~FocusRing() {}
+FocusRing::FocusRing(View* parent) : view_(parent) {
+ // A layer is necessary to paint beyond the parent's bounds.
+ SetPaintToLayer();
+ layer()->SetFillsBoundsOpaquely(false);
+ // Don't allow the view to process events.
+ set_can_process_events_within_subtree(false);
+
+ has_focus_predicate_ = [](View* p) -> bool { return p->HasFocus(); };
+}
+
+FocusRing::~FocusRing() {
+ if (parent())
+ parent()->RemoveObserver(this);
+}
+
+SkRRect FocusRing::RingRectFromPathRect(const SkRect& rect) const {
+ double thickness = PlatformStyle::kFocusHaloThickness / 2.f;
+ double corner_radius = FocusableBorder::kCornerRadiusDp + thickness;
+ return RingRectFromPathRect(
+ SkRRect::MakeRectXY(rect, corner_radius, corner_radius));
+}
+
+SkRRect FocusRing::RingRectFromPathRect(const SkRRect& rrect) const {
+ double thickness = PlatformStyle::kFocusHaloThickness / 2.f;
+ gfx::RectF r = gfx::SkRectToRectF(rrect.rect());
+ View::ConvertRectToTarget(view_, this, &r);
+
+ SkRRect skr =
+ rrect.makeOffset(r.x() - rrect.rect().x(), r.y() - rrect.rect().y());
+
+ // The focus indicator should hug the normal border, when present (as in the
+ // case of text buttons). Since it's drawn outside the parent view, increase
+ // the rounding slightly by adding half the ring thickness.
+ skr.inset(PlatformStyle::kFocusHaloInset, PlatformStyle::kFocusHaloInset);
+ skr.inset(thickness, thickness);
+
+ return skr;
+}
} // namespace views
diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h
index df1f1b330ca..1b4d99ca2b1 100644
--- a/chromium/ui/views/controls/focus_ring.h
+++ b/chromium/ui/views/controls/focus_ring.h
@@ -17,31 +17,55 @@ namespace views {
// FocusRing is a View that is designed to act as an indicator of focus for its
// parent. It is a stand-alone view that paints to a layer which extends beyond
// the bounds of its parent view.
+//
+// Using FocusRing looks something like this:
+//
+// class MyView : public View {
+// ...
+// private:
+// std::unique_ptr<FocusRing> focus_ring_;
+// };
+//
+// MyView::MyView() {
+// focus_ring_ = FocusRing::Install(this);
+// ...
+// }
+//
+// If MyView should show a rounded rectangular focus ring when it has focus and
+// hide the ring when it loses focus, no other configuration is necessary. In
+// other cases, it might be necessary to use the Set*() functions on FocusRing;
+// these take care of repainting it when the state changes.
class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
public:
static const char kViewClassName[];
- // Create a FocusRing and adds it to |parent|, or updates the one that already
- // exists with the given |color| and |corner_radius|.
- // TODO(crbug.com/831926): Prefer using the below Install() method - this one
- // should eventually be removed.
- static FocusRing* Install(
- View* parent,
- SkColor color,
- float corner_radius = FocusableBorder::kCornerRadiusDp);
+ using ViewPredicate = std::function<bool(View* view)>;
- // Similar to FocusRing::Install(View, SkColor, float), but
- // |override_color_id| will be used in place of the default coloration
- // when provided.
- static FocusRing* Install(View* parent,
- ui::NativeTheme::ColorId override_color_id =
- ui::NativeTheme::kColorId_NumColors);
+ ~FocusRing() override;
+
+ // Create a FocusRing and adds it to |parent|. The returned focus ring is
+ // owned by the client (the code calling FocusRing::Install), *not* by
+ // |parent|.
+ static std::unique_ptr<FocusRing> Install(View* parent);
+
+ // Returns whether this class can draw a focus ring from |path|. Not all paths
+ // are useable since not all paths can be easily outset.
+ static bool IsPathUseable(const SkPath& path);
- // Removes the FocusRing from |parent|.
- static void Uninstall(View* parent);
+ // Sets the path to draw this FocusRing around. This path is in the parent
+ // view's coordinate system, *not* in the FocusRing's coordinate system.
+ void SetPath(const SkPath& path);
- // Configure |view| for painting focus ring highlights.
- static void InitFocusRing(View* view);
+ // Sets whether the FocusRing should show an invalid state for the View it
+ // encloses.
+ void SetInvalid(bool invalid);
+
+ // Sets the predicate function used to tell when the parent has focus. The
+ // parent is passed into this predicate; it should return whether the parent
+ // should be treated as focused. This is useful when, for example, the parent
+ // wraps an inner view and the inner view is the one that actually receives
+ // focus, but the FocusRing sits on the parent instead of the inner view.
+ void SetHasFocusPredicate(const ViewPredicate& predicate);
// View:
const char* GetClassName() const override;
@@ -49,17 +73,33 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
void OnPaint(gfx::Canvas* canvas) override;
// ViewObserver:
- void OnViewNativeThemeChanged(View* observed_view) override;
-
- protected:
- FocusRing(SkColor color, float corner_radius);
- ~FocusRing() override;
+ void OnViewFocused(View* view) override;
+ void OnViewBlurred(View* view) override;
private:
- SkColor color_;
- float corner_radius_;
- base::Optional<ui::NativeTheme::ColorId> override_color_id_;
- ScopedObserver<View, FocusRing> view_observer_;
+ explicit FocusRing(View* parent);
+
+ // Translates the provided SkRect or SkRRect, which is in the parent's
+ // coordinate system, into this view's coordinate system, then insets it
+ // appropriately to produce the focus ring "halo" effect. If the supplied rect
+ // is an SkRect, it will have the default focus ring corner radius applied as
+ // well.
+ SkRRect RingRectFromPathRect(const SkRect& rect) const;
+ SkRRect RingRectFromPathRect(const SkRRect& rect) const;
+
+ // The View this focus ring is installed on.
+ View* view_ = nullptr;
+
+ // The path to draw this focus ring around. IsPathUseable(path_) is always
+ // true.
+ SkPath path_;
+
+ // Whether the enclosed View is in an invalid state, which controls whether
+ // the focus ring shows an invalid appearance (usually a different color).
+ bool invalid_ = false;
+
+ // The predicate used to determine whether the parent has focus.
+ ViewPredicate has_focus_predicate_;
DISALLOW_COPY_AND_ASSIGN(FocusRing);
};
diff --git a/chromium/ui/views/controls/glow_hover_controller.cc b/chromium/ui/views/controls/glow_hover_controller.cc
deleted file mode 100644
index ef08308f7e7..00000000000
--- a/chromium/ui/views/controls/glow_hover_controller.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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 "ui/views/controls/glow_hover_controller.h"
-
-#include "ui/views/view.h"
-
-namespace views {
-
-// Amount to scale the opacity.
-static const double kSubtleOpacityScale = 0.45;
-static const double kPronouncedOpacityScale = 1.0;
-
-// How long the hover state takes.
-static const int kTrackHoverDurationMs = 400;
-
-GlowHoverController::GlowHoverController(views::View* view)
- : view_(view),
- animation_(this),
- opacity_scale_(kSubtleOpacityScale) {
- animation_.set_delegate(this);
-}
-
-GlowHoverController::~GlowHoverController() {
-}
-
-void GlowHoverController::SetAnimationContainer(
- gfx::AnimationContainer* container) {
- animation_.SetContainer(container);
-}
-
-void GlowHoverController::SetLocation(const gfx::Point& location) {
- location_ = location;
- if (ShouldDraw())
- view_->SchedulePaint();
-}
-
-void GlowHoverController::Show(Style style) {
- switch (style) {
- case SUBTLE:
- opacity_scale_ = kSubtleOpacityScale;
- animation_.SetSlideDuration(kTrackHoverDurationMs);
- animation_.SetTweenType(gfx::Tween::EASE_OUT);
- animation_.Show();
- break;
- case PRONOUNCED:
- opacity_scale_ = kPronouncedOpacityScale;
- // Force the end state to show immediately.
- animation_.Show();
- animation_.End();
- break;
- }
-}
-
-void GlowHoverController::Hide() {
- animation_.SetTweenType(gfx::Tween::EASE_IN);
- animation_.Hide();
-}
-
-void GlowHoverController::HideImmediately() {
- if (ShouldDraw())
- view_->SchedulePaint();
- animation_.Reset();
-}
-
-double GlowHoverController::GetAnimationValue() const {
- return animation_.GetCurrentValue();
-}
-
-SkAlpha GlowHoverController::GetAlpha() const {
- return static_cast<SkAlpha>(animation_.CurrentValueBetween(
- 0, gfx::ToRoundedInt(255 * opacity_scale_)));
-}
-
-bool GlowHoverController::ShouldDraw() const {
- return animation_.IsShowing() || animation_.is_animating();
-}
-
-void GlowHoverController::AnimationEnded(const gfx::Animation* animation) {
- view_->SchedulePaint();
-}
-
-void GlowHoverController::AnimationProgressed(const gfx::Animation* animation) {
- view_->SchedulePaint();
-}
-
-} // namespace views
diff --git a/chromium/ui/views/controls/glow_hover_controller.h b/chromium/ui/views/controls/glow_hover_controller.h
deleted file mode 100644
index 49db0a8c4d9..00000000000
--- a/chromium/ui/views/controls/glow_hover_controller.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// 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.
-
-#ifndef UI_VIEWS_CONTROLS_GLOW_HOVER_CONTROLLER_H_
-#define UI_VIEWS_CONTROLS_GLOW_HOVER_CONTROLLER_H_
-
-#include "base/macros.h"
-#include "ui/gfx/animation/animation_delegate.h"
-#include "ui/gfx/animation/slide_animation.h"
-#include "ui/views/views_export.h"
-
-namespace gfx {
-class Point;
-}
-
-namespace views {
-
-class View;
-
-// GlowHoverController is responsible for drawing a hover effect as is used by
-// the tabstrip. Typical usage:
-// OnMouseEntered() -> invoke Show().
-// OnMouseMoved() -> invoke SetLocation().
-// OnMouseExited() -> invoke Hide().
-// OnPaint() -> if ShouldDraw() returns true invoke Draw().
-// Internally GlowHoverController uses an animation to animate the glow and
-// invokes SchedulePaint() back on the View as necessary.
-class VIEWS_EXPORT GlowHoverController : public gfx::AnimationDelegate {
- public:
- enum Style {
- SUBTLE,
- PRONOUNCED
- };
-
- explicit GlowHoverController(views::View* view);
- ~GlowHoverController() override;
-
- // Sets the AnimationContainer used by the animation.
- void SetAnimationContainer(gfx::AnimationContainer* container);
-
- // Sets the location of the hover, relative to the View passed to the
- // constructor.
- void SetLocation(const gfx::Point& location);
-
- const gfx::Point& location() const { return location_; }
-
- // Initiates showing the hover.
- void Show(Style style);
-
- // Hides the hover.
- void Hide();
-
- // Hides the hover immediately.
- void HideImmediately();
-
- // Returns the value of the animation.
- double GetAnimationValue() const;
-
- SkAlpha GetAlpha() const;
-
- // Returns true if there is something to be drawn. Use this instead of
- // invoking Draw() if creating |mask_image| is expensive.
- bool ShouldDraw() const;
-
- // gfx::AnimationDelegate overrides:
- void AnimationEnded(const gfx::Animation* animation) override;
- void AnimationProgressed(const gfx::Animation* animation) override;
-
- private:
- // View we're drawing to.
- views::View* view_;
-
- // Opacity of the glow ramps up over time.
- gfx::SlideAnimation animation_;
-
- // Location of the glow, relative to view.
- gfx::Point location_;
- double opacity_scale_;
-
- DISALLOW_COPY_AND_ASSIGN(GlowHoverController);
-};
-
-} // namespace views
-
-#endif // UI_VIEWS_CONTROLS_GLOW_HOVER_CONTROLLER_H_
diff --git a/chromium/ui/views/controls/image_view.cc b/chromium/ui/views/controls/image_view.cc
index 059a83652f7..5b7f65a5522 100644
--- a/chromium/ui/views/controls/image_view.cc
+++ b/chromium/ui/views/controls/image_view.cc
@@ -29,10 +29,10 @@ void* GetBitmapPixels(const gfx::ImageSkia& img, float image_scale) {
const char ImageView::kViewClassName[] = "ImageView";
ImageView::ImageView()
- : horiz_alignment_(CENTER),
- vert_alignment_(CENTER),
+ : horizontal_alignment_(CENTER),
+ vertical_alignment_(CENTER),
last_paint_scale_(0.f),
- last_painted_bitmap_pixels_(NULL) {}
+ last_painted_bitmap_pixels_(nullptr) {}
ImageView::~ImageView() {}
@@ -40,7 +40,7 @@ void ImageView::SetImage(const gfx::ImageSkia& img) {
if (IsImageEqual(img))
return;
- last_painted_bitmap_pixels_ = NULL;
+ last_painted_bitmap_pixels_ = nullptr;
gfx::Size pref_size(GetPreferredSize());
image_ = img;
if (pref_size != GetPreferredSize())
@@ -93,31 +93,39 @@ gfx::Size ImageView::GetImageSize() const {
gfx::Point ImageView::ComputeImageOrigin(const gfx::Size& image_size) const {
gfx::Insets insets = GetInsets();
- int x;
+ int x = 0;
// In order to properly handle alignment of images in RTL locales, we need
// to flip the meaning of trailing and leading. For example, if the
// horizontal alignment is set to trailing, then we'll use left alignment for
// the image instead of right alignment if the UI layout is RTL.
- Alignment actual_horiz_alignment = horiz_alignment_;
- if (base::i18n::IsRTL() && (horiz_alignment_ != CENTER))
- actual_horiz_alignment = (horiz_alignment_ == LEADING) ? TRAILING : LEADING;
- switch (actual_horiz_alignment) {
- case LEADING: x = insets.left(); break;
- case TRAILING: x = width() - insets.right() - image_size.width(); break;
+ Alignment actual_horizontal_alignment = horizontal_alignment_;
+ if (base::i18n::IsRTL() && (horizontal_alignment_ != CENTER)) {
+ actual_horizontal_alignment =
+ (horizontal_alignment_ == LEADING) ? TRAILING : LEADING;
+ }
+ switch (actual_horizontal_alignment) {
+ case LEADING:
+ x = insets.left();
+ break;
+ case TRAILING:
+ x = width() - insets.right() - image_size.width();
+ break;
case CENTER:
x = (width() - insets.width() - image_size.width()) / 2 + insets.left();
break;
- default: NOTREACHED(); x = 0; break;
}
- int y;
- switch (vert_alignment_) {
- case LEADING: y = insets.top(); break;
- case TRAILING: y = height() - insets.bottom() - image_size.height(); break;
+ int y = 0;
+ switch (vertical_alignment_) {
+ case LEADING:
+ y = insets.top();
+ break;
+ case TRAILING:
+ y = height() - insets.bottom() - image_size.height();
+ break;
case CENTER:
y = (height() - insets.height() - image_size.height()) / 2 + insets.top();
break;
- default: NOTREACHED(); y = 0; break;
}
return gfx::Point(x, y);
@@ -137,26 +145,26 @@ const char* ImageView::GetClassName() const {
return kViewClassName;
}
-void ImageView::SetHorizontalAlignment(Alignment ha) {
- if (ha != horiz_alignment_) {
- horiz_alignment_ = ha;
+void ImageView::SetHorizontalAlignment(Alignment alignment) {
+ if (alignment != horizontal_alignment_) {
+ horizontal_alignment_ = alignment;
SchedulePaint();
}
}
ImageView::Alignment ImageView::GetHorizontalAlignment() const {
- return horiz_alignment_;
+ return horizontal_alignment_;
}
-void ImageView::SetVerticalAlignment(Alignment va) {
- if (va != vert_alignment_) {
- vert_alignment_ = va;
+void ImageView::SetVerticalAlignment(Alignment alignment) {
+ if (alignment != vertical_alignment_) {
+ vertical_alignment_ = alignment;
SchedulePaint();
}
}
ImageView::Alignment ImageView::GetVerticalAlignment() const {
- return vert_alignment_;
+ return vertical_alignment_;
}
void ImageView::SetTooltipText(const base::string16& tooltip) {
@@ -184,7 +192,7 @@ gfx::Size ImageView::CalculatePreferredSize() const {
views::PaintInfo::ScaleType ImageView::GetPaintScaleType() const {
// ImageView contains an image which is rastered at the device scale factor.
- // By default, the paint commands are recorded at a scale factor slighlty
+ // By default, the paint commands are recorded at a scale factor slightly
// different from the device scale factor. Re-rastering the image at this
// paint recording scale will result in a distorted image. Paint recording
// scale might also not be uniform along the x & y axis, thus resulting in
@@ -197,7 +205,7 @@ views::PaintInfo::ScaleType ImageView::GetPaintScaleType() const {
void ImageView::OnPaintImage(gfx::Canvas* canvas) {
last_paint_scale_ = canvas->image_scale();
- last_painted_bitmap_pixels_ = NULL;
+ last_painted_bitmap_pixels_ = nullptr;
if (image_.isNull())
return;
diff --git a/chromium/ui/views/controls/image_view.h b/chromium/ui/views/controls/image_view.h
index 178c50e5087..c19a3c5ea8e 100644
--- a/chromium/ui/views/controls/image_view.h
+++ b/chromium/ui/views/controls/image_view.h
@@ -106,10 +106,10 @@ class VIEWS_EXPORT ImageView : public View {
gfx::ImageSkia image_;
// Horizontal alignment.
- Alignment horiz_alignment_;
+ Alignment horizontal_alignment_;
// Vertical alignment.
- Alignment vert_alignment_;
+ Alignment vertical_alignment_;
// The current tooltip text.
base::string16 tooltip_text_;
diff --git a/chromium/ui/views/controls/menu/menu_config.cc b/chromium/ui/views/controls/menu/menu_config.cc
index 62fbbca1d1d..c398009a47d 100644
--- a/chromium/ui/views/controls/menu/menu_config.cc
+++ b/chromium/ui/views/controls/menu/menu_config.cc
@@ -5,7 +5,9 @@
#include "ui/views/controls/menu/menu_config.h"
#include "base/macros.h"
+#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_image_util.h"
+#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/round_rect_painter.h"
namespace views {
@@ -18,28 +20,36 @@ MenuConfig::MenuConfig()
item_bottom_margin(3),
item_no_icon_top_margin(4),
item_no_icon_bottom_margin(4),
- fixed_text_item_height(0),
- fixed_menu_width(0),
+ minimum_text_item_height(0),
+ minimum_container_item_height(0),
+ minimum_menu_width(0),
item_left_margin(10),
touchable_item_left_margin(16),
label_to_arrow_padding(10),
arrow_to_edge_padding(5),
icon_to_label_padding(10),
- touchable_icon_to_label_padding(22),
+ touchable_icon_to_label_padding(16),
touchable_icon_size(20),
- touchable_icon_color(SkColorSetA(SK_ColorBLACK, 0xDE)),
+ touchable_icon_color(SkColorSetRGB(0x5F, 0x63, 0x60)),
check_width(kMenuCheckSize),
check_height(kMenuCheckSize),
arrow_width(kSubmenuArrowSize),
separator_height(11),
+ double_separator_height(18),
separator_upper_height(3),
separator_lower_height(4),
separator_spacing_height(3),
separator_thickness(1),
+ double_separator_thickness(2),
show_mnemonics(false),
+ use_mnemonics(true),
scroll_arrow_height(3),
label_to_minor_text_padding(10),
item_min_height(0),
+ actionable_submenu_arrow_to_edge_padding(14),
+ actionable_submenu_width(37),
+ actionable_submenu_vertical_separator_height(18),
+ actionable_submenu_vertical_separator_width(1),
show_accelerators(true),
always_use_icon_to_label_padding(false),
align_arrow_and_shortcut(false),
@@ -49,17 +59,44 @@ MenuConfig::MenuConfig()
check_selected_combobox_item(false),
show_delay(400),
corner_radius(0),
+ auxiliary_corner_radius(0),
touchable_corner_radius(8),
touchable_anchor_offset(8),
touchable_menu_height(36),
touchable_menu_width(256),
touchable_menu_shadow_elevation(12),
- vertical_touchable_menu_item_padding(8) {
+ vertical_touchable_menu_item_padding(8),
+ padded_separator_left_margin(64),
+ arrow_key_selection_wraps(true),
+ show_context_menu_accelerators(true) {
Init();
}
MenuConfig::~MenuConfig() {}
+int MenuConfig::CornerRadiusForMenu(const MenuController* controller) const {
+ if (controller && controller->use_touchable_layout())
+ return touchable_corner_radius;
+ if (controller && (controller->is_combobox() || controller->IsContextMenu()))
+ return auxiliary_corner_radius;
+ return corner_radius;
+}
+
+bool MenuConfig::ShouldShowAcceleratorText(const MenuItemView* item,
+ base::string16* text) const {
+ if (!show_accelerators || !item->GetDelegate() || !item->GetCommand())
+ return false;
+ ui::Accelerator accelerator;
+ if (!item->GetDelegate()->GetAccelerator(item->GetCommand(), &accelerator))
+ return false;
+ if (item->GetMenuController() && item->GetMenuController()->IsContextMenu() &&
+ !show_context_menu_accelerators) {
+ return false;
+ }
+ *text = accelerator.GetShortcutText();
+ return true;
+}
+
// static
const MenuConfig& MenuConfig::instance() {
CR_DEFINE_STATIC_LOCAL(MenuConfig, instance, ());
diff --git a/chromium/ui/views/controls/menu/menu_config.h b/chromium/ui/views/controls/menu/menu_config.h
index 3fbb794646a..c07195b1be6 100644
--- a/chromium/ui/views/controls/menu/menu_config.h
+++ b/chromium/ui/views/controls/menu/menu_config.h
@@ -11,6 +11,9 @@
namespace views {
+class MenuController;
+class MenuItemView;
+
// Layout type information for menu items. Use the instance() method to obtain
// the MenuConfig for the current platform.
struct VIEWS_EXPORT MenuConfig {
@@ -19,6 +22,16 @@ struct VIEWS_EXPORT MenuConfig {
static const MenuConfig& instance();
+ // Helper methods to simplify access to MenuConfig:
+ // Returns the appropriate corner radius for the menu controlled by
+ // |controller|, or the default corner radius if |controller| is nullptr.
+ int CornerRadiusForMenu(const MenuController* controller) const;
+
+ // Returns whether |item_view| should show accelerator text. If so, returns
+ // the text to show.
+ bool ShouldShowAcceleratorText(const MenuItemView* item_view,
+ base::string16* text) const;
+
// Font list used by menus.
gfx::FontList font_list;
@@ -44,12 +57,12 @@ struct VIEWS_EXPORT MenuConfig {
int item_no_icon_top_margin;
int item_no_icon_bottom_margin;
- // Fixed dimensions used for entire items. If these are nonzero, they override
- // the vertical margin constants given above - the item's text and icon are
- // vertically centered within these heights.
- int fixed_text_item_height;
- int fixed_container_item_height;
- int fixed_menu_width;
+ // Minimum dimensions used for entire items. If these are nonzero, they
+ // override the vertical margin constants given above - the item's text and
+ // icon are vertically centered within these heights.
+ int minimum_text_item_height;
+ int minimum_container_item_height;
+ int minimum_menu_width;
// Margins between the left of the item and the icon.
int item_left_margin;
@@ -87,6 +100,9 @@ struct VIEWS_EXPORT MenuConfig {
// Height of a normal separator (ui::NORMAL_SEPARATOR).
int separator_height;
+ // Height of a double separator (ui::DOUBLE_SEPARATOR).
+ int double_separator_height;
+
// Height of a ui::UPPER_SEPARATOR.
int separator_upper_height;
@@ -99,9 +115,15 @@ struct VIEWS_EXPORT MenuConfig {
// Thickness of the drawn separator line in pixels.
int separator_thickness;
+ // Thickness of the drawn separator line in pixels for double separator.
+ int double_separator_thickness;
+
// Are mnemonics shown?
bool show_mnemonics;
+ // Are mnemonics used to activate items?
+ bool use_mnemonics;
+
// Height of the scroll arrow.
int scroll_arrow_height;
@@ -112,6 +134,18 @@ struct VIEWS_EXPORT MenuConfig {
// Minimum height of menu item.
int item_min_height;
+ // Edge padding for an actionable submenu arrow.
+ int actionable_submenu_arrow_to_edge_padding;
+
+ // Width of the submenu in an actionable submenu.
+ int actionable_submenu_width;
+
+ // The height of the vertical separator used in an actionable submenu.
+ int actionable_submenu_vertical_separator_height;
+
+ // The width of the vertical separator used in an actionable submenu.
+ int actionable_submenu_vertical_separator_width;
+
// Whether the keyboard accelerators are visible.
bool show_accelerators;
@@ -140,6 +174,10 @@ struct VIEWS_EXPORT MenuConfig {
// Radius of the rounded corners of the menu border. Must be >= 0.
int corner_radius;
+ // Radius of "auxiliary" rounded corners - comboboxes and context menus.
+ // Must be >= 0.
+ int auxiliary_corner_radius;
+
// Radius of the rounded corners of the touchable menu border
int touchable_corner_radius;
@@ -158,6 +196,15 @@ struct VIEWS_EXPORT MenuConfig {
// Vertical padding for touchable menus.
int vertical_touchable_menu_item_padding;
+ // Left margin of padded separator (ui::PADDED_SEPARATOR).
+ int padded_separator_left_margin;
+
+ // Whether arrow keys should wrap around the end of the menu when selecting.
+ bool arrow_key_selection_wraps;
+
+ // Whether to show accelerators in context menus.
+ bool show_context_menu_accelerators;
+
private:
// Configures a MenuConfig as appropriate for the current platform.
void Init();
diff --git a/chromium/ui/views/controls/menu/menu_config_mac.mm b/chromium/ui/views/controls/menu/menu_config_mac.mm
index e950582f43f..7854af9d373 100644
--- a/chromium/ui/views/controls/menu/menu_config_mac.mm
+++ b/chromium/ui/views/controls/menu/menu_config_mac.mm
@@ -17,9 +17,9 @@ void InitMaterialMenuConfig(views::MenuConfig* config) {
config->menu_vertical_border_size = 8;
config->menu_horizontal_border_size = 0;
config->submenu_horizontal_inset = 0;
- config->fixed_text_item_height = 32;
- config->fixed_container_item_height = 48;
- config->fixed_menu_width = 320;
+ config->minimum_text_item_height = 32;
+ config->minimum_container_item_height = 48;
+ config->minimum_menu_width = 320;
config->item_left_margin = 8;
config->label_to_arrow_padding = 0;
config->arrow_to_edge_padding = 16;
@@ -28,12 +28,16 @@ void InitMaterialMenuConfig(views::MenuConfig* config) {
config->check_height = 16;
config->arrow_width = 8;
config->separator_height = 17;
+ config->separator_lower_height = 9;
+ config->separator_upper_height = 9;
+ config->separator_spacing_height = 9;
config->separator_thickness = 1;
config->label_to_minor_text_padding = 8;
config->align_arrow_and_shortcut = true;
config->use_outer_border = false;
config->icons_in_label = true;
config->corner_radius = 8;
+ config->auxiliary_corner_radius = 4;
}
} // namespace
@@ -43,6 +47,9 @@ namespace views {
void MenuConfig::Init() {
font_list = gfx::FontList(gfx::Font([NSFont menuFontOfSize:0.0]));
check_selected_combobox_item = true;
+ arrow_key_selection_wraps = false;
+ use_mnemonics = false;
+ show_context_menu_accelerators = false;
if (ui::MaterialDesignController::IsSecondaryUiMaterial())
InitMaterialMenuConfig(this);
}
diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc
index 46713d7170a..00590f09d91 100644
--- a/chromium/ui/views/controls/menu/menu_controller.cc
+++ b/chromium/ui/views/controls/menu/menu_controller.cc
@@ -165,7 +165,7 @@ View* GetNextFocusableView(View* ancestor, View* start_at, bool forward) {
return NULL;
}
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN)
// Determines the correct coordinates and window to repost |event| to, if it is
// a mouse or touch event.
static void RepostEventImpl(const ui::LocatedEvent* event,
@@ -252,7 +252,7 @@ static void RepostEventImpl(const ui::LocatedEvent* event,
PostMessage(target_window, event_type, target, window_coords);
return;
}
-#endif
+#endif // defined(OS_WIN)
#if defined(USE_AURA)
if (!window)
@@ -276,7 +276,7 @@ static void RepostEventImpl(const ui::LocatedEvent* event,
root->GetHost()->dispatcher()->RepostEvent(located_event.get());
#endif // defined(USE_AURA)
}
-#endif // defined(OS_WIN) || defined(OS_CHROMEOS)
+#endif // defined(OS_WIN)
} // namespace
@@ -442,9 +442,8 @@ void MenuController::Run(Widget* parent,
// If we are already showing, this new menu is being nested. Such as context
// menus on top of normal menus.
if (showing_) {
- // Only support nesting of blocking_run menus, nesting of
- // blocking/non-blocking shouldn't be needed.
- DCHECK(blocking_run_);
+ // Nesting (context menus) is not used for drag and drop.
+ DCHECK(!for_drop_);
state_.hot_button = hot_button_;
hot_button_ = nullptr;
@@ -477,7 +476,10 @@ void MenuController::Run(Widget* parent,
// Set the selection, which opens the initial menu.
SetSelection(root, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
- if (!blocking_run_) {
+ if (button)
+ pressed_lock_ = std::make_unique<MenuButton::PressedLock>(button);
+
+ if (for_drop_) {
if (!is_nested_drag) {
// Start the timer to hide the menu. This is needed as we get no
// notification when the drag has finished.
@@ -486,9 +488,6 @@ void MenuController::Run(Widget* parent,
return;
}
- if (button)
- pressed_lock_.reset(new MenuButton::PressedLock(button));
-
// Make sure Chrome doesn't attempt to shut down while the menu is showing.
if (ViewsDelegate::GetInstance())
ViewsDelegate::GetInstance()->AddRef();
@@ -516,7 +515,7 @@ void MenuController::Cancel(ExitType type) {
// Hide windows immediately.
SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT);
- if (!blocking_run_) {
+ if (for_drop_) {
// If we didn't block the caller we need to notify the menu, which
// triggers deleting us.
DCHECK(selected);
@@ -550,6 +549,10 @@ void MenuController::AddNestedDelegate(
delegate_ = delegate;
}
+bool MenuController::IsContextMenu() const {
+ return state_.context_menu;
+}
+
bool MenuController::OnMousePressed(SubmenuView* source,
const ui::MouseEvent& event) {
// We should either have no current_mouse_event_target_, or should have a
@@ -607,7 +610,7 @@ bool MenuController::OnMouseDragged(SubmenuView* source,
MenuPart part = GetMenuPart(source, event.location());
UpdateScrolling(part);
- if (!blocking_run_)
+ if (for_drop_)
return false;
if (possible_drag_) {
@@ -664,12 +667,11 @@ void MenuController::OnMouseReleased(SubmenuView* source,
return;
}
- if (!blocking_run_)
+ if (for_drop_)
return;
DCHECK(state_.item);
possible_drag_ = false;
- DCHECK(blocking_run_);
MenuPart part = GetMenuPart(source, event.location());
if (event.IsRightMouseButton() && part.type == MenuPart::MENU_ITEM) {
MenuItemView* menu = part.menu;
@@ -694,7 +696,7 @@ void MenuController::OnMouseReleased(SubmenuView* source,
// for selected folder menu items. If it's only a left click, show the
// contents of the folder.
if (!part.is_scroll() && part.menu &&
- !(part.menu->HasSubmenu() &&
+ !(part.should_submenu_show && part.menu->HasSubmenu() &&
(event.flags() & ui::EF_LEFT_MOUSE_BUTTON))) {
if (active_mouse_view_tracker_->view()) {
SendMouseReleaseToActiveView(source, event);
@@ -819,7 +821,7 @@ void MenuController::OnGestureEvent(SubmenuView* source,
}
} else if (event->type() == ui::ET_GESTURE_TAP) {
if (!part.is_scroll() && part.menu &&
- !(part.menu->HasSubmenu())) {
+ !(part.should_submenu_show && part.menu->HasSubmenu())) {
if (part.menu->GetDelegate()->IsTriggerableEvent(
part.menu, *event)) {
item_selected_by_touch_ = true;
@@ -1004,7 +1006,7 @@ int MenuController::OnPerformDrop(SubmenuView* source,
if (drop_target->id() == MenuItemView::kEmptyMenuItemViewID)
drop_target = drop_target->GetParentMenuItem();
- if (!IsBlockingRun()) {
+ if (for_drop_) {
delegate_->OnMenuClosed(
internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE,
item->GetRootMenuItem(), accept_event_flags_);
@@ -1165,7 +1167,14 @@ void MenuController::SetSelection(MenuItemView* menu_item,
size_t current_size = current_path.size();
size_t new_size = new_path.size();
- bool pending_item_changed = pending_state_.item != menu_item;
+ // ACTIONABLE_SUBMENUs can change without changing the pending item, this
+ // occurs when selection moves from the COMMAND area to the SUBMENU area of
+ // the ACTIONABLE_SUBMENU.
+ const bool pending_item_changed =
+ pending_state_.item != menu_item ||
+ pending_state_.submenu_open !=
+ !!(selection_types & SELECTION_OPEN_SUBMENU);
+
if (pending_item_changed && pending_state_.item)
SetHotTrackedButton(nullptr);
@@ -1174,7 +1183,8 @@ void MenuController::SetSelection(MenuItemView* menu_item,
current_path.empty() ? NULL : current_path.front()->GetDelegate();
for (size_t i = paths_differ_at; i < current_size; ++i) {
if (current_delegate &&
- current_path[i]->GetType() == MenuItemView::SUBMENU) {
+ (current_path[i]->GetType() == MenuItemView::SUBMENU ||
+ current_path[i]->GetType() == MenuItemView::ACTIONABLE_SUBMENU)) {
current_delegate->WillHideMenu(current_path[i]);
}
current_path[i]->SetSelected(false);
@@ -1184,6 +1194,14 @@ void MenuController::SetSelection(MenuItemView* menu_item,
for (size_t i = paths_differ_at; i < new_size; ++i) {
new_path[i]->ScrollRectToVisible(new_path[i]->GetLocalBounds());
new_path[i]->SetSelected(true);
+ if (new_path[i]->GetType() == MenuItemView::ACTIONABLE_SUBMENU) {
+ new_path[i]->SetSelectionOfActionableSubmenu(
+ (selection_types & SELECTION_OPEN_SUBMENU) != 0);
+ }
+ }
+ if (menu_item && menu_item->GetType() == MenuItemView::ACTIONABLE_SUBMENU) {
+ menu_item->SetSelectionOfActionableSubmenu(
+ (selection_types & SELECTION_OPEN_SUBMENU) != 0);
}
if (menu_item && menu_item->GetDelegate())
@@ -1206,16 +1224,17 @@ void MenuController::SetSelection(MenuItemView* menu_item,
StartShowTimer();
// Notify an accessibility focus event on all menu items except for the root.
- if (menu_item &&
- (MenuDepth(menu_item) != 1 ||
- menu_item->GetType() != MenuItemView::SUBMENU)) {
+ if (menu_item && (MenuDepth(menu_item) != 1 ||
+ menu_item->GetType() != MenuItemView::SUBMENU ||
+ (menu_item->GetType() == MenuItemView::ACTIONABLE_SUBMENU &&
+ (selection_types & SELECTION_OPEN_SUBMENU) == 0))) {
menu_item->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
}
void MenuController::SetSelectionOnPointerDown(SubmenuView* source,
const ui::LocatedEvent* event) {
- if (!blocking_run_)
+ if (for_drop_)
return;
DCHECK(!active_mouse_view_tracker_->view());
@@ -1255,7 +1274,7 @@ void MenuController::SetSelectionOnPointerDown(SubmenuView* source,
possible_drag_ = true;
press_pt_ = event->location();
}
- if (part.menu->HasSubmenu())
+ if (part.menu->HasSubmenu() && part.should_submenu_show)
selection_types |= SELECTION_OPEN_SUBMENU;
}
SetSelection(part.menu, selection_types);
@@ -1298,7 +1317,7 @@ void MenuController::StartDrag(SubmenuView* source,
void MenuController::OnKeyDown(ui::KeyboardCode key_code) {
// Do not process while performing drag-and-drop
- if (!blocking_run_)
+ if (for_drop_)
return;
switch (key_code) {
@@ -1405,9 +1424,9 @@ void MenuController::OnKeyDown(ui::KeyboardCode key_code) {
}
}
-MenuController::MenuController(bool blocking,
+MenuController::MenuController(bool for_drop,
internal::MenuControllerDelegate* delegate)
- : blocking_run_(blocking),
+ : for_drop_(for_drop),
active_mouse_view_tracker_(std::make_unique<ViewTracker>()),
delegate_(delegate) {
delegate_stack_.push_back(delegate_);
@@ -1445,11 +1464,6 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds,
bool context_menu) {
pending_state_.context_menu = context_menu;
pending_state_.initial_bounds = bounds;
- if (bounds.height() > 1) {
- // Inset the bounds slightly, otherwise drag coordinates don't line up
- // nicely and menus close prematurely.
- pending_state_.initial_bounds.Inset(0, 1);
- }
// Reverse anchor position for RTL languages.
if (base::i18n::IsRTL() &&
@@ -1491,7 +1505,7 @@ void MenuController::Accept(MenuItemView* item, int event_flags) {
}
void MenuController::ReallyAccept(MenuItemView* item, int event_flags) {
- DCHECK(IsBlockingRun());
+ DCHECK(!for_drop_);
result_ = item;
#if defined(OS_MACOSX)
// Reset the closure animation since it's now finished - this also unblocks
@@ -1562,9 +1576,8 @@ bool MenuController::ShowSiblingMenu(SubmenuView* source,
// It is currently not possible to show a submenu recursively in a bubble.
DCHECK(!MenuItemView::IsBubble(anchor));
- // Subtract 1 from the height to make the popup flush with the button border.
UpdateInitialLocation(gfx::Rect(screen_menu_loc.x(), screen_menu_loc.y(),
- button->width(), button->height() - 1),
+ button->width(), button->height()),
anchor, state_.context_menu);
alt_menu->PrepareForRun(
false, has_mnemonics,
@@ -1699,11 +1712,19 @@ bool MenuController::GetMenuPartByScreenCoordinateImpl(
part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y());
part->type = MenuPart::MENU_ITEM;
part->submenu = menu;
+ part->should_submenu_show =
+ part->submenu && part->menu &&
+ (part->menu->GetType() == MenuItemView::SUBMENU ||
+ IsLocationOverSubmenuAreaOfActionableSubmenu(part->menu, screen_loc));
if (!part->menu)
part->parent = menu->GetMenuItem();
return true;
}
+ // Return false for points on touchable menu shadows, to search parent menus.
+ if (use_touchable_layout_)
+ return false;
+
// While the mouse isn't over a menu item or the scroll buttons of menu, it
// is contained by menu and so we return true. If we didn't return true other
// menus would be searched, even though they are likely obscured by us.
@@ -1735,7 +1756,20 @@ bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu,
gfx::Point view_loc = screen_loc;
View::ConvertPointFromScreen(submenu, &view_loc);
gfx::Rect vis_rect = submenu->GetVisibleBounds();
- return vis_rect.Contains(view_loc.x(), view_loc.y());
+ return vis_rect.Contains(view_loc);
+}
+
+bool MenuController::IsLocationOverSubmenuAreaOfActionableSubmenu(
+ MenuItemView* item,
+ const gfx::Point& screen_loc) const {
+ if (!item || item->GetType() != MenuItemView::ACTIONABLE_SUBMENU)
+ return false;
+
+ gfx::Point view_loc = screen_loc;
+ View::ConvertPointFromScreen(item, &view_loc);
+ if (base::i18n::IsRTL())
+ view_loc.set_x(item->GetMirroredXInView(view_loc.x()));
+ return item->GetSubmenuAreaOfActionableSubmenu().Contains(view_loc);
}
void MenuController::CommitPendingSelection() {
@@ -1847,7 +1881,7 @@ void MenuController::OpenMenuImpl(MenuItemView* item, bool show) {
CalculateBubbleMenuBounds(item, prefer_leading, &resulting_direction) :
CalculateMenuBounds(item, prefer_leading, &resulting_direction);
state_.open_leading.push_back(resulting_direction);
- bool do_capture = (!did_capture_ && blocking_run_);
+ bool do_capture = (!did_capture_ && !for_drop_);
showing_submenu_ = true;
if (show) {
// Menus are the only place using kGroupingPropertyKey, so any value (other
@@ -2127,7 +2161,6 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
bool prefer_leading,
bool* is_leading) {
DCHECK(item);
- DCHECK(!item->GetParentMenuItem());
// Assume we can honor prefer_leading.
*is_leading = prefer_leading;
@@ -2136,103 +2169,153 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
DCHECK(submenu);
gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize();
- const gfx::Rect& owner_bounds = pending_state_.initial_bounds;
-
- // First the size gets reduced to the possible space.
- if (!state_.monitor_bounds.IsEmpty()) {
- int max_width = state_.monitor_bounds.width();
- int max_height = state_.monitor_bounds.height();
- // In case of bubbles, the maximum width is limited by the space
- // between the display corner and the target area + the tip size.
- if (state_.anchor == MENU_ANCHOR_BUBBLE_LEFT) {
- max_width = owner_bounds.x() - state_.monitor_bounds.x() +
- kBubbleTipSizeLeftRight;
- } else if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) {
- max_width = state_.monitor_bounds.right() - owner_bounds.right() +
- kBubbleTipSizeLeftRight;
- } else if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) {
- max_height = owner_bounds.y() - state_.monitor_bounds.y() +
- kBubbleTipSizeTopBottom;
- } else if (state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) {
- max_height = state_.monitor_bounds.bottom() - owner_bounds.bottom() +
- kBubbleTipSizeTopBottom;
- }
- // The space for the menu to cover should never get empty.
- DCHECK_GE(max_width, kBubbleTipSizeLeftRight);
- DCHECK_GE(max_height, kBubbleTipSizeTopBottom);
- pref.set_width(std::min(pref.width(), max_width));
- pref.set_height(std::min(pref.height(), max_height));
- }
- // Also make sure that the menu does not go too wide.
- pref.set_width(std::min(pref.width(),
- item->GetDelegate()->GetMaxWidthForMenu(item)));
-
+ int x = 0;
+ int y = 0;
const MenuConfig& menu_config = MenuConfig::instance();
// Shadow insets are built into MenuScrollView's preferred size so it must be
// compensated for when determining the bounds of touchable menus.
- gfx::Insets shadow_insets = BubbleBorder::GetBorderAndShadowInsets(
- menu_config.touchable_menu_shadow_elevation);
+ const gfx::Insets border_and_shadow_insets =
+ BubbleBorder::GetBorderAndShadowInsets(
+ menu_config.touchable_menu_shadow_elevation);
- int x, y;
- if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE ||
- state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) {
- if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE)
- y = owner_bounds.y() - pref.height() + kBubbleTipSizeTopBottom;
- else
- y = owner_bounds.bottom() - kBubbleTipSizeTopBottom;
-
- x = owner_bounds.CenterPoint().x() - pref.width() / 2;
- int x_old = x;
- if (x < state_.monitor_bounds.x()) {
- x = state_.monitor_bounds.x();
- } else if (x + pref.width() > state_.monitor_bounds.right()) {
- x = state_.monitor_bounds.right() - pref.width();
+ if (!item->GetParentMenuItem()) {
+ // This is a top-level menu, position it relative to the anchor bounds.
+ const gfx::Rect& owner_bounds = pending_state_.initial_bounds;
+
+ // First the size gets reduced to the possible space.
+ if (!state_.monitor_bounds.IsEmpty()) {
+ int max_width = state_.monitor_bounds.width();
+ int max_height = state_.monitor_bounds.height();
+ // In case of bubbles, the maximum width is limited by the space
+ // between the display corner and the target area + the tip size.
+ if (state_.anchor == MENU_ANCHOR_BUBBLE_LEFT) {
+ max_width = owner_bounds.x() - state_.monitor_bounds.x() +
+ kBubbleTipSizeLeftRight;
+ } else if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) {
+ max_width = state_.monitor_bounds.right() - owner_bounds.right() +
+ kBubbleTipSizeLeftRight;
+ } else if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) {
+ max_height = owner_bounds.y() - state_.monitor_bounds.y() +
+ kBubbleTipSizeTopBottom;
+ } else if (state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) {
+ max_height = state_.monitor_bounds.bottom() - owner_bounds.bottom() +
+ kBubbleTipSizeTopBottom;
+ }
+ // The menu should always have a non-empty available area.
+ DCHECK_GE(max_width, kBubbleTipSizeLeftRight);
+ DCHECK_GE(max_height, kBubbleTipSizeTopBottom);
+ pref.set_width(std::min(pref.width(), max_width));
+ pref.set_height(std::min(pref.height(), max_height));
}
- submenu->GetScrollViewContainer()->SetBubbleArrowOffset(
- pref.width() / 2 - x + x_old);
- } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE) {
- // Align the left edges of the menu and anchor, and the bottom of the menu
- // with the top of the anchor.
- x = owner_bounds.origin().x() - shadow_insets.left();
- y = owner_bounds.origin().y() - pref.height() + shadow_insets.bottom() -
- menu_config.touchable_anchor_offset;
- // Align the right of the container with the right of the app icon.
- if (x + pref.width() > state_.monitor_bounds.width())
- x = owner_bounds.right() - pref.width() + shadow_insets.right();
- // Align the top of the menu with the bottom of the anchor.
- if (y < 0) {
- y = owner_bounds.bottom() - shadow_insets.top() +
+ // Respect the delegate's maximum width.
+ pref.set_width(
+ std::min(pref.width(), item->GetDelegate()->GetMaxWidthForMenu(item)));
+
+ if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE ||
+ state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) {
+ if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE)
+ y = owner_bounds.y() - pref.height() + kBubbleTipSizeTopBottom;
+ else
+ y = owner_bounds.bottom() - kBubbleTipSizeTopBottom;
+
+ x = owner_bounds.CenterPoint().x() - pref.width() / 2;
+ int x_old = x;
+ if (x < state_.monitor_bounds.x())
+ x = state_.monitor_bounds.x();
+ else if (x + pref.width() > state_.monitor_bounds.right())
+ x = state_.monitor_bounds.right() - pref.width();
+ submenu->GetScrollViewContainer()->SetBubbleArrowOffset(pref.width() / 2 -
+ x + x_old);
+ } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE) {
+ // Align the left edges of the menu and anchor, and the bottom of the menu
+ // with the top of the anchor.
+ x = owner_bounds.origin().x() - border_and_shadow_insets.left();
+ y = owner_bounds.origin().y() - pref.height() +
+ border_and_shadow_insets.bottom() -
menu_config.touchable_anchor_offset;
- }
- } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT) {
- // Align the right of the menu with the left of the anchor, and the top of
- // the menu with the top of the anchor.
- x = owner_bounds.origin().x() - pref.width() + shadow_insets.right() -
- menu_config.touchable_anchor_offset;
- y = owner_bounds.origin().y() - shadow_insets.top();
- // Align the left of the menu with the right of the anchor.
- if (x < 0) {
- x = owner_bounds.right() + shadow_insets.left() +
+ // Align the right of the container with the right of the anchor.
+ if (x + pref.width() > state_.monitor_bounds.width()) {
+ x = owner_bounds.right() - pref.width() +
+ border_and_shadow_insets.right();
+ }
+ // Align the top of the menu with the bottom of the anchor.
+ if (y < 0) {
+ y = owner_bounds.bottom() - border_and_shadow_insets.top() +
+ menu_config.touchable_anchor_offset;
+ }
+ } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT) {
+ // Align the right of the menu with the left of the anchor, and the top of
+ // the menu with the top of the anchor.
+ x = owner_bounds.origin().x() - pref.width() +
+ border_and_shadow_insets.right() -
menu_config.touchable_anchor_offset;
+ y = owner_bounds.origin().y() - border_and_shadow_insets.top();
+ // Align the left of the menu with the right of the anchor.
+ if (x < 0) {
+ x = owner_bounds.right() - border_and_shadow_insets.left() +
+ menu_config.touchable_anchor_offset;
+ }
+ // Align the bottom of the menu to the bottom of the anchor.
+ if (y + pref.height() > state_.monitor_bounds.height()) {
+ y = owner_bounds.bottom() - pref.height() +
+ border_and_shadow_insets.bottom();
+ }
+ } else {
+ if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT)
+ x = owner_bounds.right() - kBubbleTipSizeLeftRight;
+ else
+ x = owner_bounds.x() - pref.width() + kBubbleTipSizeLeftRight;
+
+ y = owner_bounds.CenterPoint().y() - pref.height() / 2;
+ int y_old = y;
+ if (y < state_.monitor_bounds.y())
+ y = state_.monitor_bounds.y();
+ else if (y + pref.height() > state_.monitor_bounds.bottom())
+ y = state_.monitor_bounds.bottom() - pref.height();
+ submenu->GetScrollViewContainer()->SetBubbleArrowOffset(
+ pref.height() / 2 - y + y_old);
}
- // Align the bottom of the menu to the bottom of the anchor.
- if (y + pref.height() > state_.monitor_bounds.height())
- y = owner_bounds.bottom() - pref.height() + shadow_insets.bottom();
} else {
- if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT)
- x = owner_bounds.right() - kBubbleTipSizeLeftRight;
- else
- x = owner_bounds.x() - pref.width() + kBubbleTipSizeLeftRight;
+ if (!use_touchable_layout_) {
+ NOTIMPLEMENTED()
+ << "Nested bubble menus are only implemented for touchable menus.";
+ }
- y = owner_bounds.CenterPoint().y() - pref.height() / 2;
- int y_old = y;
- if (y < state_.monitor_bounds.y()) {
- y = state_.monitor_bounds.y();
- } else if (y + pref.height() > state_.monitor_bounds.bottom()) {
- y = state_.monitor_bounds.bottom() - pref.height();
+ // This is a sub-menu, position it relative to the parent menu.
+ const gfx::Rect item_bounds = item->GetBoundsInScreen();
+ // If the layout is RTL, then a 'leading' menu is positioned to the left of
+ // the parent menu item and not to the right.
+ const bool layout_is_rtl = base::i18n::IsRTL();
+ const bool create_on_the_right = (prefer_leading && !layout_is_rtl) ||
+ (!prefer_leading && layout_is_rtl);
+ if (create_on_the_right) {
+ x = item_bounds.right() - border_and_shadow_insets.left();
+ if (state_.monitor_bounds.width() != 0 &&
+ (x + menu_config.touchable_menu_width -
+ border_and_shadow_insets.right() >
+ state_.monitor_bounds.right())) {
+ *is_leading = prefer_leading;
+ x = item_bounds.x() - menu_config.touchable_menu_width -
+ border_and_shadow_insets.right();
+ }
+ } else {
+ x = item_bounds.x() - menu_config.touchable_menu_width -
+ border_and_shadow_insets.right();
+ if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) {
+ *is_leading = !prefer_leading;
+ x = item_bounds.x() + menu_config.touchable_menu_width -
+ border_and_shadow_insets.left();
+ }
}
- submenu->GetScrollViewContainer()->SetBubbleArrowOffset(
- pref.height() / 2 - y + y_old);
+ y = item_bounds.y() - border_and_shadow_insets.top() -
+ menu_config.vertical_touchable_menu_item_padding;
+ if (y + pref.height() - border_and_shadow_insets.bottom() >
+ state_.monitor_bounds.bottom()) {
+ y = state_.monitor_bounds.bottom() - pref.height() +
+ border_and_shadow_insets.top();
+ }
+ if (y < state_.monitor_bounds.y())
+ y = state_.monitor_bounds.y() - border_and_shadow_insets.top();
}
return gfx::Rect(x, y, pref.width(), pref.height());
}
@@ -2309,14 +2392,20 @@ MenuItemView* MenuController::FindNextSelectableMenuItem(
// Loop through the menu items skipping any invisible menus. The loop stops
// when we wrap or find a visible and enabled child.
do {
+ if (!MenuConfig::instance().arrow_key_selection_wraps) {
+ if (index == 0 && direction == INCREMENT_SELECTION_UP)
+ return nullptr;
+ if (index == parent_count - 1 && direction == INCREMENT_SELECTION_DOWN)
+ return nullptr;
+ }
index = (index + delta + parent_count) % parent_count;
if (index == stop_index && !include_all_items)
- return NULL;
+ return nullptr;
MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index);
if (child->visible() && child->enabled())
return child;
} while (index != stop_index);
- return NULL;
+ return nullptr;
}
void MenuController::OpenSubmenuChangeSelectionIfCan() {
@@ -2398,8 +2487,8 @@ void MenuController::AcceptOrSelect(MenuItemView* parent,
}
void MenuController::SelectByChar(base::char16 character) {
- // Do not process while performing drag-and-drop
- if (!blocking_run_)
+ // Do not process while performing drag-and-drop.
+ if (for_drop_)
return;
if (!character)
return;
@@ -2483,14 +2572,6 @@ void MenuController::RepostEventAndCancel(SubmenuView* source,
exit_type = EXIT_OUTERMOST;
}
Cancel(exit_type);
-
-#if defined(OS_CHROMEOS)
- // We're going to exit the menu and want to repost the event so that is
- // is handled normally after the context menu has exited. We call
- // RepostEvent after Cancel so that event capture has been released so
- // that finding the event target is unaffected by the current capture.
- RepostEventImpl(event, screen_loc, native_view, window);
-#endif
}
void MenuController::SetDropMenuItem(
@@ -2723,14 +2804,15 @@ void MenuController::HandleMouseLocation(SubmenuView* source,
UpdateScrolling(part);
- if (!blocking_run_)
+ if (for_drop_)
return;
if (part.type == MenuPart::NONE && ShowSiblingMenu(source, mouse_location))
return;
if (part.type == MenuPart::MENU_ITEM && part.menu) {
- SetSelection(part.menu, SELECTION_OPEN_SUBMENU);
+ SetSelection(part.menu, part.should_submenu_show ? SELECTION_OPEN_SUBMENU
+ : SELECTION_DEFAULT);
} else if (!part.is_scroll() && pending_state_.item &&
pending_state_.item->GetParentMenuItem() &&
!pending_state_.item->SubmenuIsShowing()) {
diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h
index b2080448720..67b8849bca5 100644
--- a/chromium/ui/views/controls/menu/menu_controller.h
+++ b/chromium/ui/views/controls/menu/menu_controller.h
@@ -95,8 +95,7 @@ class VIEWS_EXPORT MenuController
bool context_menu,
bool is_nested_drag);
- // Whether or not Run blocks.
- bool IsBlockingRun() const { return blocking_run_; }
+ bool for_drop() const { return for_drop_; }
bool in_nested_run() const { return !menu_stack_.empty(); }
@@ -144,6 +143,8 @@ class VIEWS_EXPORT MenuController
void set_is_combobox(bool is_combobox) { is_combobox_ = is_combobox; }
bool is_combobox() const { return is_combobox_; }
+ bool IsContextMenu() const;
+
// Various events, forwarded from the submenu.
//
// NOTE: the coordinates of the events are in that of the
@@ -294,13 +295,11 @@ class VIEWS_EXPORT MenuController
SCROLL_DOWN
};
- MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {}
-
// Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP.
bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; }
// Type of part.
- Type type;
+ Type type = NONE;
// If type is MENU_ITEM, this is the menu item the mouse is over, otherwise
// this is NULL.
@@ -308,14 +307,17 @@ class VIEWS_EXPORT MenuController
// but is over a menu (for example, the mouse is over a separator or
// empty menu), this is NULL and parent is the menu the mouse was
// clicked on.
- MenuItemView* menu;
+ MenuItemView* menu = nullptr;
// If type is MENU_ITEM but the mouse is not over a menu item this is the
// parent of the menu item the user clicked on. Otherwise this is NULL.
- MenuItemView* parent;
+ MenuItemView* parent = nullptr;
// This is the submenu the mouse is over.
- SubmenuView* submenu;
+ SubmenuView* submenu = nullptr;
+
+ // Whether the controller should apply SELECTION_OPEN_SUBMENU to this item.
+ bool should_submenu_show = false;
};
// Sets the selection to |menu_item|. A value of NULL unselects
@@ -334,10 +336,8 @@ class VIEWS_EXPORT MenuController
// Key processing.
void OnKeyDown(ui::KeyboardCode key_code);
- // Creates a MenuController. If |blocking| is true a nested run loop is
- // started in |Run|.
- MenuController(bool blocking,
- internal::MenuControllerDelegate* delegate);
+ // Creates a MenuController. See |for_drop_| member for details on |for_drop|.
+ MenuController(bool for_drop, internal::MenuControllerDelegate* delegate);
~MenuController() override;
@@ -419,6 +419,11 @@ class VIEWS_EXPORT MenuController
bool DoesSubmenuContainLocation(SubmenuView* submenu,
const gfx::Point& screen_loc);
+ // Returns whether the location is over the ACTIONABLE_SUBMENU's submenu area.
+ bool IsLocationOverSubmenuAreaOfActionableSubmenu(
+ MenuItemView* item,
+ const gfx::Point& screen_loc) const;
+
// Opens/Closes the necessary menus such that state_ matches that of
// pending_state_. This is invoked if submenus are not opened immediately,
// but after a delay.
@@ -576,11 +581,10 @@ class VIEWS_EXPORT MenuController
// The active instance.
static MenuController* active_instance_;
- // If true, Run blocks. If false, Run doesn't block and this is used for
- // drag and drop. Note that the semantics for drag and drop are slightly
- // different: cancel timer is kicked off any time the drag moves outside the
- // menu, mouse events do nothing...
- bool blocking_run_;
+ // If true the menu is shown for a drag and drop. Note that the semantics for
+ // drag and drop are slightly different: cancel timer is kicked off any time
+ // the drag moves outside the menu, mouse events do nothing...
+ const bool for_drop_;
// If true, we're showing.
bool showing_ = false;
diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
index 22fa8335af1..2d1c138bdc0 100644
--- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
@@ -294,6 +294,11 @@ class MenuControllerTest : public ViewsTestBase {
event_generator_->PressKey(key_code, 0);
}
+ void DispatchKey(ui::KeyboardCode key_code) {
+ ui::KeyEvent event(ui::EventType::ET_KEY_PRESSED, key_code, 0);
+ menu_controller_->OnWillDispatchKeyEvent(&event);
+ }
+
#if defined(USE_AURA)
// Verifies that a non-nested menu fully closes when receiving an escape key.
void TestAsyncEscapeKey() {
@@ -330,8 +335,9 @@ class MenuControllerTest : public ViewsTestBase {
void TestMenuControllerReplacementDuringDrag() {
DestroyMenuController();
menu_item()->GetSubmenu()->Close();
+ const bool for_drop = false;
menu_controller_ =
- new MenuController(true, menu_controller_delegate_.get());
+ new MenuController(for_drop, menu_controller_delegate_.get());
menu_controller_->owner_ = owner_.get();
menu_controller_->showing_ = true;
}
@@ -485,7 +491,7 @@ class MenuControllerTest : public ViewsTestBase {
}
MenuController* menu_controller() { return menu_controller_; }
const MenuItemView* pending_state_item() const {
- return menu_controller_->pending_state_.item;
+ return menu_controller_->pending_state_.item;
}
MenuController::ExitType menu_exit_type() const {
return menu_controller_->exit_type_;
@@ -533,6 +539,10 @@ class MenuControllerTest : public ViewsTestBase {
int CountOwnerOnGestureEvent() const { return owner_->gesture_count(); }
+ bool SelectionWraps() {
+ return MenuConfig::instance().arrow_key_selection_wraps;
+ }
+
private:
void Init() {
owner_ = std::make_unique<GestureTestWidget>();
@@ -558,8 +568,9 @@ class MenuControllerTest : public ViewsTestBase {
void SetupMenuController() {
menu_controller_delegate_.reset(new TestMenuControllerDelegate);
+ const bool for_drop = false;
menu_controller_ =
- new MenuController(true, menu_controller_delegate_.get());
+ new MenuController(for_drop, menu_controller_delegate_.get());
menu_controller_->owner_ = owner_.get();
menu_controller_->showing_ = true;
menu_controller_->SetSelection(
@@ -644,8 +655,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) {
// The last selectable item should be item "Four".
MenuItemView* last_selectable =
FindInitialSelectableMenuItemUp(menu_item());
- ASSERT_NE(nullptr, last_selectable);
- EXPECT_EQ(4, last_selectable->GetCommand());
+ if (SelectionWraps()) {
+ ASSERT_NE(nullptr, last_selectable);
+ EXPECT_EQ(4, last_selectable->GetCommand());
+ } else {
+ ASSERT_EQ(nullptr, last_selectable);
+ }
// Leave items "One" and "Two" enabled.
menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true);
@@ -658,8 +673,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) {
EXPECT_EQ(1, first_selectable->GetCommand());
// The last selectable item should be item "Two".
last_selectable = FindInitialSelectableMenuItemUp(menu_item());
- ASSERT_NE(nullptr, last_selectable);
- EXPECT_EQ(2, last_selectable->GetCommand());
+ if (SelectionWraps()) {
+ ASSERT_NE(nullptr, last_selectable);
+ EXPECT_EQ(2, last_selectable->GetCommand());
+ } else {
+ ASSERT_EQ(nullptr, last_selectable);
+ }
// Leave only a single item "One" enabled.
menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true);
@@ -672,8 +691,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) {
EXPECT_EQ(1, first_selectable->GetCommand());
// The last selectable item should be item "One".
last_selectable = FindInitialSelectableMenuItemUp(menu_item());
- ASSERT_NE(nullptr, last_selectable);
- EXPECT_EQ(1, last_selectable->GetCommand());
+ if (SelectionWraps()) {
+ ASSERT_NE(nullptr, last_selectable);
+ EXPECT_EQ(1, last_selectable->GetCommand());
+ } else {
+ ASSERT_EQ(nullptr, last_selectable);
+ }
// Leave only a single item "Three" enabled.
menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false);
@@ -686,8 +709,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) {
EXPECT_EQ(3, first_selectable->GetCommand());
// The last selectable item should be item "Three".
last_selectable = FindInitialSelectableMenuItemUp(menu_item());
- ASSERT_NE(nullptr, last_selectable);
- EXPECT_EQ(3, last_selectable->GetCommand());
+ if (SelectionWraps()) {
+ ASSERT_NE(nullptr, last_selectable);
+ EXPECT_EQ(3, last_selectable->GetCommand());
+ } else {
+ ASSERT_EQ(nullptr, last_selectable);
+ }
// Leave only a single item ("Two") selected. It should be the first and the
// last selectable item.
@@ -699,8 +726,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) {
ASSERT_NE(nullptr, first_selectable);
EXPECT_EQ(2, first_selectable->GetCommand());
last_selectable = FindInitialSelectableMenuItemUp(menu_item());
- ASSERT_NE(nullptr, last_selectable);
- EXPECT_EQ(2, last_selectable->GetCommand());
+ if (SelectionWraps()) {
+ ASSERT_NE(nullptr, last_selectable);
+ EXPECT_EQ(2, last_selectable->GetCommand());
+ } else {
+ ASSERT_EQ(nullptr, last_selectable);
+ }
// There should be no next or previous selectable item since there is only a
// single enabled item in the menu.
@@ -730,14 +761,20 @@ TEST_F(MenuControllerTest, NextSelectedItem) {
IncrementSelection();
EXPECT_EQ(4, pending_state_item()->GetCommand());
- // Wrap around.
- IncrementSelection();
- EXPECT_EQ(1, pending_state_item()->GetCommand());
-
- // Move up in the menu.
- // Wrap around.
- DecrementSelection();
- EXPECT_EQ(4, pending_state_item()->GetCommand());
+ if (SelectionWraps()) {
+ // Wrap around.
+ IncrementSelection();
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+
+ // Move up in the menu.
+ // Wrap around.
+ DecrementSelection();
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+ } else {
+ // Don't wrap.
+ IncrementSelection();
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+ }
// Skip disabled item.
DecrementSelection();
@@ -762,7 +799,10 @@ TEST_F(MenuControllerTest, PreviousSelectedItem) {
// Move up and select a previous (in our case the last enabled) item.
DecrementSelection();
- EXPECT_EQ(3, pending_state_item()->GetCommand());
+ if (SelectionWraps())
+ EXPECT_EQ(3, pending_state_item()->GetCommand());
+ else
+ EXPECT_EQ(0, pending_state_item()->GetCommand());
// Clear references in menu controller to the menu item that is going away.
ResetSelection();
@@ -843,7 +883,10 @@ TEST_F(MenuControllerTest, SelectChildButtonView) {
// Increment selection twice to wrap around.
IncrementSelection();
IncrementSelection();
- EXPECT_EQ(1, pending_state_item()->GetCommand());
+ if (SelectionWraps())
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+ else
+ EXPECT_EQ(5, pending_state_item()->GetCommand());
// Clear references in menu controller to the menu item that is going away.
ResetSelection();
@@ -1288,6 +1331,36 @@ TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) {
EXPECT_EQ(1, nested_delegate->on_menu_closed_called());
}
+TEST_F(MenuControllerTest, ArrowKeysAtEnds) {
+ menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
+
+ SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+
+ if (SelectionWraps()) {
+ DispatchKey(ui::VKEY_UP);
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+
+ DispatchKey(ui::VKEY_DOWN);
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+ } else {
+ DispatchKey(ui::VKEY_UP);
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+ }
+
+ DispatchKey(ui::VKEY_DOWN);
+ EXPECT_EQ(2, pending_state_item()->GetCommand());
+
+ DispatchKey(ui::VKEY_DOWN);
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+
+ DispatchKey(ui::VKEY_DOWN);
+ if (SelectionWraps())
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+ else
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+}
+
#if defined(USE_AURA)
// Tests that when an asynchronous menu receives a cancel event, that it closes.
TEST_F(MenuControllerTest, AsynchronousCancelEvent) {
diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc
index f1ff95b4748..f14a1c06685 100644
--- a/chromium/ui/views/controls/menu/menu_host.cc
+++ b/chromium/ui/views/controls/menu/menu_host.cc
@@ -116,9 +116,7 @@ void MenuHost::InitMenuHost(Widget* parent,
const MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
const MenuConfig& menu_config = MenuConfig::instance();
- bool rounded_border =
- menu_controller && (menu_controller->use_touchable_layout() ||
- (menu_config.corner_radius > 0));
+ bool rounded_border = menu_config.CornerRadiusForMenu(menu_controller) != 0;
bool bubble_border = submenu_->GetScrollViewContainer() &&
submenu_->GetScrollViewContainer()->HasBubbleBorder();
params.shadow_type = bubble_border ? Widget::InitParams::SHADOW_TYPE_NONE
diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc
index 257f71e6eaa..22a219b4660 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view.cc
@@ -12,6 +12,7 @@
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
+#include "ui/base/ui_base_features.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
@@ -29,6 +30,7 @@
#include "ui/views/controls/menu/menu_scroll_view_container.h"
#include "ui/views/controls/menu/menu_separator.h"
#include "ui/views/controls/menu/submenu_view.h"
+#include "ui/views/controls/separator.h"
#include "ui/views/widget/widget.h"
namespace views {
@@ -171,7 +173,8 @@ void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
switch (GetType()) {
case SUBMENU:
- node_data->AddState(ax::mojom::State::kHaspopup);
+ case ACTIONABLE_SUBMENU:
+ node_data->SetHasPopup(ax::mojom::HasPopup::kMenu);
break;
case CHECKBOX:
case RADIO: {
@@ -267,7 +270,7 @@ MenuItemView* MenuItemView::AddMenuItemAt(
item->SetMinorIcon(minor_icon);
if (!icon.isNull())
item->SetIcon(icon);
- if (type == SUBMENU)
+ if (type == SUBMENU || type == ACTIONABLE_SUBMENU)
item->CreateSubmenu();
if (GetDelegate() && !GetDelegate()->IsCommandVisible(item_id))
item->SetVisible(false);
@@ -398,6 +401,19 @@ void MenuItemView::SetSelected(bool selected) {
SchedulePaint();
}
+void MenuItemView::SetSelectionOfActionableSubmenu(
+ bool submenu_area_of_actionable_submenu_selected) {
+ DCHECK_EQ(ACTIONABLE_SUBMENU, type_);
+ if (submenu_area_of_actionable_submenu_selected_ ==
+ submenu_area_of_actionable_submenu_selected) {
+ return;
+ }
+
+ submenu_area_of_actionable_submenu_selected_ =
+ submenu_area_of_actionable_submenu_selected;
+ SchedulePaint();
+}
+
void MenuItemView::SetTooltip(const base::string16& tooltip, int item_id) {
MenuItemView* item = GetMenuItemByID(item_id);
DCHECK(item);
@@ -458,6 +474,13 @@ int MenuItemView::GetHeightForWidth(int width) const {
return height;
}
+gfx::Rect MenuItemView::GetSubmenuAreaOfActionableSubmenu() const {
+ DCHECK_EQ(ACTIONABLE_SUBMENU, type_);
+ const MenuConfig& config = MenuConfig::instance();
+ return gfx::Rect(gfx::Point(vertical_separator_->bounds().right(), 0),
+ gfx::Size(config.actionable_submenu_width, height()));
+}
+
const MenuItemView::MenuItemDimensions& MenuItemView::GetDimensions() const {
if (!is_dimensions_valid())
dimensions_ = CalculateDimensions();
@@ -495,8 +518,10 @@ const MenuItemView* MenuItemView::GetRootMenuItem() const {
}
base::char16 MenuItemView::GetMnemonic() {
- if (!GetRootMenuItem()->has_mnemonics_)
+ if (!GetRootMenuItem()->has_mnemonics_ ||
+ !MenuConfig::instance().use_mnemonics) {
return 0;
+ }
size_t index = 0;
do {
@@ -580,6 +605,8 @@ void MenuItemView::Layout() {
continue;
if (submenu_arrow_image_view_ == child)
continue;
+ if (vertical_separator_ == child)
+ continue;
int width = child->GetPreferredSize().width();
child->SetBounds(x - width, 0, width, height());
x -= width + kChildXPadding;
@@ -611,13 +638,25 @@ void MenuItemView::Layout() {
}
if (submenu_arrow_image_view_) {
- int x = width() - config.arrow_width - config.arrow_to_edge_padding;
+ int x = width() - config.arrow_width -
+ (type_ == ACTIONABLE_SUBMENU
+ ? config.actionable_submenu_arrow_to_edge_padding
+ : config.arrow_to_edge_padding);
int y =
(height() + GetTopMargin() - GetBottomMargin() - kSubmenuArrowSize) /
2;
submenu_arrow_image_view_->SetBounds(x, y, config.arrow_width,
kSubmenuArrowSize);
}
+
+ if (vertical_separator_) {
+ const gfx::Size preferred_size = vertical_separator_->GetPreferredSize();
+ int x = width() - config.actionable_submenu_width -
+ config.actionable_submenu_vertical_separator_width;
+ int y = (height() - preferred_size.height()) / 2;
+ vertical_separator_->SetBoundsRect(
+ gfx::Rect(gfx::Point(x, y), preferred_size));
+ }
}
}
@@ -682,7 +721,11 @@ void MenuItemView::UpdateMenuPartSizes() {
if (has_icons_)
icon_area_width_ = std::max(icon_area_width_, GetMaxIconViewWidth());
- label_start_ = config.item_left_margin + icon_area_width_;
+ const bool use_touchable_layout =
+ GetMenuController() && GetMenuController()->use_touchable_layout();
+ label_start_ = (use_touchable_layout ? config.touchable_item_left_margin
+ : config.item_left_margin) +
+ icon_area_width_;
int padding = 0;
if (config.always_use_icon_to_label_padding) {
padding = config.icon_to_label_padding;
@@ -690,7 +733,7 @@ void MenuItemView::UpdateMenuPartSizes() {
padding = (has_icons_ || HasChecksOrRadioButtons()) ?
config.icon_to_label_padding : 0;
}
- if (GetMenuController() && GetMenuController()->use_touchable_layout())
+ if (use_touchable_layout)
padding = config.touchable_icon_to_label_padding;
label_start_ += padding;
@@ -705,15 +748,16 @@ void MenuItemView::Init(MenuItemView* parent,
MenuItemView::Type type,
MenuDelegate* delegate) {
delegate_ = delegate;
- controller_ = NULL;
+ controller_ = nullptr;
canceled_ = false;
parent_menu_item_ = parent;
type_ = type;
selected_ = false;
command_ = command;
- submenu_ = NULL;
+ submenu_ = nullptr;
radio_check_image_view_ = nullptr;
submenu_arrow_image_view_ = nullptr;
+ vertical_separator_ = nullptr;
show_mnemonics_ = false;
// Assign our ID, this allows SubmenuItemView to find MenuItemViews.
set_id(kMenuItemViewID);
@@ -729,6 +773,20 @@ void MenuItemView::Init(MenuItemView* parent,
AddChildView(radio_check_image_view_);
}
+ if (type_ == ACTIONABLE_SUBMENU) {
+ vertical_separator_ = new Separator();
+ vertical_separator_->SetVisible(true);
+ vertical_separator_->SetFocusBehavior(FocusBehavior::NEVER);
+ const MenuConfig& config = MenuConfig::instance();
+ vertical_separator_->SetColor(GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_ActionableSubmenuVerticalSeparatorColor));
+ vertical_separator_->SetPreferredSize(
+ gfx::Size(config.actionable_submenu_vertical_separator_width,
+ config.actionable_submenu_vertical_separator_height));
+ vertical_separator_->set_can_process_events_within_subtree(false);
+ AddChildView(vertical_separator_);
+ }
+
if (submenu_arrow_image_view_)
submenu_arrow_image_view_->SetVisible(HasSubmenu());
@@ -844,6 +902,15 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
ui::NativeTheme* native_theme = GetNativeTheme();
if (render_selection) {
gfx::Rect item_bounds(0, 0, width(), height());
+ if (type_ == ACTIONABLE_SUBMENU) {
+ if (submenu_area_of_actionable_submenu_selected_) {
+ item_bounds = GetSubmenuAreaOfActionableSubmenu();
+ } else {
+ item_bounds = gfx::Rect(gfx::Size(
+ width() - MenuConfig::instance().actionable_submenu_width - 1,
+ height()));
+ }
+ }
AdjustBoundsForRTLUI(&item_bounds);
native_theme->Paint(canvas->sk_canvas(),
@@ -962,6 +1029,10 @@ SkColor MenuItemView::GetTextColor(bool minor,
if (!emphasized)
color_id = ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor;
}
+
+ if (GetMenuController() && GetMenuController()->use_touchable_layout())
+ color_id = ui::NativeTheme::kColorId_TouchableMenuItemLabelColor;
+
return GetNativeTheme()->GetSystemColor(color_id);
}
@@ -1012,6 +1083,8 @@ gfx::Size MenuItemView::GetChildPreferredSize() const {
continue;
if (submenu_arrow_image_view_ == child)
continue;
+ if (vertical_separator_ == child)
+ continue;
if (i)
width += kChildXPadding;
width += child->GetPreferredSize().width();
@@ -1034,30 +1107,18 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
const MenuConfig& menu_config = MenuConfig::instance();
if (GetMenuController() && GetMenuController()->use_touchable_layout()) {
- // MenuItemViews that use the touchable layout have fixed height and width.
+ // Touchable layout uses a fixed size, but adjusts the height for icons.
dimensions.height = menu_config.touchable_menu_height;
+ if (icon_view_) {
+ dimensions.height = icon_view_->height() +
+ 2 * menu_config.vertical_touchable_menu_item_padding;
+ }
dimensions.standard_width = menu_config.touchable_menu_width;
return dimensions;
}
const gfx::FontList& font_list = GetFontList();
base::string16 minor_text = GetMinorText();
- if (menu_config.fixed_text_item_height &&
- menu_config.fixed_container_item_height && menu_config.fixed_menu_width &&
- GetMenuController() && !GetMenuController()->is_combobox()) {
- bool has_children = NonIconChildViewsCount() > 0;
- dimensions.height = has_children ? menu_config.fixed_container_item_height
- : menu_config.fixed_text_item_height;
- dimensions.children_width = 0;
- dimensions.minor_text_width =
- minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, font_list);
- int leave_for_minor = dimensions.minor_text_width
- ? dimensions.minor_text_width +
- menu_config.label_to_minor_text_padding
- : 0;
- dimensions.standard_width = menu_config.fixed_menu_width - leave_for_minor;
- return dimensions;
- }
dimensions.height = child_size.height();
// Adjust item content height if menu has both items with and without icons.
@@ -1069,8 +1130,10 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
dimensions.height += GetBottomMargin() + GetTopMargin();
// In case of a container, only the container size needs to be filled.
- if (IsContainer())
+ if (IsContainer()) {
+ ApplyMinimumDimensions(&dimensions);
return dimensions;
+ }
// Get Icon margin overrides for this particular item.
const MenuDelegate* delegate = GetDelegate();
@@ -1105,11 +1168,37 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
font_list.GetHeight() + GetBottomMargin() + GetTopMargin());
dimensions.height =
std::max(dimensions.height, MenuConfig::instance().item_min_height);
+
+ ApplyMinimumDimensions(&dimensions);
return dimensions;
}
+void MenuItemView::ApplyMinimumDimensions(MenuItemDimensions* dims) const {
+ // Don't apply minimums to menus without controllers or to comboboxes.
+ if (!GetMenuController() || GetMenuController()->is_combobox())
+ return;
+
+ int used =
+ dims->standard_width + dims->children_width + dims->minor_text_width;
+ const MenuConfig& config = MenuConfig::instance();
+ if (used < config.minimum_menu_width)
+ dims->standard_width += (config.minimum_menu_width - used);
+
+ dims->height = std::max(dims->height,
+ IsContainer() ? config.minimum_container_item_height
+ : config.minimum_text_item_height);
+}
+
int MenuItemView::GetLabelStartForThisItem() const {
const MenuConfig& config = MenuConfig::instance();
+
+ // Touchable items with icons do not respect |label_start_|.
+ if (GetMenuController() && GetMenuController()->use_touchable_layout() &&
+ icon_view_) {
+ return config.touchable_item_left_margin + icon_view_->width() +
+ config.touchable_icon_to_label_padding;
+ }
+
int label_start = label_start_ + left_icon_margin_ + right_icon_margin_;
if ((config.icons_in_label || type_ == CHECKBOX || type_ == RADIO) &&
icon_view_)
@@ -1124,12 +1213,9 @@ base::string16 MenuItemView::GetMinorText() const {
return base::string16();
}
- ui::Accelerator accelerator;
- if (MenuConfig::instance().show_accelerators && GetDelegate() &&
- GetCommand() &&
- GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) {
- return accelerator.GetShortcutText();
- }
+ base::string16 accel_text;
+ if (MenuConfig::instance().ShouldShowAcceleratorText(this, &accel_text))
+ return accel_text;
return minor_text_;
}
@@ -1149,7 +1235,7 @@ int MenuItemView::NonIconChildViewsCount() const {
// not the number of menu items.
return child_count() - (icon_view_ ? 1 : 0) -
(radio_check_image_view_ ? 1 : 0) -
- (submenu_arrow_image_view_ ? 1 : 0);
+ (submenu_arrow_image_view_ ? 1 : 0) - (vertical_separator_ ? 1 : 0);
}
int MenuItemView::GetMaxIconViewWidth() const {
diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h
index be24fb73cf7..6276932ceaf 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.h
+++ b/chromium/ui/views/controls/menu/menu_item_view.h
@@ -17,7 +17,6 @@
#include "build/build_config.h"
#include "ui/base/models/menu_separator_types.h"
#include "ui/gfx/image/image_skia.h"
-#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_types.h"
#include "ui/views/view.h"
@@ -45,6 +44,7 @@ class TestMenuItemViewShown;
class MenuController;
class MenuDelegate;
+class Separator;
class TestMenuItemView;
class SubmenuView;
@@ -83,15 +83,16 @@ class VIEWS_EXPORT MenuItemView : public View {
// ID used to identify empty menu items.
static const int kEmptyMenuItemViewID;
- // Different types of menu items. EMPTY is a special type for empty
- // menus that is only used internally.
+ // Different types of menu items.
enum Type {
- NORMAL,
- SUBMENU,
- CHECKBOX,
- RADIO,
- SEPARATOR,
- EMPTY
+ NORMAL, // Performs an action when selected.
+ SUBMENU, // Presents a submenu within another menu.
+ ACTIONABLE_SUBMENU, // A SUBMENU that is also a COMMAND.
+ CHECKBOX, // Can be selected/checked to toggle a boolean state.
+ RADIO, // Can be selected/checked among a group of choices.
+ SEPARATOR, // Shows a horizontal line separator.
+ EMPTY, // EMPTY is a special type for empty menus that is only used
+ // internally.
};
// Where the menu should be drawn, above or below the bounds (when
@@ -257,6 +258,15 @@ class VIEWS_EXPORT MenuItemView : public View {
// Returns true if the item is selected.
bool IsSelected() const { return selected_; }
+ // Sets whether the submenu area of an ACTIONABLE_SUBMENU is selected.
+ void SetSelectionOfActionableSubmenu(
+ bool submenu_area_of_actionable_submenu_selected);
+
+ // Whether the submenu area of an ACTIONABLE_SUBMENU is selected.
+ bool IsSubmenuAreaOfActionableSubmenuSelected() const {
+ return submenu_area_of_actionable_submenu_selected_;
+ }
+
// Sets the |tooltip| for a menu item view with |item_id| identifier.
void SetTooltip(const base::string16& tooltip, int item_id);
@@ -288,6 +298,9 @@ class VIEWS_EXPORT MenuItemView : public View {
// dimensions.
int GetHeightForWidth(int width) const override;
+ // Returns the bounds of the submenu part of the ACTIONABLE_SUBMENU.
+ gfx::Rect GetSubmenuAreaOfActionableSubmenu() const;
+
// Return the preferred dimensions of the item in pixel.
const MenuItemDimensions& GetDimensions() const;
@@ -430,6 +443,14 @@ class VIEWS_EXPORT MenuItemView : public View {
// Calculates and returns the MenuItemDimensions.
MenuItemDimensions CalculateDimensions() const;
+ // Imposes MenuConfig's minimum sizes, if any, on the supplied
+ // dimensions and returns the new dimensions. It is guaranteed that:
+ // ApplyMinimumDimensions(x).standard_width >= x.standard_width
+ // ApplyMinimumDimensions(x).children_width == x.children_width
+ // ApplyMinimumDimensions(x).minor_text_width == x.minor_text_width
+ // ApplyMinimumDimensions(x).height >= x.height
+ void ApplyMinimumDimensions(MenuItemDimensions* dims) const;
+
// Get the horizontal position at which to draw the menu item's label.
int GetLabelStartForThisItem() const;
@@ -484,6 +505,9 @@ class VIEWS_EXPORT MenuItemView : public View {
// Whether we're selected.
bool selected_;
+ // Whether the submenu area of an ACTIONABLE_SUBMENU is selected.
+ bool submenu_area_of_actionable_submenu_selected_;
+
// Command id.
int command_;
@@ -565,6 +589,10 @@ class VIEWS_EXPORT MenuItemView : public View {
// The forced visual selection state of this item, if any.
base::Optional<bool> forced_visual_selection_;
+ // The vertical separator that separates the actionable and submenu regions of
+ // an ACTIONABLE_SUBMENU.
+ Separator* vertical_separator_;
+
DISALLOW_COPY_AND_ASSIGN(MenuItemView);
};
diff --git a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
index bdc22a6b784..07273f5de1a 100644
--- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
@@ -41,6 +41,8 @@ class TestMenuItemView : public MenuItemView {
void AddEmptyMenus() { MenuItemView::AddEmptyMenus(); }
+ void SetHasMnemonics(bool has_mnemonics) { has_mnemonics_ = has_mnemonics; }
+
private:
DISALLOW_COPY_AND_ASSIGN(TestMenuItemView);
};
@@ -70,15 +72,14 @@ TEST(MenuItemViewUnitTest, TestMenuItemViewWithFlexibleWidthChild) {
ASSERT_EQ(flexible_view, submenu->GetMenuItemAt(1));
gfx::Size flexible_size = flexible_view->GetPreferredSize();
- // The flexible view's "preferred size" should be 1x1...
- EXPECT_EQ(flexible_size, gfx::Size(1, 1));
+ EXPECT_EQ(1, flexible_size.width());
// ...but it should use whatever space is available to make a square.
int flex_height = flexible_view->GetHeightForWidth(label_size.width());
EXPECT_EQ(label_size.width(), flex_height);
- // The submenu should be tall enough to allow for both menu items at the given
- // width.
+ // The submenu should be tall enough to allow for both menu items at the
+ // given width.
EXPECT_EQ(label_size.height() + flex_height,
submenu->GetPreferredSize().height());
}
@@ -147,6 +148,24 @@ TEST(MenuItemViewUnitTest, TestEmptySubmenuWhenAllChildItemsAreHidden) {
empty_item->title());
}
+TEST(MenuItemViewUnitTest, UseMnemonicOnPlatform) {
+ TestMenuItemView root_menu;
+ views::MenuItemView* item1 =
+ root_menu.AppendMenuItemWithLabel(1, base::ASCIIToUTF16("&Item 1"));
+ views::MenuItemView* item2 =
+ root_menu.AppendMenuItemWithLabel(2, base::ASCIIToUTF16("I&tem 2"));
+
+ root_menu.SetHasMnemonics(true);
+
+ if (MenuConfig::instance().use_mnemonics) {
+ EXPECT_EQ('i', item1->GetMnemonic());
+ EXPECT_EQ('t', item2->GetMnemonic());
+ } else {
+ EXPECT_EQ(0, item1->GetMnemonic());
+ EXPECT_EQ(0, item2->GetMnemonic());
+ }
+}
+
class MenuItemViewPaintUnitTest : public ViewsTestBase {
public:
MenuItemViewPaintUnitTest() {}
diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc
index e1a2339bddb..e9cfe1c466c 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc
@@ -80,6 +80,9 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
case ui::MenuModel::TYPE_SUBMENU:
type = MenuItemView::SUBMENU;
break;
+ case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
+ type = MenuItemView::ACTIONABLE_SUBMENU;
+ break;
}
if (*type == MenuItemView::SEPARATOR) {
@@ -267,9 +270,11 @@ void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) {
for (int i = 0; i < item_count; ++i) {
MenuItemView* item = AppendMenuItem(menu, model, i);
- if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
+ if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU ||
+ model->GetTypeAt(i) == ui::MenuModel::TYPE_ACTIONABLE_SUBMENU) {
DCHECK(item);
- DCHECK_EQ(MenuItemView::SUBMENU, item->GetType());
+ DCHECK(item->GetType() == MenuItemView::SUBMENU ||
+ item->GetType() == MenuItemView::ACTIONABLE_SUBMENU);
ui::MenuModel* submodel = model->GetSubmenuModelAt(i);
DCHECK(submodel);
BuildMenuImpl(item, submodel);
diff --git a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
index c5df9547874..df3bb3ef251 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
@@ -16,8 +16,9 @@
namespace {
// Base command id for test menu and its submenu.
-const int kRootIdBase = 100;
-const int kSubmenuIdBase = 200;
+constexpr int kRootIdBase = 100;
+constexpr int kSubmenuIdBase = 200;
+constexpr int kActionableSubmenuIdBase = 300;
class MenuModelBase : public ui::MenuModel {
public:
@@ -139,61 +140,68 @@ class SubmenuModel : public MenuModelBase {
DISALLOW_COPY_AND_ASSIGN(SubmenuModel);
};
+class ActionableSubmenuModel : public MenuModelBase {
+ public:
+ ActionableSubmenuModel() : MenuModelBase(kActionableSubmenuIdBase) {
+ items_.push_back(Item(TYPE_COMMAND, "actionable submenu item 0", NULL));
+ items_.push_back(Item(TYPE_COMMAND, "actionable submenu item 1", NULL));
+ }
+ ~ActionableSubmenuModel() override = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ActionableSubmenuModel);
+};
+
class RootModel : public MenuModelBase {
public:
RootModel() : MenuModelBase(kRootIdBase) {
- submenu_model_.reset(new SubmenuModel);
+ submenu_model_ = std::make_unique<SubmenuModel>();
+ actionable_submenu_model_ = std::make_unique<ActionableSubmenuModel>();
items_.push_back(Item(TYPE_COMMAND, "command 0", NULL));
items_.push_back(Item(TYPE_CHECK, "check 1", NULL));
items_.push_back(Item(TYPE_SEPARATOR, "", NULL));
items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get()));
items_.push_back(Item(TYPE_RADIO, "radio 4", NULL));
+ items_.push_back(Item(TYPE_ACTIONABLE_SUBMENU, "actionable 5",
+ actionable_submenu_model_.get()));
}
~RootModel() override {}
private:
std::unique_ptr<MenuModel> submenu_model_;
+ std::unique_ptr<MenuModel> actionable_submenu_model_;
DISALLOW_COPY_AND_ASSIGN(RootModel);
};
-} // namespace
-
-namespace views {
-
-typedef ViewsTestBase MenuModelAdapterTest;
-
-TEST_F(MenuModelAdapterTest, BasicTest) {
- // Build model and adapter.
- RootModel model;
- views::MenuModelAdapter delegate(&model);
-
- // Create menu. Build menu twice to check that rebuilding works properly.
- MenuItemView* menu = new views::MenuItemView(&delegate);
- // MenuRunner takes ownership of menu.
- std::unique_ptr<MenuRunner> menu_runner(new MenuRunner(menu, 0));
- delegate.BuildMenu(menu);
- delegate.BuildMenu(menu);
- EXPECT_TRUE(menu->HasSubmenu());
+void CheckSubmenu(const RootModel& model,
+ views::MenuItemView* menu,
+ views::MenuModelAdapter* delegate,
+ int submenu_id,
+ int expected_children,
+ int submenu_model_index,
+ int id_base) {
+ views::MenuItemView* submenu = menu->GetMenuItemByID(submenu_id);
+ views::SubmenuView* subitem_container = submenu->GetSubmenu();
+ EXPECT_EQ(expected_children, subitem_container->child_count());
- // Check top level menu items.
- views::SubmenuView* item_container = menu->GetSubmenu();
- EXPECT_EQ(5, item_container->child_count());
+ for (int i = 0; i < subitem_container->child_count(); ++i) {
+ MenuModelBase* submodel = static_cast<MenuModelBase*>(
+ model.GetSubmenuModelAt(submenu_model_index));
+ EXPECT_TRUE(submodel);
- for (int i = 0; i < item_container->child_count(); ++i) {
- const MenuModelBase::Item& model_item = model.GetItemDefinition(i);
+ const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i);
- const int id = i + kRootIdBase;
- MenuItemView* item = menu->GetMenuItemByID(id);
+ const int id = i + id_base;
+ views::MenuItemView* item = menu->GetMenuItemByID(id);
if (!item) {
EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
continue;
}
-
// Check placement.
- EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item));
+ EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item));
// Check type.
switch (model_item.type) {
@@ -212,27 +220,45 @@ TEST_F(MenuModelAdapterTest, BasicTest) {
case ui::MenuModel::TYPE_SUBMENU:
EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType());
break;
+ case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
+ EXPECT_EQ(views::MenuItemView::ACTIONABLE_SUBMENU, item->GetType());
+ break;
}
// Check activation.
- static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
- EXPECT_EQ(i, model.last_activation());
- model.set_last_activation(-1);
+ static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id);
+ EXPECT_EQ(i, submodel->last_activation());
+ submodel->set_last_activation(-1);
}
+}
- // Check submenu items.
- views::MenuItemView* submenu = menu->GetMenuItemByID(103);
- views::SubmenuView* subitem_container = submenu->GetSubmenu();
- EXPECT_EQ(2, subitem_container->child_count());
+} // namespace
- for (int i = 0; i < subitem_container->child_count(); ++i) {
- MenuModelBase* submodel = static_cast<MenuModelBase*>(
- model.GetSubmenuModelAt(3));
- EXPECT_TRUE(submodel);
+namespace views {
- const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i);
+typedef ViewsTestBase MenuModelAdapterTest;
+
+TEST_F(MenuModelAdapterTest, BasicTest) {
+ // Build model and adapter.
+ RootModel model;
+ views::MenuModelAdapter delegate(&model);
- const int id = i + kSubmenuIdBase;
+ // Create menu. Build menu twice to check that rebuilding works properly.
+ MenuItemView* menu = new views::MenuItemView(&delegate);
+ // MenuRunner takes ownership of menu.
+ std::unique_ptr<MenuRunner> menu_runner(new MenuRunner(menu, 0));
+ delegate.BuildMenu(menu);
+ delegate.BuildMenu(menu);
+ EXPECT_TRUE(menu->HasSubmenu());
+
+ // Check top level menu items.
+ views::SubmenuView* item_container = menu->GetSubmenu();
+ EXPECT_EQ(6, item_container->child_count());
+
+ for (int i = 0; i < item_container->child_count(); ++i) {
+ const MenuModelBase::Item& model_item = model.GetItemDefinition(i);
+
+ const int id = i + kRootIdBase;
MenuItemView* item = menu->GetMenuItemByID(id);
if (!item) {
EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
@@ -240,7 +266,7 @@ TEST_F(MenuModelAdapterTest, BasicTest) {
}
// Check placement.
- EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item));
+ EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item));
// Check type.
switch (model_item.type) {
@@ -259,14 +285,27 @@ TEST_F(MenuModelAdapterTest, BasicTest) {
case ui::MenuModel::TYPE_SUBMENU:
EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType());
break;
+ case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU:
+ EXPECT_EQ(views::MenuItemView::ACTIONABLE_SUBMENU, item->GetType());
+ break;
}
// Check activation.
static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
- EXPECT_EQ(i, submodel->last_activation());
- submodel->set_last_activation(-1);
+ EXPECT_EQ(i, model.last_activation());
+ model.set_last_activation(-1);
}
+ // Check the submenu.
+ const int submenu_index = 3;
+ CheckSubmenu(model, menu, &delegate, kRootIdBase + submenu_index, 2,
+ submenu_index, kSubmenuIdBase);
+
+ // Check the actionable submenu.
+ const int actionable_submenu_index = 5;
+ CheckSubmenu(model, menu, &delegate, kRootIdBase + actionable_submenu_index,
+ 2, actionable_submenu_index, kActionableSubmenuIdBase);
+
// Check that selecting the root item is safe. The MenuModel does
// not care about the root so MenuModelAdapter should do nothing
// (not hit the NOTREACHED check) when the root is selected.
diff --git a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm
index 8bd58e67d17..ae0c8bafd05 100644
--- a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm
+++ b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm
@@ -12,8 +12,10 @@
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
#import "testing/gtest_mac.h"
+#include "ui/base/accelerators/accelerator.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/events/event_utils.h"
+#import "ui/events/test/cocoa_test_event_utils.h"
#include "ui/views/controls/menu/menu_runner_impl_adapter.h"
#include "ui/views/test/views_test_base.h"
@@ -21,14 +23,16 @@ namespace views {
namespace test {
namespace {
+constexpr int kTestCommandId = 0;
+
class TestModel : public ui::SimpleMenuModel {
public:
TestModel() : ui::SimpleMenuModel(&delegate_), delegate_(this) {}
void set_checked_command(int command) { checked_command_ = command; }
- void set_menu_open_callback(const base::Closure& callback) {
- menu_open_callback_ = callback;
+ void set_menu_open_callback(base::OnceClosure callback) {
+ menu_open_callback_ = std::move(callback);
}
private:
@@ -43,7 +47,17 @@ class TestModel : public ui::SimpleMenuModel {
void MenuWillShow(SimpleMenuModel* source) override {
if (!model_->menu_open_callback_.is_null())
- model_->menu_open_callback_.Run();
+ std::move(model_->menu_open_callback_).Run();
+ }
+
+ bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) const override {
+ if (command_id == kTestCommandId) {
+ *accelerator = ui::Accelerator(ui::VKEY_E, ui::EF_CONTROL_DOWN);
+ return true;
+ }
+ return false;
}
private:
@@ -55,7 +69,7 @@ class TestModel : public ui::SimpleMenuModel {
private:
int checked_command_ = -1;
Delegate delegate_;
- base::Closure menu_open_callback_;
+ base::OnceClosure menu_open_callback_;
DISALLOW_COPY_AND_ASSIGN(TestModel);
};
@@ -84,7 +98,7 @@ class MenuRunnerCocoaTest : public ViewsTestBase,
ViewsTestBase::SetUp();
menu_.reset(new TestModel());
- menu_->AddCheckItem(0, base::ASCIIToUTF16("Menu Item"));
+ menu_->AddCheckItem(kTestCommandId, base::ASCIIToUTF16("Menu Item"));
parent_ = new views::Widget();
parent_->Init(CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS));
@@ -114,16 +128,17 @@ class MenuRunnerCocoaTest : public ViewsTestBase,
int IsAsync() const { return GetParam() == MenuType::VIEWS; }
// Runs the menu after registering |callback| as the menu open callback.
- void RunMenu(const base::Closure& callback) {
+ void RunMenu(base::OnceClosure callback) {
if (IsAsync()) {
// Cancelling an async menu under MenuControllerCocoa::OpenMenuImpl()
// (which invokes WillShowMenu()) will cause a UAF when that same function
// tries to show the menu. So post a task instead.
- base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ std::move(callback));
} else {
menu_->set_menu_open_callback(
- base::Bind(&MenuRunnerCocoaTest::RunMenuWrapperCallback,
- base::Unretained(this), callback));
+ base::BindOnce(&MenuRunnerCocoaTest::RunMenuWrapperCallback,
+ base::Unretained(this), std::move(callback)));
}
runner_->RunMenuAt(parent_, nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT,
@@ -140,13 +155,15 @@ class MenuRunnerCocoaTest : public ViewsTestBase,
// go up by one (the anchor view) while the menu is shown.
EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]);
- base::Closure callback =
- base::Bind(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback,
- base::Unretained(this));
- if (IsAsync())
- base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback);
- else
- menu_->set_menu_open_callback(callback);
+ base::OnceClosure callback =
+ base::BindOnce(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback,
+ base::Unretained(this));
+ if (IsAsync()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ std::move(callback));
+ } else {
+ menu_->set_menu_open_callback(std::move(callback));
+ }
runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT,
MenuRunner::COMBOBOX);
@@ -178,6 +195,44 @@ class MenuRunnerCocoaTest : public ViewsTestBase,
QuitAsyncRunLoop();
}
+ NSMenu* GetNativeNSMenu() {
+ if (GetParam() == MenuType::VIEWS)
+ return nil;
+
+ internal::MenuRunnerImplCocoa* cocoa_runner =
+ static_cast<internal::MenuRunnerImplCocoa*>(runner_);
+ return [cocoa_runner->menu_controller_ menu];
+ }
+
+ void ModelDeleteThenSelectItemCallback() {
+ // AppKit may retain a reference to the NSMenu.
+ base::scoped_nsobject<NSMenu> native_menu(GetNativeNSMenu(),
+ base::scoped_policy::RETAIN);
+
+ // A View showing a menu typically owns a MenuRunner unique_ptr, which will
+ // will be destroyed (releasing the MenuRunnerImpl) alongside the MenuModel.
+ runner_->Release();
+ runner_ = nullptr;
+ menu_ = nullptr;
+
+ // The menu is closing (yet "alive"), but the model is destroyed. The user
+ // may have already made an event to select an item in the menu. This
+ // doesn't bother views menus (see MenuRunnerImpl::empty_delegate_) but
+ // Cocoa menu items are refcounted and have access to a raw weak pointer in
+ // the MenuController.
+ if (GetParam() == MenuType::VIEWS) {
+ QuitAsyncRunLoop();
+ return;
+ }
+
+ EXPECT_TRUE(native_menu.get());
+
+ // Simulate clicking the item using its accelerator.
+ NSEvent* accelerator = cocoa_test_event_utils::KeyEventWithKeyCode(
+ 'e', 'e', NSKeyDown, NSCommandKeyMask);
+ [native_menu performKeyEquivalent:accelerator];
+ }
+
void MenuCancelAndDeleteCallback() {
runner_->Cancel();
runner_->Release();
@@ -192,9 +247,9 @@ class MenuRunnerCocoaTest : public ViewsTestBase,
int menu_close_count_ = 0;
private:
- void RunMenuWrapperCallback(const base::Closure& callback) {
+ void RunMenuWrapperCallback(base::OnceClosure callback) {
EXPECT_TRUE(runner_->IsRunning());
- callback.Run();
+ std::move(callback).Run();
}
void ComboboxRunMenuAtCallback() {
@@ -248,8 +303,8 @@ class MenuRunnerCocoaTest : public ViewsTestBase,
TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) {
base::TimeTicks min_time = ui::EventTimeForNow();
- RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback,
- base::Unretained(this)));
+ RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuCancelCallback,
+ base::Unretained(this)));
EXPECT_EQ(1, menu_close_count_);
EXPECT_FALSE(runner_->IsRunning());
@@ -271,17 +326,26 @@ TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) {
}
TEST_P(MenuRunnerCocoaTest, RunMenuAndDelete) {
- RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuDeleteCallback,
- base::Unretained(this)));
+ RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuDeleteCallback,
+ base::Unretained(this)));
// Note the close callback is NOT invoked for deleted menus.
EXPECT_EQ(0, menu_close_count_);
}
+// Tests a potential lifetime issue using the Cocoa MenuController, which has a
+// weak reference to the model.
+TEST_P(MenuRunnerCocoaTest, RunMenuAndDeleteThenSelectItem) {
+ RunMenu(
+ base::BindOnce(&MenuRunnerCocoaTest::ModelDeleteThenSelectItemCallback,
+ base::Unretained(this)));
+ EXPECT_EQ(0, menu_close_count_);
+}
+
// Ensure a menu can be safely released immediately after a call to Cancel() in
// the same run loop iteration.
TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) {
- RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback,
- base::Unretained(this)));
+ RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback,
+ base::Unretained(this)));
if (IsAsync()) {
EXPECT_EQ(1, menu_close_count_);
@@ -294,8 +358,8 @@ TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) {
TEST_P(MenuRunnerCocoaTest, RunMenuTwice) {
for (int i = 0; i < 2; ++i) {
- RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback,
- base::Unretained(this)));
+ RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuCancelCallback,
+ base::Unretained(this)));
EXPECT_FALSE(runner_->IsRunning());
EXPECT_EQ(i + 1, menu_close_count_);
}
@@ -339,7 +403,7 @@ TEST_P(MenuRunnerCocoaTest, ComboboxAnchoring) {
combobox_rect.width(), 0),
last_anchor_frame_);
- menu_->set_checked_command(0);
+ menu_->set_checked_command(kTestCommandId);
RunMenuAt(anchor_rect);
// Native constant used by MenuRunnerImplCocoa.
diff --git a/chromium/ui/views/controls/menu/menu_runner_impl.cc b/chromium/ui/views/controls/menu/menu_runner_impl.cc
index cd9ac6df3ff..1c7b5c4c72a 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl.cc
+++ b/chromium/ui/views/controls/menu/menu_runner_impl.cc
@@ -92,9 +92,9 @@ void MenuRunnerImpl::RunMenuAt(Widget* parent,
MenuController* controller = MenuController::GetActiveInstance();
if (controller) {
if ((run_types & MenuRunner::IS_NESTED) != 0) {
- if (!controller->IsBlockingRun()) {
+ if (controller->for_drop()) {
controller->CancelAll();
- controller = NULL;
+ controller = nullptr;
} else {
// Only nest the delegate when not cancelling drag-and-drop. When
// cancelling this will become the root delegate of the new
@@ -112,7 +112,7 @@ void MenuRunnerImpl::RunMenuAt(Widget* parent,
}
// Drop menus don't block the message loop, so it's ok to create a new
// MenuController.
- controller = NULL;
+ controller = nullptr;
}
}
@@ -122,7 +122,7 @@ void MenuRunnerImpl::RunMenuAt(Widget* parent,
owns_controller_ = false;
if (!controller) {
// No menus are showing, show one.
- controller = new MenuController(!for_drop_, this);
+ controller = new MenuController(for_drop_, this);
owns_controller_ = true;
}
controller->set_is_combobox((run_types & MenuRunner::COMBOBOX) != 0);
@@ -204,6 +204,8 @@ bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) {
show_mnemonics |= ui::win::IsAltPressed();
#elif defined(USE_X11)
show_mnemonics |= ui::IsAltPressed();
+#elif defined(OS_MACOSX)
+ show_mnemonics = false;
#endif
return show_mnemonics;
}
diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h
index c89e9cee9c7..599000dd285 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h
+++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h
@@ -16,6 +16,9 @@
@class MenuControllerCocoa;
namespace views {
+namespace test {
+class MenuRunnerCocoaTest;
+}
namespace internal {
// A menu runner implementation that uses NSMenu to show a context menu.
@@ -35,6 +38,8 @@ class VIEWS_EXPORT MenuRunnerImplCocoa : public MenuRunnerImplInterface {
base::TimeTicks GetClosingEventTime() const override;
private:
+ friend class views::test::MenuRunnerCocoaTest;
+
~MenuRunnerImplCocoa() override;
// The Cocoa menu controller that this instance is bridging.
diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
index e68a76f83ed..8ccd3382da9 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
+++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
@@ -148,7 +148,12 @@ void MenuRunnerImplCocoa::Release() {
return; // We already canceled.
delete_after_run_ = true;
- [menu_controller_ cancel];
+
+ // Reset |menu_controller_| to ensure it clears itself as a delegate to
+ // prevent NSMenu attempting to access the weak pointer to the ui::MenuModel
+ // it holds (which is not owned by |this|). Toolkit-views menus use
+ // MenuRunnerImpl::empty_delegate_ to handle this case.
+ menu_controller_.reset();
} else {
delete this;
}
diff --git a/chromium/ui/views/controls/menu/menu_runner_unittest.cc b/chromium/ui/views/controls/menu/menu_runner_unittest.cc
index 79875e46f84..4c6c320cd93 100644
--- a/chromium/ui/views/controls/menu/menu_runner_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_runner_unittest.cc
@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
#include "ui/base/ui_base_types.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/controls/menu/menu_controller.h"
@@ -179,8 +180,9 @@ TEST_F(MenuRunnerTest, LatinMnemonic) {
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
+#if !defined(OS_WIN)
// Tests that a key press on a non-US keyboard layout activates the correct menu
-// item.
+// item. Disabled on Windows because a WM_CHAR event does not activate an item.
TEST_F(MenuRunnerTest, NonLatinMnemonic) {
// TODO: test uses GetContext(), which is not applicable to aura-mus.
// http://crbug.com/663809.
@@ -204,6 +206,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) {
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
+#endif // !defined(OS_WIN)
// Tests that attempting to nest a menu within a drag-and-drop menu does not
// cause a crash. Instead the drag and drop action should be canceled, and the
diff --git a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc
index 2e2e6b2aa26..abcc8e28e7e 100644
--- a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc
+++ b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -250,10 +250,8 @@ void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas* canvas) {
gfx::Rect bounds(0, 0, width(), height());
NativeTheme::ExtraParams extra;
const MenuConfig& menu_config = MenuConfig::instance();
- extra.menu_background.corner_radius =
- content_view_->GetMenuItem()->GetMenuController()->use_touchable_layout()
- ? menu_config.touchable_corner_radius
- : menu_config.corner_radius;
+ extra.menu_background.corner_radius = menu_config.CornerRadiusForMenu(
+ content_view_->GetMenuItem()->GetMenuController());
GetNativeTheme()->Paint(canvas->sk_canvas(),
NativeTheme::kMenuPopupBackground, NativeTheme::kNormal, bounds, extra);
}
@@ -277,8 +275,14 @@ void MenuScrollViewContainer::CreateDefaultBorder() {
bubble_border_ = nullptr;
const MenuConfig& menu_config = MenuConfig::instance();
-
- int padding = menu_config.use_outer_border && menu_config.corner_radius > 0
+ const ui::NativeTheme* native_theme = GetNativeTheme();
+ MenuController* controller =
+ content_view_->GetMenuItem()->GetMenuController();
+ bool use_outer_border =
+ menu_config.use_outer_border ||
+ (native_theme && native_theme->UsesHighContrastColors());
+ int corner_radius = menu_config.CornerRadiusForMenu(controller);
+ int padding = use_outer_border && corner_radius > 0
? kBorderPaddingDueToRoundedCorners
: 0;
@@ -286,14 +290,13 @@ void MenuScrollViewContainer::CreateDefaultBorder() {
const int horizontal_inset =
menu_config.menu_horizontal_border_size + padding;
- if (menu_config.use_outer_border) {
+ if (use_outer_border) {
SkColor color = GetNativeTheme()
? GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_MenuBorderColor)
: gfx::kPlaceholderColor;
SetBorder(views::CreateBorderPainter(
- std::make_unique<views::RoundRectPainter>(color,
- menu_config.corner_radius),
+ std::make_unique<views::RoundRectPainter>(color, corner_radius),
gfx::Insets(vertical_inset, horizontal_inset)));
} else {
SetBorder(CreateEmptyBorder(vertical_inset, horizontal_inset,
diff --git a/chromium/ui/views/controls/menu/menu_separator.cc b/chromium/ui/views/controls/menu/menu_separator.cc
index 55bdcf82428..aa27d3c260b 100644
--- a/chromium/ui/views/controls/menu/menu_separator.cc
+++ b/chromium/ui/views/controls/menu/menu_separator.cc
@@ -23,6 +23,8 @@ void MenuSeparator::OnPaint(gfx::Canvas* canvas) {
const MenuConfig& menu_config = MenuConfig::instance();
int pos = 0;
int separator_thickness = menu_config.separator_thickness;
+ if (type_ == ui::DOUBLE_SEPARATOR)
+ separator_thickness = menu_config.double_separator_thickness;
switch (type_) {
case ui::LOWER_SEPARATOR:
pos = height() - separator_thickness;
@@ -30,12 +32,14 @@ void MenuSeparator::OnPaint(gfx::Canvas* canvas) {
case ui::UPPER_SEPARATOR:
break;
default:
- pos = height() / 2;
+ pos = (height() - separator_thickness) / 2;
break;
}
gfx::Rect paint_rect(0, pos, width(), separator_thickness);
- if (menu_config.use_outer_border)
+ if (type_ == ui::PADDED_SEPARATOR)
+ paint_rect.Inset(menu_config.padded_separator_left_margin, 0, 0, 0);
+ else if (menu_config.use_outer_border)
paint_rect.Inset(1, 0);
#if defined(OS_WIN)
@@ -70,6 +74,12 @@ gfx::Size MenuSeparator::CalculatePreferredSize() const {
case ui::UPPER_SEPARATOR:
height = menu_config.separator_upper_height;
break;
+ case ui::DOUBLE_SEPARATOR:
+ height = menu_config.double_separator_height;
+ break;
+ case ui::PADDED_SEPARATOR:
+ height = menu_config.separator_thickness;
+ break;
default:
height = menu_config.separator_height;
break;
diff --git a/chromium/ui/views/controls/menu/menu_types.h b/chromium/ui/views/controls/menu/menu_types.h
index d95ce0b33c0..03a0c72a5db 100644
--- a/chromium/ui/views/controls/menu/menu_types.h
+++ b/chromium/ui/views/controls/menu/menu_types.h
@@ -9,8 +9,7 @@ namespace views {
// Where a popup menu should be anchored to for non-RTL languages. The opposite
// position will be used if base::i18n:IsRTL() is true. The BUBBLE flags are
-// used when the menu should get enclosed by a bubble. Note that BUBBLE flags
-// should only be used with menus which have no children. The Fixed flags are
+// used when the menu should get enclosed by a bubble. The Fixed flags are
// used for the menus that have a fixed anchor position.
enum MenuAnchorPosition {
MENU_ANCHOR_TOPLEFT,
diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc
index 9b244c9e9ea..b69a318e69f 100644
--- a/chromium/ui/views/controls/menu/submenu_view.cc
+++ b/chromium/ui/views/controls/menu/submenu_view.cc
@@ -182,8 +182,9 @@ gfx::Size SubmenuView::CalculatePreferredSize() const {
minimum_preferred_width_ - 2 * insets.width()));
if (GetMenuItem()->GetMenuController() &&
- GetMenuItem()->GetMenuController()->use_touchable_layout())
+ GetMenuItem()->GetMenuController()->use_touchable_layout()) {
width = std::max(touchable_minimum_width, width);
+ }
// Then, the height for that width.
int height = 0;
diff --git a/chromium/ui/views/controls/message_box_view.cc b/chromium/ui/views/controls/message_box_view.cc
index 27dc783cf4f..36db5787075 100644
--- a/chromium/ui/views/controls/message_box_view.cc
+++ b/chromium/ui/views/controls/message_box_view.cc
@@ -7,7 +7,6 @@
#include <stddef.h>
#include "base/i18n/rtl.h"
-#include "base/message_loop/message_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_node_data.h"
diff --git a/chromium/ui/views/controls/native/native_view_host_aura.cc b/chromium/ui/views/controls/native/native_view_host_aura.cc
index 28033ad99d5..097bcd4879b 100644
--- a/chromium/ui/views/controls/native/native_view_host_aura.cc
+++ b/chromium/ui/views/controls/native/native_view_host_aura.cc
@@ -150,7 +150,8 @@ void NativeViewHostAura::RemovedFromWidget() {
bool NativeViewHostAura::SetCornerRadius(int corner_radius) {
#if defined(OS_WIN)
- // Layer masks don't work on Windows. See crbug.com/713359
+ // TODO(crbug/843250): On Aura, layer masks don't play with HiDPI. Fix this
+ // and enable this on Windows.
return false;
#else
mask_ = views::Painter::CreatePaintedLayer(
diff --git a/chromium/ui/views/controls/native/native_view_host_mac.mm b/chromium/ui/views/controls/native/native_view_host_mac.mm
index e6f31f85f3d..0ca2c4a6a41 100644
--- a/chromium/ui/views/controls/native/native_view_host_mac.mm
+++ b/chromium/ui/views/controls/native/native_view_host_mac.mm
@@ -12,6 +12,13 @@
#include "ui/views/widget/native_widget_mac.h"
#include "ui/views/widget/widget.h"
+// NSViews that can be drawn as a ui::Layer directly will implement this
+// interface. Calling cr_setParentLayer will embed the ui::Layer of the NSView
+// under |parentUiLayer|.
+@interface NSView (UICompositor)
+- (void)cr_setParentUiLayer:(ui::Layer*)parentUiLayer;
+@end
+
namespace views {
namespace {
@@ -32,6 +39,8 @@ void EnsureNativeViewHasNoChildWidgets(NSView* native_view) {
} // namespace
NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) {
+ // Ensure that |host_| have its own ui::Layer and that it draw nothing.
+ host_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
}
NativeViewHostMac::~NativeViewHostMac() {
@@ -45,6 +54,9 @@ void NativeViewHostMac::AttachNativeView() {
DCHECK(!native_view_);
native_view_.reset([host_->native_view() retain]);
+ if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)])
+ [native_view_ cr_setParentUiLayer:host_->layer()];
+
EnsureNativeViewHasNoChildWidgets(native_view_);
BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow(
host_->GetWidget()->GetNativeWindow());
@@ -71,6 +83,9 @@ void NativeViewHostMac::NativeViewDetaching(bool destroyed) {
[host_->native_view() setHidden:YES];
[host_->native_view() removeFromSuperview];
+ if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)])
+ [native_view_ cr_setParentUiLayer:nullptr];
+
EnsureNativeViewHasNoChildWidgets(host_->native_view());
BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow(
host_->GetWidget()->GetNativeWindow());
diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc
index b1195d51c67..6590a8e0b05 100644
--- a/chromium/ui/views/controls/prefix_selector.cc
+++ b/chromium/ui/views/controls/prefix_selector.cc
@@ -89,6 +89,12 @@ bool PrefixSelector::HasCompositionText() const {
return false;
}
+ui::TextInputClient::FocusReason PrefixSelector::GetFocusReason() const {
+ // TODO(https://crbug.com/824604): Implement this correctly.
+ NOTIMPLEMENTED_LOG_ONCE();
+ return ui::TextInputClient::FOCUS_REASON_OTHER;
+}
+
bool PrefixSelector::GetTextRange(gfx::Range* range) const {
*range = gfx::Range();
return false;
@@ -145,6 +151,12 @@ const std::string& PrefixSelector::GetClientSourceInfo() const {
return base::EmptyString();
}
+bool PrefixSelector::ShouldDoLearning() {
+ // TODO(https://crbug.com/311180): Implement this method.
+ NOTIMPLEMENTED_LOG_ONCE();
+ return false;
+}
+
void PrefixSelector::OnTextInput(const base::string16& text) {
// Small hack to filter out 'tab' and 'enter' input, as the expectation is
// that they are control characters and will not affect the currently-active
diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h
index 1e70d1ad3ed..e3654eebbb3 100644
--- a/chromium/ui/views/controls/prefix_selector.h
+++ b/chromium/ui/views/controls/prefix_selector.h
@@ -44,6 +44,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
bool GetCompositionCharacterBounds(uint32_t index,
gfx::Rect* rect) const override;
bool HasCompositionText() const override;
+ FocusReason GetFocusReason() const override;
bool GetTextRange(gfx::Range* range) const override;
bool GetCompositionTextRange(gfx::Range* range) const override;
bool GetSelectionRange(gfx::Range* range) const override;
@@ -60,6 +61,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
bool IsTextEditCommandEnabled(ui::TextEditCommand command) const override;
void SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) override;
const std::string& GetClientSourceInfo() const override;
+ bool ShouldDoLearning() override;
private:
// Invoked when text is typed. Tries to change the selection appropriately.
diff --git a/chromium/ui/views/controls/scroll_view.cc b/chromium/ui/views/controls/scroll_view.cc
index 06a8b1625c7..e58ef7d3cdc 100644
--- a/chromium/ui/views/controls/scroll_view.cc
+++ b/chromium/ui/views/controls/scroll_view.cc
@@ -8,6 +8,8 @@
#include "base/logging.h"
#include "base/macros.h"
#include "ui/base/material_design/material_design_controller.h"
+#include "ui/base/ui_base_features.h"
+#include "ui/compositor/overscroll/scroll_input_handler.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/native_theme/native_theme.h"
@@ -24,15 +26,6 @@ const char ScrollView::kViewClassName[] = "ScrollView";
namespace {
-const base::Feature kToolkitViewsScrollWithLayers {
- "ToolkitViewsScrollWithLayers",
-#if defined(OS_MACOSX)
- base::FEATURE_ENABLED_BY_DEFAULT
-#else
- base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-};
-
class ScrollCornerView : public View {
public:
ScrollCornerView() {}
@@ -187,8 +180,8 @@ ScrollView::ScrollView()
min_height_(-1),
max_height_(-1),
hide_horizontal_scrollbar_(false),
- scroll_with_layers_enabled_(
- base::FeatureList::IsEnabled(kToolkitViewsScrollWithLayers)) {
+ scroll_with_layers_enabled_(base::FeatureList::IsEnabled(
+ features::kUiCompositorScrollWithLayers)) {
set_notify_enter_exit_on_child(true);
AddChildView(contents_viewport_);
@@ -221,6 +214,14 @@ ScrollView::ScrollView()
more_content_bottom_->SetPaintToLayer();
}
UpdateBackground();
+
+ if (ui::MaterialDesignController::IsSecondaryUiMaterial()) {
+ focus_ring_ = FocusRing::Install(this);
+ focus_ring_->SetHasFocusPredicate([](View* view) -> bool {
+ auto* v = static_cast<ScrollView*>(view);
+ return v->draw_focus_indicator_;
+ });
+ }
}
ScrollView::~ScrollView() {
@@ -334,17 +335,10 @@ void ScrollView::SetHasFocusIndicator(bool has_focus_indicator) {
return;
draw_focus_indicator_ = has_focus_indicator;
- if (ui::MaterialDesignController::IsSecondaryUiMaterial()) {
- DCHECK_EQ(draw_focus_indicator_, !focus_ring_);
- if (has_focus_indicator) {
- focus_ring_ = FocusRing::Install(this);
- } else {
- FocusRing::Uninstall(this);
- focus_ring_ = nullptr;
- }
- } else {
+ if (ui::MaterialDesignController::IsSecondaryUiMaterial())
+ focus_ring_->SchedulePaint();
+ else
UpdateBorder();
- }
SchedulePaint();
}
@@ -519,6 +513,30 @@ void ScrollView::Layout() {
container_size.SetToMax(viewport_bounds.size());
contents_->SetBoundsRect(gfx::Rect(container_size));
contents_->layer()->SetScrollable(viewport_bounds.size());
+
+ // Flip the viewport with layer transforms under RTL. Note the net effect is
+ // to flip twice, so the text is not mirrored. This is necessary because
+ // compositor scrolling is not RTL-aware. So although a toolkit-views layout
+ // will flip, increasing a horizontal gfx::ScrollOffset will move content to
+ // the left, regardless of RTL. A gfx::ScrollOffset must be positive, so to
+ // move (unscrolled) content to the right, we need to flip the viewport
+ // layer. That would flip all the content as well, so flip (and translate)
+ // the content layer. Compensating in this way allows the scrolling/offset
+ // logic to remain the same when scrolling via layers or bounds offsets.
+ if (base::i18n::IsRTL()) {
+ gfx::Transform flip;
+ flip.Translate(viewport_bounds.width(), 0);
+ flip.Scale(-1, 1);
+ contents_viewport_->layer()->SetTransform(flip);
+
+ // Add `contents_->width() - viewport_width` to the translation step. This
+ // is to prevent the top-left of the (flipped) contents aligning to the
+ // top-left of the viewport. Instead, the top-right should align in RTL.
+ gfx::Transform shift;
+ shift.Translate(2 * contents_->width() - viewport_bounds.width(), 0);
+ shift.Scale(-1, 1);
+ contents_->layer()->SetTransform(shift);
+ }
}
header_viewport_->SetBounds(contents_x, contents_y,
@@ -552,6 +570,7 @@ bool ScrollView::OnKeyPressed(const ui::KeyEvent& event) {
bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) {
bool processed = false;
+ // TODO(https://crbug.com/615948): Use composited scrolling.
if (vert_sb_->visible())
processed = vert_sb_->OnMouseWheel(e);
@@ -562,13 +581,18 @@ bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) {
}
void ScrollView::OnScrollEvent(ui::ScrollEvent* event) {
-#if defined(OS_MACOSX)
if (!contents_)
return;
- // TODO(tapted): Send |event| to a cc::InputHandler. For now, there's nothing
- // to do because Widget::OnScrollEvent() will automatically process an
- // unhandled ScrollEvent as a MouseWheelEvent.
+ ui::ScrollInputHandler* compositor_scroller =
+ GetWidget()->GetCompositor()->scroll_input_handler();
+ if (compositor_scroller) {
+ DCHECK(scroll_with_layers_enabled_);
+ if (compositor_scroller->OnScrollEvent(*event, contents_->layer())) {
+ event->SetHandled();
+ event->StopPropagation();
+ }
+ }
// A direction might not be known when the event stream starts, notify both
// scrollbars that they may be about scroll, or that they may need to cancel
@@ -577,7 +601,6 @@ void ScrollView::OnScrollEvent(ui::ScrollEvent* event) {
horiz_sb_->ObserveScrollEvent(*event);
if (vert_sb_)
vert_sb_->ObserveScrollEvent(*event);
-#endif
}
void ScrollView::OnGestureEvent(ui::GestureEvent* event) {
@@ -589,6 +612,7 @@ void ScrollView::OnGestureEvent(ui::GestureEvent* event) {
event->type() == ui::ET_GESTURE_SCROLL_END ||
event->type() == ui::ET_SCROLL_FLING_START;
+ // TODO(https://crbug.com/615948): Use composited scrolling.
if (vert_sb_->visible()) {
if (vert_sb_->bounds().Contains(event->location()) || scroll_event)
vert_sb_->OnGestureEvent(event);
diff --git a/chromium/ui/views/controls/scroll_view.h b/chromium/ui/views/controls/scroll_view.h
index ff7f71b19c3..e0e279ce914 100644
--- a/chromium/ui/views/controls/scroll_view.h
+++ b/chromium/ui/views/controls/scroll_view.h
@@ -11,6 +11,7 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "ui/native_theme/native_theme.h"
+#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/scrollbar/scroll_bar.h"
namespace gfx {
@@ -246,12 +247,12 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
// it overflows.
bool draw_overflow_indicator_ = true;
- // Focus ring, if one is installed.
- View* focus_ring_ = nullptr;
-
// Set to true if the scroll with layers feature is enabled.
const bool scroll_with_layers_enabled_;
+ // The focus ring for this ScrollView.
+ std::unique_ptr<FocusRing> focus_ring_;
+
DISALLOW_COPY_AND_ASSIGN(ScrollView);
};
diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc
index 6b818c25420..d2596d71109 100644
--- a/chromium/ui/views/controls/scroll_view_unittest.cc
+++ b/chromium/ui/views/controls/scroll_view_unittest.cc
@@ -6,9 +6,13 @@
#include "base/macros.h"
#include "base/run_loop.h"
+#include "base/test/icu_test_util.h"
+#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_features.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/border.h"
@@ -229,8 +233,8 @@ class ScrollViewTest : public ViewsTestBase {
class WidgetScrollViewTest : public test::WidgetTest,
public ui::CompositorObserver {
public:
- static const int kDefaultHeight = 100;
- static const int kDefaultWidth = 100;
+ static constexpr int kDefaultHeight = 100;
+ static constexpr int kDefaultWidth = 100;
WidgetScrollViewTest() {}
@@ -303,6 +307,9 @@ class WidgetScrollViewTest : public test::WidgetTest,
WidgetTest::TearDown();
}
+ protected:
+ Widget* widget() const { return widget_; }
+
private:
// ui::CompositorObserver:
void OnCompositingDidCommit(ui::Compositor* compositor) override {
@@ -330,8 +337,74 @@ class WidgetScrollViewTest : public test::WidgetTest,
DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTest);
};
-const int WidgetScrollViewTest::kDefaultHeight;
-const int WidgetScrollViewTest::kDefaultWidth;
+constexpr int WidgetScrollViewTest::kDefaultHeight;
+constexpr int WidgetScrollViewTest::kDefaultWidth;
+
+// A gtest parameter to permute over whether ScrollView uses a left-to-right or
+// right-to-left locale, or whether it uses ui::Layers or View bounds offsets to
+// position contents (i.e. features::kUiCompositorScrollWithLayers).
+enum class UiConfig { kLtr, kLtrWithLayers, kRtl, kRtlWithLayers };
+
+class WidgetScrollViewTestRTLAndLayers
+ : public WidgetScrollViewTest,
+ public ::testing::WithParamInterface<UiConfig> {
+ public:
+ WidgetScrollViewTestRTLAndLayers() : locale_(IsTestingRtl() ? "he" : "en") {
+ if (IsTestingLayers()) {
+ layer_config_.InitAndEnableFeature(
+ features::kUiCompositorScrollWithLayers);
+ } else {
+ layer_config_.InitAndDisableFeature(
+ features::kUiCompositorScrollWithLayers);
+ }
+ }
+
+ bool IsTestingRtl() const {
+ return GetParam() == UiConfig::kRtl ||
+ GetParam() == UiConfig::kRtlWithLayers;
+ }
+
+ bool IsTestingLayers() const {
+ return GetParam() == UiConfig::kLtrWithLayers ||
+ GetParam() == UiConfig::kRtlWithLayers;
+ }
+
+ // Returns a point in the coordinate space of |target| by translating a point
+ // inset one pixel from the top of the Widget and one pixel on the leading
+ // side of the Widget. There should be no scroll bar on this side. If
+ // |flip_result| is true, automatically account for flipped coordinates in
+ // RTL.
+ gfx::Point HitTestInCorner(View* target, bool flip_result = true) const {
+ const gfx::Point test_mouse_point_in_root =
+ IsTestingRtl() ? gfx::Point(kDefaultWidth - 1, 1) : gfx::Point(1, 1);
+ gfx::Point point = test_mouse_point_in_root;
+ View::ConvertPointToTarget(widget()->GetRootView(), target, &point);
+ if (flip_result)
+ return gfx::Point(target->GetMirroredXInView(point.x()), point.y());
+ return point;
+ }
+
+ private:
+ base::test::ScopedRestoreICUDefaultLocale locale_;
+ base::test::ScopedFeatureList layer_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTestRTLAndLayers);
+};
+
+std::string UiConfigToString(const testing::TestParamInfo<UiConfig>& info) {
+ switch (info.param) {
+ case UiConfig::kLtr:
+ return "LTR";
+ case UiConfig::kLtrWithLayers:
+ return "LTR_LAYERS";
+ case UiConfig::kRtl:
+ return "RTL";
+ case UiConfig::kRtlWithLayers:
+ return "RTL_LAYERS";
+ }
+ NOTREACHED();
+ return std::string();
+}
// Verifies the viewport is sized to fit the available space.
TEST_F(ScrollViewTest, ViewportSizedToFit) {
@@ -1436,26 +1509,136 @@ TEST_F(WidgetScrollViewTest, EventLocation) {
contents->last_location());
}
+// Ensure behavior of ScrollRectToVisible() is consistent when scrolling with
+// and without layers, and under LTR and RTL.
+TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetWithoutLayers) {
+ // Set up with both scrollers. And a nested view hierarchy like:
+ // +-------------+
+ // |XX |
+ // | +----------|
+ // | | |
+ // | | +-------|
+ // | | | |
+ // | | | etc. |
+ // | | | |
+ // +-------------+
+ // Note that "XX" indicates the size of the viewport.
+ constexpr int kNesting = 5;
+ constexpr int kCellWidth = kDefaultWidth;
+ constexpr int kCellHeight = kDefaultHeight;
+ constexpr gfx::Size kContentSize(kCellWidth * kNesting,
+ kCellHeight * kNesting);
+ ScrollView* scroll_view = AddScrollViewWithContentSize(kContentSize, false);
+ ScrollViewTestApi test_api(scroll_view);
+ EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
+
+ // Sanity check that the contents has a layer iff testing layers.
+ EXPECT_EQ(IsTestingLayers(), !!scroll_view->contents()->layer());
+
+ if (IsTestingRtl()) {
+ // Sanity check the hit-testing logic on the root view. That is, verify that
+ // coordinates really do flip in RTL. The difference inside the viewport is
+ // that the flipping should occur consistently in the entire contents (not
+ // just the visible contents), and take into account the scroll offset.
+ EXPECT_EQ(gfx::Point(kDefaultWidth - 1, 1),
+ HitTestInCorner(scroll_view->GetWidget()->GetRootView(), false));
+ EXPECT_EQ(gfx::Point(kContentSize.width() - 1, 1),
+ HitTestInCorner(scroll_view->contents(), false));
+ } else {
+ EXPECT_EQ(gfx::Point(1, 1),
+ HitTestInCorner(scroll_view->GetWidget()->GetRootView(), false));
+ EXPECT_EQ(gfx::Point(1, 1),
+ HitTestInCorner(scroll_view->contents(), false));
+ }
+
+ // Test vertical scrolling using coordinates on the contents canvas.
+ gfx::Rect offset(0, kCellHeight * 2, kCellWidth, kCellHeight);
+ scroll_view->contents()->ScrollRectToVisible(offset);
+ EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
+
+ // Rely on auto-flipping for this and future HitTestInCorner() calls.
+ EXPECT_EQ(gfx::Point(1, kCellHeight * 2 + 1),
+ HitTestInCorner(scroll_view->contents()));
+
+ // Test horizontal scrolling.
+ offset.set_x(kCellWidth * 2);
+ scroll_view->contents()->ScrollRectToVisible(offset);
+ EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()),
+ test_api.CurrentOffset());
+ EXPECT_EQ(gfx::Point(kCellWidth * 2 + 1, kCellHeight * 2 + 1),
+ HitTestInCorner(scroll_view->contents()));
+
+ // Reset the scrolling.
+ scroll_view->contents()->ScrollRectToVisible(gfx::Rect(0, 0, 1, 1));
+
+ // Test transformations through a nested view hierarchy.
+ View* deepest_view = scroll_view->contents();
+ constexpr gfx::Rect kCellRect(kCellWidth, kCellHeight, kContentSize.width(),
+ kContentSize.height());
+ for (int i = 1; i < kNesting; ++i) {
+ SCOPED_TRACE(testing::Message("Nesting = ") << i);
+ View* child = new View;
+ child->SetBoundsRect(kCellRect);
+ deepest_view->AddChildView(child);
+ deepest_view = child;
+
+ // Add a view in one quadrant. Scrolling just this view should only scroll
+ // far enough for it to become visible. That is, it should be positioned at
+ // the bottom right of the viewport, not the top-left. But since there are
+ // scroll bars, the scroll offset needs to go "a bit more".
+ View* partial_view = new View;
+ partial_view->SetSize(gfx::Size(kCellWidth / 3, kCellHeight / 3));
+ deepest_view->AddChildView(partial_view);
+ partial_view->ScrollViewToVisible();
+ int x_offset_in_cell = kCellWidth - partial_view->width();
+ if (!scroll_view->horizontal_scroll_bar()->OverlapsContent())
+ x_offset_in_cell -= scroll_view->horizontal_scroll_bar()->GetThickness();
+ int y_offset_in_cell = kCellHeight - partial_view->height();
+ if (!scroll_view->vertical_scroll_bar()->OverlapsContent())
+ y_offset_in_cell -= scroll_view->vertical_scroll_bar()->GetThickness();
+ EXPECT_EQ(gfx::ScrollOffset(kCellWidth * i - x_offset_in_cell,
+ kCellHeight * i - y_offset_in_cell),
+ test_api.CurrentOffset());
+
+ // Now scroll the rest.
+ deepest_view->ScrollViewToVisible();
+ EXPECT_EQ(gfx::ScrollOffset(kCellWidth * i, kCellHeight * i),
+ test_api.CurrentOffset());
+
+ // The partial view should now be at the top-left of the viewport (top-right
+ // in RTL).
+ EXPECT_EQ(gfx::Point(1, 1), HitTestInCorner(partial_view));
+
+ gfx::Point origin;
+ View::ConvertPointToWidget(partial_view, &origin);
+ constexpr gfx::Point kTestPointRTL(kDefaultWidth - kCellWidth / 3, 0);
+ EXPECT_EQ(IsTestingRtl() ? kTestPointRTL : gfx::Point(), origin);
+ }
+
+ // Scrolling to the deepest view should have moved the viewport so that the
+ // (kNesting - 1) parent views are all off-screen.
+ EXPECT_EQ(gfx::ScrollOffset(kCellWidth * (kNesting - 1),
+ kCellHeight * (kNesting - 1)),
+ test_api.CurrentOffset());
+}
+
// Test that views scroll offsets are in sync with the layer scroll offsets.
-TEST_F(WidgetScrollViewTest, ScrollOffsetUsingLayers) {
- // Set up with a vertical scroller, but don't commit the layer changes yet.
- ScrollView* scroll_view =
- AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5), false);
+TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) {
+ // Set up with both scrollers, but don't commit the layer changes yet.
+ ScrollView* scroll_view = AddScrollViewWithContentSize(
+ gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5), false);
ScrollViewTestApi test_api(scroll_view);
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// UI code may request a scroll before layer changes are committed.
- gfx::Rect offset(0, kDefaultHeight * 2, 1, kDefaultHeight);
+ gfx::Rect offset(0, kDefaultHeight * 2, kDefaultWidth, kDefaultHeight);
scroll_view->contents()->ScrollRectToVisible(offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
// The following only makes sense when layered scrolling is enabled.
View* container = scroll_view->contents();
-#if defined(OS_MACOSX)
- // Sanity check: Mac should always scroll with layers.
- EXPECT_TRUE(container->layer());
-#endif
+ EXPECT_EQ(IsTestingLayers(), !!container->layer());
if (!container->layer())
return;
@@ -1493,6 +1676,60 @@ TEST_F(WidgetScrollViewTest, ScrollOffsetUsingLayers) {
EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset);
+
+ // Test horizontal scrolling.
+ offset.set_x(kDefaultWidth * 2);
+ scroll_view->contents()->ScrollRectToVisible(offset);
+ EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()),
+ test_api.CurrentOffset());
+
+ EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
+ EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), impl_offset);
}
+// Tests to see the scroll events are handled correctly in composited and
+// non-composited scrolling.
+TEST_F(WidgetScrollViewTest, CompositedScrollEvents) {
+ // Set up with a vertical scroll bar.
+ ScrollView* scroll_view =
+ AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5));
+ ScrollViewTestApi test_api(scroll_view);
+
+ // Create a fake scroll event and send it to the scroll view.
+ ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(), base::TimeTicks::Now(), 0,
+ 0, -10, 0, -10, 3);
+ EXPECT_FALSE(scroll.handled());
+ EXPECT_FALSE(scroll.stopped_propagation());
+ scroll_view->OnScrollEvent(&scroll);
+
+ // Check to see if the scroll event is handled by the scroll view.
+ if (base::FeatureList::IsEnabled(features::kUiCompositorScrollWithLayers)) {
+ // If UiCompositorScrollWithLayers is enabled, the event is set handled
+ // and its propagation is stopped.
+ EXPECT_TRUE(scroll.handled());
+ EXPECT_TRUE(scroll.stopped_propagation());
+ } else {
+ // If UiCompositorScrollWithLayers is disabled, the event isn't handled.
+ // This informs Widget::OnScrollEvent() to convert to a MouseWheel event
+ // and dispatch again. Simulate that.
+ EXPECT_FALSE(scroll.handled());
+ EXPECT_FALSE(scroll.stopped_propagation());
+ EXPECT_EQ(gfx::ScrollOffset(), test_api.CurrentOffset());
+
+ ui::MouseWheelEvent wheel(scroll);
+ scroll_view->OnMouseEvent(&wheel);
+ }
+
+ // Check if the scroll view has been offset.
+ EXPECT_EQ(gfx::ScrollOffset(0, 10), test_api.CurrentOffset());
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ WidgetScrollViewTestRTLAndLayers,
+ ::testing::Values(UiConfig::kLtr,
+ UiConfig::kRtl,
+ UiConfig::kLtrWithLayers,
+ UiConfig::kRtlWithLayers),
+ &UiConfigToString);
+
} // namespace views
diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc b/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc
index 9add6bb5776..bf652746430 100644
--- a/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc
+++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc
@@ -8,7 +8,6 @@
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
-#include "base/message_loop/message_loop.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
diff --git a/chromium/ui/views/controls/slider.cc b/chromium/ui/views/controls/slider.cc
index 015c695e102..cf767aee4c2 100644
--- a/chromium/ui/views/controls/slider.cc
+++ b/chromium/ui/views/controls/slider.cc
@@ -7,7 +7,7 @@
#include <algorithm>
#include "base/logging.h"
-#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "cc/paint/paint_flags.h"
@@ -138,7 +138,7 @@ void Slider::SetValueInternal(float value, SliderChangeReason reason) {
if (listener_)
listener_->SliderValueChanged(this, value_, old_value, reason);
- if (old_value_valid && base::MessageLoop::current()) {
+ if (old_value_valid && base::MessageLoopCurrent::Get()) {
// Do not animate when setting the value of the slider for the first time.
// There is no message-loop when running tests. So we cannot animate then.
if (!move_animation_) {
diff --git a/chromium/ui/views/controls/styled_label.cc b/chromium/ui/views/controls/styled_label.cc
index 5e7982dcdb9..1f2d6fbcd4e 100644
--- a/chromium/ui/views/controls/styled_label.cc
+++ b/chromium/ui/views/controls/styled_label.cc
@@ -123,9 +123,7 @@ StyledLabel::StyledLabel(const base::string16& text,
width_at_last_layout_(0),
displayed_on_background_color_(SkColorSetRGB(0xFF, 0xFF, 0xFF)),
displayed_on_background_color_set_(false),
- auto_color_readability_enabled_(true),
- horizontal_alignment_(base::i18n::IsRTL() ? gfx::ALIGN_RIGHT
- : gfx::ALIGN_LEFT) {
+ auto_color_readability_enabled_(true) {
base::TrimWhitespace(text, base::TRIM_TRAILING, &text_);
}
@@ -271,7 +269,7 @@ void StyledLabel::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
if (horizontal_alignment_ == alignment)
return;
horizontal_alignment_ = alignment;
- SchedulePaint();
+ PreferredSizeChanged();
}
void StyledLabel::ClearStyleRanges() {
diff --git a/chromium/ui/views/controls/styled_label.h b/chromium/ui/views/controls/styled_label.h
index 3268ad50e2c..ae5bac5511a 100644
--- a/chromium/ui/views/controls/styled_label.h
+++ b/chromium/ui/views/controls/styled_label.h
@@ -219,7 +219,7 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// The horizontal alignment. This value is flipped for RTL. The default
// behavior is to align left in LTR UI and right in RTL UI.
- gfx::HorizontalAlignment horizontal_alignment_;
+ gfx::HorizontalAlignment horizontal_alignment_ = gfx::ALIGN_LEFT;
DISALLOW_COPY_AND_ASSIGN(StyledLabel);
};
diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc
index 7aa780d2c8f..4f18b6880c4 100644
--- a/chromium/ui/views/controls/styled_label_unittest.cc
+++ b/chromium/ui/views/controls/styled_label_unittest.cc
@@ -9,8 +9,10 @@
#include <memory>
#include <string>
+#include "base/i18n/base_i18n_switches.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -707,48 +709,64 @@ TEST_F(StyledLabelTest, LineWrapperWithCustomView) {
styled()->GetHeightForWidth(100));
}
-TEST_F(StyledLabelTest, LeftAlignment) {
- const std::string multiline_text("one\ntwo\nthree");
- InitStyledLabel(multiline_text);
- styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+TEST_F(StyledLabelTest, AlignmentInLTR) {
+ const std::string text("text");
+ InitStyledLabel(text);
styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
- ASSERT_EQ(3, styled()->child_count());
+ ASSERT_EQ(1, styled()->child_count());
+
+ // Test the default alignment puts the text on the leading side (left).
EXPECT_EQ(0, styled()->child_at(0)->bounds().x());
- EXPECT_EQ(0, styled()->child_at(1)->bounds().x());
- EXPECT_EQ(0, styled()->child_at(2)->bounds().x());
-}
-TEST_F(StyledLabelTest, RightAlignment) {
- const std::string multiline_text("one\ntwo\nthree");
- InitStyledLabel(multiline_text);
styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
- styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
- ASSERT_EQ(3, styled()->child_count());
EXPECT_EQ(1000, styled()->child_at(0)->bounds().right());
- EXPECT_EQ(1000, styled()->child_at(1)->bounds().right());
- EXPECT_EQ(1000, styled()->child_at(2)->bounds().right());
-}
-TEST_F(StyledLabelTest, CenterAlignment) {
- const std::string multiline_text("one\ntwo\nthree");
- InitStyledLabel(multiline_text);
+ styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ styled()->Layout();
+ EXPECT_EQ(0, styled()->child_at(0)->bounds().x());
+
styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ styled()->Layout();
+ Label label(ASCIIToUTF16(text));
+ EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2,
+ styled()->child_at(0)->bounds().x());
+}
+
+TEST_F(StyledLabelTest, AlignmentInRTL) {
+ // |g_icu_text_direction| is cached to prevent reading new commandline switch.
+ // Set |g_icu_text_direction| to |UNKNOWN_DIRECTION| in order to read the new
+ // commandline switch.
+ base::test::ScopedRestoreICUDefaultLocale scoped_locale("en_US");
+ base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kForceUIDirection, switches::kForceDirectionRTL);
+
+ const std::string text("text");
+ InitStyledLabel(text);
styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
+ ASSERT_EQ(1, styled()->child_count());
- Label label_one(ASCIIToUTF16("one"));
- Label label_two(ASCIIToUTF16("two"));
- Label label_three(ASCIIToUTF16("three"));
+ // Test the default alignment puts the text on the leading side (right).
+ // Note that x-coordinates in RTL place the origin (0) on the right.
+ EXPECT_EQ(0, styled()->child_at(0)->bounds().x());
- ASSERT_EQ(3, styled()->child_count());
- EXPECT_EQ((1000 - label_one.GetPreferredSize().width()) / 2,
+ // Setting |ALIGN_LEFT| should be flipped to |ALIGN_RIGHT|.
+ styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ styled()->Layout();
+ EXPECT_EQ(1000, styled()->child_at(0)->bounds().right());
+
+ // Setting |ALIGN_RIGHT| should be flipped to |ALIGN_LEFT|.
+ styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
+ styled()->Layout();
+ EXPECT_EQ(0, styled()->child_at(0)->bounds().x());
+
+ styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ styled()->Layout();
+ Label label(ASCIIToUTF16(text));
+ EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2,
styled()->child_at(0)->bounds().x());
- EXPECT_EQ((1000 - label_two.GetPreferredSize().width()) / 2,
- styled()->child_at(1)->bounds().x());
- EXPECT_EQ((1000 - label_three.GetPreferredSize().width()) / 2,
- styled()->child_at(2)->bounds().x());
}
TEST_P(MDStyledLabelTest, ViewsCenteredWithLinkAndCustomView) {
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
index bc33e72aca4..17f9259f012 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -36,17 +36,17 @@ namespace {
// TODO(markusheintz|msw): Use NativeTheme colors.
constexpr SkColor kTabTitleColor_InactiveBorder =
- SkColorSetARGBMacro(0xFF, 0x64, 0x64, 0x64);
+ SkColorSetARGB(0xFF, 0x64, 0x64, 0x64);
constexpr SkColor kTabTitleColor_InactiveHighlight =
- SkColorSetARGBMacro(0xFF, 0x80, 0x86, 0x8B);
+ SkColorSetARGB(0xFF, 0x80, 0x86, 0x8B);
constexpr SkColor kTabTitleColor_ActiveBorder = SK_ColorBLACK;
constexpr SkColor kTabTitleColor_ActiveHighlight =
- SkColorSetARGBMacro(0xFF, 0x42, 0x85, 0xF4);
+ SkColorSetARGB(0xFF, 0x42, 0x85, 0xF4);
const SkColor kTabTitleColor_Hovered = SK_ColorBLACK;
const SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8);
const SkScalar kTabBorderThickness = 1.0f;
constexpr SkColor kTabHighlightBackgroundColor =
- SkColorSetARGBMacro(0xFF, 0xE8, 0xF0, 0xFE);
+ SkColorSetARGB(0xFF, 0xE8, 0xF0, 0xFE);
constexpr int kTabHighlightBorderRadius = 32;
constexpr int kTabHighlightPreferredHeight = 32;
constexpr int kTabHighlightPreferredWidth = 208;
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
index f499e202ee4..5ad416d35b1 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
@@ -7,7 +7,6 @@
#include <memory>
#include "base/macros.h"
-#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_action_data.h"
diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc
index d7f37cf103f..c4325de2745 100644
--- a/chromium/ui/views/controls/table/table_view.cc
+++ b/chromium/ui/views/controls/table/table_view.cc
@@ -31,15 +31,12 @@
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/typography.h"
-// Size of images.
-static const int kImageSize = 16;
-
-static const int kGroupingIndicatorSize = 6;
-
namespace views {
namespace {
+constexpr int kGroupingIndicatorSize = 6;
+
// Returns result, unless ascending is false in which case -result is returned.
int SwapCompareResult(int result, bool ascending) {
return ascending ? result : -result;
@@ -588,14 +585,15 @@ void TableView::OnPaint(gfx::Canvas* canvas) {
if (j == 0 && table_type_ == ICON_AND_TEXT) {
gfx::ImageSkia image = model_->GetIcon(model_index);
if (!image.isNull()) {
- int image_x = GetMirroredXWithWidthInView(text_x, kImageSize);
+ int image_x =
+ GetMirroredXWithWidthInView(text_x, ui::TableModel::kIconSize);
canvas->DrawImageInt(
- image, 0, 0, image.width(), image.height(),
- image_x,
- cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2,
- kImageSize, kImageSize, true);
+ image, 0, 0, image.width(), image.height(), image_x,
+ cell_bounds.y() +
+ (cell_bounds.height() - ui::TableModel::kIconSize) / 2,
+ ui::TableModel::kIconSize, ui::TableModel::kIconSize, true);
}
- text_x += kImageSize + cell_element_spacing;
+ text_x += ui::TableModel::kIconSize + cell_element_spacing;
}
if (text_x < cell_bounds.right() - cell_margin) {
canvas->DrawStringRectWithFlags(
@@ -743,7 +741,7 @@ void TableView::AdjustCellBoundsForText(int visible_column_index,
if (grouper_)
text_x += kGroupingIndicatorSize + cell_element_spacing;
if (table_type_ == ICON_AND_TEXT)
- text_x += kImageSize + cell_element_spacing;
+ text_x += ui::TableModel::kIconSize + cell_element_spacing;
}
bounds->set_x(text_x);
bounds->set_width(std::max(0, bounds->right() - cell_margin - text_x));
@@ -770,7 +768,7 @@ void TableView::UpdateVisibleColumnSizes() {
const int cell_element_spacing = GetCellElementSpacing();
int first_column_padding = 0;
if (table_type_ == ICON_AND_TEXT && header_)
- first_column_padding += kImageSize + cell_element_spacing;
+ first_column_padding += ui::TableModel::kIconSize + cell_element_spacing;
if (grouper_)
first_column_padding += kGroupingIndicatorSize + cell_element_spacing;
diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc
index 82cdf5b8669..3c1d189c674 100644
--- a/chromium/ui/views/controls/textfield/textfield.cc
+++ b/chromium/ui/views/controls/textfield/textfield.cc
@@ -57,7 +57,6 @@
#if defined(OS_WIN)
#include "base/win/win_util.h"
-#include "ui/base/ime/win/osk_display_manager.h"
#endif
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
@@ -83,12 +82,6 @@ namespace views {
namespace {
#if defined(OS_MACOSX)
-const ui::EventFlags kPlatformModifier = ui::EF_COMMAND_DOWN;
-#else
-const ui::EventFlags kPlatformModifier = ui::EF_CONTROL_DOWN;
-#endif // OS_MACOSX
-
-#if defined(OS_MACOSX)
const gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_EXTEND;
const gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_CARET;
const gfx::SelectionBehavior kMoveParagraphSelectionBehavior =
@@ -299,6 +292,9 @@ Textfield::Textfield()
UpdateBorder();
SetFocusBehavior(FocusBehavior::ALWAYS);
+ if (use_focus_ring_)
+ focus_ring_ = FocusRing::Install(this);
+
#if !defined(OS_MACOSX)
// Do not map accelerators on Mac. E.g. They might not reflect custom
// keybindings that a user has set. But also on Mac, these commands dispatch
@@ -595,12 +591,8 @@ void Textfield::SetInvalid(bool invalid) {
return;
invalid_ = invalid;
UpdateBorder();
-
- if (HasFocus() && use_focus_ring_) {
- FocusRing::Install(this, invalid_
- ? ui::NativeTheme::kColorId_AlertSeverityHigh
- : ui::NativeTheme::kColorId_NumColors);
- }
+ if (focus_ring_)
+ focus_ring_->SetInvalid(invalid);
}
void Textfield::ClearEditHistory() {
@@ -646,9 +638,9 @@ const char* Textfield::GetClassName() const {
}
void Textfield::SetBorder(std::unique_ptr<Border> b) {
- if (use_focus_ring_ && HasFocus())
- FocusRing::Uninstall(this);
use_focus_ring_ = false;
+ if (focus_ring_)
+ focus_ring_.reset();
View::SetBorder(std::move(b));
}
@@ -664,16 +656,22 @@ gfx::NativeCursor Textfield::GetCursor(const ui::MouseEvent& event) {
bool Textfield::OnMousePressed(const ui::MouseEvent& event) {
const bool had_focus = HasFocus();
bool handled = controller_ && controller_->HandleMouseEvent(this, event);
+
+ // If the controller triggered the focus, then record the focus reason as
+ // other.
+ if (!had_focus && HasFocus())
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
+
if (!handled &&
(event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton())) {
if (!had_focus)
- RequestFocus();
+ RequestFocusWithPointer(ui::EventPointerType::POINTER_TYPE_MOUSE);
ShowImeIfNeeded();
}
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
if (!handled && !had_focus && event.IsOnlyMiddleMouseButton())
- RequestFocus();
+ RequestFocusWithPointer(ui::EventPointerType::POINTER_TYPE_MOUSE);
#endif
return selection_controller_.OnMousePressed(
@@ -747,7 +745,7 @@ bool Textfield::OnKeyReleased(const ui::KeyEvent& event) {
void Textfield::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
- RequestFocus();
+ RequestFocusWithPointer(event->details().primary_pointer_type());
ShowImeIfNeeded();
event->SetHandled();
break;
@@ -776,13 +774,6 @@ void Textfield::OnGestureEvent(ui::GestureEvent* event) {
OnAfterUserAction();
}
CreateTouchSelectionControllerAndNotifyIt();
-#if defined(OS_WIN)
- if (!read_only()) {
- DCHECK(ui::OnScreenKeyboardDisplayManager::GetInstance());
- ui::OnScreenKeyboardDisplayManager::GetInstance()
- ->DisplayVirtualKeyboard(nullptr);
- }
-#endif
event->SetHandled();
break;
case ui::ET_GESTURE_LONG_PRESS:
@@ -857,6 +848,28 @@ bool Textfield::CanHandleAccelerators() const {
return GetRenderText()->focused() && View::CanHandleAccelerators();
}
+void Textfield::RequestFocusWithPointer(ui::EventPointerType pointer_type) {
+ if (HasFocus())
+ return;
+
+ switch (pointer_type) {
+ case ui::EventPointerType::POINTER_TYPE_MOUSE:
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_MOUSE;
+ break;
+ case ui::EventPointerType::POINTER_TYPE_PEN:
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_PEN;
+ break;
+ case ui::EventPointerType::POINTER_TYPE_TOUCH:
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_TOUCH;
+ break;
+ default:
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
+ break;
+ }
+
+ View::RequestFocus();
+}
+
void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) {
SelectAll(PlatformStyle::kTextfieldScrollsToStartOnFocusChange);
}
@@ -1074,6 +1087,10 @@ void Textfield::OnPaint(gfx::Canvas* canvas) {
}
void Textfield::OnFocus() {
+ // Set focus reason if focused was gained without mouse or touch input.
+ if (focus_reason_ == ui::TextInputClient::FOCUS_REASON_NONE)
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
+
#if defined(OS_MACOSX)
if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
password_input_enabler_.reset(new ui::ScopedPasswordInputEnabler());
@@ -1089,16 +1106,13 @@ void Textfield::OnFocus() {
OnCaretBoundsChanged();
if (ShouldBlinkCursor())
StartBlinkingCursor();
- if (use_focus_ring_) {
- FocusRing::Install(this, invalid_
- ? ui::NativeTheme::kColorId_AlertSeverityHigh
- : ui::NativeTheme::kColorId_NumColors);
- }
SchedulePaint();
View::OnFocus();
}
void Textfield::OnBlur() {
+ focus_reason_ = ui::TextInputClient::FOCUS_REASON_NONE;
+
gfx::RenderText* render_text = GetRenderText();
render_text->set_focused(false);
@@ -1118,8 +1132,6 @@ void Textfield::OnBlur() {
DestroyTouchSelection();
- if (use_focus_ring_)
- FocusRing::Uninstall(this);
SchedulePaint();
View::OnBlur();
@@ -1354,23 +1366,23 @@ bool Textfield::GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) const {
switch (command_id) {
case IDS_APP_UNDO:
- *accelerator = ui::Accelerator(ui::VKEY_Z, kPlatformModifier);
+ *accelerator = ui::Accelerator(ui::VKEY_Z, ui::EF_PLATFORM_ACCELERATOR);
return true;
case IDS_APP_CUT:
- *accelerator = ui::Accelerator(ui::VKEY_X, kPlatformModifier);
+ *accelerator = ui::Accelerator(ui::VKEY_X, ui::EF_PLATFORM_ACCELERATOR);
return true;
case IDS_APP_COPY:
- *accelerator = ui::Accelerator(ui::VKEY_C, kPlatformModifier);
+ *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_PLATFORM_ACCELERATOR);
return true;
case IDS_APP_PASTE:
- *accelerator = ui::Accelerator(ui::VKEY_V, kPlatformModifier);
+ *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_PLATFORM_ACCELERATOR);
return true;
case IDS_APP_SELECT_ALL:
- *accelerator = ui::Accelerator(ui::VKEY_A, kPlatformModifier);
+ *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_PLATFORM_ACCELERATOR);
return true;
case IDS_CONTENT_CONTEXT_EMOJI:
@@ -1535,6 +1547,10 @@ bool Textfield::HasCompositionText() const {
return model_->HasCompositionText();
}
+ui::TextInputClient::FocusReason Textfield::GetFocusReason() const {
+ return focus_reason_;
+}
+
bool Textfield::GetTextRange(gfx::Range* range) const {
if (!ImeEditingAllowed())
return false;
@@ -1601,13 +1617,14 @@ bool Textfield::ChangeTextDirectionAndLayoutAlignment(
// Restore text directionality mode when the indicated direction matches the
// current forced mode; otherwise, force the mode indicated. This helps users
// manage BiDi text layout without getting stuck in forced LTR or RTL modes.
- const gfx::DirectionalityMode mode = direction == base::i18n::RIGHT_TO_LEFT
- ? gfx::DIRECTIONALITY_FORCE_RTL
- : gfx::DIRECTIONALITY_FORCE_LTR;
- if (mode == GetRenderText()->directionality_mode())
- GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT);
- else
- GetRenderText()->SetDirectionalityMode(mode);
+ const bool default_rtl = direction == base::i18n::RIGHT_TO_LEFT;
+ const auto new_mode = default_rtl ? gfx::DIRECTIONALITY_FORCE_RTL
+ : gfx::DIRECTIONALITY_FORCE_LTR;
+ auto* render_text = GetRenderText();
+ const bool modes_match = new_mode == render_text->directionality_mode();
+ render_text->SetDirectionalityMode(modes_match ? gfx::DIRECTIONALITY_FROM_TEXT
+ : new_mode);
+ SetHorizontalAlignment(default_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
SchedulePaint();
return true;
}
@@ -1732,6 +1749,12 @@ const std::string& Textfield::GetClientSourceInfo() const {
return base::EmptyString();
}
+bool Textfield::ShouldDoLearning() {
+ // TODO(https://crbug.com/311180): Implement this method.
+ NOTIMPLEMENTED_LOG_ONCE();
+ return false;
+}
+
////////////////////////////////////////////////////////////////////////////////
// Textfield, protected:
@@ -2157,7 +2180,9 @@ void Textfield::OnCaretBoundsChanged() {
#if defined(OS_MACOSX)
// On Mac, the context menu contains a look up item which displays the
// selected text. As such, the menu needs to be updated if the selection has
- // changed.
+ // changed. Be careful to reset the MenuRunner first so it doesn't reference
+ // the old model.
+ context_menu_runner_.reset();
context_menu_contents_.reset();
#endif
diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h
index 8f26353fa19..3df902cef6d 100644
--- a/chromium/ui/views/controls/textfield/textfield.h
+++ b/chromium/ui/views/controls/textfield/textfield.h
@@ -28,6 +28,7 @@
#include "ui/gfx/selection_model.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/context_menu_controller.h"
+#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/textfield/textfield_model.h"
#include "ui/views/drag_controller.h"
#include "ui/views/selection_controller.h"
@@ -343,6 +344,7 @@ class VIEWS_EXPORT Textfield : public View,
bool GetCompositionCharacterBounds(uint32_t index,
gfx::Rect* rect) const override;
bool HasCompositionText() const override;
+ FocusReason GetFocusReason() const override;
bool GetTextRange(gfx::Range* range) const override;
bool GetCompositionTextRange(gfx::Range* range) const override;
bool GetSelectionRange(gfx::Range* range) const override;
@@ -358,6 +360,7 @@ class VIEWS_EXPORT Textfield : public View,
bool IsTextEditCommandEnabled(ui::TextEditCommand command) const override;
void SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) override;
const std::string& GetClientSourceInfo() const override;
+ bool ShouldDoLearning() override;
protected:
// Inserts or appends a character in response to an IME operation.
@@ -481,6 +484,13 @@ class VIEWS_EXPORT Textfield : public View,
// Textfield::GetCaretBlinkMs().
void OnCursorBlinkTimerFired();
+ // Like RequestFocus, but explicitly states that the focus is triggered by
+ // a pointer event.
+ void RequestFocusWithPointer(ui::EventPointerType pointer_type);
+
+ // Returns the color to use for the FocusRing, if one is present.
+ SkColor GetFocusRingColor() const;
+
// The text model.
std::unique_ptr<TextfieldModel> model_;
@@ -602,6 +612,13 @@ class VIEWS_EXPORT Textfield : public View,
std::unique_ptr<ui::ScopedPasswordInputEnabler> password_input_enabler_;
#endif // defined(OS_MACOSX)
+ // How this textfield was focused.
+ ui::TextInputClient::FocusReason focus_reason_ =
+ ui::TextInputClient::FOCUS_REASON_NONE;
+
+ // The focus ring for this TextField.
+ std::unique_ptr<FocusRing> focus_ring_;
+
// Used to bind callback functions to this object.
base::WeakPtrFactory<Textfield> weak_ptr_factory_;
diff --git a/chromium/ui/views/controls/textfield/textfield_model.cc b/chromium/ui/views/controls/textfield/textfield_model.cc
index 7b06af51838..4d77de34ffc 100644
--- a/chromium/ui/views/controls/textfield/textfield_model.cc
+++ b/chromium/ui/views/controls/textfield/textfield_model.cc
@@ -36,16 +36,15 @@ class Edit {
// Revert the change made by this edit in |model|.
void Undo(TextfieldModel* model) {
- model->ModifyText(new_text_start_, new_text_end(),
- old_text_, old_text_start_,
- old_cursor_pos_);
+ model->ModifyText(new_text_start_, new_text_end(), old_text_,
+ old_text_start_, old_selection_);
}
// Apply the change of this edit to the |model|.
void Redo(TextfieldModel* model) {
- model->ModifyText(old_text_start_, old_text_end(),
- new_text_, new_text_start_,
- new_cursor_pos_);
+ model->ModifyText(old_text_start_, old_text_end(), new_text_,
+ new_text_start_,
+ gfx::Range(new_cursor_pos_, new_cursor_pos_));
}
// Try to merge the |edit| into this edit and returns true on success. The
@@ -72,23 +71,22 @@ class Edit {
Edit(Type type,
MergeType merge_type,
- size_t old_cursor_pos,
const base::string16& old_text,
size_t old_text_start,
+ gfx::Range old_selection,
bool delete_backward,
size_t new_cursor_pos,
const base::string16& new_text,
size_t new_text_start)
: type_(type),
merge_type_(merge_type),
- old_cursor_pos_(old_cursor_pos),
old_text_(old_text),
old_text_start_(old_text_start),
+ old_selection_(old_selection),
delete_backward_(delete_backward),
new_cursor_pos_(new_cursor_pos),
new_text_(new_text),
- new_text_start_(new_text_start) {
- }
+ new_text_start_(new_text_start) {}
// Each type of edit provides its own specific merge implementation.
virtual bool DoMerge(const Edit* edit) = 0;
@@ -131,12 +129,12 @@ class Edit {
// The type of merging allowed.
MergeType merge_type_;
- // Old cursor position.
- size_t old_cursor_pos_;
// Deleted text by this edit.
base::string16 old_text_;
// The index of |old_text_|.
size_t old_text_start_;
+ // The range of the text selection prior to the edit.
+ gfx::Range old_selection_;
// True if the deletion is made backward.
bool delete_backward_;
// New cursor position.
@@ -154,14 +152,13 @@ class InsertEdit : public Edit {
InsertEdit(bool mergeable, const base::string16& new_text, size_t at)
: Edit(INSERT_EDIT,
mergeable ? MERGEABLE : DO_NOT_MERGE,
- at /* old cursor */,
base::string16(),
at,
- false /* N/A */,
- at + new_text.length() /* new cursor */,
+ gfx::Range(at, at),
+ false /* N/A */,
+ at + new_text.length() /* new cursor */,
new_text,
- at) {
- }
+ at) {}
// Edit implementation.
bool DoMerge(const Edit* edit) override {
@@ -180,21 +177,21 @@ class ReplaceEdit : public Edit {
public:
ReplaceEdit(MergeType merge_type,
const base::string16& old_text,
- size_t old_cursor_pos,
size_t old_text_start,
+ gfx::Range old_selection,
bool backward,
size_t new_cursor_pos,
const base::string16& new_text,
size_t new_text_start)
- : Edit(REPLACE_EDIT, merge_type,
- old_cursor_pos,
+ : Edit(REPLACE_EDIT,
+ merge_type,
old_text,
old_text_start,
+ old_selection,
backward,
new_cursor_pos,
new_text,
- new_text_start) {
- }
+ new_text_start) {}
// Edit implementation.
bool DoMerge(const Edit* edit) override {
@@ -214,17 +211,17 @@ class DeleteEdit : public Edit {
DeleteEdit(bool mergeable,
const base::string16& text,
size_t text_start,
- bool backward)
+ bool backward,
+ gfx::Range old_selection)
: Edit(DELETE_EDIT,
mergeable ? MERGEABLE : DO_NOT_MERGE,
- (backward ? text_start + text.length() : text_start),
text,
text_start,
+ old_selection,
backward,
text_start,
base::string16(),
- text_start) {
- }
+ text_start) {}
// Edit implementation.
bool DoMerge(const Edit* edit) override {
@@ -334,14 +331,13 @@ bool TextfieldModel::SetText(const base::string16& new_text) {
if (text() != new_text) {
if (changed) // No need to remember composition.
Undo();
- size_t old_cursor = GetCursorPosition();
// SetText moves the cursor to the end.
size_t new_cursor = new_text.length();
- SelectAll(false);
// If there is a composition text, don't merge with previous edit.
// Otherwise, force merge the edits.
ExecuteAndRecordReplace(changed ? DO_NOT_MERGE : FORCE_MERGE,
- old_cursor, new_cursor, new_text, 0U);
+ gfx::Range(0, text().length()), new_cursor,
+ new_text, 0U);
render_text_->SetCursorPosition(new_cursor);
}
ClearSelection();
@@ -627,11 +623,8 @@ void TextfieldModel::DeleteSelectionAndInsertTextAt(
size_t position) {
if (HasCompositionText())
CancelCompositionText();
- ExecuteAndRecordReplace(DO_NOT_MERGE,
- GetCursorPosition(),
- position + new_text.length(),
- new_text,
- position);
+ ExecuteAndRecordReplace(DO_NOT_MERGE, render_text_->selection(),
+ position + new_text.length(), new_text, position);
}
base::string16 TextfieldModel::GetTextFromRange(const gfx::Range& range) const {
@@ -776,8 +769,9 @@ void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) {
size_t old_text_start = range.GetMin();
const base::string16 old_text = text().substr(old_text_start, range.length());
bool backward = range.is_reversed();
+ gfx::Range curr_selection = render_text_->selection();
auto edit = std::make_unique<DeleteEdit>(mergeable, old_text, old_text_start,
- backward);
+ backward, curr_selection);
edit->Redo(this);
AddOrMergeEditHistory(std::move(edit));
}
@@ -787,23 +781,21 @@ void TextfieldModel::ExecuteAndRecordReplaceSelection(
const base::string16& new_text) {
size_t new_text_start = render_text_->selection().GetMin();
size_t new_cursor_pos = new_text_start + new_text.length();
- ExecuteAndRecordReplace(merge_type,
- GetCursorPosition(),
- new_cursor_pos,
- new_text,
- new_text_start);
+ ExecuteAndRecordReplace(merge_type, render_text_->selection(), new_cursor_pos,
+ new_text, new_text_start);
}
void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type,
- size_t old_cursor_pos,
+ gfx::Range replacement_range,
size_t new_cursor_pos,
const base::string16& new_text,
size_t new_text_start) {
- size_t old_text_start = render_text_->selection().GetMin();
- bool backward = render_text_->selection().is_reversed();
+ size_t old_text_start = replacement_range.GetMin();
+ bool backward = replacement_range.is_reversed();
auto edit = std::make_unique<ReplaceEdit>(
- merge_type, GetSelectedText(), old_cursor_pos, old_text_start, backward,
- new_cursor_pos, new_text, new_text_start);
+ merge_type, GetTextFromRange(replacement_range), old_text_start,
+ render_text_->selection(), backward, new_cursor_pos, new_text,
+ new_text_start);
edit->Redo(this);
AddOrMergeEditHistory(std::move(edit));
}
@@ -840,7 +832,7 @@ void TextfieldModel::ModifyText(size_t delete_from,
size_t delete_to,
const base::string16& new_text,
size_t new_text_insert_at,
- size_t new_cursor_pos) {
+ gfx::Range selection) {
DCHECK_LE(delete_from, delete_to);
base::string16 old_text = text();
ClearComposition();
@@ -848,8 +840,11 @@ void TextfieldModel::ModifyText(size_t delete_from,
render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from));
if (!new_text.empty())
render_text_->SetText(old_text.insert(new_text_insert_at, new_text));
- render_text_->SetCursorPosition(new_cursor_pos);
- // TODO(oshima): Select text that was just undone, like Mac (but not GTK).
+ if (selection.start() == selection.end()) {
+ render_text_->SetCursorPosition(selection.start());
+ } else {
+ render_text_->SelectRange(selection);
+ }
}
// static
diff --git a/chromium/ui/views/controls/textfield/textfield_model.h b/chromium/ui/views/controls/textfield/textfield_model.h
index 20c6687a97c..771d7a9c330 100644
--- a/chromium/ui/views/controls/textfield/textfield_model.h
+++ b/chromium/ui/views/controls/textfield/textfield_model.h
@@ -260,7 +260,7 @@ class VIEWS_EXPORT TextfieldModel {
void ExecuteAndRecordReplaceSelection(internal::MergeType merge_type,
const base::string16& new_text);
void ExecuteAndRecordReplace(internal::MergeType merge_type,
- size_t old_cursor_pos,
+ gfx::Range replacement_range,
size_t new_cursor_pos,
const base::string16& new_text,
size_t new_text_start);
@@ -270,15 +270,15 @@ class VIEWS_EXPORT TextfieldModel {
void AddOrMergeEditHistory(std::unique_ptr<internal::Edit> edit);
// Modify the text buffer in following way:
- // 1) Delete the string from |delete_from| to |delte_to|.
+ // 1) Delete the string from |delete_from| to |delete_to|.
// 2) Insert the |new_text| at the index |new_text_insert_at|.
// Note that the index is after deletion.
- // 3) Move the cursor to |new_cursor_pos|.
+ // 3) Select |selection|.
void ModifyText(size_t delete_from,
size_t delete_to,
const base::string16& new_text,
size_t new_text_insert_at,
- size_t new_cursor_pos);
+ gfx::Range selection);
void ClearComposition();
diff --git a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
index c35b12260bf..60884ff58b0 100644
--- a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
@@ -11,7 +11,6 @@
#include "base/auto_reset.h"
#include "base/macros.h"
-#include "base/message_loop/message_loop.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
@@ -1387,7 +1386,9 @@ TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) {
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
+ gfx::Range(1, 3)));
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
@@ -1418,7 +1419,9 @@ TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) {
EXPECT_EQ(1U, model.GetCursorPosition());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
- EXPECT_EQ(3U, model.GetCursorPosition());
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
+ gfx::Range(1, 3)));
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
@@ -1460,8 +1463,9 @@ TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) {
// An empty cut shouldn't create an edit.
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCBCBCDE", model.text());
- EXPECT_EQ(3U, model.GetCursorPosition());
-
+ EXPECT_EQ(1U, model.GetCursorPosition());
+ EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection(
+ gfx::Range(1, 3)));
// Test Copy.
ResetModel(&model);
model.SetText(base::ASCIIToUTF16("12345"));
@@ -1533,6 +1537,47 @@ TEST_F(TextfieldModelTest, UndoRedo_CursorTest) {
EXPECT_FALSE(model.Redo());
}
+TEST_F(TextfieldModelTest, Undo_SelectionTest) {
+ gfx::Range range = gfx::Range(2, 4);
+ TextfieldModel model(nullptr);
+ model.SetText(base::ASCIIToUTF16("abcdef"));
+ model.SelectRange(range);
+ EXPECT_EQ(model.render_text()->selection(), range);
+
+ // Deleting the selected text should change the text and the range.
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("abef", model.text());
+ EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2));
+
+ // Undoing the deletion should restore the former range.
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abcdef", model.text());
+ EXPECT_EQ(model.render_text()->selection(), range);
+
+ // When range.start = range.end, nothing is selected and
+ // range.start = range.end = cursor position
+ model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE);
+ EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2));
+
+ // Deleting a single character should change the text and cursor location.
+ EXPECT_TRUE(model.Backspace());
+ EXPECT_STR_EQ("acdef", model.text());
+ EXPECT_EQ(model.render_text()->selection(), gfx::Range(1, 1));
+
+ // Undoing the deletion should restore the former range.
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abcdef", model.text());
+ EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2));
+
+ MoveCursorTo(model, model.text().length());
+ EXPECT_TRUE(model.Backspace());
+ model.SelectRange(gfx::Range(1, 3));
+ model.SetText(base::ASCIIToUTF16("[set]"));
+ EXPECT_TRUE(model.Undo());
+ EXPECT_STR_EQ("abcde", model.text());
+ EXPECT_EQ(model.render_text()->selection(), gfx::Range(1, 3));
+}
+
void RunInsertReplaceTest(TextfieldModel& model) {
const bool reverse = model.render_text()->selection().is_reversed();
model.InsertChar('1');
diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc
index 775c61fd31d..12e9a987863 100644
--- a/chromium/ui/views/controls/textfield/textfield_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc
@@ -26,6 +26,7 @@
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/ime/input_method_base.h"
#include "ui/base/ime/input_method_delegate.h"
#include "ui/base/ime/input_method_factory.h"
@@ -704,9 +705,11 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* LOOK UP */));
EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
}
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */));
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
#endif
+ if (ui::IsEmojiPanelSupported()) {
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */));
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
+ }
EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */));
EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
@@ -3432,6 +3435,69 @@ TEST_F(TextfieldTest, SendingDeletePreservesShiftFlag) {
EXPECT_EQ(ui::EF_SHIFT_DOWN, textfield_->event_flags());
}
+TEST_F(TextfieldTest, EmojiItem_EmptyField) {
+ InitTextfield();
+ EXPECT_TRUE(textfield_->context_menu_controller());
+
+ // Enable the emoji feature.
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(features::kEnableEmojiContextMenu);
+
+ // A normal empty field may show the Emoji option (if supported).
+ ui::MenuModel* context_menu = GetContextMenuModel();
+ EXPECT_TRUE(context_menu);
+ EXPECT_GT(context_menu->GetItemCount(), 0);
+ // Not all OS/versions support the emoji menu.
+ EXPECT_EQ(ui::IsEmojiPanelSupported(),
+ context_menu->GetLabelAt(0) ==
+ l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI));
+}
+
+TEST_F(TextfieldTest, EmojiItem_ReadonlyField) {
+ InitTextfield();
+ EXPECT_TRUE(textfield_->context_menu_controller());
+
+ // Enable the emoji feature.
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(features::kEnableEmojiContextMenu);
+
+ textfield_->SetReadOnly(true);
+ // In no case is the emoji option showing on a read-only field.
+ ui::MenuModel* context_menu = GetContextMenuModel();
+ EXPECT_TRUE(context_menu);
+ EXPECT_GT(context_menu->GetItemCount(), 0);
+ EXPECT_NE(context_menu->GetLabelAt(0),
+ l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI));
+}
+
+TEST_F(TextfieldTest, EmojiItem_FieldWithText) {
+ InitTextfield();
+ EXPECT_TRUE(textfield_->context_menu_controller());
+
+#if defined(OS_MACOSX)
+ // On Mac, when there is text, the "Look up" item (+ separator) takes the top
+ // position, and emoji comes after.
+ constexpr int kExpectedEmojiIndex = 2;
+#else
+ constexpr int kExpectedEmojiIndex = 0;
+#endif
+
+ // Enable the emoji feature.
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(features::kEnableEmojiContextMenu);
+
+ // A field with text may still show the Emoji option (if supported).
+ textfield_->SetText(base::ASCIIToUTF16("some text"));
+ textfield_->SelectAll(false);
+ ui::MenuModel* context_menu = GetContextMenuModel();
+ EXPECT_TRUE(context_menu);
+ EXPECT_GT(context_menu->GetItemCount(), 0);
+ // Not all OS/versions support the emoji menu.
+ EXPECT_EQ(ui::IsEmojiPanelSupported(),
+ context_menu->GetLabelAt(kExpectedEmojiIndex) ==
+ l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI));
+}
+
#if defined(OS_MACOSX)
// Tests to see if the BiDi submenu items are updated correctly when the
// textfield's text direction is changed.
@@ -3470,6 +3536,10 @@ TEST_F(TextfieldTest, LookUpItemUpdate) {
InitTextfield();
EXPECT_TRUE(textfield_->context_menu_controller());
+ // Make sure the Emoji feature is disabled for this test.
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndDisableFeature(features::kEnableEmojiContextMenu);
+
const base::string16 kTextOne = ASCIIToUTF16("crake");
textfield_->SetText(kTextOne);
textfield_->SelectAll(false);
@@ -3552,4 +3622,122 @@ TEST_F(TextfieldTest, AccessibilitySelectionEvents) {
textfield_->GetAccessibilitySelectionFiredCount());
}
+TEST_F(TextfieldTest, FocusReasonMouse) {
+ InitTextfield();
+ widget_->GetFocusManager()->ClearFocus();
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE,
+ textfield_->GetFocusReason());
+
+ const auto& bounds = textfield_->bounds();
+ MouseClick(bounds, 10);
+
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_MOUSE,
+ textfield_->GetFocusReason());
+}
+
+TEST_F(TextfieldTest, FocusReasonTouchTap) {
+ InitTextfield();
+ widget_->GetFocusManager()->ClearFocus();
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE,
+ textfield_->GetFocusReason());
+
+ ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN);
+ tap_details.set_primary_pointer_type(
+ ui::EventPointerType::POINTER_TYPE_TOUCH);
+ GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details);
+ textfield_->OnGestureEvent(&tap);
+
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_TOUCH,
+ textfield_->GetFocusReason());
+}
+
+TEST_F(TextfieldTest, FocusReasonPenTap) {
+ InitTextfield();
+ widget_->GetFocusManager()->ClearFocus();
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE,
+ textfield_->GetFocusReason());
+
+ ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN);
+ tap_details.set_primary_pointer_type(ui::EventPointerType::POINTER_TYPE_PEN);
+ GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details);
+ textfield_->OnGestureEvent(&tap);
+
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN,
+ textfield_->GetFocusReason());
+}
+
+TEST_F(TextfieldTest, FocusReasonMultipleEvents) {
+ InitTextfield();
+ widget_->GetFocusManager()->ClearFocus();
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE,
+ textfield_->GetFocusReason());
+
+ // Pen tap, followed by a touch tap
+ {
+ ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN);
+ tap_details.set_primary_pointer_type(
+ ui::EventPointerType::POINTER_TYPE_PEN);
+ GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details);
+ textfield_->OnGestureEvent(&tap);
+ }
+
+ {
+ ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN);
+ tap_details.set_primary_pointer_type(
+ ui::EventPointerType::POINTER_TYPE_TOUCH);
+ GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details);
+ textfield_->OnGestureEvent(&tap);
+ }
+
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN,
+ textfield_->GetFocusReason());
+}
+
+TEST_F(TextfieldTest, FocusReasonFocusBlurFocus) {
+ InitTextfield();
+ widget_->GetFocusManager()->ClearFocus();
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE,
+ textfield_->GetFocusReason());
+
+ // Pen tap, blur, then programmatic focus.
+ ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN);
+ tap_details.set_primary_pointer_type(ui::EventPointerType::POINTER_TYPE_PEN);
+ GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details);
+ textfield_->OnGestureEvent(&tap);
+
+ widget_->GetFocusManager()->ClearFocus();
+
+ textfield_->RequestFocus();
+
+ EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_OTHER,
+ textfield_->GetFocusReason());
+}
+
+TEST_F(TextfieldTest, ChangeTextDirectionAndLayoutAlignmentTest) {
+ InitTextfield();
+
+ textfield_->ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection::RIGHT_TO_LEFT);
+ EXPECT_EQ(textfield_->GetTextDirection(),
+ base::i18n::TextDirection::RIGHT_TO_LEFT);
+ EXPECT_EQ(textfield_->GetHorizontalAlignment(),
+ gfx::HorizontalAlignment::ALIGN_RIGHT);
+
+ textfield_->ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection::RIGHT_TO_LEFT);
+ const base::string16& text = test_api_->GetRenderText()->GetDisplayText();
+ base::i18n::TextDirection text_direction =
+ base::i18n::GetFirstStrongCharacterDirection(text);
+ EXPECT_EQ(textfield_->GetTextDirection(), text_direction);
+ EXPECT_EQ(textfield_->GetHorizontalAlignment(),
+ gfx::HorizontalAlignment::ALIGN_RIGHT);
+
+ textfield_->ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection::LEFT_TO_RIGHT);
+ EXPECT_EQ(textfield_->GetTextDirection(),
+ base::i18n::TextDirection::LEFT_TO_RIGHT);
+ EXPECT_EQ(textfield_->GetHorizontalAlignment(),
+ gfx::HorizontalAlignment::ALIGN_LEFT);
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/tree/tree_view.cc b/chromium/ui/views/controls/tree/tree_view.cc
index 4b8d78b239a..b8b07ce5377 100644
--- a/chromium/ui/views/controls/tree/tree_view.cc
+++ b/chromium/ui/views/controls/tree/tree_view.cc
@@ -7,7 +7,6 @@
#include <algorithm>
#include "base/i18n/rtl.h"
-#include "base/message_loop/message_loop.h"
#include "build/build_config.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/accessibility/ax_node_data.h"
@@ -262,8 +261,13 @@ void TreeView::SetSelectedNode(TreeModelNode* model_node) {
SchedulePaintForNode(selected_node_);
}
- if (selected_node_)
- ScrollRectToVisible(GetForegroundBoundsForNode(selected_node_));
+ if (selected_node_) {
+ // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
+ // Un-flip before passing to ScrollRectToVisible(), which uses layout
+ // coordinates.
+ ScrollRectToVisible(
+ GetMirroredRect(GetForegroundBoundsForNode(selected_node_)));
+ }
// Notify controller if the old selection was empty to handle the case of
// remove explicitly resetting selected_node_ before invoking this.
@@ -739,6 +743,9 @@ void TreeView::LayoutEditor() {
DCHECK(selected_node_);
// Position the editor so that its text aligns with the text we drew.
gfx::Rect row_bounds = GetForegroundBoundsForNode(selected_node_);
+
+ // GetForegroundBoundsForNode() returns a "flipped" x for painting. First, un-
+ // flip it for the following calculations and ScrollRectToVisible().
row_bounds.set_x(
GetMirroredXWithWidthInView(row_bounds.x(), row_bounds.width()));
row_bounds.set_x(row_bounds.x() + text_offset_);
diff --git a/chromium/ui/views/controls/views_text_services_context_menu.cc b/chromium/ui/views/controls/views_text_services_context_menu.cc
index ec5975312cc..03a3a7b6e61 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu.cc
+++ b/chromium/ui/views/controls/views_text_services_context_menu.cc
@@ -4,7 +4,10 @@
#include "ui/views/controls/views_text_services_context_menu.h"
+#include <memory>
+
#include "base/logging.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
namespace views {
@@ -12,7 +15,7 @@ namespace views {
std::unique_ptr<ViewsTextServicesContextMenu>
ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
Textfield* client) {
- return nullptr;
+ return std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
}
bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.cc b/chromium/ui/views/controls/views_text_services_context_menu_base.cc
new file mode 100644
index 00000000000..e5fdea933b5
--- /dev/null
+++ b/chromium/ui/views/controls/views_text_services_context_menu_base.cc
@@ -0,0 +1,53 @@
+// Copyright 2018 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 "ui/views/controls/views_text_services_context_menu_base.h"
+
+#include "ui/base/emoji/emoji_panel_helper.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/resources/grit/ui_resources.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace views {
+
+ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase(
+ ui::SimpleMenuModel* menu,
+ Textfield* client)
+ : client_(client) {
+ DCHECK(client);
+ DCHECK(menu);
+ // Not inserted on read-only fields or if the OS/version doesn't support it.
+ if (!client_->read_only() && ui::IsEmojiPanelSupported()) {
+ menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR);
+ menu->InsertItemWithStringIdAt(0, IDS_CONTENT_CONTEXT_EMOJI,
+ IDS_CONTENT_CONTEXT_EMOJI);
+ }
+}
+
+ViewsTextServicesContextMenuBase::~ViewsTextServicesContextMenuBase() {}
+
+bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
+ return command_id == IDS_CONTENT_CONTEXT_EMOJI;
+}
+
+bool ViewsTextServicesContextMenuBase::IsCommandIdChecked(
+ int command_id) const {
+ return false;
+}
+
+bool ViewsTextServicesContextMenuBase::IsCommandIdEnabled(
+ int command_id) const {
+ if (command_id == IDS_CONTENT_CONTEXT_EMOJI)
+ return true;
+
+ return false;
+}
+
+void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id) {
+ if (command_id == IDS_CONTENT_CONTEXT_EMOJI)
+ ui::ShowEmojiPanel();
+}
+
+} // namespace views \ No newline at end of file
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.h b/chromium/ui/views/controls/views_text_services_context_menu_base.h
new file mode 100644
index 00000000000..7c39439b83a
--- /dev/null
+++ b/chromium/ui/views/controls/views_text_services_context_menu_base.h
@@ -0,0 +1,41 @@
+// Copyright 2018 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.
+
+#ifndef UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_
+#define UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_
+
+#include "base/macros.h"
+#include "ui/views/controls/views_text_services_context_menu.h"
+
+namespace views {
+
+// This base class is used to add and handle text service items in the text
+// context menu. Specific platforms may subclass and add additional items.
+class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu {
+ public:
+ ViewsTextServicesContextMenuBase(ui::SimpleMenuModel* menu,
+ Textfield* client);
+ ~ViewsTextServicesContextMenuBase() override;
+
+ // Returns true if the given |command_id| is handled by the menu.
+ bool SupportsCommand(int command_id) const override;
+
+ // Methods associated with SimpleMenuModel::Delegate.
+ bool IsCommandIdChecked(int command_id) const override;
+ bool IsCommandIdEnabled(int command_id) const override;
+ void ExecuteCommand(int command_id) override;
+
+ protected:
+ Textfield* client() const { return client_; }
+
+ private:
+ // The view associated with the menu. Weak. Owns |this|.
+ Textfield* client_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuBase);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_mac.mm b/chromium/ui/views/controls/views_text_services_context_menu_mac.mm
index 0c4cd260bed..c7731fb7547 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu_mac.mm
+++ b/chromium/ui/views/controls/views_text_services_context_menu_mac.mm
@@ -6,18 +6,16 @@
#import <Cocoa/Cocoa.h>
-#include "base/feature_list.h"
#include "ui/base/cocoa/text_services_context_menu.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/base/resource/resource_bundle.h"
-#include "ui/base/ui_base_features.h"
#include "ui/gfx/decorated_text.h"
#import "ui/gfx/decorated_text_mac.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/controls/textfield/textfield.h"
-#include "ui/views/view.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
#include "ui/views/widget/widget.h"
namespace views {
@@ -28,26 +26,21 @@ namespace {
// text service items in the context menu. The items include Speech, Look Up
// and BiDi.
class ViewsTextServicesContextMenuMac
- : public ViewsTextServicesContextMenu,
+ : public ViewsTextServicesContextMenuBase,
public ui::TextServicesContextMenu::Delegate {
public:
ViewsTextServicesContextMenuMac(ui::SimpleMenuModel* menu, Textfield* client)
- : text_services_menu_(this), client_(client) {
- // The index to use when inserting items into the menu.
- int index = 0;
-
+ : ViewsTextServicesContextMenuBase(menu, client),
+ text_services_menu_(this) {
+ // Insert the "Look up" item in the first position.
base::string16 text = GetSelectedText();
if (!text.empty()) {
+ menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR);
menu->InsertItemAt(
- index++, IDS_CONTENT_CONTEXT_LOOK_UP,
+ 0, IDS_CONTENT_CONTEXT_LOOK_UP,
l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, text));
- menu->InsertSeparatorAt(index++, ui::NORMAL_SEPARATOR);
- }
- if (base::FeatureList::IsEnabled(features::kEnableEmojiContextMenu)) {
- menu->InsertItemWithStringIdAt(index++, IDS_CONTENT_CONTEXT_EMOJI,
- IDS_CONTENT_CONTEXT_EMOJI);
- menu->InsertSeparatorAt(index++, ui::NORMAL_SEPARATOR);
}
+
text_services_menu_.AppendToContextMenu(menu);
text_services_menu_.AppendEditableItems(menu);
}
@@ -57,13 +50,8 @@ class ViewsTextServicesContextMenuMac
// ViewsTextServicesContextMenu:
bool SupportsCommand(int command_id) const override {
return text_services_menu_.SupportsCommand(command_id) ||
- command_id == IDS_CONTENT_CONTEXT_EMOJI ||
- command_id == IDS_CONTENT_CONTEXT_LOOK_UP;
- }
-
- bool IsCommandIdChecked(int command_id) const override {
- DCHECK_EQ(IDS_CONTENT_CONTEXT_LOOK_UP, command_id);
- return false;
+ command_id == IDS_CONTENT_CONTEXT_LOOK_UP ||
+ ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
}
bool IsCommandIdEnabled(int command_id) const override {
@@ -71,35 +59,32 @@ class ViewsTextServicesContextMenuMac
return text_services_menu_.IsCommandIdEnabled(command_id);
switch (command_id) {
- case IDS_CONTENT_CONTEXT_EMOJI:
- return true;
-
case IDS_CONTENT_CONTEXT_LOOK_UP:
return true;
default:
- return false;
+ return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
}
}
void ExecuteCommand(int command_id) override {
switch (command_id) {
- case IDS_CONTENT_CONTEXT_EMOJI:
- [NSApp orderFrontCharacterPalette:nil];
- break;
-
case IDS_CONTENT_CONTEXT_LOOK_UP:
LookUpInDictionary();
break;
+
+ default:
+ ViewsTextServicesContextMenuBase::ExecuteCommand(command_id);
+ break;
}
}
// TextServicesContextMenu::Delegate:
base::string16 GetSelectedText() const override {
- if (client_->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD)
+ if (client()->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD)
return base::string16();
- return client_->GetSelectedText();
+ return client()->GetSelectedText();
}
bool IsTextDirectionEnabled(
@@ -110,7 +95,7 @@ class ViewsTextServicesContextMenuMac
bool IsTextDirectionChecked(
base::i18n::TextDirection direction) const override {
return direction != base::i18n::UNKNOWN_DIRECTION &&
- client_->GetTextDirection() == direction;
+ client()->GetTextDirection() == direction;
}
void UpdateTextDirection(base::i18n::TextDirection direction) override {
@@ -119,7 +104,7 @@ class ViewsTextServicesContextMenuMac
base::i18n::TextDirection text_direction =
direction == base::i18n::LEFT_TO_RIGHT ? base::i18n::LEFT_TO_RIGHT
: base::i18n::RIGHT_TO_LEFT;
- client_->ChangeTextDirectionAndLayoutAlignment(text_direction);
+ client()->ChangeTextDirectionAndLayoutAlignment(text_direction);
}
private:
@@ -127,10 +112,10 @@ class ViewsTextServicesContextMenuMac
void LookUpInDictionary() {
gfx::Point baseline_point;
gfx::DecoratedText text;
- if (client_->GetWordLookupDataFromSelection(&text, &baseline_point)) {
- Widget* widget = client_->GetWidget();
+ if (client()->GetWordLookupDataFromSelection(&text, &baseline_point)) {
+ Widget* widget = client()->GetWidget();
gfx::NativeView view = widget->GetNativeView();
- views::View::ConvertPointToTarget(client_, widget->GetRootView(),
+ views::View::ConvertPointToTarget(client(), widget->GetRootView(),
&baseline_point);
NSPoint lookup_point = NSMakePoint(
@@ -144,9 +129,6 @@ class ViewsTextServicesContextMenuMac
// Appends and handles the text service menu.
ui::TextServicesContextMenu text_services_menu_;
- // The view associated with the menu. Weak. Owns |this|.
- Textfield* client_;
-
DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuMac);
};
diff --git a/chromium/ui/views/controls/webview/web_dialog_view.cc b/chromium/ui/views/controls/webview/web_dialog_view.cc
index ef8b793f745..323d9f895f8 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view.cc
+++ b/chromium/ui/views/controls/webview/web_dialog_view.cc
@@ -307,19 +307,16 @@ content::WebContents* WebDialogView::OpenURLFromTab(
return WebDialogWebContentsDelegate::OpenURLFromTab(source, params);
}
-void WebDialogView::AddNewContents(content::WebContents* source,
- content::WebContents* new_contents,
- WindowOpenDisposition disposition,
- const gfx::Rect& initial_rect,
- bool user_gesture,
- bool* was_blocked) {
- if (delegate_ && delegate_->HandleAddNewContents(
- source, new_contents, disposition, initial_rect, user_gesture)) {
- return;
- }
- WebDialogWebContentsDelegate::AddNewContents(
- source, new_contents, disposition, initial_rect, user_gesture,
- was_blocked);
+void WebDialogView::AddNewContents(
+ content::WebContents* source,
+ std::unique_ptr<content::WebContents> new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_rect,
+ bool user_gesture,
+ bool* was_blocked) {
+ WebDialogWebContentsDelegate::AddNewContents(source, std::move(new_contents),
+ disposition, initial_rect,
+ user_gesture, was_blocked);
}
void WebDialogView::LoadingStateChanged(content::WebContents* source,
diff --git a/chromium/ui/views/controls/webview/web_dialog_view.h b/chromium/ui/views/controls/webview/web_dialog_view.h
index 3a91e99a7f5..bfba06181e7 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view.h
+++ b/chromium/ui/views/controls/webview/web_dialog_view.h
@@ -104,7 +104,7 @@ class WEBVIEW_EXPORT WebDialogView : public views::ClientView,
content::WebContents* source,
const content::OpenURLParams& params) override;
void AddNewContents(content::WebContents* source,
- content::WebContents* new_contents,
+ std::unique_ptr<content::WebContents> new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc
index 4ec50f3b056..161185af3b8 100644
--- a/chromium/ui/views/controls/webview/webview.cc
+++ b/chromium/ui/views/controls/webview/webview.cc
@@ -6,6 +6,7 @@
#include <utility>
+#include "base/no_destructor.h"
#include "build/build_config.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_context.h"
@@ -23,6 +24,27 @@
namespace views {
+namespace {
+
+// A testing stub that creates web contents.
+WebView::WebContentsCreator* GetCreatorForTesting() {
+ static base::NoDestructor<WebView::WebContentsCreator> creator;
+ return creator.get();
+}
+
+} // namespace
+
+WebView::ScopedWebContentsCreatorForTesting::ScopedWebContentsCreatorForTesting(
+ WebContentsCreator creator) {
+ DCHECK(!*GetCreatorForTesting());
+ *GetCreatorForTesting() = creator;
+}
+
+WebView::ScopedWebContentsCreatorForTesting::
+ ~ScopedWebContentsCreatorForTesting() {
+ *GetCreatorForTesting() = WebView::WebContentsCreator();
+}
+
// static
const char WebView::kViewClassName[] = "WebView";
@@ -44,7 +66,7 @@ WebView::~WebView() {
content::WebContents* WebView::GetWebContents() {
if (!web_contents()) {
- wc_owner_.reset(CreateWebContents(browser_context_));
+ wc_owner_ = CreateWebContents(browser_context_);
wc_owner_->SetDelegate(this);
SetWebContents(wc_owner_.get());
}
@@ -364,7 +386,6 @@ void WebView::UpdateCrashedOverlayView() {
base::TERMINATION_STATUS_STILL_RUNNING &&
crashed_overlay_view_) {
SetFocusBehavior(FocusBehavior::NEVER);
- holder_->SetVisible(false);
crashed_overlay_view_->SetVisible(true);
return;
}
@@ -374,7 +395,6 @@ void WebView::UpdateCrashedOverlayView() {
if (crashed_overlay_view_)
crashed_overlay_view_->SetVisible(false);
- holder_->SetVisible(true);
}
void WebView::NotifyAccessibilityWebContentsChanged() {
@@ -382,12 +402,11 @@ void WebView::NotifyAccessibilityWebContentsChanged() {
NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false);
}
-content::WebContents* WebView::CreateWebContents(
- content::BrowserContext* browser_context) {
- content::WebContents* contents = NULL;
- if (ViewsDelegate::GetInstance()) {
- contents =
- ViewsDelegate::GetInstance()->CreateWebContents(browser_context, NULL);
+std::unique_ptr<content::WebContents> WebView::CreateWebContents(
+ content::BrowserContext* browser_context) {
+ std::unique_ptr<content::WebContents> contents;
+ if (*GetCreatorForTesting()) {
+ contents = GetCreatorForTesting()->Run(browser_context);
}
if (!contents) {
diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h
index 99affc147bc..87d9a1e7259 100644
--- a/chromium/ui/views/controls/webview/webview.h
+++ b/chromium/ui/views/controls/webview/webview.h
@@ -9,6 +9,7 @@
#include <memory>
+#include "base/callback.h"
#include "base/macros.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
@@ -94,6 +95,20 @@ class WEBVIEW_EXPORT WebView : public View,
const char* GetClassName() const override;
NativeViewHost* holder() { return holder_; }
+ using WebContentsCreator =
+ base::RepeatingCallback<std::unique_ptr<content::WebContents>(
+ content::BrowserContext*)>;
+
+ // An instance of this class registers a WebContentsCreator on construction
+ // and deregisters the WebContentsCreator on destruction.
+ class WEBVIEW_EXPORT ScopedWebContentsCreatorForTesting {
+ public:
+ explicit ScopedWebContentsCreatorForTesting(WebContentsCreator creator);
+ ~ScopedWebContentsCreatorForTesting();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedWebContentsCreatorForTesting);
+ };
protected:
// Swaps the owned WebContents |wc_owner_| with |new_web_contents|. Returns
@@ -154,7 +169,7 @@ class WEBVIEW_EXPORT WebView : public View,
// Create a regular or test web contents (based on whether we're running
// in a unit test or not).
- content::WebContents* CreateWebContents(
+ std::unique_ptr<content::WebContents> CreateWebContents(
content::BrowserContext* browser_context);
NativeViewHost* const holder_;
diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc
index b679af9d8d2..f1d0ce7758d 100644
--- a/chromium/ui/views/controls/webview/webview_unittest.cc
+++ b/chromium/ui/views/controls/webview/webview_unittest.cc
@@ -33,29 +33,11 @@ namespace views {
namespace {
-// Provides functionality to create a test WebContents.
-class WebViewTestViewsDelegate : public views::TestViewsDelegate {
- public:
- WebViewTestViewsDelegate() {}
- ~WebViewTestViewsDelegate() override {}
-
- // Overriden from TestViewsDelegate.
- content::WebContents* CreateWebContents(
- content::BrowserContext* browser_context,
- content::SiteInstance* site_instance) override {
- return content::WebContentsTester::CreateTestWebContents(browser_context,
- site_instance);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(WebViewTestViewsDelegate);
-};
-
// Provides functionality to observe events on a WebContents like
// OnVisibilityChanged/WebContentsDestroyed.
class WebViewTestWebContentsObserver : public content::WebContentsObserver {
public:
- WebViewTestWebContentsObserver(content::WebContents* web_contents)
+ explicit WebViewTestWebContentsObserver(content::WebContents* web_contents)
: web_contents_(web_contents),
was_shown_(false),
shown_count_(0),
@@ -146,8 +128,19 @@ class WebViewUnitTest : public views::test::WidgetTest {
~WebViewUnitTest() override {}
+ std::unique_ptr<content::WebContents> CreateWebContentsForWebView(
+ content::BrowserContext* browser_context) {
+ return content::WebContentsTester::CreateTestWebContents(browser_context,
+ nullptr);
+ }
+
void SetUp() override {
- set_views_delegate(base::WrapUnique(new WebViewTestViewsDelegate));
+ views::WebView::WebContentsCreator creator = base::BindRepeating(
+ &WebViewUnitTest::CreateWebContentsForWebView, base::Unretained(this));
+ scoped_web_contents_creator_ =
+ std::make_unique<views::WebView::ScopedWebContentsCreatorForTesting>(
+ creator);
+ set_views_delegate(base::WrapUnique(new views::TestViewsDelegate));
browser_context_.reset(new content::TestBrowserContext);
WidgetTest::SetUp();
// Set the test content browser client to avoid pulling in needless
@@ -171,6 +164,7 @@ class WebViewUnitTest : public views::test::WidgetTest {
}
void TearDown() override {
+ scoped_web_contents_creator_.reset();
top_level_widget_->Close(); // Deletes all children and itself.
RunPendingMessages();
@@ -187,18 +181,16 @@ class WebViewUnitTest : public views::test::WidgetTest {
NativeViewHost* holder() const { return web_view_->holder_; }
std::unique_ptr<content::WebContents> CreateWebContents() const {
- return base::WrapUnique(content::WebContents::Create(
- content::WebContents::CreateParams(browser_context_.get())));
+ return content::WebContents::Create(
+ content::WebContents::CreateParams(browser_context_.get()));
}
private:
- // TODO(lukasza): https://crbug.com/832100: Move the factory into
- // TestingProfile, so individual tests don't need to worry about it.
- content::ScopedMockRenderProcessHostFactory process_factory_;
-
content::TestBrowserThreadBundle test_browser_thread_bundle_;
std::unique_ptr<content::TestBrowserContext> browser_context_;
content::TestContentBrowserClient test_browser_client_;
+ std::unique_ptr<views::WebView::ScopedWebContentsCreatorForTesting>
+ scoped_web_contents_creator_;
Widget* top_level_widget_ = nullptr;
WebView* web_view_ = nullptr;