summaryrefslogtreecommitdiff
path: root/chromium/ui/views/controls
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/ui/views/controls
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 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.cc9
-rw-r--r--chromium/ui/views/controls/button/button.h4
-rw-r--r--chromium/ui/views/controls/button/button_unittest.cc34
-rw-r--r--chromium/ui/views/controls/button/checkbox_unittest.cc4
-rw-r--r--chromium/ui/views/controls/button/image_button_factory.cc14
-rw-r--r--chromium/ui/views/controls/button/image_button_factory.h6
-rw-r--r--chromium/ui/views/controls/button/image_button_factory_unittest.cc10
-rw-r--r--chromium/ui/views/controls/button/label_button.cc39
-rw-r--r--chromium/ui/views/controls/button/label_button_label_unittest.cc26
-rw-r--r--chromium/ui/views/controls/button/label_button_unittest.cc23
-rw-r--r--chromium/ui/views/controls/button/md_text_button.cc14
-rw-r--r--chromium/ui/views/controls/button/md_text_button.h7
-rw-r--r--chromium/ui/views/controls/button/md_text_button_unittest.cc25
-rw-r--r--chromium/ui/views/controls/button/radio_button_unittest.cc3
-rw-r--r--chromium/ui/views/controls/combobox/combobox.cc97
-rw-r--r--chromium/ui/views/controls/combobox/combobox.h4
-rw-r--r--chromium/ui/views/controls/combobox/combobox_unittest.cc82
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.cc78
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.h18
-rw-r--r--chromium/ui/views/controls/focus_ring.cc33
-rw-r--r--chromium/ui/views/controls/focus_ring.h32
-rw-r--r--chromium/ui/views/controls/label.cc25
-rw-r--r--chromium/ui/views/controls/label.h14
-rw-r--r--chromium/ui/views/controls/label_unittest.cc5
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.cc33
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.h10
-rw-r--r--chromium/ui/views/controls/menu/menu_controller_unittest.cc21
-rw-r--r--chromium/ui/views/controls/menu/menu_delegate.h1
-rw-r--r--chromium/ui/views/controls/menu/menu_host.cc1
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.cc161
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.h32
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view_unittest.cc33
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter.cc17
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc22
-rw-r--r--chromium/ui/views/controls/menu/submenu_view.cc33
-rw-r--r--chromium/ui/views/controls/prefix_selector.cc9
-rw-r--r--chromium/ui/views/controls/prefix_selector.h5
-rw-r--r--chromium/ui/views/controls/scroll_view.h2
-rw-r--r--chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h3
-rw-r--r--chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm48
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc15
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.h2
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc2
-rw-r--r--chromium/ui/views/controls/table/table_view.cc2
-rw-r--r--chromium/ui/views/controls/table/table_view.h3
-rw-r--r--chromium/ui/views/controls/textfield/textfield.cc34
-rw-r--r--chromium/ui/views/controls/textfield/textfield.h13
-rw-r--r--chromium/ui/views/controls/textfield/textfield_test_api.cc7
-rw-r--r--chromium/ui/views/controls/textfield/textfield_test_api.h10
-rw-r--r--chromium/ui/views/controls/textfield/textfield_unittest.cc77
-rw-r--r--chromium/ui/views/controls/tree/tree_view.cc341
-rw-r--r--chromium/ui/views/controls/tree/tree_view.h41
-rw-r--r--chromium/ui/views/controls/tree/tree_view_unittest.cc273
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu.cc28
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu.h21
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.cc29
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.h27
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_mac.mm187
-rw-r--r--chromium/ui/views/controls/webview/webview.cc8
-rw-r--r--chromium/ui/views/controls/webview/webview.h2
-rw-r--r--chromium/ui/views/controls/webview/webview_unittest.cc4
61 files changed, 1406 insertions, 757 deletions
diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc
index d74e05dc34c..5bb670ce30a 100644
--- a/chromium/ui/views/controls/button/button.cc
+++ b/chromium/ui/views/controls/button/button.cc
@@ -56,6 +56,7 @@ Button::WidgetObserverButtonBridge::WidgetObserverButtonBridge(Button* button)
Button::WidgetObserverButtonBridge::~WidgetObserverButtonBridge() {
if (owner_)
owner_->GetWidget()->RemoveObserver(this);
+ CHECK(!IsInObserverList());
}
void Button::WidgetObserverButtonBridge::OnWidgetPaintAsActiveChanged(
@@ -240,10 +241,12 @@ void Button::SetAnimationDuration(base::TimeDelta duration) {
}
void Button::SetInstallFocusRingOnFocus(bool install) {
- if (install)
+ if (focus_ring_ && !install) {
+ RemoveChildViewT(focus_ring_);
+ focus_ring_ = nullptr;
+ } else if (!focus_ring_ && install) {
focus_ring_ = FocusRing::Install(this);
- else
- focus_ring_.reset();
+ }
}
void Button::SetHotTracked(bool is_hot_tracked) {
diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h
index 79bab1f6034..6411eb3514b 100644
--- a/chromium/ui/views/controls/button/button.h
+++ b/chromium/ui/views/controls/button/button.h
@@ -292,7 +292,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
return hover_animation_;
}
- FocusRing* focus_ring() { return focus_ring_.get(); }
+ FocusRing* focus_ring() { return focus_ring_; }
// The button's listener. Notified when clicked.
ButtonListener* listener_;
@@ -368,7 +368,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
SkColor ink_drop_base_color_;
// The focus ring for this Button.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
std::unique_ptr<Painter> focus_painter_;
diff --git a/chromium/ui/views/controls/button/button_unittest.cc b/chromium/ui/views/controls/button/button_unittest.cc
index 3dfeba259fd..5850b215f25 100644
--- a/chromium/ui/views/controls/button/button_unittest.cc
+++ b/chromium/ui/views/controls/button/button_unittest.cc
@@ -35,6 +35,7 @@
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/style/platform_style.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"
@@ -178,15 +179,6 @@ TestInkDrop* AddTestInkDrop(TestButton* button) {
return ink_drop;
}
-// TODO(tluk): remove when the appropriate ownership APIs have been added for
-// Widget's SetContentsView().
-template <typename T>
-T* AddContentsView(Widget* widget, std::unique_ptr<T> view) {
- T* view_ptr = view.get();
- widget->SetContentsView(view.release());
- return view_ptr;
-}
-
} // namespace
class ButtonTest : public ViewsTestBase {
@@ -207,7 +199,7 @@ class ButtonTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- button_ = AddContentsView(widget(), std::make_unique<TestButton>(false));
+ button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
event_generator_ =
std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget()));
@@ -225,24 +217,21 @@ class ButtonTest : public ViewsTestBase {
}
TestInkDrop* CreateButtonWithInkDrop(bool has_ink_drop_action_on_click) {
- button_ = AddContentsView(
- widget(), std::make_unique<TestButton>(has_ink_drop_action_on_click));
- widget_->SetContentsView(button_);
+ button_ = widget()->SetContentsView(
+ std::make_unique<TestButton>(has_ink_drop_action_on_click));
return AddTestInkDrop(button_);
}
void CreateButtonWithRealInkDrop() {
- button_ = AddContentsView(widget(), std::make_unique<TestButton>(false));
+ button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
InkDropHostViewTestApi(button_).SetInkDrop(
std::make_unique<InkDropImpl>(button_, button_->size()));
- widget_->SetContentsView(button_);
}
void CreateButtonWithObserver() {
- button_ = AddContentsView(widget(), std::make_unique<TestButton>(false));
+ button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
button_observer_ = std::make_unique<TestButtonObserver>();
button_->AddButtonObserver(button_observer_.get());
- widget_->SetContentsView(button_);
}
protected:
@@ -263,7 +252,12 @@ class ButtonTest : public ViewsTestBase {
DISALLOW_COPY_AND_ASSIGN(ButtonTest);
};
-// Tests that hover state changes correctly when visiblity/enableness changes.
+// Iterate through the metadata for Button to ensure it all works.
+TEST_F(ButtonTest, MetadataTest) {
+ test::TestViewMetadata(button());
+}
+
+// Tests that hover state changes correctly when visibility/enableness changes.
TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
@@ -599,7 +593,7 @@ TEST_F(ButtonTest, InkDropAfterTryingToShowContextMenu) {
}
TEST_F(ButtonTest, HideInkDropHighlightWhenRemoved) {
- View* contents_view = AddContentsView(widget(), std::make_unique<View>());
+ View* contents_view = widget()->SetContentsView(std::make_unique<View>());
TestButton* button =
contents_view->AddChildView(std::make_unique<TestButton>(false));
@@ -779,7 +773,7 @@ class VisibilityTestButton : public TestButton {
// changed visibility states.
TEST_F(ButtonTest, NoLayerAddedForWidgetVisibilityChanges) {
VisibilityTestButton* button =
- AddContentsView(widget(), std::make_unique<VisibilityTestButton>());
+ widget()->SetContentsView(std::make_unique<VisibilityTestButton>());
// Ensure no layers are created during construction.
EXPECT_TRUE(button->GetVisible());
diff --git a/chromium/ui/views/controls/button/checkbox_unittest.cc b/chromium/ui/views/controls/button/checkbox_unittest.cc
index 3b3e5d83d76..a468b3d7212 100644
--- a/chromium/ui/views/controls/button/checkbox_unittest.cc
+++ b/chromium/ui/views/controls/button/checkbox_unittest.cc
@@ -32,8 +32,8 @@ class CheckboxTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- checkbox_ = new Checkbox(base::string16());
- widget_->SetContentsView(checkbox_);
+ checkbox_ =
+ widget_->SetContentsView(std::make_unique<Checkbox>(base::string16()));
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/button/image_button_factory.cc b/chromium/ui/views/controls/button/image_button_factory.cc
index 5ac49262cfe..7d7d6a9a520 100644
--- a/chromium/ui/views/controls/button/image_button_factory.cc
+++ b/chromium/ui/views/controls/button/image_button_factory.cc
@@ -110,21 +110,11 @@ void SetImageFromVectorIconWithColor(ImageButton* button,
button->set_ink_drop_base_color(icon_color);
}
-void SetToggledImageFromVectorIcon(ToggleImageButton* button,
- const gfx::VectorIcon& icon,
- int dip_size,
- SkColor related_text_color) {
- const SkColor icon_color =
- color_utils::DeriveDefaultIconColor(related_text_color);
- SetToggledImageFromVectorIconWithColor(button, icon, dip_size, icon_color);
-}
-
void SetToggledImageFromVectorIconWithColor(ToggleImageButton* button,
const gfx::VectorIcon& icon,
int dip_size,
- SkColor icon_color) {
- const SkColor disabled_color =
- SkColorSetA(icon_color, gfx::kDisabledControlAlpha);
+ SkColor icon_color,
+ SkColor disabled_color) {
const gfx::ImageSkia normal_image =
gfx::CreateVectorIcon(icon, dip_size, icon_color);
const gfx::ImageSkia disabled_image =
diff --git a/chromium/ui/views/controls/button/image_button_factory.h b/chromium/ui/views/controls/button/image_button_factory.h
index 38d7c70ad52..de2d57501b2 100644
--- a/chromium/ui/views/controls/button/image_button_factory.h
+++ b/chromium/ui/views/controls/button/image_button_factory.h
@@ -32,8 +32,7 @@ VIEWS_EXPORT std::unique_ptr<ImageButton> CreateVectorImageButton(
ButtonListener* listener);
// Creates a ToggleImageButton with an ink drop and a centered image in
-// preperation for applying a vector icon from SetImageFromVectorIcon and
-// SetToggledImageFromVectorIcon below.
+// preparation for applying a vector icon from SetImageFromVectorIcon below.
VIEWS_EXPORT std::unique_ptr<ToggleImageButton> CreateVectorToggleImageButton(
ButtonListener* listener);
@@ -73,7 +72,8 @@ VIEWS_EXPORT void SetToggledImageFromVectorIconWithColor(
ToggleImageButton* button,
const gfx::VectorIcon& icon,
int dip_size,
- SkColor icon_color);
+ SkColor icon_color,
+ SkColor disabled_color);
} // namespace views
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 5b8a5e4aee6..2197b35a527 100644
--- a/chromium/ui/views/controls/button/image_button_factory_unittest.cc
+++ b/chromium/ui/views/controls/button/image_button_factory_unittest.cc
@@ -56,24 +56,22 @@ class ImageButtonFactoryWidgetTest : public ViewsTestBase {
}
void TearDown() override {
- button_.reset();
widget_.reset();
ViewsTestBase::TearDown();
}
ImageButton* AddImageButton(std::unique_ptr<ImageButton> button) {
- button_ = std::move(button);
- widget_->SetContentsView(button_.get());
- return button_.get();
+ button_ = widget_->SetContentsView(std::move(button));
+ return button_;
}
protected:
Widget* widget() { return widget_.get(); }
- ImageButton* button() { return button_.get(); }
+ ImageButton* button() { return button_; }
private:
std::unique_ptr<Widget> widget_;
- std::unique_ptr<ImageButton> button_;
+ ImageButton* button_ = nullptr; // owned by |widget_|.
DISALLOW_COPY_AND_ASSIGN(ImageButtonFactoryWidgetTest);
};
diff --git a/chromium/ui/views/controls/button/label_button.cc b/chromium/ui/views/controls/button/label_button.cc
index bb9105f1a18..3aef54f32cd 100644
--- a/chromium/ui/views/controls/button/label_button.cc
+++ b/chromium/ui/views/controls/button/label_button.cc
@@ -281,20 +281,21 @@ int LabelButton::GetHeightForWidth(int width) const {
}
void LabelButton::Layout() {
- gfx::Rect child_area = GetLocalBounds();
+ gfx::Rect image_area = GetLocalBounds();
- ink_drop_container_->SetBoundsRect(child_area);
- // The space that the label can use. Its actual bounds may be smaller if the
- // label is short.
- gfx::Rect label_area = child_area;
+ ink_drop_container_->SetBoundsRect(image_area);
gfx::Insets insets = GetInsets();
- child_area.Inset(insets);
- // Labels can paint over the vertical component of the border insets.
- label_area.Inset(insets.left(), 0, insets.right(), 0);
+ // If the button have a limited space to fit in, the image and the label
+ // may overlap with the border, which often times contains a lot of empty
+ // padding.
+ image_area.Inset(insets.left(), 0, insets.right(), 0);
+ // The space that the label can use. Labels truncate horizontally, so there
+ // is no need to allow the label to take up the complete horizontal space.
+ gfx::Rect label_area = image_area;
gfx::Size image_size = image_->GetPreferredSize();
- image_size.SetToMin(child_area.size());
+ image_size.SetToMin(image_area.size());
const auto horizontal_alignment = GetHorizontalAlignment();
if (!image_size.IsEmpty()) {
@@ -309,22 +310,28 @@ void LabelButton::Layout() {
std::min(label_area.width(), label_->GetPreferredSize().width()),
label_area.height());
- gfx::Point image_origin = child_area.origin();
+ gfx::Point image_origin = image_area.origin();
if (label_->GetMultiLine() && !image_centered_) {
- image_origin.Offset(
- 0, std::max(
- 0, (label_->font_list().GetHeight() - image_size.height()) / 2));
+ // This code assumes the text is vertically centered.
+ DCHECK_EQ(gfx::ALIGN_MIDDLE, label_->GetVerticalAlignment());
+ int label_height = label_->GetHeightForWidth(label_size.width());
+ int first_line_y =
+ label_area.y() + (label_area.height() - label_height) / 2;
+ int image_origin_y =
+ first_line_y +
+ (label_->font_list().GetHeight() - image_size.height()) / 2;
+ image_origin.Offset(0, std::max(0, image_origin_y));
} else {
- image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
+ image_origin.Offset(0, (image_area.height() - image_size.height()) / 2);
}
if (horizontal_alignment == gfx::ALIGN_CENTER) {
const int spacing = (image_size.width() > 0 && label_size.width() > 0)
? GetImageLabelSpacing()
: 0;
const int total_width = image_size.width() + label_size.width() + spacing;
- image_origin.Offset((child_area.width() - total_width) / 2, 0);
+ image_origin.Offset((image_area.width() - total_width) / 2, 0);
} else if (horizontal_alignment == gfx::ALIGN_RIGHT) {
- image_origin.Offset(child_area.width() - image_size.width(), 0);
+ image_origin.Offset(image_area.width() - image_size.width(), 0);
}
image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
diff --git a/chromium/ui/views/controls/button/label_button_label_unittest.cc b/chromium/ui/views/controls/button/label_button_label_unittest.cc
index 505c2d5ed6c..8e880de4eaf 100644
--- a/chromium/ui/views/controls/button/label_button_label_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_label_unittest.cc
@@ -62,12 +62,22 @@ class LabelButtonLabelTest : public ViewsTestBase {
void SetUp() override {
ViewsTestBase::SetUp();
- label_ = std::make_unique<TestLabel>(&last_color_);
+
+ widget_ = CreateTestWidget();
+ label_ =
+ widget_->SetContentsView(std::make_unique<TestLabel>(&last_color_));
+ label_->SetAutoColorReadabilityEnabled(false);
+ }
+
+ void TearDown() override {
+ widget_.reset();
+ ViewsTestBase::TearDown();
}
protected:
- SkColor last_color_ = SK_ColorCYAN;
- std::unique_ptr<TestLabel> label_;
+ SkColor last_color_ = gfx::kPlaceholderColor;
+ std::unique_ptr<views::Widget> widget_;
+ TestLabel* label_;
TestNativeTheme theme1_;
TestNativeTheme theme2_;
@@ -77,16 +87,6 @@ class LabelButtonLabelTest : public ViewsTestBase {
// Test that LabelButtonLabel reacts properly to themed and overridden colors.
TEST_F(LabelButtonLabelTest, Colors) {
- // The OnDidSchedulePaint() override won't be called while the base
- // class is initialized. Not much we can do about that, so give it the first
- // for free.
- EXPECT_EQ(SK_ColorCYAN, last_color_); // Sanity check.
-
- // At the same time we can check that changing the auto color readability
- // schedules a paint. Currently it does. Although it technically doesn't need
- // to since the color isn't actually changing.
- label_->SetAutoColorReadabilityEnabled(false);
-
// First one comes from the default theme. This check ensures the SK_ColorRED
// placeholder initializers were replaced.
SkColor default_theme_enabled_color =
diff --git a/chromium/ui/views/controls/button/label_button_unittest.cc b/chromium/ui/views/controls/button/label_button_unittest.cc
index cd33dbe4c0a..38443891fff 100644
--- a/chromium/ui/views/controls/button/label_button_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_unittest.cc
@@ -627,8 +627,8 @@ TEST_F(LabelButtonTest, HighlightedButtonStyle) {
// Ensure the label resets the enabled color after LabelButton::OnThemeChanged()
// is invoked.
TEST_F(LabelButtonTest, OnThemeChanged) {
- ASSERT_NE(button_->GetNativeTheme()->GetHighContrastColorScheme(),
- ui::NativeTheme::HighContrastColorScheme::kDark);
+ ASSERT_NE(button_->GetNativeTheme()->GetPlatformHighContrastColorScheme(),
+ ui::NativeTheme::PlatformHighContrastColorScheme::kDark);
ASSERT_NE(button_->label()->GetBackgroundColor(), SK_ColorBLACK);
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
@@ -667,6 +667,25 @@ TEST_F(LabelButtonTest, SetEnabledTextColorsResetsToThemeColors) {
EXPECT_EQ(TestNativeTheme::kSystemColor, button_->label()->GetEnabledColor());
}
+TEST_F(LabelButtonTest, ImageOrLabelGetClipped) {
+ const base::string16 text(ASCIIToUTF16("abc"));
+ button_->SetText(text);
+
+ const gfx::FontList font_list = button_->label()->font_list();
+ const int image_size = font_list.GetHeight();
+ button_->SetImage(Button::STATE_NORMAL,
+ CreateTestImage(image_size, image_size));
+
+ button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
+ // The border size + the content height is more than button's preferred size.
+ button_->SetBorder(CreateEmptyBorder(image_size / 2, 0, image_size / 2, 0));
+ button_->Layout();
+
+ // Ensure that content (image and label) doesn't get clipped by the border.
+ EXPECT_GE(button_->image()->height(), image_size);
+ EXPECT_GE(button_->label()->height(), image_size);
+}
+
// Test fixture for a LabelButton that has an ink drop configured.
class InkDropLabelButtonTest : public ViewsTestBase {
public:
diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc
index b9905fc6359..79c42e7b9ac 100644
--- a/chromium/ui/views/controls/button/md_text_button.cc
+++ b/chromium/ui/views/controls/button/md_text_button.cc
@@ -142,6 +142,11 @@ void MdTextButton::SetEnabledTextColors(base::Optional<SkColor> color) {
UpdateColors();
}
+void MdTextButton::SetCustomPadding(const gfx::Insets& padding) {
+ custom_padding_ = padding;
+ UpdatePadding();
+}
+
void MdTextButton::SetText(const base::string16& text) {
LabelButton::SetText(text);
UpdatePadding();
@@ -183,6 +188,11 @@ void MdTextButton::UpdatePadding() {
return;
}
+ SetBorder(
+ CreateEmptyBorder(custom_padding_.value_or(CalculateDefaultPadding())));
+}
+
+gfx::Insets MdTextButton::CalculateDefaultPadding() const {
// Text buttons default to 28dp in height on all platforms when the base font
// is in use, but should grow or shrink if the font size is adjusted up or
// down. When the system font size has been adjusted, the base font will be
@@ -213,8 +223,8 @@ void MdTextButton::UpdatePadding() {
// we apply the MD treatment to all buttons, even GTK buttons?
const int horizontal_padding = LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_BUTTON_HORIZONTAL_PADDING);
- SetBorder(CreateEmptyBorder(top_padding, horizontal_padding, bottom_padding,
- horizontal_padding));
+ return gfx::Insets(top_padding, horizontal_padding, bottom_padding,
+ horizontal_padding);
}
void MdTextButton::UpdateColors() {
diff --git a/chromium/ui/views/controls/button/md_text_button.h b/chromium/ui/views/controls/button/md_text_button.h
index 760ab2a80ae..1d293a70166 100644
--- a/chromium/ui/views/controls/button/md_text_button.h
+++ b/chromium/ui/views/controls/button/md_text_button.h
@@ -39,6 +39,9 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
void SetCornerRadius(float radius);
float GetCornerRadius() const;
+ // See |custom_padding_|.
+ void SetCustomPadding(const gfx::Insets& padding);
+
// LabelButton:
void OnThemeChanged() override;
std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
@@ -59,6 +62,7 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
private:
void UpdatePadding();
void UpdateColors();
+ gfx::Insets CalculateDefaultPadding() const;
// True if this button uses prominent styling (blue fill, etc.).
bool is_prominent_ = false;
@@ -68,6 +72,9 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
float corner_radius_ = 0.0f;
+ // Used to override default padding.
+ base::Optional<gfx::Insets> custom_padding_;
+
DISALLOW_COPY_AND_ASSIGN(MdTextButton);
};
diff --git a/chromium/ui/views/controls/button/md_text_button_unittest.cc b/chromium/ui/views/controls/button/md_text_button_unittest.cc
new file mode 100644
index 00000000000..5fe5d9c1bf1
--- /dev/null
+++ b/chromium/ui/views/controls/button/md_text_button_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 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/button/md_text_button.h"
+
+#include "ui/views/test/views_test_base.h"
+
+namespace views {
+
+using MdTextButtonTest = ViewsTestBase;
+
+TEST_F(MdTextButtonTest, CustomPadding) {
+ const base::string16 text = base::ASCIIToUTF16("abc");
+ std::unique_ptr<MdTextButton> button =
+ MdTextButton::Create(nullptr, text, views::style::CONTEXT_BUTTON_MD);
+
+ const gfx::Insets custom_padding(10, 20);
+ ASSERT_NE(button->GetInsets(), custom_padding);
+
+ button->SetCustomPadding(custom_padding);
+ EXPECT_EQ(button->GetInsets(), custom_padding);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/button/radio_button_unittest.cc b/chromium/ui/views/controls/button/radio_button_unittest.cc
index 3d25994df2a..8d22480097e 100644
--- a/chromium/ui/views/controls/button/radio_button_unittest.cc
+++ b/chromium/ui/views/controls/button/radio_button_unittest.cc
@@ -34,8 +34,7 @@ class RadioButtonTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- button_container_ = new View();
- widget_->SetContentsView(button_container_);
+ button_container_ = widget_->SetContentsView(std::make_unique<View>());
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc
index b0364bcdb3e..72903d758de 100644
--- a/chromium/ui/views/controls/combobox/combobox.cc
+++ b/chromium/ui/views/controls/combobox/combobox.cc
@@ -17,12 +17,14 @@
#include "ui/base/ime/input_method.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/menu_model.h"
+#include "ui/base/ui_base_types.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
+#include "ui/native_theme/themed_vector_icon.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/background.h"
@@ -54,6 +56,15 @@ SkColor GetTextColorForEnableState(const Combobox& combobox, bool enabled) {
return style::GetColor(combobox, style::CONTEXT_TEXTFIELD, style);
}
+gfx::ImageSkia GetImageSkiaFromImageModel(const ui::ImageModel* model,
+ const ui::NativeTheme* native_theme) {
+ DCHECK(model);
+ DCHECK(!model->IsEmpty());
+ return model->IsImage() ? model->GetImage().AsImageSkia()
+ : ui::ThemedVectorIcon(model->GetVectorIcon())
+ .GetImageSkia(native_theme);
+}
+
// The transparent button which holds a button state but is not rendered.
class TransparentButton : public Button {
public:
@@ -132,7 +143,13 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
}
// Overridden from MenuModel:
- bool HasIcons() const override { return false; }
+ bool HasIcons() const override {
+ for (int i = 0; i < GetItemCount(); ++i) {
+ if (!GetIconAt(i).IsEmpty())
+ return true;
+ }
+ return false;
+ }
int GetItemCount() const override { return model_->GetItemCount(); }
@@ -155,7 +172,13 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
base::string16 GetLabelAt(int index) const override {
// Inserting the Unicode formatting characters if necessary so that the
// text is displayed correctly in right-to-left UIs.
- base::string16 text = model_->GetItemAt(index);
+ base::string16 text = model_->GetDropDownTextAt(index);
+ base::i18n::AdjustStringForLocaleDirection(&text);
+ return text;
+ }
+
+ base::string16 GetSecondaryLabelAt(int index) const override {
+ base::string16 text = model_->GetDropDownSecondaryTextAt(index);
base::i18n::AdjustStringForLocaleDirection(&text);
return text;
}
@@ -178,7 +201,7 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
int GetGroupIdAt(int index) const override { return -1; }
ui::ImageModel GetIconAt(int index) const override {
- return ui::ImageModel();
+ return model_->GetDropDownIconAt(index);
}
ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
@@ -443,7 +466,7 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
void Combobox::OnPaint(gfx::Canvas* canvas) {
OnPaintBackground(canvas);
- PaintText(canvas);
+ PaintIconAndText(canvas);
OnPaintBorder(canvas);
}
@@ -504,16 +527,8 @@ void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
// TODO(hajimehoshi): Fix the problem that the arrow button blinks when
// cliking this while the dropdown menu is opened.
- const base::TimeDelta delta = base::TimeTicks::Now() - closed_time_;
- if (delta <= kMinimumTimeBetweenButtonClicks)
- return;
-
- ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
- if (event.IsKeyEvent())
- source_type = ui::MENU_SOURCE_KEYBOARD;
- else if (event.IsGestureEvent() || event.IsTouchEvent())
- source_type = ui::MENU_SOURCE_TOUCH;
- ShowDropDownMenu(source_type);
+ if ((base::TimeTicks::Now() - closed_time_) > kMinimumTimeBetweenButtonClicks)
+ ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event));
}
void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
@@ -543,7 +558,7 @@ void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
rect->set_x(GetMirroredXForRect(*rect));
}
-void Combobox::PaintText(gfx::Canvas* canvas) {
+void Combobox::PaintIconAndText(gfx::Canvas* canvas) {
gfx::Insets insets = GetInsets();
insets += gfx::Insets(0, LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING));
@@ -553,7 +568,20 @@ void Combobox::PaintText(gfx::Canvas* canvas) {
int x = insets.left();
int y = insets.top();
- int text_height = height() - insets.height();
+ int contents_height = height() - insets.height();
+
+ // Draw the icon.
+ ui::ImageModel icon = model()->GetIconAt(selected_index_);
+ if (!icon.IsEmpty()) {
+ gfx::ImageSkia icon_skia =
+ GetImageSkiaFromImageModel(&icon, GetNativeTheme());
+ int icon_y = y + (contents_height - icon_skia.height()) / 2;
+ canvas->DrawImageInt(icon_skia, x, icon_y);
+ x += icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
+ }
+
+ // Draw the text.
SkColor text_color = GetTextColorForEnableState(*this, GetEnabled());
DCHECK_GE(selected_index_, 0);
DCHECK_LT(selected_index_, model()->GetItemCount());
@@ -565,10 +593,10 @@ void Combobox::PaintText(gfx::Canvas* canvas) {
const gfx::FontList& font_list = GetFontList();
int text_width = gfx::GetStringWidth(text, font_list);
- if ((text_width + insets.width()) > disclosure_arrow_offset)
- text_width = disclosure_arrow_offset - insets.width();
+ text_width =
+ std::min(text_width, disclosure_arrow_offset - insets.right() - x);
- gfx::Rect text_bounds(x, y, text_width, text_height);
+ gfx::Rect text_bounds(x, y, text_width, contents_height);
AdjustBoundsForRTLUI(&text_bounds);
canvas->DrawStringRect(text, font_list, text_color, text_bounds);
@@ -581,21 +609,15 @@ void Combobox::PaintText(gfx::Canvas* canvas) {
}
void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
- // Menu border widths.
- constexpr int kMenuBorderWidthLeft = 1;
constexpr int kMenuBorderWidthTop = 1;
- constexpr int kMenuBorderWidthRight = 1;
-
+ // Menu's requested position's width should be the same as local bounds so the
+ // border of the menu lines up with the border of the combobox. The y
+ // coordinate however should be shifted to the bottom with the border with not
+ // to overlap with the combobox border.
gfx::Rect lb = GetLocalBounds();
gfx::Point menu_position(lb.origin());
-
- // Inset the menu's requested position so the border of the menu lines up
- // with the border of the combobox.
- menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
- lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
-
View::ConvertPointToScreen(this, &menu_position);
gfx::Rect bounds(menu_position, lb.size());
@@ -633,18 +655,27 @@ void Combobox::OnPerformAction() {
gfx::Size Combobox::GetContentSize() const {
const gfx::FontList& font_list = GetFontList();
-
+ int height = font_list.GetHeight();
int width = 0;
for (int i = 0; i < model()->GetItemCount(); ++i) {
if (model_->IsItemSeparatorAt(i))
continue;
if (size_to_largest_label_ || i == selected_index_) {
- width = std::max(
- width, gfx::GetStringWidth(menu_model_->GetLabelAt(i), font_list));
+ int item_width = gfx::GetStringWidth(model()->GetItemAt(i), font_list);
+ ui::ImageModel icon = model()->GetIconAt(i);
+ if (!icon.IsEmpty()) {
+ gfx::ImageSkia icon_skia =
+ GetImageSkiaFromImageModel(&icon, GetNativeTheme());
+ item_width +=
+ icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
+ height = std::max(height, icon_skia.height());
+ }
+ width = std::max(width, item_width);
}
}
- return gfx::Size(width, font_list.GetHeight());
+ return gfx::Size(width, height);
}
PrefixSelector* Combobox::GetPrefixSelector() {
diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h
index d6da07f7122..91ad60c9a14 100644
--- a/chromium/ui/views/controls/combobox/combobox.h
+++ b/chromium/ui/views/controls/combobox/combobox.h
@@ -126,7 +126,7 @@ class VIEWS_EXPORT Combobox : public View,
void AdjustBoundsForRTLUI(gfx::Rect* rect) const;
// Draws the selected value of the drop down list
- void PaintText(gfx::Canvas* canvas);
+ void PaintIconAndText(gfx::Canvas* canvas);
// Show the drop down list
void ShowDropDownMenu(ui::MenuSourceType source_type);
@@ -207,7 +207,7 @@ class VIEWS_EXPORT Combobox : public View,
bool size_to_largest_label_;
// The focus ring for this Combobox.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
ScopedObserver<ui::ComboboxModel, ui::ComboboxModelObserver> observer_{this};
diff --git a/chromium/ui/views/controls/combobox/combobox_unittest.cc b/chromium/ui/views/controls/combobox/combobox_unittest.cc
index 9f0698b0d0e..b081a0b7408 100644
--- a/chromium/ui/views/controls/combobox/combobox_unittest.cc
+++ b/chromium/ui/views/controls/combobox/combobox_unittest.cc
@@ -30,7 +30,9 @@
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/combobox_test_api.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
@@ -42,36 +44,7 @@ using test::ComboboxTestApi;
namespace {
-// A wrapper of Combobox to intercept the result of OnKeyPressed() and
-// OnKeyReleased() methods.
-class TestCombobox : public Combobox {
- public:
- explicit TestCombobox(ui::ComboboxModel* model)
- : Combobox(model), key_handled_(false), key_received_(false) {}
-
- bool OnKeyPressed(const ui::KeyEvent& e) override {
- key_received_ = true;
- key_handled_ = Combobox::OnKeyPressed(e);
- return key_handled_;
- }
-
- bool OnKeyReleased(const ui::KeyEvent& e) override {
- key_received_ = true;
- key_handled_ = Combobox::OnKeyReleased(e);
- return key_handled_;
- }
-
- bool key_handled() const { return key_handled_; }
- bool key_received() const { return key_received_; }
-
- void clear() { key_received_ = key_handled_ = false; }
-
- private:
- bool key_handled_;
- bool key_received_;
-
- DISALLOW_COPY_AND_ASSIGN(TestCombobox);
-};
+using TestCombobox = Combobox;
// A concrete class is needed to test the combobox.
class TestComboboxModel : public ui::ComboboxModel {
@@ -83,14 +56,14 @@ class TestComboboxModel : public ui::ComboboxModel {
// ui::ComboboxModel:
int GetItemCount() const override { return item_count_; }
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
if (IsItemSeparatorAt(index)) {
NOTREACHED();
return ASCIIToUTF16("SEPARATOR");
}
return ASCIIToUTF16(index % 2 == 0 ? "PEANUT BUTTER" : "JELLY");
}
- bool IsItemSeparatorAt(int index) override {
+ bool IsItemSeparatorAt(int index) const override {
return separators_.find(index) != separators_.end();
}
@@ -147,10 +120,10 @@ class VectorComboboxModel : public ui::ComboboxModel {
int GetItemCount() const override {
return static_cast<int>(values_->size());
}
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
return ASCIIToUTF16(values_->at(index));
}
- bool IsItemSeparatorAt(int index) override { return false; }
+ bool IsItemSeparatorAt(int index) const override { return false; }
int GetDefaultIndex() const override { return default_index_; }
void AddObserver(ui::ComboboxModelObserver* observer) override {
observers_.AddObserver(observer);
@@ -222,8 +195,7 @@ class ComboboxTest : public ViewsTestBase {
ComboboxTest() = default;
void TearDown() override {
- if (widget_)
- widget_->Close();
+ widget_.reset();
ViewsTestBase::TearDown();
}
@@ -234,26 +206,25 @@ class ComboboxTest : public ViewsTestBase {
model_->SetSeparators(*separators);
ASSERT_FALSE(combobox_);
- combobox_ = new TestCombobox(model_.get());
- test_api_ = std::make_unique<ComboboxTestApi>(combobox_);
+ auto combobox = std::make_unique<TestCombobox>(model_.get());
+ test_api_ = std::make_unique<ComboboxTestApi>(combobox.get());
test_api_->InstallTestMenuRunner(&menu_show_count_);
- combobox_->SetID(1);
+ combobox->SetID(1);
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(200, 200, 200, 200);
widget_->Init(std::move(params));
- View* container = new View();
- widget_->SetContentsView(container);
- container->AddChildView(combobox_);
+ View* container = widget_->SetContentsView(std::make_unique<View>());
+ combobox_ = container->AddChildView(std::move(combobox));
widget_->Show();
combobox_->RequestFocus();
combobox_->SizeToPreferredSize();
- event_generator_ =
- std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_));
+ event_generator_ = std::make_unique<ui::test::EventGenerator>(
+ GetRootWindow(widget_.get()));
event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW);
}
@@ -291,7 +262,7 @@ class ComboboxTest : public ViewsTestBase {
}
// We need widget to populate wrapper class.
- Widget* widget_ = nullptr;
+ UniqueWidgetPtr widget_;
// |combobox_| will be allocated InitCombobox() and then owned by |widget_|.
TestCombobox* combobox_ = nullptr;
@@ -356,23 +327,28 @@ TEST_F(ComboboxTest, KeyTestMac) {
}
#endif
+// Iterate through all the metadata and test each property.
+TEST_F(ComboboxTest, MetadataTest) {
+ InitCombobox(nullptr);
+ test::TestViewMetadata(combobox_);
+}
+
// Check that if a combobox is disabled before it has a native wrapper, then the
// native wrapper inherits the disabled state when it gets created.
TEST_F(ComboboxTest, DisabilityTest) {
model_ = std::make_unique<TestComboboxModel>();
ASSERT_FALSE(combobox_);
- combobox_ = new TestCombobox(model_.get());
- combobox_->SetEnabled(false);
+ auto combobox = std::make_unique<TestCombobox>(model_.get());
+ combobox->SetEnabled(false);
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(100, 100, 100, 100);
widget_->Init(std::move(params));
- View* container = new View();
- widget_->SetContentsView(container);
- container->AddChildView(combobox_);
+ View* container = widget_->SetContentsView(std::make_unique<View>());
+ combobox_ = container->AddChildView(std::move(combobox));
EXPECT_FALSE(combobox_->GetEnabled());
}
@@ -559,7 +535,7 @@ TEST_F(ComboboxTest, ListenerHandlesDelete) {
// |combobox| will be deleted on change.
TestCombobox* combobox = new TestCombobox(&model);
- std::unique_ptr<EvilListener> evil_listener(new EvilListener());
+ auto evil_listener = std::make_unique<EvilListener>();
combobox->set_listener(evil_listener.get());
ASSERT_NO_FATAL_FAILURE(ComboboxTestApi(combobox).PerformActionAt(2));
EXPECT_TRUE(evil_listener->deleted());
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
index 5f76ed0db20..b4c14a33505 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -37,7 +37,6 @@
#include "ui/gfx/range/range.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/scoped_canvas.h"
-#include "ui/native_theme/native_theme.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_host_view.h"
@@ -65,8 +64,7 @@ namespace {
class Arrow : public Button {
public:
- Arrow(const SkColor color, ButtonListener* listener)
- : Button(listener), color_(color) {
+ explicit Arrow(ButtonListener* listener) : Button(listener) {
// Similar to Combobox's TransparentButton.
SetFocusBehavior(FocusBehavior::NEVER);
button_controller()->set_notify_action(
@@ -93,8 +91,7 @@ class Arrow : public Button {
std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override {
return std::make_unique<views::FloodFillInkDropRipple>(
size(), GetInkDropCenterBasedOnLastEvent(),
- GetNativeTheme()->GetSystemColor(
- ui::NativeTheme::kColorId_LabelEnabledColor),
+ style::GetColor(*this, style::CONTEXT_TEXTFIELD, style::STYLE_PRIMARY),
ink_drop_visible_opacity());
}
@@ -104,7 +101,11 @@ class Arrow : public Button {
canvas->ClipRect(GetContentsBounds());
gfx::Rect arrow_bounds = GetLocalBounds();
arrow_bounds.ClampToCenteredSize(ComboboxArrowSize());
- PaintComboboxArrow(color_, arrow_bounds, canvas);
+ // Make sure the arrow use the same color as the text in the combobox.
+ PaintComboboxArrow(style::GetColor(*this, style::CONTEXT_TEXTFIELD,
+ GetEnabled() ? style::STYLE_PRIMARY
+ : style::STYLE_DISABLED),
+ arrow_bounds, canvas);
}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
@@ -115,8 +116,6 @@ class Arrow : public Button {
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen);
}
- const SkColor color_;
-
DISALLOW_COPY_AND_ASSIGN(Arrow);
};
@@ -176,15 +175,10 @@ class EditableCombobox::EditableComboboxMenuModel
gfx::RenderText::kPasswordReplacementChar);
}
- //////////////////////////////////////////////////////////////////////////////
- // Overridden from ComboboxModelObserver:
void OnComboboxModelChanged(ui::ComboboxModel* model) override {
UpdateItemsShown();
}
- //////////////////////////////////////////////////////////////////////////////
- // Overridden from MenuModel:
-
int GetItemCount() const override { return items_shown_.size(); }
private:
@@ -309,8 +303,6 @@ class EditableCombobox::EditableComboboxPreTargetHandler
DISALLOW_COPY_AND_ASSIGN(EditableComboboxPreTargetHandler);
};
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, public, non-overridden methods:
EditableCombobox::EditableCombobox(
std::unique_ptr<ui::ComboboxModel> combobox_model,
const bool filter_on_edit,
@@ -341,8 +333,7 @@ EditableCombobox::EditableCombobox(
textfield_->SetExtraInsets(gfx::Insets(
/*top=*/0, /*left=*/0, /*bottom=*/0,
/*right=*/kComboboxArrowContainerWidth - kComboboxArrowPaddingWidth));
- arrow_ = new Arrow(textfield_->GetTextColor(), this);
- AddChildView(arrow_);
+ arrow_ = AddChildView(std::make_unique<Arrow>(this));
}
SetLayoutManager(std::make_unique<views::FillLayout>());
}
@@ -399,9 +390,6 @@ base::string16 EditableCombobox::GetItemForTest(int index) {
return menu_model_->GetItemTextAt(index, showing_password_text_);
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, View overrides:
-
void EditableCombobox::Layout() {
View::Layout();
if (arrow_) {
@@ -411,11 +399,6 @@ void EditableCombobox::Layout() {
}
}
-void EditableCombobox::OnThemeChanged() {
- View::OnThemeChanged();
- textfield_->OnThemeChanged();
-}
-
void EditableCombobox::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kComboBoxGrouping;
@@ -435,9 +418,6 @@ void EditableCombobox::OnVisibleBoundsChanged() {
CloseMenu();
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, TextfieldController overrides:
-
void EditableCombobox::ContentsChanged(Textfield* sender,
const base::string16& new_contents) {
HandleNewContent(new_contents);
@@ -455,32 +435,25 @@ bool EditableCombobox::HandleKeyEvent(Textfield* sender,
return false;
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, View overrides:
-
void EditableCombobox::OnViewBlurred(View* observed_view) {
CloseMenu();
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, ButtonListener overrides:
-
void EditableCombobox::ButtonPressed(Button* sender, const ui::Event& event) {
textfield_->RequestFocus();
- if (menu_runner_ && menu_runner_->IsRunning()) {
+ if (menu_runner_ && menu_runner_->IsRunning())
CloseMenu();
- return;
- }
- ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
- if (event.IsKeyEvent())
- source_type = ui::MENU_SOURCE_KEYBOARD;
- else if (event.IsGestureEvent() || event.IsTouchEvent())
- source_type = ui::MENU_SOURCE_TOUCH;
- ShowDropDownMenu(source_type);
+ else
+ ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event));
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, Private methods:
+void EditableCombobox::OnLayoutIsAnimatingChanged(
+ views::AnimatingLayoutManager* source,
+ bool is_animating) {
+ dropdown_blocked_for_animation_ = is_animating;
+ if (dropdown_blocked_for_animation_)
+ CloseMenu();
+}
void EditableCombobox::CloseMenu() {
menu_runner_.reset();
@@ -517,9 +490,10 @@ void EditableCombobox::HandleNewContent(const base::string16& new_content) {
}
void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
- constexpr int kMenuBorderWidthLeft = 1;
constexpr int kMenuBorderWidthTop = 1;
- constexpr int kMenuBorderWidthRight = 1;
+
+ if (dropdown_blocked_for_animation_)
+ return;
if (!menu_model_->GetItemCount()) {
CloseMenu();
@@ -540,13 +514,13 @@ void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
this, GetWidget()->GetRootView());
gfx::Rect local_bounds = textfield_->GetLocalBounds();
+
+ // Menu's requested position's width should be the same as local bounds so the
+ // border of the menu lines up with the border of the combobox. The y
+ // coordinate however should be shifted to the bottom with the border width
+ // not to overlap with the combobox border.
gfx::Point menu_position(local_bounds.origin());
- // Inset the menu's requested position so the border of the menu lines up
- // with the border of the textfield.
- menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
- local_bounds.set_width(local_bounds.width() -
- (kMenuBorderWidthLeft + kMenuBorderWidthRight));
View::ConvertPointToScreen(this, &menu_position);
gfx::Rect bounds(menu_position, local_bounds.size());
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.h b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
index c0f6b26fad8..e4a0d3c3698 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
@@ -14,6 +14,7 @@
#include "ui/base/ui_base_types.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/layout/animating_layout_manager.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
@@ -37,10 +38,12 @@ class MenuRunner;
class Textfield;
// Textfield that also shows a drop-down list with suggestions.
-class VIEWS_EXPORT EditableCombobox : public View,
- public TextfieldController,
- public ViewObserver,
- public ButtonListener {
+class VIEWS_EXPORT EditableCombobox
+ : public View,
+ public TextfieldController,
+ public ViewObserver,
+ public ButtonListener,
+ public views::AnimatingLayoutManager::Observer {
public:
METADATA_HEADER(EditableCombobox);
@@ -121,7 +124,6 @@ class VIEWS_EXPORT EditableCombobox : public View,
// Overridden from View:
void Layout() override;
- void OnThemeChanged() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
void RequestFocus() override;
bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
@@ -139,6 +141,10 @@ class VIEWS_EXPORT EditableCombobox : public View,
// Overridden from ButtonListener:
void ButtonPressed(Button* sender, const ui::Event& event) override;
+ // Overridden from views::AnimatingLayoutManager::Observer:
+ void OnLayoutIsAnimatingChanged(views::AnimatingLayoutManager* source,
+ bool is_animating) override;
+
Textfield* textfield_;
Button* arrow_ = nullptr;
std::unique_ptr<ui::ComboboxModel> combobox_model_;
@@ -171,6 +177,8 @@ class VIEWS_EXPORT EditableCombobox : public View,
// Type::kPassword.
bool showing_password_text_;
+ bool dropdown_blocked_for_animation_ = false;
+
ScopedObserver<View, ViewObserver> observer_{this};
DISALLOW_COPY_AND_ASSIGN(EditableCombobox);
diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc
index 443c199003c..0ee9512f5c3 100644
--- a/chromium/ui/views/controls/focus_ring.cc
+++ b/chromium/ui/views/controls/focus_ring.cc
@@ -8,6 +8,8 @@
#include <utility>
#include "base/memory/ptr_util.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/highlight_path_generator.h"
@@ -53,15 +55,15 @@ SkPath GetHighlightPathInternal(const View* view) {
} // namespace
// static
-std::unique_ptr<FocusRing> FocusRing::Install(View* parent) {
+FocusRing* FocusRing::Install(View* parent) {
auto ring = base::WrapUnique<FocusRing>(new FocusRing());
- ring->set_owned_by_client();
- parent->AddChildView(ring.get());
ring->InvalidateLayout();
ring->SchedulePaint();
- return ring;
+ return parent->AddChildView(std::move(ring));
}
+FocusRing::~FocusRing() = default;
+
void FocusRing::SetPathGenerator(
std::unique_ptr<HighlightPathGenerator> generator) {
path_generator_ = std::move(generator);
@@ -102,14 +104,14 @@ void FocusRing::ViewHierarchyChanged(
if (details.is_add) {
// Need to start observing the parent.
- details.parent->AddObserver(this);
- } else {
+ view_observer_.Add(details.parent);
+ RefreshLayer();
+ } else if (view_observer_.IsObserving(details.parent)) {
// This view is being removed from its parent. It needs to remove itself
- // from its parent's observer list. Otherwise, since its |parent_| will
- // become a nullptr, it won't be able to do so in its destructor.
- details.parent->RemoveObserver(this);
+ // from its parent's observer list in the case where the FocusView is
+ // removed from its parent but not deleted.
+ view_observer_.Remove(details.parent);
}
- RefreshLayer();
}
void FocusRing::OnPaint(gfx::Canvas* canvas) {
@@ -155,6 +157,12 @@ void FocusRing::OnPaint(gfx::Canvas* canvas) {
}
}
+void FocusRing::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+ // Mark the focus ring in the accessibility tree as invisible so that it will
+ // not be accessed by assistive technologies.
+ node_data->AddState(ax::mojom::State::kInvisible);
+}
+
void FocusRing::OnViewFocused(View* view) {
RefreshLayer();
}
@@ -168,11 +176,6 @@ FocusRing::FocusRing() {
set_can_process_events_within_subtree(false);
}
-FocusRing::~FocusRing() {
- if (parent())
- parent()->RemoveObserver(this);
-}
-
void FocusRing::RefreshLayer() {
// TODO(pbos): This always keeps the layer alive if |has_focus_predicate_| is
// set. This is done because we're not notified when the predicate might
diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h
index 9ac2f4ebeeb..c3e42b6a4da 100644
--- a/chromium/ui/views/controls/focus_ring.h
+++ b/chromium/ui/views/controls/focus_ring.h
@@ -19,38 +19,25 @@ namespace views {
class HighlightPathGenerator;
// 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);
-// ...
-// }
-//
+// parent. It is a view that paints to a layer which extends beyond the bounds
+// of its parent view.
// 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.
+// TODO(tluk): FocusRing should not be a view but instead a new concept which
+// only participates in view painting ( https://crbug.com/840796 ).
class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
public:
METADATA_HEADER(FocusRing);
using ViewPredicate = std::function<bool(View* view)>;
- ~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);
+ // owned by the |parent|.
+ static FocusRing* Install(View* parent);
+
+ ~FocusRing() override;
// Sets the HighlightPathGenerator to draw this FocusRing around.
// Note: This method should only be used if the focus ring needs to differ
@@ -77,6 +64,7 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) override;
void OnPaint(gfx::Canvas* canvas) override;
+ void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
// ViewObserver:
void OnViewFocused(View* view) override;
@@ -108,6 +96,8 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
// The predicate used to determine whether the parent has focus.
base::Optional<ViewPredicate> has_focus_predicate_;
+ ScopedObserver<View, ViewObserver> view_observer_{this};
+
DISALLOW_COPY_AND_ASSIGN(FocusRing);
};
diff --git a/chromium/ui/views/controls/label.cc b/chromium/ui/views/controls/label.cc
index ad6af149e28..febce15ff86 100644
--- a/chromium/ui/views/controls/label.cc
+++ b/chromium/ui/views/controls/label.cc
@@ -108,6 +108,19 @@ int Label::GetTextContext() const {
return text_context_;
}
+int Label::GetTextStyle() const {
+ return text_style_;
+}
+
+void Label::SetTextStyle(int style) {
+ if (style == text_style_)
+ return;
+
+ text_style_ = style;
+ UpdateColorsFromTheme();
+ OnPropertyChanged(&text_style_, kPropertyEffectsPreferredSizeChanged);
+}
+
bool Label::GetAutoColorReadabilityEnabled() const {
return auto_color_readability_enabled_;
}
@@ -291,6 +304,14 @@ void Label::SetAllowCharacterBreak(bool allow_character_break) {
kPropertyEffectsLayout);
}
+size_t Label::GetTextIndexOfLine(size_t line) const {
+ return full_text_->GetTextIndexOfLine(line);
+}
+
+void Label::SetTruncateLength(size_t truncate_length) {
+ return full_text_->set_truncate_length(truncate_length);
+}
+
gfx::ElideBehavior Label::GetElideBehavior() const {
return elide_behavior_;
}
@@ -950,7 +971,6 @@ void Label::Init(const base::string16& text,
full_text_->SetCursorEnabled(false);
full_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS);
- UpdateColorsFromTheme();
SetText(text);
// Only selectable labels will get requests to show the context menu, due to
@@ -1102,8 +1122,9 @@ void Label::BuildContextMenuContents() {
BEGIN_METADATA(Label)
METADATA_PARENT_CLASS(View)
-ADD_PROPERTY_METADATA(Label, bool, AutoColorReadabilityEnabled)
ADD_PROPERTY_METADATA(Label, base::string16, Text)
+ADD_PROPERTY_METADATA(Label, int, TextStyle)
+ADD_PROPERTY_METADATA(Label, bool, AutoColorReadabilityEnabled)
ADD_PROPERTY_METADATA(Label, SkColor, EnabledColor)
ADD_PROPERTY_METADATA(Label, gfx::ElideBehavior, ElideBehavior)
ADD_PROPERTY_METADATA(Label, SkColor, BackgroundColor)
diff --git a/chromium/ui/views/controls/label.h b/chromium/ui/views/controls/label.h
index dc690b22dc2..5a45ef3cbf0 100644
--- a/chromium/ui/views/controls/label.h
+++ b/chromium/ui/views/controls/label.h
@@ -91,6 +91,11 @@ class VIEWS_EXPORT Label : public View,
// a value from views::style::TextContext or an enum that extends it.
int GetTextContext() const;
+ // The style of the label. This is a value from views::style::TextStyle or an
+ // enum that extends it.
+ int GetTextStyle() const;
+ void SetTextStyle(int style);
+
// Enables or disables auto-color-readability (enabled by default). If this
// is enabled, then calls to set any foreground or background color will
// trigger an automatic mapper that uses color_utils::BlendForMinContrast()
@@ -177,6 +182,13 @@ class VIEWS_EXPORT Label : public View,
bool GetAllowCharacterBreak() const;
void SetAllowCharacterBreak(bool allow_character_break);
+ // For the provided line index, gets the corresponding rendered line and
+ // returns the text position of the first character of that line.
+ size_t GetTextIndexOfLine(size_t line) const;
+
+ // Set the truncate length of the rendered text.
+ void SetTruncateLength(size_t truncate_length);
+
// Gets/Sets the eliding or fading behavior, applied as necessary. The default
// is to elide at the end. Eliding is not well-supported for multi-line
// labels.
@@ -377,7 +389,7 @@ class VIEWS_EXPORT Label : public View,
void BuildContextMenuContents();
const int text_context_;
- const int text_style_;
+ int text_style_;
// An un-elided and single-line RenderText object used for preferred sizing.
std::unique_ptr<gfx::RenderText> full_text_;
diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc
index 763a169b27e..33d969862cc 100644
--- a/chromium/ui/views/controls/label_unittest.cc
+++ b/chromium/ui/views/controls/label_unittest.cc
@@ -291,6 +291,11 @@ TEST_F(LabelTest, TextProperty) {
EXPECT_EQ(test_text, label()->GetText());
}
+TEST_F(LabelTest, TextStyleProperty) {
+ label()->SetTextStyle(views::style::STYLE_DISABLED);
+ EXPECT_EQ(views::style::STYLE_DISABLED, label()->GetTextStyle());
+}
+
TEST_F(LabelTest, ColorProperty) {
SkColor color = SkColorSetARGB(20, 40, 10, 5);
label()->SetAutoColorReadabilityEnabled(false);
diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc
index d9938916a53..87f9291b67d 100644
--- a/chromium/ui/views/controls/menu/menu_controller.cc
+++ b/chromium/ui/views/controls/menu/menu_controller.cc
@@ -621,6 +621,11 @@ bool MenuController::IsContextMenu() const {
return state_.context_menu;
}
+void MenuController::SelectItemAndOpenSubmenu(MenuItemView* item) {
+ DCHECK(item);
+ SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+}
+
bool MenuController::OnMousePressed(SubmenuView* source,
const ui::MouseEvent& event) {
// We should either have no current_mouse_event_target_, or should have a
@@ -1379,6 +1384,11 @@ void MenuController::SetSelection(MenuItemView* menu_item,
menu_item->GetType() != MenuItemView::Type::kSubMenu ||
(menu_item->GetType() == MenuItemView::Type::kActionableSubMenu &&
(selection_types & SELECTION_OPEN_SUBMENU) == 0))) {
+ // Before firing the selection event, ensure that focus appears to be
+ // within the popup. This is helpful for ATs on some platforms,
+ // specifically on Windows, where selection events in a list are mapped
+ // to focus events. Without this call, the focus appears to be elsewhere.
+ menu_item->GetViewAccessibility().SetPopupFocusOverride();
menu_item->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
// Notify an accessibility selected children changed event on the parent
// submenu.
@@ -1635,6 +1645,7 @@ MenuController::~MenuController() {
active_instance_ = nullptr;
StopShowTimer();
StopCancelAllTimer();
+ CHECK(!IsInObserverList());
}
bool MenuController::SendAcceleratorToHotTrackedView() {
@@ -3104,20 +3115,18 @@ void MenuController::SetNextHotTrackedView(
}
void MenuController::SetHotTrackedButton(Button* hot_button) {
- if (hot_button == hot_button_) {
- // Hot-tracked state may change outside of the MenuController. Correct it.
- if (hot_button && !hot_button->IsHotTracked()) {
- hot_button->SetHotTracked(true);
- hot_button->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
- }
- return;
- }
- if (hot_button_)
+ // If we're providing a new hot-tracked button, first remove the existing one.
+ if (hot_button_ && hot_button_ != hot_button) {
hot_button_->SetHotTracked(false);
+ hot_button_->GetViewAccessibility().EndPopupFocusOverride();
+ }
+
+ // Then set the new one.
hot_button_ = hot_button;
- if (hot_button) {
- hot_button->SetHotTracked(true);
- hot_button->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
+ if (hot_button_ && !hot_button_->IsHotTracked()) {
+ hot_button_->GetViewAccessibility().SetPopupFocusOverride();
+ hot_button_->SetHotTracked(true);
+ hot_button_->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
}
diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h
index 5fab7290f4b..2b3625c5b5a 100644
--- a/chromium/ui/views/controls/menu/menu_controller.h
+++ b/chromium/ui/views/controls/menu/menu_controller.h
@@ -125,7 +125,15 @@ class VIEWS_EXPORT MenuController
// WARNING: this may be NULL.
Widget* owner() { return owner_; }
- // Get the anchor position which is used to show this menu.
+ // Gets the most-current selected menu item, if any, including if the
+ // selection has not been committed yet.
+ views::MenuItemView* GetSelectedMenuItem() { return pending_state_.item; }
+
+ // Selects a menu-item and opens its sub-menu (if one exists) if not already
+ // so. Clears any selections within the submenu if it is already open.
+ void SelectItemAndOpenSubmenu(MenuItemView* item);
+
+ // Gets the anchor position which is used to show this menu.
MenuAnchorPosition GetAnchorPosition() { return state_.anchor; }
// Cancels the current Run. See ExitType for a description of what happens
diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
index 3176d01e445..67168197f0a 100644
--- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
@@ -1125,6 +1125,27 @@ TEST_F(MenuControllerTest, PreviousSelectedItem) {
ResetSelection();
}
+// Tests that the APIs related to the current selected item work correctly.
+TEST_F(MenuControllerTest, CurrentSelectedItem) {
+ SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+
+ // Select the first menu-item.
+ DispatchKey(ui::VKEY_HOME);
+ EXPECT_EQ(pending_state_item(), menu_controller()->GetSelectedMenuItem());
+
+ // The API should let the submenu stay open if already so, but clear any
+ // selections within it.
+ EXPECT_TRUE(IsShowing());
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+ menu_controller()->SelectItemAndOpenSubmenu(menu_item());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_EQ(0, pending_state_item()->GetCommand());
+
+ // Clear references in menu controller to the menu item that is going away.
+ ResetSelection();
+}
+
// Tests that opening menu and calling SelectByChar works correctly.
TEST_F(MenuControllerTest, SelectByChar) {
SetComboboxType(MenuController::ComboboxType::kReadonly);
diff --git a/chromium/ui/views/controls/menu/menu_delegate.h b/chromium/ui/views/controls/menu/menu_delegate.h
index 2a03ff43f4a..158724b4752 100644
--- a/chromium/ui/views/controls/menu/menu_delegate.h
+++ b/chromium/ui/views/controls/menu/menu_delegate.h
@@ -8,7 +8,6 @@
#include <set>
#include <string>
-#include "base/logging.h"
#include "base/strings/string16.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/dragdrop/drag_drop_types.h"
diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc
index 0b9230bda55..fc94bfc7f17 100644
--- a/chromium/ui/views/controls/menu/menu_host.cc
+++ b/chromium/ui/views/controls/menu/menu_host.cc
@@ -103,6 +103,7 @@ MenuHost::MenuHost(SubmenuView* submenu)
MenuHost::~MenuHost() {
if (owner_)
owner_->RemoveObserver(this);
+ CHECK(!IsInObserverList());
}
void MenuHost::InitMenuHost(Widget* parent,
diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc
index d00618f37c3..262ea70539a 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view.cc
@@ -10,6 +10,7 @@
#include <algorithm>
#include <memory>
#include <numeric>
+#include <utility>
#include "base/containers/adapters.h"
#include "base/i18n/case_conversion.h"
@@ -45,12 +46,48 @@
#include "ui/views/style/typography.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view_class_properties.h"
+#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
+// Difference in the font size (in pixels) between menu label font and "new"
+// badge font size.
+constexpr int kNewBadgeFontSizeAdjustment = -1;
+
+// Space between primary text and "new" badge.
+constexpr int kNewBadgeHorizontalMargin = 8;
+
+// Highlight size around "new" badge.
+constexpr gfx::Insets kNewBadgeInternalPadding{4};
+
+// The corner radius of the rounded rect for the "new" badge.
+constexpr int kNewBadgeCornerRadius = 3;
+static_assert(kNewBadgeCornerRadius <= kNewBadgeInternalPadding.left(),
+ "New badge corner radius should not exceed padding.");
+
+// Returns the horizontal space required for the "new" badge.
+int GetNewBadgeRequiredWidth(const gfx::FontList& primary_font) {
+ const base::string16 new_text =
+ l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
+ gfx::FontList badge_font =
+ primary_font.DeriveWithSizeDelta(kNewBadgeFontSizeAdjustment);
+ return gfx::GetStringWidth(new_text, badge_font) +
+ kNewBadgeInternalPadding.width() + 2 * kNewBadgeHorizontalMargin;
+}
+
+// Returns the highlight rect for the "new" badge given the font and text rect
+// for the badge text.
+gfx::Rect GetNewBadgeRectOutsetAroundText(const gfx::FontList& badge_font,
+ const gfx::Rect& badge_text_rect) {
+ gfx::Rect badge_rect = badge_text_rect;
+ badge_rect.Inset(
+ -gfx::AdjustVisualBorderForFont(badge_font, kNewBadgeInternalPadding));
+ return badge_rect;
+}
+
// EmptyMenuMenuItem ---------------------------------------------------------
// EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem
@@ -261,6 +298,7 @@ MenuItemView* MenuItemView::AddMenuItemAt(
int index,
int item_id,
const base::string16& label,
+ const base::string16& secondary_label,
const base::string16& minor_text,
const ui::ThemedVectorIcon& minor_icon,
const gfx::ImageSkia& icon,
@@ -282,6 +320,7 @@ MenuItemView* MenuItemView::AddMenuItemAt(
item->SetTitle(GetDelegate()->GetLabel(item_id));
else
item->SetTitle(label);
+ item->SetSecondaryTitle(secondary_label);
item->SetMinorText(minor_text);
item->SetMinorIcon(minor_icon);
if (!vector_icon.empty()) {
@@ -337,6 +376,7 @@ void MenuItemView::AppendSeparator() {
void MenuItemView::AddSeparatorAt(int index) {
AddMenuItemAt(index, /*item_id=*/0, /*label=*/base::string16(),
+ /*secondary_label=*/base::string16(),
/*minor_text=*/base::string16(),
/*minor_icon=*/ui::ThemedVectorIcon(),
/*icon=*/gfx::ImageSkia(),
@@ -351,8 +391,8 @@ MenuItemView* MenuItemView::AppendMenuItemImpl(int item_id,
Type type) {
const int index = submenu_ ? int{submenu_->children().size()} : 0;
return AddMenuItemAt(index, item_id, label, base::string16(),
- ui::ThemedVectorIcon(), icon, ui::ThemedVectorIcon(),
- type, ui::NORMAL_SEPARATOR);
+ base::string16(), ui::ThemedVectorIcon(), icon,
+ ui::ThemedVectorIcon(), type, ui::NORMAL_SEPARATOR);
}
SubmenuView* MenuItemView::CreateSubmenu() {
@@ -383,6 +423,11 @@ void MenuItemView::SetTitle(const base::string16& title) {
invalidate_dimensions(); // Triggers preferred size recalculation.
}
+void MenuItemView::SetSecondaryTitle(const base::string16& secondary_title) {
+ secondary_title_ = secondary_title;
+ invalidate_dimensions(); // Triggers preferred size recalculation.
+}
+
void MenuItemView::SetMinorText(const base::string16& minor_text) {
minor_text_ = minor_text;
invalidate_dimensions(); // Triggers preferred size recalculation.
@@ -438,19 +483,16 @@ void MenuItemView::SetIcon(const gfx::ImageSkia& icon) {
void MenuItemView::SetIcon(const ui::ThemedVectorIcon& icon) {
vector_icon_ = icon;
+ UpdateIconViewFromVectorIconAndTheme();
}
void MenuItemView::UpdateIconViewFromVectorIconAndTheme() {
if (vector_icon_.empty())
return;
- if (!icon_view_)
- SetIconView(std::make_unique<ImageView>());
-
- const bool use_touchable_layout =
- GetMenuController() && GetMenuController()->use_touchable_layout();
- const int icon_size = use_touchable_layout ? 20 : 16;
- icon_view_->SetImage(vector_icon_.GetImageSkia(GetNativeTheme(), icon_size));
+ auto icon_view = std::make_unique<ImageView>();
+ icon_view->SetImage(vector_icon_.GetImageSkia(GetNativeTheme()));
+ SetIconView(std::move(icon_view));
}
void MenuItemView::SetIconView(std::unique_ptr<ImageView> icon_view) {
@@ -740,14 +782,17 @@ void MenuItemView::UpdateMenuPartSizes() {
icon_area_width_;
int padding = 0;
if (config.always_use_icon_to_label_padding) {
- padding = config.item_horizontal_padding;
+ padding = LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
} else if (!config.icons_in_label) {
padding = (has_icons_ || HasChecksOrRadioButtons())
- ? config.item_horizontal_padding
+ ? LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL)
: 0;
}
if (use_touchable_layout)
- padding = config.touchable_item_horizontal_padding;
+ padding = LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
label_start_ += padding;
@@ -904,17 +949,22 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
// selected.
PaintBackground(canvas, mode, render_selection);
- const int top_margin = GetTopMargin();
- const int bottom_margin = GetBottomMargin();
- const int available_height = height() - top_margin - bottom_margin;
-
// Calculate some colors.
MenuDelegate::LabelStyle style;
- style.foreground = GetTextColor(false, render_selection);
+ style.foreground = GetTextColor(/*minor=*/false, render_selection);
GetLabelStyle(&style);
SkColor icon_color = color_utils::DeriveDefaultIconColor(style.foreground);
+ // Calculate the margins.
+ int top_margin = GetTopMargin();
+ const int bottom_margin = GetBottomMargin();
+ const int available_height = height() - top_margin - bottom_margin;
+ const int text_height = style.font_list.GetHeight();
+ const int total_text_height =
+ secondary_title().empty() ? text_height : text_height * 2;
+ top_margin += (available_height - total_text_height) / 2;
+
// Render the check.
if (type_ == Type::kCheckbox && delegate->IsItemChecked(GetCommand())) {
radio_check_image_view_->SetImage(GetMenuCheckImage(icon_color));
@@ -937,15 +987,33 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
(!delegate || delegate->ShouldReserveSpaceForSubmenuIndicator()
? item_right_margin_
: config.arrow_to_edge_padding);
- gfx::Rect text_bounds(label_start, top_margin, width, available_height);
+ gfx::Rect text_bounds(label_start, top_margin, width, text_height);
text_bounds.set_x(GetMirroredXForRect(text_bounds));
int flags = GetDrawStringFlags();
if (mode == PaintButtonMode::kForDrag)
flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
canvas->DrawStringRectWithFlags(title(), style.font_list, style.foreground,
text_bounds, flags);
+
+ // The rest should be drawn with the minor foreground color.
+ style.foreground = GetTextColor(/*minor=*/true, render_selection);
+ if (!secondary_title().empty()) {
+ text_bounds.set_y(text_bounds.y() + text_height);
+ canvas->DrawStringRectWithFlags(secondary_title(), style.font_list,
+ style.foreground, text_bounds, flags);
+ }
+
PaintMinorIconAndText(canvas, style);
+ if (ShouldShowNewBadge()) {
+ DrawNewBadge(
+ canvas,
+ gfx::Point(label_start + gfx::GetStringWidth(title(), style.font_list) +
+ kNewBadgeHorizontalMargin,
+ top_margin),
+ style.font_list, flags);
+ }
+
// Set the submenu indicator (arrow) image and color.
if (HasSubmenu())
submenu_arrow_image_view_->SetImage(GetSubmenuArrowImage(icon_color));
@@ -1198,12 +1266,19 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
dimensions.standard_width = string_width + label_start + item_right_margin_;
// Determine the length of the right-side text.
dimensions.minor_text_width =
- minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, style.font_list);
+ (minor_text.empty() ? 0
+ : gfx::GetStringWidth(minor_text, style.font_list));
+
+ if (ShouldShowNewBadge())
+ dimensions.minor_text_width += GetNewBadgeRequiredWidth(style.font_list);
// Determine the height to use.
+ int label_text_height = secondary_title().empty()
+ ? style.font_list.GetHeight()
+ : style.font_list.GetHeight() * 2;
dimensions.height =
- std::max(dimensions.height, style.font_list.GetHeight() +
- GetBottomMargin() + GetTopMargin());
+ std::max(dimensions.height,
+ label_text_height + GetBottomMargin() + GetTopMargin());
dimensions.height =
std::max(dimensions.height, MenuConfig::instance().item_min_height);
@@ -1246,12 +1321,48 @@ int MenuItemView::GetLabelStartForThisItem() const {
if ((config.icons_in_label || type_ == Type::kCheckbox ||
type_ == Type::kRadio) &&
icon_view_) {
- label_start += icon_view_->size().width() + config.item_horizontal_padding;
+ label_start +=
+ icon_view_->size().width() + LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
}
return label_start;
}
+void MenuItemView::DrawNewBadge(gfx::Canvas* canvas,
+ const gfx::Point& unmirrored_badge_start,
+ const gfx::FontList& primary_font,
+ int text_render_flags) {
+ gfx::FontList badge_font =
+ primary_font.DeriveWithSizeDelta(kNewBadgeFontSizeAdjustment);
+ const base::string16 new_text =
+ l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
+
+ // Calculate bounding box for badge text.
+ gfx::Rect badge_text_bounds(unmirrored_badge_start,
+ gfx::GetStringSize(new_text, badge_font));
+ badge_text_bounds.Offset(
+ kNewBadgeInternalPadding.left(),
+ gfx::GetFontCapHeightCenterOffset(primary_font, badge_font));
+ if (base::i18n::IsRTL())
+ badge_text_bounds.set_x(GetMirroredXForRect(badge_text_bounds));
+
+ // Render the badge itself.
+ cc::PaintFlags new_flags;
+ const SkColor background_color = GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_ProminentButtonColor);
+ new_flags.setColor(background_color);
+ canvas->DrawRoundRect(
+ GetNewBadgeRectOutsetAroundText(badge_font, badge_text_bounds),
+ kNewBadgeCornerRadius, new_flags);
+
+ // Render the badge text.
+ const SkColor foreground_color = GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_TextOnProminentButtonColor);
+ canvas->DrawStringRectWithFlags(new_text, badge_font, foreground_color,
+ badge_text_bounds, text_render_flags);
+}
+
base::string16 MenuItemView::GetMinorText() const {
if (GetID() == kEmptyMenuItemViewID) {
// Don't query the delegate for menus that represent no children.
@@ -1329,6 +1440,12 @@ bool MenuItemView::HasChecksOrRadioButtons() const {
[](const auto* item) { return item->HasChecksOrRadioButtons(); });
}
+bool MenuItemView::ShouldShowNewBadge() const {
+ static const bool feature_enabled =
+ base::FeatureList::IsEnabled(features::kEnableNewBadgeOnMenuItems);
+ return feature_enabled && is_new_;
+}
+
BEGIN_METADATA(MenuItemView)
METADATA_PARENT_CLASS(View)
END_METADATA()
diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h
index b38a048c22e..01b797549ac 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.h
+++ b/chromium/ui/views/controls/menu/menu_item_view.h
@@ -5,11 +5,11 @@
#ifndef UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
#define UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
+#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
-#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
@@ -148,6 +148,7 @@ class VIEWS_EXPORT MenuItemView : public View {
MenuItemView* AddMenuItemAt(int index,
int item_id,
const base::string16& label,
+ const base::string16& secondary_label,
const base::string16& minor_text,
const ui::ThemedVectorIcon& minor_icon,
const gfx::ImageSkia& icon,
@@ -214,6 +215,11 @@ class VIEWS_EXPORT MenuItemView : public View {
void SetTitle(const base::string16& title);
const base::string16& title() const { return title_; }
+ // Sets/Gets the secondary title. When not empty, they are shown in the line
+ // below the title.
+ void SetSecondaryTitle(const base::string16& secondary_title);
+ const base::string16& secondary_title() const { return secondary_title_; }
+
// Sets the minor text.
void SetMinorText(const base::string16& minor_text);
@@ -264,6 +270,9 @@ class VIEWS_EXPORT MenuItemView : public View {
// Returns the command id of this item.
int GetCommand() const { return command_; }
+ void set_is_new(bool is_new) { is_new_ = is_new; }
+ bool is_new() const { return is_new_; }
+
// Paints the menu item.
void OnPaint(gfx::Canvas* canvas) override;
@@ -444,6 +453,13 @@ class VIEWS_EXPORT MenuItemView : public View {
// Get the horizontal position at which to draw the menu item's label.
int GetLabelStartForThisItem() const;
+ // Draws the "new" badge on |canvas|. |unmirrored_badge_start| is the
+ // upper-left corner of the badge, not mirrored for RTL.
+ void DrawNewBadge(gfx::Canvas* canvas,
+ const gfx::Point& unmirrored_badge_start,
+ const gfx::FontList& primary_font,
+ int text_render_flags);
+
// Used by MenuController to cache the menu position in use by the
// active menu.
MenuPosition actual_menu_position() const { return actual_menu_position_; }
@@ -475,6 +491,10 @@ class VIEWS_EXPORT MenuItemView : public View {
// Returns true if the menu has items with a checkbox or a radio button.
bool HasChecksOrRadioButtons() const;
+ // Returns whether or not a "new" badge should be shown on this menu item.
+ // Takes into account whether the badging feature is enabled.
+ bool ShouldShowNewBadge() const;
+
void invalidate_dimensions() { dimensions_.height = 0; }
bool is_dimensions_valid() const { return dimensions_.height > 0; }
@@ -505,16 +525,16 @@ class VIEWS_EXPORT MenuItemView : public View {
// Command id.
int command_ = 0;
+ // Whether the menu item should be badged as "New" (if badging is enabled) as
+ // a way to highlight a new feature for users.
+ bool is_new_ = false;
+
// Submenu, created via CreateSubmenu.
SubmenuView* submenu_ = nullptr;
- // Title.
base::string16 title_;
-
- // Minor text.
+ base::string16 secondary_title_;
base::string16 minor_text_;
-
- // Minor icon.
ui::ThemedVectorIcon minor_icon_;
// The icon used for |icon_view_| when a vector icon has been set instead of a
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 119aa4a49c3..9c132d932b8 100644
--- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
@@ -320,20 +320,37 @@ class MenuItemViewPaintUnitTest : public ViewsTestBase {
DISALLOW_COPY_AND_ASSIGN(MenuItemViewPaintUnitTest);
};
-// Provides assertion coverage for painting minor text and icons.
+// Provides assertion coverage for painting, secondary label, minor text and
+// icons.
TEST_F(MenuItemViewPaintUnitTest, MinorTextAndIconAssertionCoverage) {
- auto AddItem = [this](auto label, auto minor_label, auto minor_icon) {
+ auto AddItem = [this](auto label, auto secondary_label, auto minor_label,
+ auto minor_icon) {
menu_item_view()->AddMenuItemAt(
- 0, 1000, base::ASCIIToUTF16(label), minor_label, minor_icon,
- gfx::ImageSkia(), ui::ThemedVectorIcon(),
+ 0, 1000, base::ASCIIToUTF16(label), secondary_label, minor_label,
+ minor_icon, gfx::ImageSkia(), ui::ThemedVectorIcon(),
views::MenuItemView::Type::kNormal, ui::NORMAL_SEPARATOR);
};
- AddItem("No minor content", base::string16(), ui::ThemedVectorIcon());
- AddItem("Minor text only", base::ASCIIToUTF16("minor text"),
+ AddItem("No secondary label, no minor content", base::string16(),
+ base::string16(), ui::ThemedVectorIcon());
+ AddItem("No secondary label, minor text only", base::string16(),
+ base::ASCIIToUTF16("minor text"), ui::ThemedVectorIcon());
+ AddItem("No secondary label, minor icon only", base::string16(),
+ base::string16(), ui::ThemedVectorIcon(&views::kMenuCheckIcon));
+ AddItem("No secondary label, minor text and icon", base::string16(),
+ base::ASCIIToUTF16("minor text"),
+ ui::ThemedVectorIcon(&views::kMenuCheckIcon));
+ AddItem("Secondary label, no minor content",
+ base::ASCIIToUTF16("secondary label"), base::string16(),
ui::ThemedVectorIcon());
- AddItem("Minor icon only", base::string16(),
+ AddItem("Secondary label, minor text only",
+ base::ASCIIToUTF16("secondary label"),
+ base::ASCIIToUTF16("minor text"), ui::ThemedVectorIcon());
+ AddItem("Secondary label, minor icon only",
+ base::ASCIIToUTF16("secondary label"), base::string16(),
ui::ThemedVectorIcon(&views::kMenuCheckIcon));
- AddItem("Minor text and icon", base::ASCIIToUTF16("minor text"),
+ AddItem("Secondary label, minor text and icon",
+ base::ASCIIToUTF16("secondary label"),
+ base::ASCIIToUTF16("minor text"),
ui::ThemedVectorIcon(&views::kMenuCheckIcon));
menu_runner()->RunMenuAt(widget(), nullptr, gfx::Rect(),
diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc
index 79af9195b11..b0b1cf9a7a7 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc
@@ -99,16 +99,17 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
}
if (*type == MenuItemView::Type::kSeparator) {
- return menu->AddMenuItemAt(menu_index, item_id, base::string16(),
- base::string16(), ui::ThemedVectorIcon(),
- gfx::ImageSkia(), ui::ThemedVectorIcon(), *type,
- model->GetSeparatorTypeAt(model_index));
+ return menu->AddMenuItemAt(
+ menu_index, item_id, base::string16(), base::string16(),
+ base::string16(), ui::ThemedVectorIcon(), gfx::ImageSkia(),
+ ui::ThemedVectorIcon(), *type, model->GetSeparatorTypeAt(model_index));
}
ui::ImageModel icon = model->GetIconAt(model_index);
ui::ImageModel minor_icon = model->GetMinorIconAt(model_index);
- return menu->AddMenuItemAt(
+ auto* menu_item_view = menu->AddMenuItemAt(
menu_index, item_id, model->GetLabelAt(model_index),
+ model->GetSecondaryLabelAt(model_index),
model->GetMinorTextAt(model_index),
minor_icon.IsVectorIcon()
? ui::ThemedVectorIcon(minor_icon.GetVectorIcon())
@@ -117,6 +118,12 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
icon.IsVectorIcon() ? ui::ThemedVectorIcon(icon.GetVectorIcon())
: ui::ThemedVectorIcon(),
*type, ui::NORMAL_SEPARATOR);
+
+ if (model->IsAlertedAt(model_index))
+ menu_item_view->SetAlerted();
+ menu_item_view->set_is_new(model->IsNewFeatureAt(model_index));
+
+ return menu_item_view;
}
// Static.
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 6241a9d7916..111b070d725 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
@@ -78,6 +78,12 @@ class MenuModelBase : public ui::MenuModel {
bool IsVisibleAt(int index) const override { return items_[index].visible; }
+ bool IsAlertedAt(int index) const override { return items_[index].alerted; }
+
+ bool IsNewFeatureAt(int index) const override {
+ return items_[index].new_feature;
+ }
+
MenuModel* GetSubmenuModelAt(int index) const override {
return items_[index].submenu;
}
@@ -117,6 +123,8 @@ class MenuModelBase : public ui::MenuModel {
ui::MenuModel* submenu;
bool enabled;
bool visible;
+ bool alerted = false;
+ bool new_feature = false;
};
const Item& GetItemDefinition(size_t index) { return items_[index]; }
@@ -142,6 +150,7 @@ class SubmenuModel : public MenuModelBase {
SubmenuModel() : MenuModelBase(kSubmenuIdBase) {
items_.emplace_back(TYPE_COMMAND, "submenu item 0", nullptr, false, true);
items_.emplace_back(TYPE_COMMAND, "submenu item 1", nullptr);
+ items_[1].alerted = true;
}
~SubmenuModel() override = default;
@@ -155,6 +164,7 @@ class ActionableSubmenuModel : public MenuModelBase {
ActionableSubmenuModel() : MenuModelBase(kActionableSubmenuIdBase) {
items_.emplace_back(TYPE_COMMAND, "actionable submenu item 0", nullptr);
items_.emplace_back(TYPE_COMMAND, "actionable submenu item 1", nullptr);
+ items_[1].new_feature = true;
}
~ActionableSubmenuModel() override = default;
@@ -246,6 +256,12 @@ void CheckSubmenu(const RootModel& model,
// Check visibility.
EXPECT_EQ(model_item.visible, item->GetVisible());
+ // Check alert state.
+ EXPECT_EQ(model_item.alerted, item->is_alerted());
+
+ // Check new feature flag.
+ EXPECT_EQ(model_item.new_feature, item->is_new());
+
// Check activation.
static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id);
EXPECT_EQ(i, size_t{submodel->last_activation()});
@@ -324,6 +340,12 @@ TEST_F(MenuModelAdapterTest, BasicTest) {
// Check visibility.
EXPECT_EQ(model_item.visible, item->GetVisible());
+ // Check alert state.
+ EXPECT_EQ(model_item.alerted, item->is_alerted());
+
+ // Check new feature flag.
+ EXPECT_EQ(model_item.new_feature, item->is_new());
+
// Check activation.
static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
EXPECT_EQ(i, size_t{model.last_activation()});
diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc
index 413a1de18f9..28a9c908988 100644
--- a/chromium/ui/views/controls/menu/submenu_view.cc
+++ b/chromium/ui/views/controls/menu/submenu_view.cc
@@ -16,6 +16,7 @@
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
+#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_host.h"
@@ -396,8 +397,12 @@ void SubmenuView::ShowAt(Widget* parent,
host_->InitMenuHost(parent, bounds, scroll_view_container_, do_capture);
}
- GetScrollViewContainer()->NotifyAccessibilityEvent(
- ax::mojom::Event::kMenuStart, true);
+ // Only fire kMenuStart for the top level menu, not for each submenu.
+ if (!GetMenuItem()->GetParentMenuItem()) {
+ GetScrollViewContainer()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kMenuStart, true);
+ }
+ // Fire kMenuPopupStart for each menu/submenu that is shown.
NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupStart, true);
}
@@ -408,14 +413,6 @@ void SubmenuView::Reposition(const gfx::Rect& bounds) {
void SubmenuView::Close() {
if (host_) {
- // We send the event to the ScrollViewContainer first because the View
- // accessibility delegate sets up a focus override when receiving the
- // kMenuStart event that we want to be disabled when we send the
- // kMenuPopupEnd event in order to access the previously focused node.
- GetScrollViewContainer()->NotifyAccessibilityEvent(
- ax::mojom::Event::kMenuEnd, true);
- NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupEnd, true);
-
host_->DestroyMenuHost();
host_ = nullptr;
}
@@ -423,8 +420,22 @@ void SubmenuView::Close() {
void SubmenuView::Hide() {
if (host_) {
+ /// -- Fire accessibility events ----
+ // Both of these must be fired before HideMenuHost().
+ // Only fire kMenuStart for as top levels menu closes, not for each submenu.
+ // This is sent before kMenuPopupEnd to allow ViewAXPlatformNodeDelegate to
+ // remove its focus override before AXPlatformNodeAuraLinux needs to access
+ // the previously-focused node while handling kMenuPopupEnd.
+ if (!GetMenuItem()->GetParentMenuItem()) {
+ GetScrollViewContainer()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kMenuEnd, true);
+ GetViewAccessibility().EndPopupFocusOverride();
+ }
+ // Fire these kMenuPopupEnd for each menu/submenu that closes/hides.
+ if (host_->IsVisible())
+ NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupEnd, true);
+
host_->HideMenuHost();
- NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupHide, true);
}
if (scroll_animator_->is_scrolling())
diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc
index 704855a3897..aa0d2fe8423 100644
--- a/chromium/ui/views/controls/prefix_selector.cc
+++ b/chromium/ui/views/controls/prefix_selector.cc
@@ -171,6 +171,15 @@ bool PrefixSelector::SetCompositionFromExistingText(
}
#endif
+#if defined(OS_CHROMEOS)
+bool PrefixSelector::SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) {
+ // TODO(crbug.com/1091088) Implement setAutocorrectRange.
+ NOTIMPLEMENTED_LOG_ONCE();
+ return false;
+}
+#endif
+
#if defined(OS_WIN)
void PrefixSelector::SetActiveCompositionForAccessibility(
const gfx::Range& range,
diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h
index d61f6cf2ed8..12dffcc111b 100644
--- a/chromium/ui/views/controls/prefix_selector.h
+++ b/chromium/ui/views/controls/prefix_selector.h
@@ -82,6 +82,11 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
#endif
+#if defined(OS_CHROMEOS)
+ bool SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) override;
+#endif
+
#if defined(OS_WIN)
void GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
diff --git a/chromium/ui/views/controls/scroll_view.h b/chromium/ui/views/controls/scroll_view.h
index 6df7bca1027..54b1e11e5ce 100644
--- a/chromium/ui/views/controls/scroll_view.h
+++ b/chromium/ui/views/controls/scroll_view.h
@@ -278,7 +278,7 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
const bool scroll_with_layers_enabled_;
// The focus ring for this ScrollView.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(ScrollView);
};
diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h
index 68e804b5d06..4f102e97a09 100644
--- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h
+++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h
@@ -11,7 +11,6 @@
#import "components/remote_cocoa/app_shim/views_scrollbar_bridge.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/gfx/animation/slide_animation.h"
-#include "ui/gfx/mac/cocoa_scrollbar_painter.h"
#include "ui/views/controls/scrollbar/scroll_bar.h"
#include "ui/views/views_export.h"
@@ -63,7 +62,7 @@ class VIEWS_EXPORT CocoaScrollBar : public ScrollBar,
bool IsScrollbarFullyHidden() const;
// Get the parameters for painting.
- gfx::CocoaScrollbarPainter::Params GetPainterParams() const;
+ ui::NativeTheme::ExtraParams GetPainterParams() const;
protected:
// ScrollBar:
diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
index 1b4d5a5d129..05960d9ad3e 100644
--- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
+++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
@@ -15,8 +15,6 @@
#include "ui/gfx/canvas.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
-using gfx::CocoaScrollbarPainter;
-
namespace views {
namespace {
@@ -100,9 +98,14 @@ gfx::Size CocoaScrollBarThumb::CalculatePreferredSize() const {
void CocoaScrollBarThumb::OnPaint(gfx::Canvas* canvas) {
auto params = cocoa_scroll_bar_->GetPainterParams();
// Set the hover state based only on the thumb.
- params.hovered = IsStateHovered() || IsStatePressed();
- CocoaScrollbarPainter::PaintThumb(
- canvas->sk_canvas(), gfx::RectToSkIRect(GetLocalBounds()), params);
+ params.scrollbar_extra.is_hovering = IsStateHovered() || IsStatePressed();
+ ui::NativeTheme::Part thumb_part =
+ params.scrollbar_extra.orientation ==
+ ui::NativeTheme::ScrollbarOrientation::kHorizontal
+ ? ui::NativeTheme::kScrollbarHorizontalThumb
+ : ui::NativeTheme::kScrollbarVerticalThumb;
+ GetNativeTheme()->Paint(canvas->sk_canvas(), thumb_part,
+ ui::NativeTheme::kNormal, GetLocalBounds(), params);
}
bool CocoaScrollBarThumb::OnMousePressed(const ui::MouseEvent& event) {
@@ -208,9 +211,14 @@ void CocoaScrollBar::OnPaint(gfx::Canvas* canvas) {
auto params = GetPainterParams();
// Transparency of the track is handled by the View opacity, so always draw
// using the non-overlay path.
- params.overlay = false;
- CocoaScrollbarPainter::PaintTrack(
- canvas->sk_canvas(), gfx::RectToSkIRect(GetLocalBounds()), params);
+ params.scrollbar_extra.is_overlay = false;
+ ui::NativeTheme::Part track_part =
+ params.scrollbar_extra.orientation ==
+ ui::NativeTheme::ScrollbarOrientation::kHorizontal
+ ? ui::NativeTheme::kScrollbarHorizontalTrack
+ : ui::NativeTheme::kScrollbarVerticalTrack;
+ GetNativeTheme()->Paint(canvas->sk_canvas(), track_part,
+ ui::NativeTheme::kNormal, GetLocalBounds(), params);
}
bool CocoaScrollBar::CanProcessEventsWithinSubtree() const {
@@ -400,16 +408,20 @@ bool CocoaScrollBar::IsScrollbarFullyHidden() const {
return layer()->opacity() == 0.0f;
}
-CocoaScrollbarPainter::Params CocoaScrollBar::GetPainterParams() const {
- CocoaScrollbarPainter::Params params;
- if (IsHorizontal())
- params.orientation = CocoaScrollbarPainter::Orientation::kHorizontal;
- else if (base::i18n::IsRTL())
- params.orientation = CocoaScrollbarPainter::Orientation::kVerticalOnLeft;
- else
- params.orientation = CocoaScrollbarPainter::Orientation::kVerticalOnRight;
- params.overlay = GetScrollerStyle() == NSScrollerStyleOverlay;
- params.dark_mode = GetNativeTheme()->ShouldUseDarkColors();
+ui::NativeTheme::ExtraParams CocoaScrollBar::GetPainterParams() const {
+ ui::NativeTheme::ExtraParams params;
+ if (IsHorizontal()) {
+ params.scrollbar_extra.orientation =
+ ui::NativeTheme::ScrollbarOrientation::kHorizontal;
+ } else if (base::i18n::IsRTL()) {
+ params.scrollbar_extra.orientation =
+ ui::NativeTheme::ScrollbarOrientation::kVerticalOnLeft;
+ } else {
+ params.scrollbar_extra.orientation =
+ ui::NativeTheme::ScrollbarOrientation::kVerticalOnRight;
+ }
+ params.scrollbar_extra.is_overlay =
+ GetScrollerStyle() == NSScrollerStyleOverlay;
return params;
}
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 4fdef37e512..50ecb7c042b 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -510,6 +510,11 @@ TabbedPane::TabbedPane(TabbedPane::Orientation orientation,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded));
contents_->SetLayoutManager(std::make_unique<views::FillLayout>());
+
+ // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
+ AddAccelerator(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
+ AddAccelerator(ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN));
}
TabbedPane::~TabbedPane() = default;
@@ -600,16 +605,6 @@ bool TabbedPane::MoveSelectionBy(int delta) {
return true;
}
-void TabbedPane::ViewHierarchyChanged(
- const ViewHierarchyChangedDetails& details) {
- if (details.is_add) {
- // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
- AddAccelerator(
- ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
- AddAccelerator(ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN));
- }
-}
-
bool TabbedPane::AcceleratorPressed(const ui::Accelerator& accelerator) {
// Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages.
DCHECK(accelerator.key_code() == ui::VKEY_TAB && accelerator.IsCtrlDown());
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h
index 4c42c238ecb..97553af580f 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h
@@ -123,8 +123,6 @@ class VIEWS_EXPORT TabbedPane : public View {
bool MoveSelectionBy(int delta);
// Overridden from View:
- void ViewHierarchyChanged(
- const ViewHierarchyChangedDetails& details) override;
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
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 808afa4d777..6bb23566546 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
@@ -121,7 +121,7 @@ class TabbedPaneWithWidgetTest : public ViewsTestBase {
params.bounds = gfx::Rect(0, 0, 650, 650);
widget_->Init(std::move(params));
tabbed_pane_ = tabbed_pane.get();
- widget_->SetContentsView(tabbed_pane.release());
+ widget_->SetContentsView(std::move(tabbed_pane));
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc
index 94eb7a0527c..fa791c4700c 100644
--- a/chromium/ui/views/controls/table/table_view.cc
+++ b/chromium/ui/views/controls/table/table_view.cc
@@ -183,6 +183,8 @@ TableView::TableView(ui::TableModel* model,
SetModel(model);
if (model_)
UpdateVirtualAccessibilityChildren();
+
+ focus_ring_ = FocusRing::Install(this);
}
TableView::~TableView() {
diff --git a/chromium/ui/views/controls/table/table_view.h b/chromium/ui/views/controls/table/table_view.h
index 1a640fdf53e..30f1b107602 100644
--- a/chromium/ui/views/controls/table/table_view.h
+++ b/chromium/ui/views/controls/table/table_view.h
@@ -13,7 +13,6 @@
#include "ui/base/models/table_model.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/gfx/font_list.h"
-#include "ui/views/controls/focus_ring.h"
#include "ui/views/view.h"
#include "ui/views/views_export.h"
@@ -396,7 +395,7 @@ class VIEWS_EXPORT TableView : public views::View,
int active_visible_column_index_ = -1;
// Used to draw a focus indicator around the active cell.
- std::unique_ptr<FocusRing> focus_ring_ = FocusRing::Install(this);
+ FocusRing* focus_ring_ = nullptr;
// The header. This is only created if more than one column is specified or
// the first column has a non-empty title.
diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc
index d8e8fd0ace0..d0c5e12a9d1 100644
--- a/chromium/ui/views/controls/textfield/textfield.cc
+++ b/chromium/ui/views/controls/textfield/textfield.cc
@@ -65,8 +65,8 @@
#endif
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
-#include "ui/base/ime/linux/text_edit_command_auralinux.h"
-#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
+#include "ui/base/ime/linux/text_edit_command_auralinux.h" // nogncheck
+#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h" // nogncheck
#endif
#if defined(USE_X11)
@@ -330,6 +330,12 @@ Textfield::Textfield()
AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
AddAccelerator(ui::Accelerator(ui::VKEY_V, ui::EF_CONTROL_DOWN));
#endif
+
+ // Sometimes there are additional ignored views, such as the View representing
+ // the cursor, inside the text field. These should always be ignored by
+ // accessibility since a plain text field should always be a leaf node in the
+ // accessibility trees of all the platforms we support.
+ GetViewAccessibility().OverrideIsLeaf(true);
}
Textfield::~Textfield() {
@@ -677,8 +683,10 @@ gfx::Size Textfield::GetMinimumSize() const {
void Textfield::SetBorder(std::unique_ptr<Border> b) {
use_focus_ring_ = false;
- if (focus_ring_)
- focus_ring_.reset();
+ if (focus_ring_) {
+ RemoveChildViewT(focus_ring_);
+ focus_ring_ = nullptr;
+ }
View::SetBorder(std::move(b));
}
@@ -1025,6 +1033,7 @@ void Textfield::OnDragDone() {
void Textfield::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kTextField;
+
if (label_ax_id_) {
node_data->AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
{label_ax_id_});
@@ -1427,7 +1436,7 @@ bool Textfield::GetAcceleratorForCommandId(int command_id,
void Textfield::ExecuteCommand(int command_id, int event_flags) {
if (text_services_context_menu_ &&
text_services_context_menu_->SupportsCommand(command_id)) {
- text_services_context_menu_->ExecuteCommand(command_id);
+ text_services_context_menu_->ExecuteCommand(command_id, event_flags);
return;
}
@@ -1786,8 +1795,11 @@ ukm::SourceId Textfield::GetClientSourceForMetrics() const {
}
bool Textfield::ShouldDoLearning() {
- // TODO(https://crbug.com/311180): Implement this method.
- NOTIMPLEMENTED_LOG_ONCE();
+ if (should_do_learning_.has_value())
+ return should_do_learning_.value();
+
+ NOTIMPLEMENTED_LOG_ONCE() << "A Textfield does not support ShouldDoLearning";
+ DVLOG(1) << "This Textfield instance does not support ShouldDoLearning";
return false;
}
@@ -1806,6 +1818,14 @@ bool Textfield::SetCompositionFromExistingText(
}
#endif
+#if defined(OS_CHROMEOS)
+bool Textfield::SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) {
+ // TODO(crbug.com/1091088) Implement autocorrect range textfield handling.
+ return false;
+}
+#endif
+
#if defined(OS_WIN)
void Textfield::GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h
index 825c45513cf..775ce4f9683 100644
--- a/chromium/ui/views/controls/textfield/textfield.h
+++ b/chromium/ui/views/controls/textfield/textfield.h
@@ -377,12 +377,20 @@ class VIEWS_EXPORT Textfield : public View,
ukm::SourceId GetClientSourceForMetrics() const override;
bool ShouldDoLearning() override;
+ // Set whether the text should be used to improve typing suggestions.
+ void SetShouldDoLearning(bool value) { should_do_learning_ = value; }
+
#if defined(OS_WIN) || defined(OS_CHROMEOS)
bool SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
#endif
+#if defined(OS_CHROMEOS)
+ bool SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) override;
+#endif
+
#if defined(OS_WIN)
void GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
@@ -651,6 +659,9 @@ class VIEWS_EXPORT Textfield : public View,
// True if this textfield should use a focus ring to indicate focus.
bool use_focus_ring_ = true;
+ // Whether the text should be used to improve typing suggestions.
+ base::Optional<bool> should_do_learning_;
+
// Context menu related members.
std::unique_ptr<ui::SimpleMenuModel> context_menu_contents_;
std::unique_ptr<ViewsTextServicesContextMenu> text_services_context_menu_;
@@ -669,7 +680,7 @@ class VIEWS_EXPORT Textfield : public View,
ui::TextInputClient::FOCUS_REASON_NONE;
// The focus ring for this TextField.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
// The password char reveal index, for testing only.
int password_char_reveal_index_ = -1;
diff --git a/chromium/ui/views/controls/textfield/textfield_test_api.cc b/chromium/ui/views/controls/textfield/textfield_test_api.cc
index 099f22e9bcf..40176fa3a73 100644
--- a/chromium/ui/views/controls/textfield/textfield_test_api.cc
+++ b/chromium/ui/views/controls/textfield/textfield_test_api.cc
@@ -5,7 +5,6 @@
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/gfx/geometry/rect.h"
-#include "ui/views/controls/views_text_services_context_menu.h"
namespace views {
@@ -34,12 +33,6 @@ void TextfieldTestApi::SetCursorViewRect(gfx::Rect bounds) {
textfield_->cursor_view_->SetBoundsRect(bounds);
}
-bool TextfieldTestApi::IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection direction) const {
- return ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
- textfield_->text_services_context_menu_.get(), direction);
-}
-
bool TextfieldTestApi::ShouldShowCursor() const {
return textfield_->ShouldShowCursor();
}
diff --git a/chromium/ui/views/controls/textfield/textfield_test_api.h b/chromium/ui/views/controls/textfield/textfield_test_api.h
index 3f346da6059..78436bcb5a4 100644
--- a/chromium/ui/views/controls/textfield/textfield_test_api.h
+++ b/chromium/ui/views/controls/textfield/textfield_test_api.h
@@ -5,8 +5,6 @@
#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_TEST_API_H_
#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_TEST_API_H_
-#include "base/i18n/rtl.h"
-#include "base/macros.h"
#include "ui/views/controls/textfield/textfield.h"
namespace views {
@@ -15,6 +13,9 @@ namespace views {
class TextfieldTestApi {
public:
explicit TextfieldTestApi(Textfield* textfield);
+ TextfieldTestApi(const TextfieldTestApi&) = delete;
+ TextfieldTestApi& operator=(const TextfieldTestApi&) = delete;
+ ~TextfieldTestApi() = default;
void UpdateContextMenu();
@@ -53,15 +54,10 @@ class TextfieldTestApi {
return textfield_->cursor_view_->GetVisible();
}
- bool IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection direction) const;
-
bool ShouldShowCursor() const;
private:
Textfield* textfield_;
-
- DISALLOW_COPY_AND_ASSIGN(TextfieldTestApi);
};
} // namespace views
diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc
index 456cecaba14..5a25faebf58 100644
--- a/chromium/ui/views/controls/textfield/textfield_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc
@@ -64,7 +64,7 @@
#endif
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
-#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
+#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h" // nogncheck
#endif
#if defined(OS_CHROMEOS)
@@ -74,6 +74,7 @@
#if defined(OS_MACOSX)
#include "ui/base/cocoa/secure_password_input.h"
+#include "ui/base/cocoa/text_services_context_menu.h"
#endif
using base::ASCIIToUTF16;
@@ -459,8 +460,7 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
widget_->Init(std::move(params));
input_method_->SetDelegate(
test::WidgetTest::GetInputMethodDelegateForWidget(widget_));
- View* container = new View();
- widget_->SetContentsView(container);
+ View* container = widget_->SetContentsView(std::make_unique<View>());
container->AddChildView(textfield_);
textfield_->SetBoundsRect(params.bounds);
textfield_->SetID(1);
@@ -1395,6 +1395,17 @@ TEST_F(TextfieldTest, TextInputType_InsertionTest) {
textfield_->GetText());
}
+TEST_F(TextfieldTest, ShouldDoLearning) {
+ InitTextfield();
+
+ // Defaults to false.
+ EXPECT_EQ(false, textfield_->ShouldDoLearning());
+
+ // The value can be set.
+ textfield_->SetShouldDoLearning(true);
+ EXPECT_EQ(true, textfield_->ShouldDoLearning());
+}
+
TEST_F(TextfieldTest, TextInputType) {
InitTextfield();
@@ -3478,13 +3489,12 @@ TEST_F(TextfieldTest, TextfieldBoundsChangeTest) {
TEST_F(TextfieldTest, TextfieldInitialization) {
TestTextfield* new_textfield = new TestTextfield();
new_textfield->set_controller(this);
- View* container = new View();
Widget* widget(new Widget());
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(100, 100, 100, 100);
widget->Init(std::move(params));
- widget->SetContentsView(container);
+ View* container = widget->SetContentsView(std::make_unique<View>());
container->AddChildView(new_textfield);
new_textfield->SetBoundsRect(params.bounds);
@@ -3624,56 +3634,23 @@ TEST_F(TextfieldTest, TextServicesContextMenuTextDirectionTest) {
base::i18n::TextDirection::LEFT_TO_RIGHT);
test_api_->UpdateContextMenu();
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::UNKNOWN_DIRECTION));
- EXPECT_TRUE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::LEFT_TO_RIGHT));
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::RIGHT_TO_LEFT));
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionDefault));
+ EXPECT_TRUE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionLtr));
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionRtl));
textfield_->ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection::RIGHT_TO_LEFT);
test_api_->UpdateContextMenu();
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::UNKNOWN_DIRECTION));
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::LEFT_TO_RIGHT));
- EXPECT_TRUE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::RIGHT_TO_LEFT));
-}
-
-// Tests to see if the look up item is updated when the textfield's selected
-// text has changed.
-TEST_F(TextfieldTest, LookUpItemUpdate) {
- InitTextfield();
- EXPECT_TRUE(textfield_->context_menu_controller());
-
- const base::string16 kTextOne = ASCIIToUTF16("crake");
- textfield_->SetText(kTextOne);
- textfield_->SelectAll(false);
-
- ui::MenuModel* context_menu = GetContextMenuModel();
- EXPECT_TRUE(context_menu);
- EXPECT_GT(context_menu->GetItemCount(), 0);
- EXPECT_EQ(context_menu->GetLabelAt(0),
- l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kTextOne));
-
-#if !defined(OS_MACOSX)
- // Mac context menus don't behave this way: it's not possible to update the
- // text while the menu is still "open", but also the selection can't change
- // while the menu is open (because the user can't interact with the rest of
- // the app).
- const base::string16 kTextTwo = ASCIIToUTF16("rail");
- textfield_->SetText(kTextTwo);
- textfield_->SelectAll(false);
-
- context_menu = GetContextMenuModel();
- EXPECT_TRUE(context_menu);
- EXPECT_GT(context_menu->GetItemCount(), 0);
- EXPECT_EQ(context_menu->GetLabelAt(0),
- l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kTextTwo));
-#endif
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionDefault));
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionLtr));
+ EXPECT_TRUE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionRtl));
}
// Tests to see if the look up item is hidden for password fields.
diff --git a/chromium/ui/views/controls/tree/tree_view.cc b/chromium/ui/views/controls/tree/tree_view.cc
index bd84a3f38d7..69333ebe84c 100644
--- a/chromium/ui/views/controls/tree/tree_view.cc
+++ b/chromium/ui/views/controls/tree/tree_view.cc
@@ -139,6 +139,7 @@ void TreeView::SetModel(TreeModel* model) {
model_ = model;
selected_node_ = nullptr;
+ active_node_ = nullptr;
icons_.clear();
if (model_) {
model_->AddObserver(this);
@@ -154,18 +155,9 @@ void TreeView::SetModel(TreeModel* model) {
root_.set_is_expanded(true);
if (root_shown_)
- selected_node_ = &root_;
+ SetSelectedNode(root_.model_node());
else if (!root_.children().empty())
- selected_node_ = root_.children().front().get();
-
- if (selected_node_) {
- AXVirtualView* ax_selected_view = selected_node_->accessibility_view();
- if (ax_selected_view) {
- GetViewAccessibility().OverrideFocus(ax_selected_view);
- ax_selected_view->NotifyAccessibilityEvent(
- ax::mojom::Event::kSelection);
- }
- }
+ SetSelectedNode(root_.children().front().get()->model_node());
}
DrawnNodesChanged();
@@ -252,57 +244,21 @@ TreeModelNode* TreeView::GetEditingNode() {
}
void TreeView::SetSelectedNode(TreeModelNode* model_node) {
- if (editing_ || model_node != selected_node_)
- CancelEdit();
- if (model_node && model_->GetParent(model_node))
- Expand(model_->GetParent(model_node));
- if (model_node && model_node == root_.model_node() && !root_shown_)
- return; // Ignore requests to select the root when not shown.
- InternalNode* node =
- model_node ? GetInternalNodeForModelNode(model_node, CREATE_IF_NOT_LOADED)
- : nullptr;
- bool was_empty_selection = (selected_node_ == nullptr);
- bool changed = (selected_node_ != node);
- if (changed) {
- SchedulePaintForNode(selected_node_);
- selected_node_ = node;
- if (selected_node_ == &root_ && !root_shown_)
- selected_node_ = nullptr;
- if (selected_node_ && selected_node_ != &root_)
- Expand(model_->GetParent(selected_node_->model_node()));
- SchedulePaintForNode(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.
- if (controller_ && (changed || was_empty_selection))
- controller_->OnTreeViewSelectionChanged(this);
-
- if (changed) {
- AXVirtualView* ax_selected_view =
- selected_node_ ? selected_node_->accessibility_view() : nullptr;
- if (ax_selected_view) {
- GetViewAccessibility().OverrideFocus(ax_selected_view);
- ax_selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);
- } else {
- GetViewAccessibility().OverrideFocus(nullptr);
- NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
- }
- }
+ UpdateSelection(model_node, kActiveAndSelected);
}
const TreeModelNode* TreeView::GetSelectedNode() const {
return selected_node_ ? selected_node_->model_node() : nullptr;
}
+void TreeView::SetActiveNode(TreeModelNode* model_node) {
+ UpdateSelection(model_node, kActive);
+}
+
+const TreeModelNode* TreeView::GetActiveNode() const {
+ return active_node_ ? active_node_->model_node() : nullptr;
+}
+
void TreeView::Collapse(ui::TreeModelNode* model_node) {
// Don't collapse the root if the root isn't shown, otherwise nothing is
// displayed.
@@ -315,7 +271,9 @@ void TreeView::Collapse(ui::TreeModelNode* model_node) {
bool was_expanded = IsExpanded(model_node);
if (node->is_expanded()) {
if (selected_node_ && selected_node_->HasAncestor(node))
- SetSelectedNode(model_node);
+ UpdateSelection(model_node, kActiveAndSelected);
+ else if (active_node_ && active_node_->HasAncestor(node))
+ UpdateSelection(model_node, kActive);
node->set_is_expanded(false);
}
if (was_expanded) {
@@ -392,9 +350,13 @@ void TreeView::SetRootShown(bool root_shown) {
if (root_shown_ == root_shown)
return;
root_shown_ = root_shown;
- if (!root_shown_ && selected_node_ == &root_) {
+ if (!root_shown_ && (selected_node_ == &root_ || active_node_ == &root_)) {
const auto& children = model_->GetChildren(root_.model_node());
- SetSelectedNode(children.empty() ? nullptr : children.front());
+ TreeModelNode* first_child = children.empty() ? nullptr : children.front();
+ if (selected_node_ == &root_)
+ UpdateSelection(first_child, kActiveAndSelected);
+ else if (active_node_ == &root_)
+ UpdateSelection(first_child, kActive);
}
AXVirtualView* ax_view = root_.accessibility_view();
@@ -491,36 +453,53 @@ bool TreeView::HandleAccessibleAction(const ui::AXActionData& action_data) {
if (!model_)
return false;
- switch (action_data.action) {
- case ax::mojom::Action::kDoDefault: {
- CommitEdit();
- RequestFocus();
- TreeModelNode* selected_model_node = GetSelectedNode();
- if (!selected_model_node)
+ AXVirtualView* ax_view = AXVirtualView::GetFromId(action_data.target_node_id);
+ InternalNode* node =
+ ax_view ? GetInternalNodeForVirtualView(ax_view) : nullptr;
+ if (!node) {
+ switch (action_data.action) {
+ case ax::mojom::Action::kFocus:
+ if (active_node_)
+ return false;
+ if (!HasFocus())
+ RequestFocus();
return true;
+ case ax::mojom::Action::kBlur:
+ case ax::mojom::Action::kScrollToMakeVisible:
+ return View::HandleAccessibleAction(action_data);
+ default:
+ return false;
+ }
+ }
- if (IsExpanded(selected_model_node))
- Collapse(selected_model_node);
+ switch (action_data.action) {
+ case ax::mojom::Action::kDoDefault:
+ SetSelectedNode(node->model_node());
+ if (!HasFocus())
+ RequestFocus();
+ if (IsExpanded(node->model_node()))
+ Collapse(node->model_node());
else
- Expand(selected_model_node);
+ Expand(node->model_node());
break;
- }
case ax::mojom::Action::kFocus:
- RequestFocus();
+ SetSelectedNode(node->model_node());
+ if (!HasFocus())
+ RequestFocus();
break;
case ax::mojom::Action::kScrollToMakeVisible:
- 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_)));
- }
+ // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
+ // Un-flip before passing to ScrollRectToVisible(), which uses layout
+ // coordinates.
+ ScrollRectToVisible(GetMirroredRect(GetForegroundBoundsForNode(node)));
break;
case ax::mojom::Action::kShowContextMenu:
+ SetSelectedNode(node->model_node());
+ if (!HasFocus())
+ RequestFocus();
ShowContextMenu(GetBoundsInScreen().CenterPoint(),
ui::MENU_SOURCE_KEYBOARD);
break;
@@ -565,11 +544,14 @@ void TreeView::TreeNodesRemoved(TreeModel* model,
GetInternalNodeForModelNode(parent, DONT_CREATE_IF_NOT_LOADED);
if (!parent_node || !parent_node->loaded_children())
return;
- bool reset_selection = false;
+ bool reset_selected_node = false;
+ bool reset_active_node = false;
for (size_t i = 0; i < count; ++i) {
InternalNode* child_removing = parent_node->children()[start].get();
if (selected_node_ && selected_node_->HasAncestor(child_removing))
- reset_selection = true;
+ reset_selected_node = true;
+ if (active_node_ && active_node_->HasAncestor(child_removing))
+ reset_active_node = true;
DCHECK(parent_node->accessibility_view()->Contains(
child_removing->accessibility_view()));
@@ -578,21 +560,31 @@ void TreeView::TreeNodesRemoved(TreeModel* model,
child_removing->set_accessibility_view(nullptr);
parent_node->Remove(start);
}
- if (reset_selection) {
- // selected_node_ is no longer valid (at the time we enter this function
- // its model_node() is likely deleted). Explicitly NULL out the field
- // rather than invoking SetSelectedNode() otherwise, we'll try and use a
- // deleted value.
- selected_node_ = nullptr;
+
+ if (reset_selected_node || reset_active_node) {
+ // selected_node_ or active_node_ or both were no longer valid (i.e. the
+ // model_node() was likely deleted by the time we entered this function).
+ // Explicitly set to nullptr before continuing; otherwise, we might try to
+ // use a deleted value.
+ if (reset_selected_node)
+ selected_node_ = nullptr;
+ if (reset_active_node)
+ active_node_ = nullptr;
+
+ // Replace invalidated states with the nearest valid node.
const auto& children = model_->GetChildren(parent);
- TreeModelNode* to_select = nullptr;
+ TreeModelNode* nearest_node = nullptr;
if (!children.empty()) {
- to_select = children[std::min(start, children.size() - 1)];
+ nearest_node = children[std::min(start, children.size() - 1)];
} else if (parent != root_.model_node() || root_shown_) {
- to_select = parent;
+ nearest_node = parent;
}
- SetSelectedNode(to_select);
+ if (reset_selected_node)
+ UpdateSelection(nearest_node, kActiveAndSelected);
+ else if (reset_active_node)
+ UpdateSelection(nearest_node, kActive);
}
+
if (IsExpanded(parent)) {
NotifyAccessibilityEvent(ax::mojom::Event::kRowCountChanged, true);
DrawnNodesChanged();
@@ -652,11 +644,17 @@ int TreeView::GetRowCount() {
}
int TreeView::GetSelectedRow() {
- ui::TreeModelNode* model_node = GetSelectedNode();
+ // Type-ahead searches should be relative to the active node, so return the
+ // row of the active node for |PrefixSelector|.
+ ui::TreeModelNode* model_node = GetActiveNode();
return model_node ? GetRowForNode(model_node) : -1;
}
void TreeView::SetSelectedRow(int row) {
+ // Type-ahead manipulates selection because active node is synced to selected
+ // node, so call SetSelectedNode() instead of SetActiveNode().
+ // TODO(crbug.com/1080944): Decouple active node from selected node by adding
+ // new keyboard affordances.
SetSelectedNode(GetNodeForRow(row));
}
@@ -665,18 +663,20 @@ base::string16 TreeView::GetTextForRow(int row) {
}
gfx::Point TreeView::GetKeyboardContextMenuLocation() {
- int y = height() / 2;
- if (selected_node_) {
- gfx::Rect node_bounds(GetForegroundBoundsForNode(selected_node_));
- gfx::Rect vis_bounds(GetVisibleBounds());
- if (node_bounds.y() >= vis_bounds.y() &&
- node_bounds.y() < vis_bounds.bottom()) {
- y = node_bounds.y();
- }
+ gfx::Rect vis_bounds(GetVisibleBounds());
+ int x = 0;
+ int y = 0;
+ if (active_node_) {
+ gfx::Rect node_bounds(GetForegroundBoundsForNode(active_node_));
+ if (node_bounds.Intersects(vis_bounds))
+ node_bounds.Intersect(vis_bounds);
+ gfx::Point menu_point(node_bounds.CenterPoint());
+ x = base::ClampToRange(menu_point.x(), vis_bounds.x(), vis_bounds.right());
+ y = base::ClampToRange(menu_point.y(), vis_bounds.y(), vis_bounds.bottom());
}
- gfx::Point screen_loc(0, y);
+ gfx::Point screen_loc(x, y);
if (base::i18n::IsRTL())
- screen_loc.set_x(width());
+ screen_loc.set_x(vis_bounds.width() - screen_loc.x());
ConvertPointToScreen(this, &screen_loc);
return screen_loc;
}
@@ -688,10 +688,10 @@ bool TreeView::OnKeyPressed(const ui::KeyEvent& event) {
switch (event.key_code()) {
case ui::VKEY_F2:
if (!editing_) {
- TreeModelNode* selected_node = GetSelectedNode();
- if (selected_node &&
- (!controller_ || controller_->CanEdit(this, selected_node))) {
- StartEditing(selected_node);
+ TreeModelNode* active_node = GetActiveNode();
+ if (active_node &&
+ (!controller_ || controller_->CanEdit(this, active_node))) {
+ StartEditing(active_node);
}
}
return true;
@@ -762,13 +762,6 @@ void TreeView::OnFocus() {
GetInputMethod()->OnCaretBoundsChanged(GetPrefixSelector());
SetHasFocusIndicator(true);
- AXVirtualView* ax_selected_view =
- selected_node_ ? selected_node_->accessibility_view() : nullptr;
- if (ax_selected_view) {
- GetViewAccessibility().OverrideFocus(ax_selected_view);
- } else {
- GetViewAccessibility().OverrideFocus(nullptr);
- }
}
void TreeView::OnBlur() {
@@ -780,24 +773,82 @@ void TreeView::OnBlur() {
SetHasFocusIndicator(false);
}
+void TreeView::UpdateSelection(TreeModelNode* model_node,
+ SelectionType selection_type) {
+ CancelEdit();
+ if (model_node && model_->GetParent(model_node))
+ Expand(model_->GetParent(model_node));
+ if (model_node && model_node == root_.model_node() && !root_shown_)
+ return; // Ignore requests for the root when not shown.
+ InternalNode* node =
+ model_node ? GetInternalNodeForModelNode(model_node, CREATE_IF_NOT_LOADED)
+ : nullptr;
+
+ // Force update if old value was nullptr to handle case of TreeNodesRemoved
+ // explicitly resetting selected_node_ or active_node_ before invoking this.
+ bool active_changed = (!active_node_ || active_node_ != node);
+ bool selection_changed = (selection_type == kActiveAndSelected &&
+ (!selected_node_ || selected_node_ != node));
+
+ // Update tree view states to new values.
+ if (active_changed)
+ active_node_ = node;
+
+ if (selection_changed) {
+ SchedulePaintForNode(selected_node_);
+ selected_node_ = node;
+ SchedulePaintForNode(selected_node_);
+ }
+
+ if (active_changed && node) {
+ // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
+ // Un-flip before passing to ScrollRectToVisible(), which uses layout
+ // coordinates.
+ ScrollRectToVisible(GetMirroredRect(GetForegroundBoundsForNode(node)));
+ }
+
+ // Notify assistive technologies of state changes.
+ if (active_changed) {
+ // Update |ViewAccessibility| so that focus lands directly on this node when
+ // |FocusManager| gives focus to the tree view. This update also fires an
+ // accessible focus event.
+ GetViewAccessibility().OverrideFocus(node ? node->accessibility_view()
+ : nullptr);
+ }
+
+ if (selection_changed) {
+ AXVirtualView* ax_selected_view =
+ node ? node->accessibility_view() : nullptr;
+ if (ax_selected_view)
+ ax_selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);
+ else
+ NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
+ }
+
+ // Notify controller of state changes.
+ if (selection_changed && controller_)
+ controller_->OnTreeViewSelectionChanged(this);
+}
+
bool TreeView::OnClickOrTap(const ui::LocatedEvent& event) {
CommitEdit();
- RequestFocus();
InternalNode* node = GetNodeAtPoint(event.location());
- if (!node)
- return true;
-
- bool hits_arrow = IsPointInExpandControl(node, event.location());
- if (!hits_arrow)
- SetSelectedNode(node->model_node());
+ if (node) {
+ bool hits_arrow = IsPointInExpandControl(node, event.location());
+ if (!hits_arrow)
+ SetSelectedNode(node->model_node());
- if (hits_arrow || EventIsDoubleTapOrClick(event)) {
- if (node->is_expanded())
- Collapse(node->model_node());
- else
- Expand(node->model_node());
+ if (hits_arrow || EventIsDoubleTapOrClick(event)) {
+ if (node->is_expanded())
+ Collapse(node->model_node());
+ else
+ Expand(node->model_node());
+ }
}
+
+ if (!HasFocus())
+ RequestFocus();
return true;
}
@@ -859,6 +910,7 @@ void TreeView::PopulateAccessibilityData(InternalNode* node,
: nullptr;
const bool selected = (node == selected_node);
data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected);
+ data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kSelect);
if (node->is_expanded())
data->AddState(ax::mojom::State::kExpanded);
@@ -916,7 +968,8 @@ void TreeView::PopulateAccessibilityData(InternalNode* node,
gfx::Rect node_bounds = GetBackgroundBoundsForNode(node);
data->relative_bounds.bounds = gfx::RectF(node_bounds);
} else {
- data->AddState(ax::mojom::State::kInvisible);
+ data->AddState(node != &root_ || root_shown_ ? ax::mojom::State::kInvisible
+ : ax::mojom::State::kIgnored);
}
}
@@ -1123,6 +1176,22 @@ TreeView::InternalNode* TreeView::GetInternalNodeForModelNode(
return parent_internal_node->children()[index].get();
}
+TreeView::InternalNode* TreeView::GetInternalNodeForVirtualView(
+ AXVirtualView* ax_view) {
+ if (ax_view == root_.accessibility_view())
+ return &root_;
+ DCHECK(ax_view);
+ InternalNode* parent_internal_node =
+ GetInternalNodeForVirtualView(ax_view->virtual_parent_view());
+ if (!parent_internal_node)
+ return nullptr;
+ DCHECK(parent_internal_node->loaded_children());
+ AXVirtualView* parent_ax_view = parent_internal_node->accessibility_view();
+ DCHECK(parent_ax_view);
+ size_t index = parent_ax_view->GetIndexOf(ax_view);
+ return parent_internal_node->children()[index].get();
+}
+
gfx::Rect TreeView::GetBoundsForNode(InternalNode* node) {
int row, ignored_depth;
row = GetRowForInternalNode(node, &ignored_depth);
@@ -1248,7 +1317,7 @@ void TreeView::IncrementSelection(IncrementType type) {
if (!model_)
return;
- if (!GetSelectedNode()) {
+ if (!active_node_) {
// If nothing is selected select the first or last node.
if (root_.children().empty())
return;
@@ -1268,7 +1337,7 @@ void TreeView::IncrementSelection(IncrementType type) {
int depth = 0;
int delta = type == INCREMENT_PREVIOUS ? -1 : 1;
- int row = GetRowForInternalNode(selected_node_, &depth);
+ int row = GetRowForInternalNode(active_node_, &depth);
int new_row = base::ClampToRange(row + delta, 0, GetRowCount() - 1);
if (new_row == row)
return; // At the end/beginning.
@@ -1276,20 +1345,20 @@ void TreeView::IncrementSelection(IncrementType type) {
}
void TreeView::CollapseOrSelectParent() {
- if (selected_node_) {
- if (selected_node_->is_expanded())
- Collapse(selected_node_->model_node());
- else if (selected_node_->parent())
- SetSelectedNode(selected_node_->parent()->model_node());
+ if (active_node_) {
+ if (active_node_->is_expanded())
+ Collapse(active_node_->model_node());
+ else if (active_node_->parent())
+ SetSelectedNode(active_node_->parent()->model_node());
}
}
void TreeView::ExpandOrSelectChild() {
- if (selected_node_) {
- if (!selected_node_->is_expanded())
- Expand(selected_node_->model_node());
- else if (!selected_node_->children().empty())
- SetSelectedNode(selected_node_->children().front()->model_node());
+ if (active_node_) {
+ if (!active_node_->is_expanded())
+ Expand(active_node_->model_node());
+ else if (!active_node_->children().empty())
+ SetSelectedNode(active_node_->children().front()->model_node());
}
}
diff --git a/chromium/ui/views/controls/tree/tree_view.h b/chromium/ui/views/controls/tree/tree_view.h
index b03b860f7b2..19504e30d01 100644
--- a/chromium/ui/views/controls/tree/tree_view.h
+++ b/chromium/ui/views/controls/tree/tree_view.h
@@ -44,6 +44,13 @@ class TreeViewController;
// can expand, collapse and edit the items. A Controller may be attached to
// receive notification of selection changes and restrict editing.
//
+// In addition to tracking selection, TreeView also tracks the active node,
+// which is the item that receives keyboard input when the tree has focus.
+// Active/focus is like a pointer for keyboard navigation, and operations such
+// as selection are performed at the point of focus. The active node is synced
+// to the selected node. When the active node is nullptr, the TreeView itself is
+// the target of keyboard input.
+//
// Note on implementation. This implementation doesn't scale well. In particular
// it does not store any row information, but instead calculates it as
// necessary. But it's more than adequate for current uses.
@@ -103,6 +110,20 @@ class VIEWS_EXPORT TreeView : public View,
}
const ui::TreeModelNode* GetSelectedNode() const;
+ // Marks the specified node as active, scrolls it into view, and reports a
+ // keyboard focus update to ATs. Active node should be synced to the selected
+ // node and should be nullptr when the tree is empty.
+ // TODO(crbug.com/1080944): Decouple active node from selected node by adding
+ // new keyboard affordances.
+ void SetActiveNode(ui::TreeModelNode* model_node);
+
+ // Returns the active node, or nullptr if nothing is active.
+ ui::TreeModelNode* GetActiveNode() {
+ return const_cast<ui::TreeModelNode*>(
+ const_cast<const TreeView*>(this)->GetActiveNode());
+ }
+ const ui::TreeModelNode* GetActiveNode() const;
+
// Marks |model_node| as collapsed. This only effects the UI if node and all
// its parents are expanded (IsExpanded(model_node) returns true).
void Collapse(ui::TreeModelNode* model_node);
@@ -193,6 +214,20 @@ class VIEWS_EXPORT TreeView : public View,
private:
friend class TreeViewTest;
+ // Enumeration of possible changes to tree view state when the UI is updated.
+ enum SelectionType {
+ // Active state is being set to a tree item.
+ kActive,
+
+ // Active and selected states are being set to a tree item.
+ kActiveAndSelected,
+ };
+
+ // Performs active node and selected node state transitions. Updates states
+ // and scrolling before notifying assistive technologies and the controller.
+ void UpdateSelection(ui::TreeModelNode* model_node,
+ SelectionType selection_type);
+
// Selects, expands or collapses nodes in the tree. Consistent behavior for
// tap gesture and click events.
bool OnClickOrTap(const ui::LocatedEvent& event);
@@ -354,6 +389,9 @@ class VIEWS_EXPORT TreeView : public View,
ui::TreeModelNode* model_node,
GetInternalNodeCreateType create_type);
+ // Returns the InternalNode for a virtual view.
+ InternalNode* GetInternalNodeForVirtualView(AXVirtualView* ax_view);
+
// Returns the bounds for a node. This rectangle contains the node's icon,
// text, arrow, and auxiliary text (if any). All of the other bounding
// rectangles computed by the functions below lie inside this rectangle.
@@ -435,6 +473,9 @@ class VIEWS_EXPORT TreeView : public View,
// The selected node, may be null.
InternalNode* selected_node_ = nullptr;
+ // The current active node, may be null.
+ InternalNode* active_node_ = nullptr;
+
bool editing_ = false;
// The editor; lazily created and never destroyed (well, until TreeView is
diff --git a/chromium/ui/views/controls/tree/tree_view_unittest.cc b/chromium/ui/views/controls/tree/tree_view_unittest.cc
index 7f56294c155..6081d640219 100644
--- a/chromium/ui/views/controls/tree/tree_view_unittest.cc
+++ b/chromium/ui/views/controls/tree/tree_view_unittest.cc
@@ -14,6 +14,7 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
@@ -24,7 +25,9 @@
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/tree/tree_view_controller.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
using ui::TreeModel;
@@ -102,10 +105,27 @@ class TreeViewTest : public ViewsTestBase {
std::string TreeViewAccessibilityContentsAsString() const;
+ // Gets the selected node from the tree view. The result can be compared with
+ // GetSelectedAccessibilityViewName() to check consistency between the tree
+ // view state and the accessibility data.
std::string GetSelectedNodeTitle();
+ // Finds the selected node via iterative depth first search over the internal
+ // accessibility tree, examining both ignored and unignored nodes. The result
+ // can be compared with GetSelectedNodeTitle() to check consistency between
+ // the tree view state and the accessibility data.
std::string GetSelectedAccessibilityViewName() const;
+ // Gets the active node from the tree view. The result can be compared with
+ // GetSelectedAccessibilityViewName() to check consistency between the tree
+ // view state and the accessibility data.
+ std::string GetActiveNodeTitle();
+
+ // Gets the active node from the tree view's |ViewAccessibility|. The result
+ // can be compared with GetSelectedNodeTitle() to check consistency between
+ // the tree view internal state and the accessibility data.
+ std::string GetActiveAccessibilityViewName() const;
+
std::string GetEditingNodeTitle();
AXVirtualView* GetRootAccessibilityView() const;
@@ -125,7 +145,7 @@ class TreeViewTest : public ViewsTestBase {
ui::TreeNodeModel<TestNode> model_;
TreeView* tree_;
- Widget* widget_;
+ UniqueWidgetPtr widget_;
private:
std::string InternalNodeAsString(TreeView::InternalNode* node);
@@ -141,12 +161,13 @@ class TreeViewTest : public ViewsTestBase {
void TreeViewTest::SetUp() {
ViewsTestBase::SetUp();
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
widget_->Init(std::move(params));
- tree_ = new TreeView();
- widget_->GetContentsView()->AddChildView(tree_);
+ tree_ =
+ widget_->GetContentsView()->AddChildView(std::make_unique<TreeView>());
+ tree_->RequestFocus();
ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback =
base::BindRepeating(
@@ -164,8 +185,7 @@ void TreeViewTest::SetUp() {
}
void TreeViewTest::TearDown() {
- if (!widget_->IsClosed())
- widget_->Close();
+ widget_.reset();
ViewsTestBase::TearDown();
}
@@ -233,6 +253,20 @@ std::string TreeViewTest::GetSelectedAccessibilityViewName() const {
return {};
}
+std::string TreeViewTest::GetActiveNodeTitle() {
+ TreeModelNode* model_node = tree_->GetActiveNode();
+ return model_node ? base::UTF16ToASCII(model_node->GetTitle())
+ : std::string();
+}
+
+std::string TreeViewTest::GetActiveAccessibilityViewName() const {
+ const AXVirtualView* ax_view =
+ tree_->GetViewAccessibility().FocusedVirtualChild();
+ return ax_view ? ax_view->GetData().GetStringAttribute(
+ ax::mojom::StringAttribute::kName)
+ : std::string();
+}
+
std::string TreeViewTest::GetEditingNodeTitle() {
TreeModelNode* model_node = tree_->GetEditingNode();
return model_node ? base::UTF16ToASCII(model_node->GetTitle())
@@ -336,6 +370,12 @@ std::string TreeViewTest::InternalNodeAsString(TreeView::InternalNode* node) {
return result;
}
+// Verify properties are accessible via metadata.
+TEST_F(TreeViewTest, MetadataTest) {
+ tree_->SetModel(&model_);
+ test::TestViewMetadata(tree_);
+}
+
// Verifies setting model correctly updates internal state.
TEST_F(TreeViewTest, SetModel) {
tree_->SetModel(&model_);
@@ -930,4 +970,225 @@ TEST_F(TreeViewTest, CommitOnFocusLost) {
ax::mojom::StringAttribute::kName));
}
+// Verifies that virtual accessible actions go to virtual view targets.
+TEST_F(TreeViewTest, VirtualAccessibleAction) {
+ tree_->SetModel(&model_);
+ tree_->Expand(GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
+ EXPECT_EQ(5, GetRowCount());
+
+ // Set to nullptr should clear the selection.
+ tree_->SetSelectedNode(nullptr);
+ EXPECT_EQ(std::string(), GetActiveNodeTitle());
+ EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+ EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
+
+ // Test using each virtual view as target.
+ ui::AXActionData data;
+ const std::string test_cases[] = {"root", "a", "b", "b1", "c"};
+ for (const std::string& name : test_cases) {
+ data.target_node_id = GetAccessibilityViewByName(name)->GetData().id;
+ data.action = ax::mojom::Action::kDoDefault;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ(name, GetActiveNodeTitle());
+ EXPECT_EQ(name, GetActiveAccessibilityViewName());
+ EXPECT_EQ(name, GetSelectedNodeTitle());
+ EXPECT_EQ(name, GetSelectedAccessibilityViewName());
+ }
+
+ // Do nothing when a valid node id is not provided. This can happen if the
+ // actions target the owner view itself.
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kDoDefault;
+ EXPECT_FALSE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ("b", GetActiveNodeTitle());
+ EXPECT_EQ("b", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+
+ // Check that the active node is set if assistive technologies set focus.
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ data.target_node_id = GetAccessibilityViewByName("a")->GetData().id;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ("a", GetSelectedAccessibilityViewName());
+
+ // Do not handle accessible actions when no node is selected.
+ tree_->SetSelectedNode(nullptr);
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kDoDefault;
+ EXPECT_FALSE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ(std::string(), GetActiveNodeTitle());
+ EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+ EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
+}
+
+// Verifies that accessibility focus events get fired for the correct nodes when
+// the tree view is given focus.
+TEST_F(TreeViewTest, OnFocusAccessibilityEvents) {
+ // Without keyboard focus, model changes should not fire focus events.
+ tree_->GetFocusManager()->ClearFocus();
+ EXPECT_FALSE(tree_->HasFocus());
+ tree_->SetModel(&model_);
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ("root", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(4, GetRowCount());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetRootAccessibilityView(),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+
+ // The initial focus should fire a focus event for the active node
+ // (in this case, the root node).
+ ClearAccessibilityEvents();
+ tree_->RequestFocus();
+ EXPECT_EQ((AccessibilityEventsVector{std::make_pair(
+ GetRootAccessibilityView(), ax::mojom::Event::kFocus)}),
+ accessibility_events());
+
+ // Focus clear and restore should fire a focus event for the active node.
+ ClearAccessibilityEvents();
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ tree_->SetActiveNode(GetNodeByTitle("a"));
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+ tree_->GetFocusManager()->ClearFocus();
+ EXPECT_FALSE(tree_->HasFocus());
+ tree_->GetFocusManager()->RestoreFocusedView();
+ EXPECT_TRUE(tree_->HasFocus());
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(
+ (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("b"),
+ ax::mojom::Event::kFocus),
+ std::make_pair(GetAccessibilityViewByName("b"),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kFocus),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kFocus)}),
+ accessibility_events());
+
+ // Without keyboard focus, selection should not fire focus events.
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ tree_->SetSelectedNode(GetNodeByTitle("a"));
+ EXPECT_FALSE(tree_->HasFocus());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ("a", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(
+ (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+
+ // A direct focus action on a tree item should give focus to the tree view but
+ // only fire a focus event for the target node.
+ ui::AXActionData data;
+ const std::string test_cases[] = {"root", "a", "b", "c"};
+ for (const std::string& name : test_cases) {
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ EXPECT_FALSE(tree_->HasFocus());
+ data.target_node_id = GetAccessibilityViewByName(name)->GetData().id;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_TRUE(tree_->HasFocus());
+ EXPECT_EQ(name, GetActiveNodeTitle());
+ EXPECT_EQ(name, GetActiveAccessibilityViewName());
+ EXPECT_EQ(name, GetSelectedNodeTitle());
+ EXPECT_EQ(name, GetSelectedAccessibilityViewName());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetAccessibilityViewByName(name),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetAccessibilityViewByName(name),
+ ax::mojom::Event::kFocus)}),
+ accessibility_events());
+ }
+
+ // A direct focus action on the tree view itself with an active node should
+ // have no effect.
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_FALSE(tree_->HandleAccessibleAction(data));
+ EXPECT_FALSE(tree_->HasFocus());
+ EXPECT_EQ("b", GetActiveNodeTitle());
+ EXPECT_EQ("b", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(
+ (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("b"),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+
+ // A direct focus action on a tree view without an active node (i.e. empty
+ // tree) should fire a focus event for the tree view.
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ ui::TreeNodeModel<TestNode> empty_model(std::make_unique<TestNode>());
+ static_cast<TestNode*>(empty_model.GetRoot())->SetTitle(ASCIIToUTF16("root"));
+ tree_->SetModel(&empty_model);
+ tree_->SetRootShown(false);
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_TRUE(tree_->HasFocus());
+ EXPECT_EQ(std::string(), GetActiveNodeTitle());
+ EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+ EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetRootAccessibilityView(),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetRootAccessibilityView(),
+ ax::mojom::Event::kStateChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kFocus)}),
+ accessibility_events());
+
+ // When a focused empty tree is populated with nodes, it should immediately
+ // hand off focus to one of them and select it.
+ ClearAccessibilityEvents();
+ tree_->SetModel(&model_);
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ("a", GetSelectedAccessibilityViewName());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kFocus),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu.cc b/chromium/ui/views/controls/views_text_services_context_menu.cc
deleted file mode 100644
index 0cebc532c69..00000000000
--- a/chromium/ui/views/controls/views_text_services_context_menu.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.h"
-
-#include <memory>
-
-#include "base/notreached.h"
-#include "ui/views/controls/views_text_services_context_menu_base.h"
-
-namespace views {
-
-// static
-std::unique_ptr<ViewsTextServicesContextMenu>
-ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
- Textfield* client) {
- return std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
-}
-
-bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
- ViewsTextServicesContextMenu* menu,
- base::i18n::TextDirection direction) {
- NOTREACHED();
- return false;
-}
-
-} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu.h b/chromium/ui/views/controls/views_text_services_context_menu.h
index 1b84aa43f20..ade35947552 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu.h
+++ b/chromium/ui/views/controls/views_text_services_context_menu.h
@@ -7,13 +7,7 @@
#include <memory>
-#include "base/i18n/rtl.h"
-#include "ui/base/accelerators/accelerator.h"
-#include "ui/views/views_export.h"
-
-namespace ui {
-class SimpleMenuModel;
-}
+#include "ui/base/models/simple_menu_model.h"
namespace views {
@@ -21,26 +15,15 @@ class Textfield;
// This class is used to add and handle text service items in the text context
// menu.
-class ViewsTextServicesContextMenu : public ui::AcceleratorProvider {
+class ViewsTextServicesContextMenu : public ui::SimpleMenuModel::Delegate {
public:
// Creates a platform-specific ViewsTextServicesContextMenu object.
static std::unique_ptr<ViewsTextServicesContextMenu> Create(
ui::SimpleMenuModel* menu,
Textfield* textfield);
- // Method for testing. Returns true if the text direction BiDi submenu item
- // in |menu| should be checked.
- VIEWS_EXPORT static bool IsTextDirectionCheckedForTesting(
- ViewsTextServicesContextMenu* menu,
- base::i18n::TextDirection direction);
-
// Returns true if the given |command_id| is handled by the menu.
virtual bool SupportsCommand(int command_id) const = 0;
-
- // Methods associated with SimpleMenuModel::Delegate.
- virtual bool IsCommandIdChecked(int command_id) const = 0;
- virtual bool IsCommandIdEnabled(int command_id) const = 0;
- virtual void ExecuteCommand(int command_id) = 0;
};
} // namespace views
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
index b1f7b1d9890..a9b53aeb9c2 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu_base.cc
+++ b/chromium/ui/views/controls/views_text_services_context_menu_base.cc
@@ -4,6 +4,8 @@
#include "ui/views/controls/views_text_services_context_menu_base.h"
+#include <memory>
+
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "ui/base/accelerators/accelerator.h"
@@ -41,10 +43,6 @@ ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase(
ViewsTextServicesContextMenuBase::~ViewsTextServicesContextMenuBase() = default;
-bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
- return command_id == IDS_CONTENT_CONTEXT_EMOJI;
-}
-
bool ViewsTextServicesContextMenuBase::GetAcceleratorForCommandId(
int command_id,
ui::Accelerator* accelerator) const {
@@ -72,17 +70,28 @@ bool ViewsTextServicesContextMenuBase::IsCommandIdChecked(
bool ViewsTextServicesContextMenuBase::IsCommandIdEnabled(
int command_id) const {
- if (command_id == IDS_CONTENT_CONTEXT_EMOJI)
- return true;
-
- return false;
+ return command_id == IDS_CONTENT_CONTEXT_EMOJI;
}
-void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id) {
+void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id,
+ int event_flags) {
if (command_id == IDS_CONTENT_CONTEXT_EMOJI) {
- client()->GetWidget()->ShowEmojiPanel();
+ client_->GetWidget()->ShowEmojiPanel();
UMA_HISTOGRAM_BOOLEAN(kViewsTextServicesContextMenuEmoji, true);
}
}
+bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
+ return command_id == IDS_CONTENT_CONTEXT_EMOJI;
+}
+
+#if !defined(OS_MACOSX)
+// static
+std::unique_ptr<ViewsTextServicesContextMenu>
+ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
+ Textfield* client) {
+ return std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
+}
+#endif
+
} // namespace views
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
index bbc3e19d206..3c8484778f8 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu_base.h
+++ b/chromium/ui/views/controls/views_text_services_context_menu_base.h
@@ -5,39 +5,40 @@
#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 "build/build_config.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
+// This base class is used to add and handle text service items in the textfield
// context menu. Specific platforms may subclass and add additional items.
class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu {
public:
ViewsTextServicesContextMenuBase(ui::SimpleMenuModel* menu,
Textfield* client);
+ ViewsTextServicesContextMenuBase(const ViewsTextServicesContextMenuBase&) =
+ delete;
+ ViewsTextServicesContextMenuBase& operator=(
+ const ViewsTextServicesContextMenuBase&) = delete;
~ViewsTextServicesContextMenuBase() override;
- // Returns true if the given |command_id| is handled by the menu.
- bool SupportsCommand(int command_id) const override;
-
- // ui::AcceleratorProvider:
+ // ViewsTextServicesContextMenu:
bool GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) 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;
+ void ExecuteCommand(int command_id, int event_flags) override;
+ bool SupportsCommand(int command_id) const override;
protected:
- Textfield* client() const { return client_; }
+#if defined(OS_MACOSX)
+ Textfield* client() { return client_; }
+ const Textfield* client() const { return client_; }
+#endif
private:
// The view associated with the menu. Weak. Owns |this|.
- Textfield* client_ = nullptr;
-
- DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuBase);
+ Textfield* const client_;
};
} // namespace views
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 a95d7058b80..4f7510d6a1e 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
@@ -29,108 +29,118 @@ class ViewsTextServicesContextMenuMac
: public ViewsTextServicesContextMenuBase,
public ui::TextServicesContextMenu::Delegate {
public:
- ViewsTextServicesContextMenuMac(ui::SimpleMenuModel* menu, Textfield* client)
- : 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(
- 0, IDS_CONTENT_CONTEXT_LOOK_UP,
- l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, text));
- }
-
- text_services_menu_.AppendToContextMenu(menu);
- text_services_menu_.AppendEditableItems(menu);
- }
-
+ ViewsTextServicesContextMenuMac(ui::SimpleMenuModel* menu, Textfield* client);
+ ViewsTextServicesContextMenuMac(const ViewsTextServicesContextMenuMac&) =
+ delete;
+ ViewsTextServicesContextMenuMac& operator=(
+ const ViewsTextServicesContextMenuMac&) = delete;
~ViewsTextServicesContextMenuMac() override = default;
// ViewsTextServicesContextMenu:
- bool SupportsCommand(int command_id) const override {
- return text_services_menu_.SupportsCommand(command_id) ||
- command_id == IDS_CONTENT_CONTEXT_LOOK_UP ||
- ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
- }
+ bool IsCommandIdChecked(int command_id) const override;
+ bool IsCommandIdEnabled(int command_id) const override;
+ void ExecuteCommand(int command_id, int event_flags) override;
+ bool SupportsCommand(int command_id) const override;
- bool IsCommandIdEnabled(int command_id) const override {
- if (text_services_menu_.SupportsCommand(command_id))
- return text_services_menu_.IsCommandIdEnabled(command_id);
-
- switch (command_id) {
- case IDS_CONTENT_CONTEXT_LOOK_UP:
- return true;
+ // TextServicesContextMenu::Delegate:
+ base::string16 GetSelectedText() const override;
+ bool IsTextDirectionEnabled(
+ base::i18n::TextDirection direction) const override;
+ bool IsTextDirectionChecked(
+ base::i18n::TextDirection direction) const override;
+ void UpdateTextDirection(base::i18n::TextDirection direction) override;
- default:
- return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
- }
- }
+ private:
+ // Handler for the "Look Up" menu item.
+ void LookUpInDictionary();
- void ExecuteCommand(int command_id) override {
- switch (command_id) {
- case IDS_CONTENT_CONTEXT_LOOK_UP:
- LookUpInDictionary();
- break;
+ ui::TextServicesContextMenu text_services_menu_{this};
+};
- default:
- ViewsTextServicesContextMenuBase::ExecuteCommand(command_id);
- break;
- }
+ViewsTextServicesContextMenuMac::ViewsTextServicesContextMenuMac(
+ ui::SimpleMenuModel* menu,
+ Textfield* client)
+ : ViewsTextServicesContextMenuBase(menu, client) {
+ // Insert the "Look up" item in the first position.
+ const base::string16 text = GetSelectedText();
+ if (!text.empty()) {
+ menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR);
+ menu->InsertItemAt(
+ 0, IDS_CONTENT_CONTEXT_LOOK_UP,
+ l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, text));
}
- // TextServicesContextMenu::Delegate:
- base::string16 GetSelectedText() const override {
- if (client()->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD)
- return base::string16();
+ text_services_menu_.AppendToContextMenu(menu);
+ text_services_menu_.AppendEditableItems(menu);
+}
- return client()->GetSelectedText();
- }
+bool ViewsTextServicesContextMenuMac::IsCommandIdChecked(int command_id) const {
+ return text_services_menu_.SupportsCommand(command_id)
+ ? text_services_menu_.IsCommandIdChecked(command_id)
+ : ViewsTextServicesContextMenuBase::IsCommandIdChecked(command_id);
+}
- bool IsTextDirectionEnabled(
- base::i18n::TextDirection direction) const override {
- return direction != base::i18n::UNKNOWN_DIRECTION;
- }
+bool ViewsTextServicesContextMenuMac::IsCommandIdEnabled(int command_id) const {
+ if (text_services_menu_.SupportsCommand(command_id))
+ return text_services_menu_.IsCommandIdEnabled(command_id);
+ return (command_id == IDS_CONTENT_CONTEXT_LOOK_UP) ||
+ ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
+}
- bool IsTextDirectionChecked(
- base::i18n::TextDirection direction) const override {
- return direction != base::i18n::UNKNOWN_DIRECTION &&
- client()->GetTextDirection() == direction;
- }
+void ViewsTextServicesContextMenuMac::ExecuteCommand(int command_id,
+ int event_flags) {
+ if (text_services_menu_.SupportsCommand(command_id))
+ text_services_menu_.ExecuteCommand(command_id, event_flags);
+ else if (command_id == IDS_CONTENT_CONTEXT_LOOK_UP)
+ LookUpInDictionary();
+ else
+ ViewsTextServicesContextMenuBase::ExecuteCommand(command_id, event_flags);
+}
- void UpdateTextDirection(base::i18n::TextDirection direction) override {
- DCHECK_NE(direction, base::i18n::UNKNOWN_DIRECTION);
+bool ViewsTextServicesContextMenuMac::SupportsCommand(int command_id) const {
+ return text_services_menu_.SupportsCommand(command_id) ||
+ command_id == IDS_CONTENT_CONTEXT_LOOK_UP ||
+ ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
+}
- 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);
- }
+base::string16 ViewsTextServicesContextMenuMac::GetSelectedText() const {
+ return (client()->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD)
+ ? base::string16()
+ : client()->GetSelectedText();
+}
- private:
- // Handler for the "Look Up" menu item.
- void LookUpInDictionary() {
- gfx::Point baseline_point;
- gfx::DecoratedText text;
- if (client()->GetWordLookupDataFromSelection(&text, &baseline_point)) {
- Widget* widget = client()->GetWidget();
- NSView* view = widget->GetNativeView().GetNativeNSView();
- views::View::ConvertPointToTarget(client(), widget->GetRootView(),
- &baseline_point);
-
- NSPoint lookup_point = NSMakePoint(
- baseline_point.x(), NSHeight([view frame]) - baseline_point.y());
- [view showDefinitionForAttributedString:
- gfx::GetAttributedStringFromDecoratedText(text)
- atPoint:lookup_point];
- }
- }
+bool ViewsTextServicesContextMenuMac::IsTextDirectionEnabled(
+ base::i18n::TextDirection direction) const {
+ return direction != base::i18n::UNKNOWN_DIRECTION;
+}
- // Appends and handles the text service menu.
- ui::TextServicesContextMenu text_services_menu_;
+bool ViewsTextServicesContextMenuMac::IsTextDirectionChecked(
+ base::i18n::TextDirection direction) const {
+ return IsTextDirectionEnabled(direction) &&
+ client()->GetTextDirection() == direction;
+}
- DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuMac);
-};
+void ViewsTextServicesContextMenuMac::UpdateTextDirection(
+ base::i18n::TextDirection direction) {
+ DCHECK(IsTextDirectionEnabled(direction));
+ client()->ChangeTextDirectionAndLayoutAlignment(direction);
+}
+
+void ViewsTextServicesContextMenuMac::LookUpInDictionary() {
+ gfx::DecoratedText text;
+ gfx::Point baseline_point;
+ if (client()->GetWordLookupDataFromSelection(&text, &baseline_point)) {
+ Widget* widget = client()->GetWidget();
+ views::View::ConvertPointToTarget(client(), widget->GetRootView(),
+ &baseline_point);
+ NSView* view = widget->GetNativeView().GetNativeNSView();
+ NSPoint lookup_point = NSMakePoint(
+ baseline_point.x(), NSHeight([view frame]) - baseline_point.y());
+ [view showDefinitionForAttributedString:
+ gfx::GetAttributedStringFromDecoratedText(text)
+ atPoint:lookup_point];
+ }
+}
} // namespace
@@ -141,11 +151,4 @@ ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
return std::make_unique<ViewsTextServicesContextMenuMac>(menu, client);
}
-// static
-bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
- ViewsTextServicesContextMenu* menu,
- base::i18n::TextDirection direction) {
- return static_cast<views::ViewsTextServicesContextMenuMac*>(menu)
- ->IsTextDirectionChecked(direction);
-}
} // namespace views
diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc
index 7bef84b8c9e..0c523877565 100644
--- a/chromium/ui/views/controls/webview/webview.cc
+++ b/chromium/ui/views/controls/webview/webview.cc
@@ -331,14 +331,6 @@ void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen,
ReattachForFullscreenChange(entered_fullscreen);
}
-void WebView::DidAttachInterstitialPage() {
- NotifyAccessibilityWebContentsChanged();
-}
-
-void WebView::DidDetachInterstitialPage() {
- NotifyAccessibilityWebContentsChanged();
-}
-
void WebView::OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) {
RequestFocus();
diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h
index 84d5cd520f7..624dcb1a4d8 100644
--- a/chromium/ui/views/controls/webview/webview.h
+++ b/chromium/ui/views/controls/webview/webview.h
@@ -149,8 +149,6 @@ class WEBVIEW_EXPORT WebView : public View,
void DidDestroyFullscreenWidget() override;
void DidToggleFullscreenModeForTab(bool entered_fullscreen,
bool will_cause_resize) override;
- void DidAttachInterstitialPage() override;
- void DidDetachInterstitialPage() override;
// Workaround for MSVC++ linker bug/feature that requires
// instantiation of the inline IPC::Listener methods in all translation units.
void OnChannelConnected(int32_t peer_id) override {}
diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc
index 5bfd431308d..5c28d3091f8 100644
--- a/chromium/ui/views/controls/webview/webview_unittest.cc
+++ b/chromium/ui/views/controls/webview/webview_unittest.cc
@@ -159,8 +159,8 @@ class WebViewUnitTest : public views::test::WidgetTest {
// child.
top_level_widget_ = CreateTopLevelFramelessPlatformWidget();
top_level_widget_->SetBounds(gfx::Rect(0, 10, 100, 100));
- View* const contents_view = new View();
- top_level_widget_->SetContentsView(contents_view);
+ View* const contents_view =
+ top_level_widget_->SetContentsView(std::make_unique<View>());
web_view_ = new WebView(browser_context_.get());
web_view_->SetBoundsRect(gfx::Rect(contents_view->size()));
contents_view->AddChildView(web_view_);