summaryrefslogtreecommitdiff
path: root/chromium/ui/views/controls
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/views/controls')
-rw-r--r--chromium/ui/views/controls/button/button.cc6
-rw-r--r--chromium/ui/views/controls/button/button.h4
-rw-r--r--chromium/ui/views/controls/button/button_unittest.cc4
-rw-r--r--chromium/ui/views/controls/button/checkbox.cc2
-rw-r--r--chromium/ui/views/controls/button/checkbox.h2
-rw-r--r--chromium/ui/views/controls/button/image_button.cc16
-rw-r--r--chromium/ui/views/controls/button/image_button.h9
-rw-r--r--chromium/ui/views/controls/button/image_button_unittest.cc2
-rw-r--r--chromium/ui/views/controls/button/label_button.cc5
-rw-r--r--chromium/ui/views/controls/button/label_button.h5
-rw-r--r--chromium/ui/views/controls/button/label_button_label.cc12
-rw-r--r--chromium/ui/views/controls/button/label_button_label.h9
-rw-r--r--chromium/ui/views/controls/button/label_button_unittest.cc49
-rw-r--r--chromium/ui/views/controls/button/menu_button.h13
-rw-r--r--chromium/ui/views/controls/button/menu_button_controller.cc4
-rw-r--r--chromium/ui/views/controls/button/menu_button_controller.h2
-rw-r--r--chromium/ui/views/controls/combobox/combobox.cc8
-rw-r--r--chromium/ui/views/controls/combobox/combobox.h5
-rw-r--r--chromium/ui/views/controls/dot_indicator.cc75
-rw-r--r--chromium/ui/views/controls/dot_indicator.h43
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.cc17
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.h5
-rw-r--r--chromium/ui/views/controls/focus_ring.cc6
-rw-r--r--chromium/ui/views/controls/focus_ring.h4
-rw-r--r--chromium/ui/views/controls/image_view.h4
-rw-r--r--chromium/ui/views/controls/label.cc103
-rw-r--r--chromium/ui/views/controls/label.h25
-rw-r--r--chromium/ui/views/controls/label_unittest.cc40
-rw-r--r--chromium/ui/views/controls/link.cc4
-rw-r--r--chromium/ui/views/controls/link.h2
-rw-r--r--chromium/ui/views/controls/menu/README.md198
-rw-r--r--chromium/ui/views/controls/menu/menu_config.h7
-rw-r--r--chromium/ui/views/controls/menu/menu_config_chromeos.cc6
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.cc155
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.h26
-rw-r--r--chromium/ui/views/controls/menu/menu_controller_unittest.cc68
-rw-r--r--chromium/ui/views/controls/menu/menu_delegate.cc19
-rw-r--r--chromium/ui/views/controls/menu/menu_delegate.h19
-rw-r--r--chromium/ui/views/controls/menu/menu_host.cc5
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.cc78
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.h15
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view_unittest.cc62
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter.cc2
-rw-r--r--chromium/ui/views/controls/menu/menu_runner.cc2
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm290
-rw-r--r--chromium/ui/views/controls/menu/menu_scroll_view_container.cc30
-rw-r--r--chromium/ui/views/controls/menu/menu_separator.cc6
-rw-r--r--chromium/ui/views/controls/menu/menu_separator.h2
-rw-r--r--chromium/ui/views/controls/menu/menu_types.h1
-rw-r--r--chromium/ui/views/controls/menu/native_menu_win.cc13
-rw-r--r--chromium/ui/views/controls/menu/native_menu_win.h5
-rw-r--r--chromium/ui/views/controls/menu/new_badge.h2
-rw-r--r--chromium/ui/views/controls/menu/submenu_view.cc3
-rw-r--r--chromium/ui/views/controls/menu/submenu_view.h3
-rw-r--r--chromium/ui/views/controls/message_box_view.cc6
-rw-r--r--chromium/ui/views/controls/message_box_view.h3
-rw-r--r--chromium/ui/views/controls/native/native_view_host_mac.mm7
-rw-r--r--chromium/ui/views/controls/prefix_selector.cc18
-rw-r--r--chromium/ui/views/controls/prefix_selector.h12
-rw-r--r--chromium/ui/views/controls/prefix_selector_unittest.cc32
-rw-r--r--chromium/ui/views/controls/progress_bar.cc15
-rw-r--r--chromium/ui/views/controls/scroll_view.cc123
-rw-r--r--chromium/ui/views/controls/scroll_view.h37
-rw-r--r--chromium/ui/views/controls/scroll_view_unittest.cc82
-rw-r--r--chromium/ui/views/controls/separator.cc4
-rw-r--r--chromium/ui/views/controls/styled_label_unittest.cc8
-rw-r--r--chromium/ui/views/controls/tabbed_pane/DIR_METADATA3
-rw-r--r--chromium/ui/views/controls/tabbed_pane/OWNERS2
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc5
-rw-r--r--chromium/ui/views/controls/table/table_header.cc9
-rw-r--r--chromium/ui/views/controls/table/table_header.h9
-rw-r--r--chromium/ui/views/controls/table/table_view.cc58
-rw-r--r--chromium/ui/views/controls/table/table_view.h13
-rw-r--r--chromium/ui/views/controls/table/table_view_unittest.cc73
-rw-r--r--chromium/ui/views/controls/textarea/textarea.cc114
-rw-r--r--chromium/ui/views/controls/textarea/textarea.h36
-rw-r--r--chromium/ui/views/controls/textarea/textarea_unittest.cc299
-rw-r--r--chromium/ui/views/controls/textfield/DIR_METADATA3
-rw-r--r--chromium/ui/views/controls/textfield/OWNERS2
-rw-r--r--chromium/ui/views/controls/textfield/textfield.cc468
-rw-r--r--chromium/ui/views/controls/textfield/textfield.h62
-rw-r--r--chromium/ui/views/controls/textfield/textfield_controller.cc7
-rw-r--r--chromium/ui/views/controls/textfield/textfield_controller.h5
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model.cc48
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model.h21
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model_unittest.cc5
-rw-r--r--chromium/ui/views/controls/textfield/textfield_unittest.cc1064
-rw-r--r--chromium/ui/views/controls/textfield/textfield_unittest.h184
-rw-r--r--chromium/ui/views/controls/theme_tracking_image_view.cc36
-rw-r--r--chromium/ui/views/controls/theme_tracking_image_view.h41
-rw-r--r--chromium/ui/views/controls/tree/tree_view_unittest.cc17
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.cc2
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.h7
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_chromeos.cc79
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_chromeos.h51
-rw-r--r--chromium/ui/views/controls/webview/OWNERS1
-rw-r--r--chromium/ui/views/controls/webview/web_contents_set_background_color.cc27
-rw-r--r--chromium/ui/views/controls/webview/web_contents_set_background_color.h5
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view.cc8
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view.h16
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view_unittest.cc5
-rw-r--r--chromium/ui/views/controls/webview/webview.cc85
-rw-r--r--chromium/ui/views/controls/webview/webview.h23
-rw-r--r--chromium/ui/views/controls/webview/webview_unittest.cc26
104 files changed, 3455 insertions, 1313 deletions
diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc
index e1f389d6e1c..506241b8e2c 100644
--- a/chromium/ui/views/controls/button/button.cc
+++ b/chromium/ui/views/controls/button/button.cc
@@ -347,7 +347,7 @@ void Button::SetHighlighted(bool bubble_visible) {
nullptr);
}
-PropertyChangedSubscription Button::AddStateChangedCallback(
+base::CallbackListSubscription Button::AddStateChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&state_, std::move(callback));
}
@@ -591,7 +591,7 @@ Button::Button(PressedCallback callback)
: AnimationDelegateViews(this),
callback_(std::move(callback)),
ink_drop_base_color_(gfx::kPlaceholderColor) {
- SetFocusBehavior(PlatformStyle::DefaultFocusBehavior());
+ SetFocusBehavior(PlatformStyle::kDefaultFocusBehavior);
SetProperty(kIsButtonProperty, true);
hover_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(150));
SetInstallFocusRingOnFocus(true);
@@ -695,7 +695,7 @@ ADD_PROPERTY_METADATA(PressedCallback, Callback)
ADD_PROPERTY_METADATA(bool, AnimateOnStateChange)
ADD_PROPERTY_METADATA(bool, HasInkDropActionOnClick)
ADD_PROPERTY_METADATA(bool, HideInkDropWhenShowingContextMenu)
-ADD_PROPERTY_METADATA(SkColor, InkDropBaseColor)
+ADD_PROPERTY_METADATA(SkColor, InkDropBaseColor, metadata::SkColorConverter)
ADD_PROPERTY_METADATA(bool, InstallFocusRingOnFocus)
ADD_PROPERTY_METADATA(bool, RequestFocusOnPress)
ADD_PROPERTY_METADATA(ButtonState, State)
diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h
index d2ae90642d6..6e059dcb12c 100644
--- a/chromium/ui/views/controls/button/button.h
+++ b/chromium/ui/views/controls/button/button.h
@@ -182,7 +182,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
// Highlights the ink drop for the button.
void SetHighlighted(bool bubble_visible);
- PropertyChangedSubscription AddStateChangedCallback(
+ base::CallbackListSubscription AddStateChangedCallback(
PropertyChangedCallback callback);
// Overridden from View:
@@ -355,7 +355,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
// ButtonController.
std::unique_ptr<ButtonController> button_controller_;
- PropertyChangedSubscription enabled_changed_subscription_{
+ base::CallbackListSubscription enabled_changed_subscription_{
AddEnabledChangedCallback(base::BindRepeating(&Button::OnEnabledChanged,
base::Unretained(this)))};
diff --git a/chromium/ui/views/controls/button/button_unittest.cc b/chromium/ui/views/controls/button/button_unittest.cc
index 8955695509f..74cce33fc2b 100644
--- a/chromium/ui/views/controls/button/button_unittest.cc
+++ b/chromium/ui/views/controls/button/button_unittest.cc
@@ -155,8 +155,8 @@ class TestButtonObserver {
bool highlighted_changed_ = false;
bool state_changed_ = false;
- PropertyChangedSubscription highlighted_changed_subscription_;
- PropertyChangedSubscription state_changed_subscription_;
+ base::CallbackListSubscription highlighted_changed_subscription_;
+ base::CallbackListSubscription state_changed_subscription_;
DISALLOW_COPY_AND_ASSIGN(TestButtonObserver);
};
diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc
index a0c317ab848..3b67a434853 100644
--- a/chromium/ui/views/controls/button/checkbox.cc
+++ b/chromium/ui/views/controls/button/checkbox.cc
@@ -83,7 +83,7 @@ bool Checkbox::GetChecked() const {
return checked_;
}
-PropertyChangedSubscription Checkbox::AddCheckedChangedCallback(
+base::CallbackListSubscription Checkbox::AddCheckedChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&checked_, callback);
}
diff --git a/chromium/ui/views/controls/button/checkbox.h b/chromium/ui/views/controls/button/checkbox.h
index 86cc2f134db..57fcacdc1ab 100644
--- a/chromium/ui/views/controls/button/checkbox.h
+++ b/chromium/ui/views/controls/button/checkbox.h
@@ -36,7 +36,7 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
virtual void SetChecked(bool checked);
bool GetChecked() const;
- PropertyChangedSubscription AddCheckedChangedCallback(
+ base::CallbackListSubscription AddCheckedChangedCallback(
PropertyChangedCallback callback) WARN_UNUSED_RESULT;
void SetMultiLine(bool multi_line);
diff --git a/chromium/ui/views/controls/button/image_button.cc b/chromium/ui/views/controls/button/image_button.cc
index 0762225e0ba..98c22e003c2 100644
--- a/chromium/ui/views/controls/button/image_button.cc
+++ b/chromium/ui/views/controls/button/image_button.cc
@@ -13,6 +13,7 @@
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/scoped_canvas.h"
+#include "ui/views/background.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/painter.h"
#include "ui/views/widget/widget.h"
@@ -246,6 +247,11 @@ void ToggleImageButton::SetToggledImage(ButtonState image_state,
}
}
+void ToggleImageButton::SetToggledBackground(std::unique_ptr<Background> b) {
+ toggled_background_ = std::move(b);
+ SchedulePaint();
+}
+
base::string16 ToggleImageButton::GetToggledTooltipText() const {
return toggled_tooltip_text_;
}
@@ -290,6 +296,15 @@ void ToggleImageButton::SetImage(ButtonState image_state,
PreferredSizeChanged();
}
+void ToggleImageButton::OnPaintBackground(gfx::Canvas* canvas) {
+ if (toggled_ && toggled_background_) {
+ TRACE_EVENT0("views", "View::OnPaintBackground");
+ toggled_background_->Paint(canvas, this);
+ } else {
+ ImageButton::OnPaintBackground(canvas);
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// ToggleImageButton, View overrides:
@@ -342,6 +357,7 @@ END_METADATA
BEGIN_METADATA(ToggleImageButton, ImageButton)
ADD_PROPERTY_METADATA(bool, Toggled)
+ADD_PROPERTY_METADATA(std::unique_ptr<Background>, ToggledBackground)
ADD_PROPERTY_METADATA(base::string16, ToggledTooltipText)
ADD_PROPERTY_METADATA(base::string16, ToggledAccessibleName)
END_METADATA
diff --git a/chromium/ui/views/controls/button/image_button.h b/chromium/ui/views/controls/button/image_button.h
index 6ea3fdfb46e..6bf0541e6bc 100644
--- a/chromium/ui/views/controls/button/image_button.h
+++ b/chromium/ui/views/controls/button/image_button.h
@@ -138,6 +138,11 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
// before the button is toggled.
void SetToggledImage(ButtonState state, const gfx::ImageSkia* image);
+ // Like Views::SetBackground(), but to set the background color used for the
+ // "has been toggled" state.
+ void SetToggledBackground(std::unique_ptr<Background> b);
+ Background* GetToggledBackground() const { return toggled_background_.get(); }
+
// Get/Set the tooltip text displayed when the button is toggled.
base::string16 GetToggledTooltipText() const;
void SetToggledTooltipText(const base::string16& tooltip);
@@ -153,6 +158,7 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
// Overridden from View:
base::string16 GetTooltipText(const gfx::Point& p) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+ void OnPaintBackground(gfx::Canvas* canvas) override;
private:
// The parent class's images_ member is used for the current images,
@@ -163,6 +169,8 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
// True if the button is currently toggled.
bool toggled_ = false;
+ std::unique_ptr<Background> toggled_background_;
+
// The parent class's tooltip_text_ is displayed when not toggled, and
// this one is shown when toggled.
base::string16 toggled_tooltip_text_;
@@ -176,6 +184,7 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
BEGIN_VIEW_BUILDER(VIEWS_EXPORT, ToggleImageButton, ImageButton)
VIEW_BUILDER_PROPERTY(bool, Toggled)
+VIEW_BUILDER_PROPERTY(std::unique_ptr<Background>, ToggledBackground)
VIEW_BUILDER_PROPERTY(base::string16, ToggledTooltipText)
VIEW_BUILDER_PROPERTY(base::string16, ToggledAccessibleName)
END_VIEW_BUILDER
diff --git a/chromium/ui/views/controls/button/image_button_unittest.cc b/chromium/ui/views/controls/button/image_button_unittest.cc
index c5830643357..cf3c9310c3d 100644
--- a/chromium/ui/views/controls/button/image_button_unittest.cc
+++ b/chromium/ui/views/controls/button/image_button_unittest.cc
@@ -43,7 +43,7 @@ using ImageButtonTest = ViewsTestBase;
TEST_F(ImageButtonTest, FocusBehavior) {
ImageButton button;
- EXPECT_EQ(PlatformStyle::DefaultFocusBehavior(), button.GetFocusBehavior());
+ EXPECT_EQ(PlatformStyle::kDefaultFocusBehavior, button.GetFocusBehavior());
}
TEST_F(ImageButtonTest, Basics) {
diff --git a/chromium/ui/views/controls/button/label_button.cc b/chromium/ui/views/controls/button/label_button.cc
index 563bc70f496..e8b6cd6b290 100644
--- a/chromium/ui/views/controls/button/label_button.cc
+++ b/chromium/ui/views/controls/button/label_button.cc
@@ -246,6 +246,9 @@ gfx::Size LabelButton::CalculatePreferredSize() const {
// Account for the label only when the button is not shrinking down to hide
// the label entirely.
if (!shrinking_down_label_) {
+ if (!label_->GetMultiLine() && max_size_.width() > 0)
+ label_->SetMaximumWidthSingleLine(max_size_.width() - size.width());
+
const gfx::Size preferred_label_size = label_->GetPreferredSize();
size.Enlarge(preferred_label_size.width(), 0);
size.SetToMax(
@@ -476,7 +479,7 @@ void LabelButton::AddedToWidget() {
}
void LabelButton::RemovedFromWidget() {
- paint_as_active_subscription_.reset();
+ paint_as_active_subscription_ = {};
}
void LabelButton::OnFocus() {
diff --git a/chromium/ui/views/controls/button/label_button.h b/chromium/ui/views/controls/button/label_button.h
index 5dbf8e3e9c8..39887eb2625 100644
--- a/chromium/ui/views/controls/button/label_button.h
+++ b/chromium/ui/views/controls/button/label_button.h
@@ -263,10 +263,9 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// UI direction).
gfx::HorizontalAlignment horizontal_alignment_ = gfx::ALIGN_LEFT;
- std::unique_ptr<Widget::PaintAsActiveCallbackList::Subscription>
- paint_as_active_subscription_;
+ base::CallbackListSubscription paint_as_active_subscription_;
- PropertyChangedSubscription flip_canvas_on_paint_subscription_ =
+ base::CallbackListSubscription flip_canvas_on_paint_subscription_ =
AddFlipCanvasOnPaintForRTLUIChangedCallback(
base::BindRepeating(&LabelButton::FlipCanvasOnPaintForRTLUIChanged,
base::Unretained(this)));
diff --git a/chromium/ui/views/controls/button/label_button_label.cc b/chromium/ui/views/controls/button/label_button_label.cc
index a1ec3993631..7bb2053e0e3 100644
--- a/chromium/ui/views/controls/button/label_button_label.cc
+++ b/chromium/ui/views/controls/button/label_button_label.cc
@@ -4,6 +4,8 @@
#include "ui/views/controls/button/label_button_label.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+
namespace views {
namespace internal {
@@ -35,15 +37,19 @@ void LabelButtonLabel::OnEnabledChanged() {
}
void LabelButtonLabel::SetColorForEnableState() {
- if (GetEnabled() ? requested_enabled_color_ : requested_disabled_color_) {
- Label::SetEnabledColor(GetEnabled() ? *requested_enabled_color_
- : *requested_disabled_color_);
+ const base::Optional<SkColor>& color =
+ GetEnabled() ? requested_enabled_color_ : requested_disabled_color_;
+ if (color) {
+ Label::SetEnabledColor(*color);
} else {
int style = GetEnabled() ? style::STYLE_PRIMARY : style::STYLE_DISABLED;
Label::SetEnabledColor(style::GetColor(*this, GetTextContext(), style));
}
}
+BEGIN_METADATA(LabelButtonLabel, Label)
+END_METADATA
+
} // namespace internal
} // namespace views
diff --git a/chromium/ui/views/controls/button/label_button_label.h b/chromium/ui/views/controls/button/label_button_label.h
index 88ab37127f1..0c7d0c0291f 100644
--- a/chromium/ui/views/controls/button/label_button_label.h
+++ b/chromium/ui/views/controls/button/label_button_label.h
@@ -6,12 +6,12 @@
#define UI_VIEWS_CONTROLS_BUTTON_LABEL_BUTTON_LABEL_H_
#include "base/bind.h"
-#include "base/macros.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/controls/label.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/views_export.h"
namespace views {
@@ -22,7 +22,10 @@ namespace internal {
// views::LabelButton.
class VIEWS_EXPORT LabelButtonLabel : public Label {
public:
+ METADATA_HEADER(LabelButtonLabel);
LabelButtonLabel(const base::string16& text, int text_context);
+ LabelButtonLabel(const LabelButtonLabel&) = delete;
+ LabelButtonLabel& operator=(const LabelButtonLabel&) = delete;
~LabelButtonLabel() override;
// Set an explicit disabled color. This will stop the Label responding to
@@ -42,12 +45,10 @@ class VIEWS_EXPORT LabelButtonLabel : public Label {
base::Optional<SkColor> requested_disabled_color_;
base::Optional<SkColor> requested_enabled_color_;
- PropertyChangedSubscription enabled_changed_subscription_ =
+ base::CallbackListSubscription enabled_changed_subscription_ =
AddEnabledChangedCallback(
base::BindRepeating(&LabelButtonLabel::OnEnabledChanged,
base::Unretained(this)));
-
- DISALLOW_COPY_AND_ASSIGN(LabelButtonLabel);
};
} // namespace internal
diff --git a/chromium/ui/views/controls/button/label_button_unittest.cc b/chromium/ui/views/controls/button/label_button_unittest.cc
index 0f55418d4ba..5b50d07d5c1 100644
--- a/chromium/ui/views/controls/button/label_button_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_unittest.cc
@@ -5,6 +5,7 @@
#include "ui/views/controls/button/label_button.h"
#include <algorithm>
+#include <string>
#include <utility>
#include "base/command_line.h"
@@ -143,7 +144,7 @@ class LabelButtonTest : public test::WidgetTest {
};
TEST_F(LabelButtonTest, FocusBehavior) {
- EXPECT_EQ(PlatformStyle::DefaultFocusBehavior(), button_->GetFocusBehavior());
+ EXPECT_EQ(PlatformStyle::kDefaultFocusBehavior, button_->GetFocusBehavior());
}
TEST_F(LabelButtonTest, Init) {
@@ -193,7 +194,9 @@ TEST_F(LabelButtonTest, Label) {
// Clamp the size to a maximum value.
button_->SetText(long_text);
button_->SetMaxSize(gfx::Size(short_text_width, 1));
- EXPECT_EQ(button_->GetPreferredSize(), gfx::Size(short_text_width, 1));
+ const gfx::Size preferred_size = button_->GetPreferredSize();
+ EXPECT_LE(preferred_size.width(), short_text_width);
+ EXPECT_EQ(1, preferred_size.height());
// Clamp the size to a minimum value.
button_->SetText(short_text);
@@ -203,6 +206,48 @@ TEST_F(LabelButtonTest, Label) {
gfx::Size(long_text_width, font_list.GetHeight() * 2));
}
+// Tests LabelButton's usage of SetMaximumWidthSingleLine.
+TEST_F(LabelButtonTest, LabelPreferredSizeWithMaxWidth) {
+ const std::string text_cases[] = {
+ {"The"},
+ {"The quick"},
+ {"The quick brown"},
+ {"The quick brown fox"},
+ {"The quick brown fox jumps"},
+ {"The quick brown fox jumps over"},
+ {"The quick brown fox jumps over the"},
+ {"The quick brown fox jumps over the lazy"},
+ {"The quick brown fox jumps over the lazy dog"},
+ };
+
+ const int width_cases[] = {
+ 10, 30, 50, 70, 90, 110, 130, 170, 200, 500,
+ };
+
+ for (bool set_image = false; button_->GetImage(Button::STATE_NORMAL).isNull();
+ set_image = true) {
+ if (set_image)
+ button_->SetImage(Button::STATE_NORMAL, CreateTestImage(16, 16));
+
+ bool preferred_size_is_sometimes_narrower_than_max = false;
+
+ for (size_t i = 0; i < base::size(text_cases); ++i) {
+ for (size_t j = 0; j < base::size(width_cases); ++j) {
+ button_->SetText(ASCIIToUTF16(text_cases[i]));
+ button_->SetMaxSize(gfx::Size(width_cases[j], 30));
+
+ const gfx::Size preferred_size = button_->GetPreferredSize();
+ EXPECT_LE(preferred_size.width(), width_cases[j]);
+
+ if (preferred_size.width() < width_cases[j])
+ preferred_size_is_sometimes_narrower_than_max = true;
+ }
+ }
+
+ EXPECT_TRUE(preferred_size_is_sometimes_narrower_than_max);
+ }
+}
+
TEST_F(LabelButtonTest, LabelShrinkDown) {
ASSERT_TRUE(button_->GetText().empty());
diff --git a/chromium/ui/views/controls/button/menu_button.h b/chromium/ui/views/controls/button/menu_button.h
index 44a66ee24fd..a05b763a36a 100644
--- a/chromium/ui/views/controls/button/menu_button.h
+++ b/chromium/ui/views/controls/button/menu_button.h
@@ -5,9 +5,10 @@
#ifndef UI_VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_
#define UI_VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_
-#include "base/macros.h"
#include "base/strings/string16.h"
#include "ui/views/controls/button/label_button.h"
+#include "ui/views/metadata/metadata_header_macros.h"
+#include "ui/views/metadata/view_factory.h"
namespace views {
@@ -23,10 +24,11 @@ class MenuButtonController;
class VIEWS_EXPORT MenuButton : public LabelButton {
public:
METADATA_HEADER(MenuButton);
-
explicit MenuButton(PressedCallback callback = PressedCallback(),
const base::string16& text = base::string16(),
int button_context = style::CONTEXT_BUTTON);
+ MenuButton(const MenuButton&) = delete;
+ MenuButton& operator=(const MenuButton&) = delete;
~MenuButton() override;
MenuButtonController* button_controller() const {
@@ -41,10 +43,13 @@ class VIEWS_EXPORT MenuButton : public LabelButton {
private:
MenuButtonController* menu_button_controller_;
-
- DISALLOW_COPY_AND_ASSIGN(MenuButton);
};
+BEGIN_VIEW_BUILDER(VIEWS_EXPORT, MenuButton, LabelButton)
+END_VIEW_BUILDER
+
} // namespace views
+DEFINE_VIEW_BUILDER(VIEWS_EXPORT, MenuButton)
+
#endif // UI_VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_
diff --git a/chromium/ui/views/controls/button/menu_button_controller.cc b/chromium/ui/views/controls/button/menu_button_controller.cc
index f621f8b1620..ee45a1bf1c3 100644
--- a/chromium/ui/views/controls/button/menu_button_controller.cc
+++ b/chromium/ui/views/controls/button/menu_button_controller.cc
@@ -223,7 +223,7 @@ bool MenuButtonController::Activate(const ui::Event* event) {
// mouse target during the mouse press we explicitly set the mouse handler
// to NULL.
static_cast<internal::RootView*>(button()->GetWidget()->GetRootView())
- ->SetMouseHandler(nullptr);
+ ->SetMouseAndGestureHandler(nullptr);
DCHECK(increment_pressed_lock_called_ == nullptr);
// Observe if IncrementPressedLocked() was called so we can trigger the
@@ -319,7 +319,7 @@ void MenuButtonController::DecrementPressedLocked() {
// If this was the last lock, manually reset state to the desired state.
if (pressed_lock_count_ == 0) {
menu_closed_time_ = TimeTicks::Now();
- state_changed_subscription_.reset();
+ state_changed_subscription_ = {};
LabelButton::ButtonState desired_state = Button::STATE_NORMAL;
if (should_disable_after_press_) {
desired_state = Button::STATE_DISABLED;
diff --git a/chromium/ui/views/controls/button/menu_button_controller.h b/chromium/ui/views/controls/button/menu_button_controller.h
index a7129e6ceea..6c0afacf5a7 100644
--- a/chromium/ui/views/controls/button/menu_button_controller.h
+++ b/chromium/ui/views/controls/button/menu_button_controller.h
@@ -115,7 +115,7 @@ class VIEWS_EXPORT MenuButtonController : public ButtonController {
bool should_disable_after_press_ = false;
// Subscribes to state changes on the button while pressed lock is engaged.
- views::PropertyChangedSubscription state_changed_subscription_;
+ base::CallbackListSubscription state_changed_subscription_;
base::WeakPtrFactory<MenuButtonController> weak_factory_{this};
diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc
index f911fa3aeb0..8a1bd561de0 100644
--- a/chromium/ui/views/controls/combobox/combobox.cc
+++ b/chromium/ui/views/controls/combobox/combobox.cc
@@ -309,14 +309,16 @@ void Combobox::SetOwnedModel(std::unique_ptr<ui::ComboboxModel> model) {
void Combobox::SetModel(ui::ComboboxModel* model) {
DCHECK(model) << "After construction, the model must not be null.";
- if (model_)
- observer_.Remove(model_);
+ if (model_) {
+ DCHECK(observation_.IsObservingSource(model_));
+ observation_.Reset();
+ }
model_ = model;
if (model_) {
menu_model_ = std::make_unique<ComboboxMenuModel>(this, model_);
- observer_.Add(model_);
+ observation_.Observe(model_);
SetSelectedIndex(model_->GetDefaultIndex());
OnComboboxModelChanged(model_);
}
diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h
index 812810cc3b3..fa781688732 100644
--- a/chromium/ui/views/controls/combobox/combobox.h
+++ b/chromium/ui/views/controls/combobox/combobox.h
@@ -9,7 +9,7 @@
#include <utility>
#include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "ui/base/models/combobox_model.h"
@@ -224,7 +224,8 @@ class VIEWS_EXPORT Combobox : public View,
// The focus ring for this Combobox.
FocusRing* focus_ring_ = nullptr;
- ScopedObserver<ui::ComboboxModel, ui::ComboboxModelObserver> observer_{this};
+ base::ScopedObservation<ui::ComboboxModel, ui::ComboboxModelObserver>
+ observation_{this};
DISALLOW_COPY_AND_ASSIGN(Combobox);
};
diff --git a/chromium/ui/views/controls/dot_indicator.cc b/chromium/ui/views/controls/dot_indicator.cc
new file mode 100644
index 00000000000..d1b3e4410d8
--- /dev/null
+++ b/chromium/ui/views/controls/dot_indicator.cc
@@ -0,0 +1,75 @@
+// Copyright 2021 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/dot_indicator.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+
+namespace views {
+
+DotIndicator::~DotIndicator() = default;
+
+// static
+DotIndicator* DotIndicator::Install(View* parent) {
+ auto dot = base::WrapUnique<DotIndicator>(new DotIndicator());
+ dot->SetPaintToLayer();
+ dot->layer()->SetFillsBoundsOpaquely(false);
+ dot->SetVisible(false);
+ return parent->AddChildView(std::move(dot));
+}
+
+void DotIndicator::SetColor(SkColor dot_color, SkColor border_color) {
+ dot_color_ = dot_color;
+ border_color_ = border_color;
+ SchedulePaint();
+}
+
+void DotIndicator::Show() {
+ SetVisible(true);
+}
+
+void DotIndicator::Hide() {
+ SetVisible(false);
+}
+
+DotIndicator::DotIndicator() {
+ // Don't allow the view to process events.
+ SetCanProcessEventsWithinSubtree(false);
+}
+
+void DotIndicator::OnPaint(gfx::Canvas* canvas) {
+ canvas->SaveLayerAlpha(SK_AlphaOPAQUE);
+
+ DCHECK_EQ(width(), height());
+ float radius = width() / 2.0f;
+ const float scale = canvas->UndoDeviceScaleFactor();
+ const int kStrokeWidthPx = 1;
+ gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
+ center.Scale(scale);
+
+ // Fill the center.
+ cc::PaintFlags flags;
+ flags.setColor(dot_color_);
+ flags.setAntiAlias(true);
+ canvas->DrawCircle(center, scale * radius - kStrokeWidthPx, flags);
+
+ // Draw the border.
+ flags.setColor(border_color_);
+ flags.setStyle(cc::PaintFlags::kStroke_Style);
+ flags.setStrokeWidth(kStrokeWidthPx * scale);
+ canvas->DrawCircle(center, scale * radius - kStrokeWidthPx / 2.0f, flags);
+}
+
+BEGIN_METADATA(DotIndicator, View)
+END_METADATA
+
+} // namespace views
diff --git a/chromium/ui/views/controls/dot_indicator.h b/chromium/ui/views/controls/dot_indicator.h
new file mode 100644
index 00000000000..f3473de642b
--- /dev/null
+++ b/chromium/ui/views/controls/dot_indicator.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_DOT_INDICATOR_H_
+#define UI_VIEWS_CONTROLS_DOT_INDICATOR_H_
+
+#include "base/macros.h"
+#include "ui/views/view.h"
+
+namespace views {
+
+// Dot indicator that can be added to a view, usually used as a status
+// indicator.
+class VIEWS_EXPORT DotIndicator : public View {
+ public:
+ METADATA_HEADER(DotIndicator);
+ DotIndicator(DotIndicator&) = delete;
+ DotIndicator& operator=(const DotIndicator&) = delete;
+ ~DotIndicator() override;
+
+ // Create a DotIndicator and adds it to |parent|. The returned dot indicator
+ // is owned by the |parent|.
+ static DotIndicator* Install(View* parent);
+
+ void SetColor(SkColor dot_color, SkColor border_color);
+
+ void Show();
+ void Hide();
+
+ private:
+ DotIndicator();
+
+ // View:
+ void OnPaint(gfx::Canvas* canvas) override;
+
+ SkColor dot_color_ = SK_ColorRED;
+ SkColor border_color_ = SK_ColorWHITE;
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_DOT_INDICATOR_H_
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
index b9203707279..696e8ae29c5 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -52,6 +52,7 @@
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/layout/layout_provider.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/style/typography.h"
@@ -64,6 +65,8 @@ namespace {
class Arrow : public Button {
public:
+ METADATA_HEADER(Arrow);
+
explicit Arrow(PressedCallback callback) : Button(std::move(callback)) {
// Similar to Combobox's TransparentButton.
SetFocusBehavior(FocusBehavior::NEVER);
@@ -73,6 +76,8 @@ class Arrow : public Button {
SetInkDropMode(InkDropMode::ON);
SetHasInkDropActionOnClick(true);
}
+ Arrow(const Arrow&) = delete;
+ Arrow& operator=(const Arrow&) = delete;
~Arrow() override = default;
double GetAnimationValue() const {
@@ -115,10 +120,11 @@ class Arrow : public Button {
if (GetEnabled())
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen);
}
-
- DISALLOW_COPY_AND_ASSIGN(Arrow);
};
+BEGIN_METADATA(Arrow, Button)
+END_METADATA
+
} // namespace
// Adapts a ui::ComboboxModel to a ui::MenuModel to be used by EditableCombobox.
@@ -136,7 +142,7 @@ class EditableCombobox::EditableComboboxMenuModel
filter_on_edit_(filter_on_edit),
show_on_empty_(show_on_empty) {
UpdateItemsShown();
- observer_.Add(combobox_model_);
+ observation_.Observe(combobox_model_);
}
~EditableComboboxMenuModel() override = default;
@@ -252,7 +258,8 @@ class EditableCombobox::EditableComboboxMenuModel
// When false, UpdateItemsShown doesn't do anything.
bool update_items_shown_enabled_ = true;
- ScopedObserver<ui::ComboboxModel, ui::ComboboxModelObserver> observer_{this};
+ base::ScopedObservation<ui::ComboboxModel, ui::ComboboxModelObserver>
+ observation_{this};
DISALLOW_COPY_AND_ASSIGN(EditableComboboxMenuModel);
};
@@ -322,7 +329,7 @@ EditableCombobox::EditableCombobox(
show_on_empty_(show_on_empty),
showing_password_text_(type != Type::kPassword) {
SetModel(std::move(combobox_model));
- observer_.Add(textfield_);
+ observation_.Observe(textfield_);
textfield_->set_controller(this);
textfield_->SetFontList(GetFontList());
textfield_->SetTextInputType((type == Type::kPassword)
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.h b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
index a87863dd9cc..d2e68f3e257 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
@@ -10,12 +10,13 @@
#include "base/callback.h"
#include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "ui/base/ui_base_types.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/layout/animating_layout_manager.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
@@ -187,7 +188,7 @@ class VIEWS_EXPORT EditableCombobox
bool dropdown_blocked_for_animation_ = false;
- ScopedObserver<View, ViewObserver> observer_{this};
+ base::ScopedObservation<View, ViewObserver> observation_{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 e34713127b3..95d55d5347a 100644
--- a/chromium/ui/views/controls/focus_ring.cc
+++ b/chromium/ui/views/controls/focus_ring.cc
@@ -113,13 +113,13 @@ void FocusRing::ViewHierarchyChanged(
if (details.is_add) {
// Need to start observing the parent.
- view_observer_.Add(details.parent);
+ view_observation_.Observe(details.parent);
RefreshLayer();
- } else if (view_observer_.IsObserving(details.parent)) {
+ } else if (view_observation_.IsObservingSource(details.parent)) {
// This view is being removed from its parent. It needs to remove itself
// 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);
+ view_observation_.Reset();
}
}
diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h
index c3e42b6a4da..7882505d6ec 100644
--- a/chromium/ui/views/controls/focus_ring.h
+++ b/chromium/ui/views/controls/focus_ring.h
@@ -7,7 +7,7 @@
#include <memory>
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/view.h"
@@ -96,7 +96,7 @@ 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};
+ base::ScopedObservation<View, ViewObserver> view_observation_{this};
DISALLOW_COPY_AND_ASSIGN(FocusRing);
};
diff --git a/chromium/ui/views/controls/image_view.h b/chromium/ui/views/controls/image_view.h
index c39d116b6a6..1eebb3f4252 100644
--- a/chromium/ui/views/controls/image_view.h
+++ b/chromium/ui/views/controls/image_view.h
@@ -91,7 +91,9 @@ class VIEWS_EXPORT ImageView : public View {
void OnPaintImage(gfx::Canvas* canvas);
- // Gets an ImageSkia to paint that has proper rep for |scale|.
+ // Gets an ImageSkia to paint that has proper rep for |scale|. Note that if
+ // there is no existing rep of `scale`, we will utilize the image resize
+ // operation to create one. The resize may be time consuming for a big image.
gfx::ImageSkia GetPaintImage(float scale);
// Returns true if |img| is the same as the last image we painted. This is
diff --git a/chromium/ui/views/controls/label.cc b/chromium/ui/views/controls/label.cc
index 70370d27c47..d7f4226a37f 100644
--- a/chromium/ui/views/controls/label.cc
+++ b/chromium/ui/views/controls/label.cc
@@ -108,6 +108,19 @@ void Label::SetText(const base::string16& new_text) {
stored_selection_range_ = gfx::Range::InvalidRange();
}
+void Label::SetAccessibleName(const base::string16& name) {
+ if (name == accessible_name_)
+ return;
+ accessible_name_ = name;
+ OnPropertyChanged(&accessible_name_, kPropertyEffectsNone);
+ NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
+}
+
+const base::string16& Label::GetAccessibleName() const {
+ return accessible_name_.empty() ? full_text_->GetDisplayText()
+ : accessible_name_;
+}
+
int Label::GetTextContext() const {
return text_context_;
}
@@ -116,8 +129,11 @@ void Label::SetTextContext(int text_context) {
if (text_context == text_context_)
return;
text_context_ = text_context;
+ full_text_->SetFontList(style::GetFont(text_context_, text_style_));
+ full_text_->SetMinLineHeight(GetLineHeight());
+ ClearDisplayText();
UpdateColorsFromTheme();
- OnPropertyChanged(&text_context_, views::kPropertyEffectsPaint);
+ OnPropertyChanged(&text_context_, kPropertyEffectsPreferredSizeChanged);
}
int Label::GetTextStyle() const {
@@ -129,12 +145,30 @@ void Label::SetTextStyle(int style) {
return;
text_style_ = style;
- // TODO(pkasting): Seems like potentially |full_text_|'s font list and line
- // height should be updated here?
+ full_text_->SetFontList(style::GetFont(text_context_, text_style_));
+ full_text_->SetMinLineHeight(GetLineHeight());
+ ClearDisplayText();
UpdateColorsFromTheme();
OnPropertyChanged(&text_style_, kPropertyEffectsPreferredSizeChanged);
}
+void Label::SetTextStyleRange(int style, const gfx::Range& range) {
+ if (style == text_style_ || !range.IsValid() || range.is_empty() ||
+ !gfx::Range(0, GetText().size()).Contains(range))
+ return;
+
+ const auto details = style::GetFontDetails(text_context_, style);
+ // This function is not prepared to handle style requests that vary by
+ // anything other than weight.
+ DCHECK_EQ(details.typeface,
+ style::GetFontDetails(text_context_, text_style_).typeface);
+ DCHECK_EQ(details.size_delta,
+ style::GetFontDetails(text_context_, text_style_).size_delta);
+ full_text_->ApplyWeight(details.weight, range);
+ ClearDisplayText();
+ PreferredSizeChanged();
+}
+
bool Label::GetAutoColorReadabilityEnabled() const {
return auto_color_readability_enabled_;
}
@@ -370,6 +404,7 @@ void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
if (elide_behavior_ == elide_behavior)
return;
elide_behavior_ = elide_behavior;
+ UpdateFullTextElideBehavior();
ClearDisplayText();
OnPropertyChanged(&elide_behavior_, kPropertyEffectsPreferredSizeChanged);
}
@@ -417,6 +452,16 @@ void Label::SetMaximumWidth(int max_width) {
OnPropertyChanged(&max_width_, kPropertyEffectsPreferredSizeChanged);
}
+void Label::SetMaximumWidthSingleLine(int max_width) {
+ DCHECK(!GetMultiLine());
+ if (max_width_single_line_ == max_width)
+ return;
+ max_width_single_line_ = max_width;
+ UpdateFullTextElideBehavior();
+ OnPropertyChanged(&max_width_single_line_,
+ kPropertyEffectsPreferredSizeChanged);
+}
+
bool Label::GetCollapseWhenHidden() const {
return collapse_when_hidden_;
}
@@ -504,7 +549,7 @@ std::vector<gfx::Rect> Label::GetSubstringBounds(const gfx::Range& range) {
return substring_bounds;
}
-views::PropertyChangedSubscription Label::AddTextChangedCallback(
+base::CallbackListSubscription Label::AddTextChangedCallback(
views::PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&full_text_ + kLabelText,
std::move(callback));
@@ -619,7 +664,7 @@ void Label::GetAccessibleNodeData(ui::AXNodeData* node_data) {
else
node_data->role = ax::mojom::Role::kStaticText;
- node_data->SetName(full_text_->GetDisplayText());
+ node_data->SetName(GetAccessibleName());
}
base::string16 Label::GetTooltipText(const gfx::Point& p) const {
@@ -642,17 +687,13 @@ std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const {
: elide_behavior_;
std::unique_ptr<gfx::RenderText> render_text =
- gfx::RenderText::CreateRenderText();
+ full_text_->CreateInstanceOfSameStyle(GetText());
render_text->SetHorizontalAlignment(GetHorizontalAlignment());
render_text->SetVerticalAlignment(GetVerticalAlignment());
- render_text->SetDirectionalityMode(full_text_->directionality_mode());
render_text->SetElideBehavior(elide_behavior);
render_text->SetObscured(GetObscured());
render_text->SetMinLineHeight(GetLineHeight());
- render_text->SetFontList(font_list());
render_text->set_shadows(GetShadows());
- render_text->SetCursorEnabled(false);
- render_text->SetText(GetText());
const bool multiline = GetMultiLine();
render_text->SetMultiline(multiline);
render_text->SetMaxLines(multiline ? GetMaxLines() : 0);
@@ -684,7 +725,8 @@ void Label::PaintText(gfx::Canvas* canvas) {
if (display_text_)
display_text_->Draw(canvas);
-#if DCHECK_IS_ON() && !defined(OS_CHROMEOS) && !BUILDFLAG(IS_LACROS)
+#if DCHECK_IS_ON() && !BUILDFLAG(IS_CHROMEOS_ASH) && \
+ !BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/1139395): Enable this DCHECK on ChromeOS and LaCrOS by
// fixing either this check (to correctly idenfify more paints-on-opaque
// cases), refactoring parents to use background() or by fixing
@@ -773,7 +815,9 @@ bool Label::OnMousePressed(const ui::MouseEvent& event) {
GetFocusManager()->SetFocusedView(this);
}
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
if (event.IsOnlyMiddleMouseButton() && GetFocusManager() && !had_focus)
GetFocusManager()->SetFocusedView(this);
#endif
@@ -960,7 +1004,9 @@ bool Label::PasteSelectionClipboard() {
}
void Label::UpdateSelectionClipboard() {
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
if (!GetObscured()) {
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kSelection)
.WriteText(GetSelectedText());
@@ -1031,9 +1077,7 @@ void Label::Init(const base::string16& text,
full_text_->SetCursorEnabled(false);
full_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS);
full_text_->SetMinLineHeight(GetLineHeight());
- // NOTE: |full_text_| should not be elided at all. This is used to keep
- // some properties and to compute the size of the string.
- full_text_->SetElideBehavior(gfx::NO_ELIDE);
+ UpdateFullTextElideBehavior();
full_text_->SetDirectionalityMode(directionality_mode);
SetText(text);
@@ -1067,6 +1111,15 @@ gfx::Size Label::GetTextSize() const {
gfx::Size size;
if (GetText().empty()) {
size = gfx::Size(0, GetLineHeight());
+ } else if (max_width_single_line_ > 0) {
+ DCHECK(!GetMultiLine());
+ // Enable eliding during text width calculation. This allows the RenderText
+ // to report an accurate width given the constraints and how it determines
+ // to elide the text. If we simply clamp the width to the max after the
+ // fact, then there may be some empty space left over *after* an ellipsis.
+ full_text_->SetDisplayRect(
+ gfx::Rect(0, 0, max_width_single_line_ - GetInsets().width(), 0));
+ size = full_text_->GetStringSize();
} else {
// Cancel the display rect of |full_text_|. The display rect may be
// specified in GetHeightForWidth(), and specifying empty Rect cancels
@@ -1181,16 +1234,25 @@ void Label::BuildContextMenuContents() {
IDS_APP_SELECT_ALL);
}
+void Label::UpdateFullTextElideBehavior() {
+ // In single line mode when a max width has been set, |full_text_| uses
+ // elision to properly calculate the text size. Otherwise, it is not elided.
+ full_text_->SetElideBehavior(max_width_single_line_ > 0 ? elide_behavior_
+ : gfx::NO_ELIDE);
+}
+
BEGIN_METADATA(Label, View)
ADD_PROPERTY_METADATA(base::string16, Text)
ADD_PROPERTY_METADATA(int, TextContext)
ADD_PROPERTY_METADATA(int, TextStyle)
ADD_PROPERTY_METADATA(bool, AutoColorReadabilityEnabled)
-ADD_PROPERTY_METADATA(SkColor, EnabledColor)
+ADD_PROPERTY_METADATA(SkColor, EnabledColor, metadata::SkColorConverter)
ADD_PROPERTY_METADATA(gfx::ElideBehavior, ElideBehavior)
-ADD_PROPERTY_METADATA(SkColor, BackgroundColor)
-ADD_PROPERTY_METADATA(SkColor, SelectionTextColor)
-ADD_PROPERTY_METADATA(SkColor, SelectionBackgroundColor)
+ADD_PROPERTY_METADATA(SkColor, BackgroundColor, metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(SkColor, SelectionTextColor, metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(SkColor,
+ SelectionBackgroundColor,
+ metadata::SkColorConverter)
ADD_PROPERTY_METADATA(bool, SubpixelRenderingEnabled)
ADD_PROPERTY_METADATA(bool, SkipSubpixelRenderingOpacityCheck)
ADD_PROPERTY_METADATA(gfx::ShadowValues, Shadows)
@@ -1205,6 +1267,7 @@ ADD_PROPERTY_METADATA(base::string16, TooltipText)
ADD_PROPERTY_METADATA(bool, HandlesTooltips)
ADD_PROPERTY_METADATA(bool, CollapseWhenHidden)
ADD_PROPERTY_METADATA(int, MaximumWidth)
+ADD_PROPERTY_METADATA(base::string16, AccessibleName)
END_METADATA
} // namespace views
diff --git a/chromium/ui/views/controls/label.h b/chromium/ui/views/controls/label.h
index 0a6bac23a2f..793189681a6 100644
--- a/chromium/ui/views/controls/label.h
+++ b/chromium/ui/views/controls/label.h
@@ -90,6 +90,12 @@ class VIEWS_EXPORT Label : public View,
const base::string16& GetText() const;
virtual void SetText(const base::string16& text);
+ // Set the accessibility name that will be announced by the screen reader.
+ // If this function is not called, the screen reader defaults to verbalizing
+ // the text value.
+ void SetAccessibleName(const base::string16& name);
+ const base::string16& GetAccessibleName() const;
+
// Where the label appears in the UI. Passed in from the constructor. This is
// a value from views::style::TextContext or an enum that extends it.
int GetTextContext() const;
@@ -100,6 +106,10 @@ class VIEWS_EXPORT Label : public View,
int GetTextStyle() const;
void SetTextStyle(int style);
+ // Applies |style| to a specific |range|. This is unimplemented for styles
+ // that vary from the global text style by anything besides weight.
+ void SetTextStyleRange(int style, const gfx::Range& range);
+
// 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 +187,10 @@ class VIEWS_EXPORT Label : public View,
int GetMaxLines() const;
void SetMaxLines(int max_lines);
+ // If single-line, a non-zero value will help determine the amount of space
+ // needed *after* elision, which may be less than the passed |max_width|.
+ void SetMaximumWidthSingleLine(int max_width);
+
// Returns the number of lines required to render all text. The actual number
// of rendered lines might be limited by |max_lines_| which elides the rest.
size_t GetRequiredLines() const;
@@ -280,7 +294,7 @@ class VIEWS_EXPORT Label : public View,
// within the |range|. See gfx::RenderText.
std::vector<gfx::Rect> GetSubstringBounds(const gfx::Range& range);
- views::PropertyChangedSubscription AddTextChangedCallback(
+ base::CallbackListSubscription AddTextChangedCallback(
views::PropertyChangedCallback callback) WARN_UNUSED_RESULT;
// View:
@@ -409,6 +423,9 @@ class VIEWS_EXPORT Label : public View,
// Builds |context_menu_contents_|.
void BuildContextMenuContents();
+ // Updates the elide behavior used by |full_text_|.
+ void UpdateFullTextElideBehavior();
+
int text_context_;
int text_style_;
base::Optional<int> line_height_;
@@ -450,10 +467,16 @@ class VIEWS_EXPORT Label : public View,
// Whether to collapse the label when it's not visible.
bool collapse_when_hidden_ = false;
int fixed_width_ = 0;
+ // This is used only for multi-line mode.
int max_width_ = 0;
+ // This is used in single-line mode.
+ int max_width_single_line_ = 0;
std::unique_ptr<SelectionController> selection_controller_;
+ // Accessibility data.
+ base::string16 accessible_name_;
+
// Context menu related members.
ui::SimpleMenuModel context_menu_contents_;
std::unique_ptr<views::MenuRunner> context_menu_runner_;
diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc
index 740d4cd7c59..4e58e3bdee7 100644
--- a/chromium/ui/views/controls/label_unittest.cc
+++ b/chromium/ui/views/controls/label_unittest.cc
@@ -16,6 +16,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/test/gtest_util.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
@@ -604,7 +605,9 @@ TEST_F(LabelTest, TooltipProperty) {
}
TEST_F(LabelTest, Accessibility) {
- label()->SetText(ASCIIToUTF16("My special text."));
+ const base::string16 accessible_name = ASCIIToUTF16("A11y text.");
+
+ label()->SetText(ASCIIToUTF16("Displayed text."));
ui::AXNodeData node_data;
label()->GetAccessibleNodeData(&node_data);
@@ -613,6 +616,33 @@ TEST_F(LabelTest, Accessibility) {
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
EXPECT_FALSE(
node_data.HasIntAttribute(ax::mojom::IntAttribute::kRestriction));
+
+ // Setting a custom accessible name overrides the displayed text in
+ // screen reader announcements.
+ label()->SetAccessibleName(accessible_name);
+
+ label()->GetAccessibleNodeData(&node_data);
+ EXPECT_EQ(accessible_name,
+ node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
+ EXPECT_NE(label()->GetText(),
+ node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
+
+ // Changing the displayed text will not impact the non-empty accessible name.
+ label()->SetText(ASCIIToUTF16("Different displayed Text."));
+
+ label()->GetAccessibleNodeData(&node_data);
+ EXPECT_EQ(accessible_name,
+ node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
+ EXPECT_NE(label()->GetText(),
+ node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
+
+ // Clearing the accessible name will cause the screen reader to default to
+ // verbalizing the displayed text.
+ label()->SetAccessibleName(ASCIIToUTF16(""));
+
+ label()->GetAccessibleNodeData(&node_data);
+ EXPECT_EQ(label()->GetText(),
+ node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
}
TEST_F(LabelTest, TextChangeWithoutLayout) {
@@ -1083,7 +1113,7 @@ TEST_F(LabelTest, GetSubstringBounds) {
}
// TODO(crbug.com/1139395): Enable on ChromeOS along with the DCHECK in Label.
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
// Ensures DCHECK for subpixel rendering on transparent layer is working.
TEST_F(LabelTest, ChecksSubpixelRenderingOntoOpaqueSurface) {
View view;
@@ -1115,7 +1145,7 @@ TEST_F(LabelTest, ChecksSubpixelRenderingOntoOpaqueSurface) {
view.SetBackground(CreateSolidBackground(SK_ColorWHITE));
label->OnPaint(&canvas);
}
-#endif // !defined(OS_CHROMEOS)
+#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(LabelSelectionTest, Selectable) {
// By default, labels don't support text selection.
@@ -1413,7 +1443,9 @@ TEST_F(LabelSelectionTest, MouseDragWord) {
EXPECT_STR_EQ("drag word", GetSelectedText());
}
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
// Verify selection clipboard behavior on text selection.
TEST_F(LabelSelectionTest, SelectionClipboard) {
label()->SetText(ASCIIToUTF16("Label selection clipboard"));
diff --git a/chromium/ui/views/controls/link.cc b/chromium/ui/views/controls/link.cc
index 0ef69a5d335..d11926195e6 100644
--- a/chromium/ui/views/controls/link.cc
+++ b/chromium/ui/views/controls/link.cc
@@ -230,7 +230,9 @@ void Link::ConfigureFocus() {
}
BEGIN_METADATA(Link, Label)
-ADD_READONLY_PROPERTY_METADATA(SkColor, Color)
+ADD_READONLY_PROPERTY_METADATA(SkColor,
+ Color,
+ views::metadata::SkColorConverter)
END_METADATA
} // namespace views
diff --git a/chromium/ui/views/controls/link.h b/chromium/ui/views/controls/link.h
index 6db2cd4a3b4..056489620af 100644
--- a/chromium/ui/views/controls/link.h
+++ b/chromium/ui/views/controls/link.h
@@ -96,7 +96,7 @@ class VIEWS_EXPORT Link : public Label {
// The color when the link is neither pressed nor disabled.
base::Optional<SkColor> requested_enabled_color_;
- PropertyChangedSubscription enabled_changed_subscription_;
+ base::CallbackListSubscription enabled_changed_subscription_;
// Whether the link text should use underline style regardless of enabled or
// focused state.
diff --git a/chromium/ui/views/controls/menu/README.md b/chromium/ui/views/controls/menu/README.md
new file mode 100644
index 00000000000..588641c7a58
--- /dev/null
+++ b/chromium/ui/views/controls/menu/README.md
@@ -0,0 +1,198 @@
+# Views Menus
+
+This document outlines how menus are implemented in Views. You should probably
+read the [Views overview] if you haven't yet.
+
+[TOC]
+
+## Key Classes in //ui/views/controls/menu
+
+ * [MenuController]
+ * [SubmenuView]
+ * [MenuRunner]
+ * [MenuHost]
+ * [MenuItemView]
+
+## Key Classes Elsewhere
+
+ * [ui::MenuModel]
+
+## Creating & Showing A Menu
+
+Conceptually, client code uses Views menus like so:
+
+ MenuRunner runner(model, ...);
+ runner.RunMenuAt(...);
+
+The constructor of MenuRunner does little actual work; it is primarily concerned
+with choosing an appropriate [MenuRunnerImplInterface] depending on the platform
+and the requested type of menu. This document will mostly focus on
+MenuRunnerImpl, which runs a Views menu; the only other MenuRunnerImplInterface
+implementation, named [MenuRunnerImplCocoa], runs a native Mac menu instead
+which has extremely different behavior.
+
+The call to `MenuRunnerImpl::RunMenuAt` is responsible for determining which
+MenuController will control this menu invocation, which may involve either
+nesting the menu off an existing parent menu or cancelling an existing menu
+first. In this context, "nesting" specifically means a fully separate menu that
+runs while another menu is still running. Here's an example menu:
+
+ Item 1
+ Item 1.1
+ Item 1.2
+ Item 2
+ Item 3
+
+Mousing over Item 1 would open the submenu containing Items 1.1 and 1.2, but
+that submenu is controlled by the same MenuController instance as the original
+menu containing Item 1. However, if the user right-clicked on Item 2 to open its
+context menu, that would create a nested MenuController to run the context menu.
+At any given time, only one MenuController can be active, since menus are
+conceptually modal.
+
+Once that's done, `MenuController::Run` takes over.
+
+That method has many responsibilities, but explaining them will require a brief
+detour into the structure of MenuController itself.
+
+## MenuController
+
+MenuController is the "uber class" of the Views menu system. It has a large
+amount of state and nearly all interesting logic is delegated to it, but the most important things it stores are:
+
+ * The current and pending [MenuController::State]
+ * A stack of prior states for parent MenuControllers, if the current
+ MenuController is nested
+ * The active mouse view, which is the view (if there is one) that the mouse is
+ over; this is only used for dragging
+ * The "hot tracked button" - this is a button which is a descendant view of
+ the selected MenuItemView, used to deliver mouse events to both that subview
+ and the MenuItemView itself, which can be useful for (eg) drawing hover
+ effects
+ * Various timers and locations used for animation & display
+ * The "pre-target handler", which listens for any mouse click anywhere and
+ closes the menu if they are outside the menu
+
+The MenuController also contains the logic to allow dragging within menus, which
+is used in bookmark menus to support reordering items.
+
+## Menu Running & Selection
+
+Back to `MenuController::Run`: after setting up some state, this method invokes
+`MenuController::SetSelection`. That method is responsible for changing the
+selection from the current selected MenuItemView (which may be nullptr) to a
+provided new MenuItemView (which again may be nullptr), which involves closing
+and opening submenus as needed and notifying accessibility events up and down
+the menu tree. For example, if the current menu is:
+
+ A [open]
+ A1
+ A2 [open]
+ A2.1
+ A2.2 [selected]
+ A3
+ A3.1
+
+and the new selected node is A3.1, `MenuController::SetSelection` would be
+responsible for:
+
+ * Unselecting A2.2
+ * Closing A2
+ * Opening A3
+ * Selecting A3.1
+
+Note that, if the selection is not immediate, this fills the pending selection
+but not the actual selection until later, to allow for menus to animate out and
+in a bit; if this isn't done, menus flicker in and out as the mouse moves over
+multiple items with submenus.
+
+When the selection *is* immediate, as it is when invoked by
+`MenuController::Run`, SetSelection will end up opening the root menu for this
+MenuController (via `MenuController::CommitPendingSelection`). This method
+handles actually closing and opening menus as needed to make the pending
+selection visible. Since there is no existing open menu during the initial call,
+practically this calls `MenuController::OpenMenu` on the first selectable item
+in the menu.
+
+That method, in turn, ultimately calls `SubmenuView::ShowAt` on the root
+MenuItemView's submenu, which is the root submenu. That method constructs a
+MenuHost, which is a special kind of [Widget] that contains a SubmenuView. The
+MenuHost is responsible for:
+
+ * Detecting touch events anywhere (not just in the menu) and forwarding them
+ to the MenuController to maybe cancel the menu if they are out of bounds
+ * Managing mouse capture if the menu has capture (some but not all menus do
+ this)
+
+Once `MenuHost::InitMenuHost` and `MenuHost::ShowMenuHost` are done, the menu is
+on screen!
+
+## Painting
+
+Once a menu is on screen, its view tree looks like this:
+
+ MenuHost (Widget)
+ MenuHostRootView
+ MenuItemView (root)
+ MenuScrollViewContainer
+ SubmenuView
+ MenuItemView
+ MenuItemView
+ ...
+
+None of these have any visuals except the MenuItemView, which contains:
+
+ * A title, a secondary title, and an icon
+ * A checkmark/radio marker
+ * "Minor" text and icon
+ * Arbitrary other child views
+
+None of these things are separate Views - instead they are drawn directly by
+`MenuItemView::OnPaint`, so the painting step for MenuItemView is also the
+layout step.
+
+## Event Handling
+
+MenuController centralizes input event handling for menus. Key events enter
+MenuController from multiple sources:
+
+ * SubmenuView, via the normal Views event path
+ * MenuHostRootView, which overrides Views hit-testing to ensure that mouse
+ events within the MenuHost always go to the controller
+ * Widget, which will dispatch incoming key events to a running MenuController
+ if there is one - this allows the Widget to retain activation while the menu handles events
+
+MenuController::OnWillDispatchKeyEvent is one of the entry points to this logic,
+but MenuController::OnKeyPressed handles all of the navigation and functional
+keys, and MenuController::SelectByChar implements incremental searching and menu
+accelerators.
+
+Mouse events are mostly handled via the normal Views flow, except that
+pre-target handlers can deliver mouse events to MenuController that did not
+actually target the menu's widget, which MenuController uses to close itself in
+response to those events.
+
+Events can also be "reposted", where the menu decides to dismiss itself in
+response to them and then propagate them up to the menu's parent widget. This
+behavior is platform-specific and tricky, so it's best to read the code to
+understand it.
+
+## Positioning
+
+TODO(ellyjones): How does this work?
+
+## Drag & Drop
+
+TODO(ellyjones): How does this work?
+
+[Views overview]: ../../../../docs/ui/views/overview.md
+[MenuController]: menu_controller.h
+[MenuController::State]: https://source.chromium.org/chromium/chromium/src/+/master:ui/views/controls/menu/menu_controller.h;drc=ce8d17ff494cf684f35c8ff64cb6bd0947adcf46;bpv=0;bpt=1;l=289
+[MenuHost]: menu_host.h
+[MenuItemView]: menu_item_view.h
+[MenuRunner]: menu_runner.h
+[MenuRunnerImplCocoa]: menu_runner_impl_cocoa.h
+[MenuRunnerImplInterface]: menu_runner_impl_interface.h
+[SubmenuView]: submenu_view.h
+[ui::MenuModel]: ../../base/models/menu_model.h
+[Widget]: ../widget/widget.h
diff --git a/chromium/ui/views/controls/menu/menu_config.h b/chromium/ui/views/controls/menu/menu_config.h
index c84d1377c94..ade60b9aa37 100644
--- a/chromium/ui/views/controls/menu/menu_config.h
+++ b/chromium/ui/views/controls/menu/menu_config.h
@@ -184,8 +184,11 @@ struct VIEWS_EXPORT MenuConfig {
// Height of child MenuItemViews for touchable menus.
int touchable_menu_height = 36;
- // Width of touchable menus.
- int touchable_menu_width = 256;
+ // Minimum width of touchable menus.
+ int touchable_menu_min_width = 256;
+
+ // Maximum width of touchable menus.
+ int touchable_menu_max_width = 352;
// Shadow elevation of touchable menus.
int touchable_menu_shadow_elevation = 12;
diff --git a/chromium/ui/views/controls/menu/menu_config_chromeos.cc b/chromium/ui/views/controls/menu/menu_config_chromeos.cc
index 085c1adb937..fd19d1223c7 100644
--- a/chromium/ui/views/controls/menu/menu_config_chromeos.cc
+++ b/chromium/ui/views/controls/menu/menu_config_chromeos.cc
@@ -4,6 +4,7 @@
#include "ui/views/controls/menu/menu_config.h"
+#include "build/chromeos_buildflags.h"
#include "ui/views/controls/menu/menu_image_util.h"
namespace views {
@@ -22,8 +23,13 @@ void MenuConfig::Init() {
offset_context_menus = true;
corner_radius = 2;
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+ // TODO(crbug/1129012): Change to false once pop-up menus have shadows.
+ use_outer_border = true;
+#else
// In Ash, the border is provided by the shadow.
use_outer_border = false;
+#endif
}
} // namespace views
diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc
index ca6146dd053..3beb5516e12 100644
--- a/chromium/ui/views/controls/menu/menu_controller.cc
+++ b/chromium/ui/views/controls/menu/menu_controller.cc
@@ -17,7 +17,8 @@
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
-#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
+#include "build/chromeos_buildflags.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
@@ -361,7 +362,7 @@ class MenuController::MenuScrollTask {
}
DCHECK(part.submenu);
SubmenuView* new_menu = part.submenu;
- bool new_is_up = (part.type == MenuController::MenuPart::SCROLL_UP);
+ bool new_is_up = (part.type == MenuController::MenuPart::Type::kScrollUp);
if (new_menu == submenu_ && is_scrolling_up_ == new_is_up)
return;
@@ -707,7 +708,7 @@ bool MenuController::OnMouseDragged(SubmenuView* source,
return true;
}
MenuItemView* mouse_menu = nullptr;
- if (part.type == MenuPart::MENU_ITEM) {
+ if (part.type == MenuPart::Type::kMenuItem) {
// If there is no menu target, but a submenu target, then we are interacting
// with an empty menu item within a submenu. These cannot become selection
// targets for mouse interaction, so do not attempt to update selection.
@@ -718,7 +719,7 @@ bool MenuController::OnMouseDragged(SubmenuView* source,
mouse_menu = part.menu;
SetSelection(part.menu ? part.menu : state_.item, SELECTION_OPEN_SUBMENU);
}
- } else if (part.type == MenuPart::NONE) {
+ } else if (part.type == MenuPart::Type::kNone) {
// If there is a sibling menu, show it. Otherwise, if the user has selected
// a menu item with no accompanying sibling menu or submenu, move selection
// back to the parent menu item.
@@ -761,7 +762,7 @@ void MenuController::OnMouseReleased(SubmenuView* source,
DCHECK(state_.item);
possible_drag_ = false;
MenuPart part = GetMenuPart(source, event.location());
- if (event.IsRightMouseButton() && part.type == MenuPart::MENU_ITEM) {
+ if (event.IsRightMouseButton() && part.type == MenuPart::Type::kMenuItem) {
MenuItemView* menu = part.menu;
// |menu| is null means this event is from an empty menu or a separator.
// If it is from an empty menu, use parent context menu instead of that.
@@ -817,7 +818,7 @@ void MenuController::OnMouseReleased(SubmenuView* source,
Accept(part.menu, event.flags());
return;
}
- } else if (part.type == MenuPart::MENU_ITEM) {
+ } else if (part.type == MenuPart::Type::kMenuItem) {
// User either clicked on empty space, or a menu that has children.
SetSelection(part.menu ? part.menu : state_.item,
SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
@@ -913,7 +914,7 @@ void MenuController::OnGestureEvent(SubmenuView* source,
SetSelectionOnPointerDown(source, event);
event->StopPropagation();
} else if (event->type() == ui::ET_GESTURE_LONG_PRESS) {
- if (part.type == MenuPart::MENU_ITEM && part.menu) {
+ if (part.type == MenuPart::Type::kMenuItem && part.menu) {
gfx::Point screen_location(event->location());
View::ConvertPointToScreen(source->GetScrollViewContainer(),
&screen_location);
@@ -928,14 +929,14 @@ void MenuController::OnGestureEvent(SubmenuView* source,
Accept(part.menu, event->flags());
}
event->StopPropagation();
- } else if (part.type == MenuPart::MENU_ITEM) {
+ } else if (part.type == MenuPart::Type::kMenuItem) {
// User either tapped on empty space, or a menu that has children.
SetSelection(part.menu ? part.menu : state_.item,
SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
event->StopPropagation();
}
} else if (event->type() == ui::ET_GESTURE_TAP_CANCEL && part.menu &&
- part.type == MenuPart::MENU_ITEM) {
+ part.type == MenuPart::Type::kMenuItem) {
// Move the selection to the parent menu so that the selection in the
// current menu is unset. Make sure the submenu remains open by sending the
// appropriate SetSelectionTypes flags.
@@ -959,7 +960,7 @@ void MenuController::OnTouchEvent(SubmenuView* source, ui::TouchEvent* event) {
if (event->type() == ui::ET_TOUCH_PRESSED) {
MenuPart part = GetMenuPart(source, event->location());
- if (part.type == MenuPart::NONE) {
+ if (part.type == MenuPart::Type::kNone) {
RepostEventAndCancel(source, event);
event->SetHandled();
}
@@ -1059,8 +1060,9 @@ int MenuController::OnDragUpdated(SubmenuView* source,
query_menu_item = menu_item->GetParentMenuItem();
drop_position = MenuDelegate::DropPosition::kOn;
}
- drop_operation = menu_item->GetDelegate()->GetDropOperation(
- query_menu_item, event, &drop_position);
+ drop_operation =
+ static_cast<int>(menu_item->GetDelegate()->GetDropOperation(
+ query_menu_item, event, &drop_position));
// If the menu has a submenu, schedule the submenu to open.
SetSelection(menu_item, menu_item->HasSubmenu() ? SELECTION_OPEN_SUBMENU
@@ -1086,8 +1088,9 @@ void MenuController::OnDragExited(SubmenuView* source) {
}
}
-int MenuController::OnPerformDrop(SubmenuView* source,
- const ui::DropTargetEvent& event) {
+ui::mojom::DragOperation MenuController::OnPerformDrop(
+ SubmenuView* source,
+ const ui::DropTargetEvent& event) {
DCHECK(drop_target_);
// NOTE: the delegate may delete us after invoking OnPerformDrop, as such
// we don't call cancel here.
@@ -1125,7 +1128,7 @@ int MenuController::OnPerformDrop(SubmenuView* source,
void MenuController::OnDragEnteredScrollButton(SubmenuView* source,
bool is_up) {
MenuPart part;
- part.type = is_up ? MenuPart::SCROLL_UP : MenuPart::SCROLL_DOWN;
+ part.type = is_up ? MenuPart::Type::kScrollUp : MenuPart::Type::kScrollDown;
part.submenu = source;
UpdateScrolling(part);
@@ -1431,8 +1434,8 @@ void MenuController::SetSelectionOnPointerDown(SubmenuView* source,
(event->flags() & ui::EF_FROM_TOUCH))
return;
- if (part.type == MenuPart::NONE ||
- (part.type == MenuPart::MENU_ITEM && part.menu &&
+ if (part.type == MenuPart::Type::kNone ||
+ (part.type == MenuPart::Type::kMenuItem && part.menu &&
part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) {
// Remember the time stamp of the current (press down) event. The owner can
// then use this to figure out if this menu was finished with the same click
@@ -1478,7 +1481,8 @@ void MenuController::StartDrag(SubmenuView* source,
float raster_scale = ScaleFactorForDragFromWidget(source->GetWidget());
gfx::Canvas canvas(item->size(), raster_scale, false /* opaque */);
item->PaintButton(&canvas, MenuItemView::PaintButtonMode::kForDrag);
- gfx::ImageSkia image(gfx::ImageSkiaRep(canvas.GetBitmap(), raster_scale));
+ gfx::ImageSkia image =
+ gfx::ImageSkia::CreateFromBitmap(canvas.GetBitmap(), raster_scale);
std::unique_ptr<OSExchangeData> data(std::make_unique<OSExchangeData>());
item->GetDelegate()->WriteDragData(item, data.get());
@@ -1869,11 +1873,11 @@ bool MenuController::IsScrollButtonAt(SubmenuView* source,
scroll_view->GetEventHandlerForPoint(gfx::Point(x, y));
if (child_under_mouse && child_under_mouse->GetEnabled()) {
if (child_under_mouse == scroll_view->scroll_up_button()) {
- *part = MenuPart::SCROLL_UP;
+ *part = MenuPart::Type::kScrollUp;
return true;
}
if (child_under_mouse == scroll_view->scroll_down_button()) {
- *part = MenuPart::SCROLL_DOWN;
+ *part = MenuPart::Type::kScrollDown;
return true;
}
}
@@ -1928,7 +1932,7 @@ bool MenuController::GetMenuPartByScreenCoordinateImpl(
gfx::Point menu_loc = screen_loc;
View::ConvertPointFromScreen(menu, &menu_loc);
part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y());
- part->type = MenuPart::MENU_ITEM;
+ part->type = MenuPart::Type::kMenuItem;
part->submenu = menu;
part->should_submenu_show =
part->submenu && part->menu &&
@@ -2124,7 +2128,7 @@ void MenuController::OpenMenuImpl(MenuItemView* item, bool show) {
View::ConvertPointFromScreen(item->submenu_->GetWidget()->GetRootView(),
&mouse_pos);
MenuPart part_under_mouse = GetMenuPart(item->submenu_, mouse_pos);
- if (part_under_mouse.type != MenuPart::NONE)
+ if (part_under_mouse.type != MenuPart::Type::kNone)
menu_open_mouse_loc_ = mouse_pos;
}
@@ -2378,7 +2382,7 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
bool* is_leading) {
DCHECK(item);
- // Assume we can honor prefer_leading.
+ // Assume we can honor `prefer_leading`.
*is_leading = prefer_leading;
SubmenuView* submenu = item->GetSubmenu();
@@ -2427,26 +2431,58 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
menu_size.set_width(std::min(
menu_size.width(), item->GetDelegate()->GetMaxWidthForMenu(item)));
- if (state_.anchor == MenuAnchorPosition::kBubbleAbove) {
- // Align the left edges of the menu and anchor, and the bottom of the menu
- // with the top of the anchor.
+ if (state_.anchor == MenuAnchorPosition::kBubbleAbove ||
+ state_.anchor == MenuAnchorPosition::kBubbleBelow) {
+ // Align the left edges of the menu and anchor.
x = std::max(monitor_bounds.x(),
anchor_bounds.x() - border_and_shadow_insets.left());
- y = anchor_bounds.y() - menu_size.height() +
- border_and_shadow_insets.bottom() -
- menu_config.touchable_anchor_offset;
-
- // Align the right of the menu with the right of the anchor.
if (x + menu_size.width() > monitor_bounds.right()) {
+ // Align the right of the menu with the right of the anchor.
x = anchor_bounds.right() - menu_size.width() +
border_and_shadow_insets.right();
}
- // Align the top of the menu with the bottom of the anchor.
- if (y < monitor_bounds.y()) {
- y = anchor_bounds.bottom() - border_and_shadow_insets.top() +
- menu_config.touchable_anchor_offset;
+
+ const int y_for_menu_below = anchor_bounds.bottom() -
+ border_and_shadow_insets.top() +
+ menu_config.touchable_anchor_offset;
+ const int y_for_menu_above = anchor_bounds.y() - menu_size.height() +
+ border_and_shadow_insets.bottom() -
+ menu_config.touchable_anchor_offset;
+ if (state_.anchor == MenuAnchorPosition::kBubbleAbove) {
+ y = y_for_menu_above;
+ item->set_actual_menu_position(MenuPosition::kAboveBounds);
+ if (y < monitor_bounds.y()) {
+ y = y_for_menu_below;
+ item->set_actual_menu_position(MenuPosition::kBelowBounds);
+ }
+ } else if (state_.anchor == MenuAnchorPosition::kBubbleBelow) {
+ // Respect the previous MenuPosition. The menu contents could change
+ // while the menu is shown, the menu position should not change.
+ const bool able_to_show_menu_below =
+ (y_for_menu_below + menu_size.height() <= monitor_bounds.bottom());
+ const bool able_to_show_menu_above =
+ y_for_menu_above >= monitor_bounds.y();
+ if (item->actual_menu_position() == MenuPosition::kBelowBounds &&
+ able_to_show_menu_below) {
+ y = y_for_menu_below;
+ } else if (item->actual_menu_position() == MenuPosition::kAboveBounds &&
+ able_to_show_menu_above) {
+ y = y_for_menu_above;
+ } else if (able_to_show_menu_below) {
+ y = y_for_menu_below;
+ item->set_actual_menu_position(MenuPosition::kBelowBounds);
+ } else if (able_to_show_menu_above) {
+ // No room below, but there is room above. Show above the anchor.
+ // Align the bottom of the menu with the bottom of the anchor.
+ y = y_for_menu_above;
+ item->set_actual_menu_position(MenuPosition::kAboveBounds);
+ } else {
+ // No room above or below. Show as low as possible. Align the bottom
+ // of the menu with the bottom of the screen.
+ y = monitor_bounds.bottom() - menu_size.height();
+ item->set_actual_menu_position(MenuPosition::kBestFit);
+ }
}
- item->set_actual_menu_position(MenuPosition::kAboveBounds);
} else if (state_.anchor == MenuAnchorPosition::kBubbleLeft ||
state_.anchor == MenuAnchorPosition::kBubbleRight) {
if (state_.anchor == MenuAnchorPosition::kBubbleLeft) {
@@ -2473,32 +2509,34 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
}
}
- const int y_below = anchor_bounds.y() - border_and_shadow_insets.top();
- const int y_above = anchor_bounds.bottom() - menu_size.height() +
- border_and_shadow_insets.bottom();
+ const int y_for_menu_below =
+ anchor_bounds.y() - border_and_shadow_insets.top();
+ const int y_for_menu_above = anchor_bounds.bottom() - menu_size.height() +
+ border_and_shadow_insets.bottom();
- const bool able_to_show_below =
- (y_below + menu_size.height() <= monitor_bounds.bottom());
- const bool able_to_show_above = (y_above >= monitor_bounds.y());
+ const bool able_to_show_menu_below =
+ (y_for_menu_below + menu_size.height() <= monitor_bounds.bottom());
+ const bool able_to_show_menu_above =
+ (y_for_menu_above >= monitor_bounds.y());
// Respect the actual menu position calculated earlier if possible, to
// prevent changing positions during menu size updates.
if (item->actual_menu_position() == MenuPosition::kBelowBounds &&
- able_to_show_below) {
- y = y_below;
+ able_to_show_menu_below) {
+ y = y_for_menu_below;
} else if (item->actual_menu_position() == MenuPosition::kAboveBounds &&
- able_to_show_above) {
- y = y_above;
- } else if (able_to_show_below) {
+ able_to_show_menu_above) {
+ y = y_for_menu_above;
+ } else if (able_to_show_menu_below) {
// Show below the anchor. Align the top of the menu with the top of the
// anchor.
- y = y_below;
+ y = y_for_menu_below;
item->set_actual_menu_position(MenuPosition::kBelowBounds);
- } else if (able_to_show_above) {
+ } else if (able_to_show_menu_above) {
// No room below, but there is room above. Show above the anchor. Align
// the bottom of the menu with the bottom of the anchor.
- y = y_above;
+ y = y_for_menu_above;
item->set_actual_menu_position(MenuPosition::kAboveBounds);
} else {
// No room above or below. Show as low as possible. Align the bottom of
@@ -2533,7 +2571,7 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
const bool create_on_right = prefer_leading != layout_is_rtl;
const int width_with_right_inset =
- menu_config.touchable_menu_width + border_and_shadow_insets.right();
+ menu_config.touchable_menu_min_width + border_and_shadow_insets.right();
const int x_max = monitor_bounds.right() - width_with_right_inset;
const int x_left = item_bounds.x() - width_with_right_inset;
const int x_right = item_bounds.right() - border_and_shadow_insets.left();
@@ -2692,7 +2730,7 @@ MenuItemView* MenuController::FindNextSelectableMenuItem(
if (index == stop_index && !include_all_items)
return nullptr;
MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index);
- if (child->GetVisible() && child->GetEnabled())
+ if (child->IsTraversableByKeyboard())
return child;
} while (index != stop_index);
return nullptr;
@@ -2807,7 +2845,9 @@ void MenuController::SelectByChar(base::char16 character) {
if (IsReadonlyCombobox() ||
MenuConfig::instance().all_menus_use_prefix_selection) {
- item->GetSubmenu()->GetPrefixSelector()->InsertText(char_array);
+ item->GetSubmenu()->GetPrefixSelector()->InsertText(
+ char_array,
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
} else {
// If no mnemonics found, look at first character of titles.
details = FindChildForMnemonic(item, key, &TitleMatchesMnemonic);
@@ -2823,7 +2863,7 @@ void MenuController::RepostEventAndCancel(SubmenuView* source,
gfx::Point screen_loc(event->location());
View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
gfx::NativeView native_view = source->GetWidget()->GetNativeView();
gfx::NativeWindow window = nullptr;
if (native_view) {
@@ -2862,7 +2902,7 @@ void MenuController::RepostEventAndCancel(SubmenuView* source,
// of the menus from the last run.
MenuPart last_part = GetMenuPartByScreenCoordinateUsingMenu(
menu_stack_.back().first.item, screen_loc);
- if (last_part.type != MenuPart::NONE)
+ if (last_part.type != MenuPart::Type::kNone)
exit_type = ExitType::kOutermost;
}
#if defined(OS_APPLE)
@@ -3109,10 +3149,11 @@ void MenuController::HandleMouseLocation(SubmenuView* source,
if (for_drop_)
return;
- if (part.type == MenuPart::NONE && ShowSiblingMenu(source, mouse_location))
+ if (part.type == MenuPart::Type::kNone &&
+ ShowSiblingMenu(source, mouse_location))
return;
- if (part.type == MenuPart::MENU_ITEM && part.menu) {
+ if (part.type == MenuPart::Type::kMenuItem && part.menu) {
SetSelection(part.menu, part.should_submenu_show ? SELECTION_OPEN_SUBMENU
: SELECTION_DEFAULT);
} else if (!part.is_scroll() && pending_state_.item &&
diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h
index 6908150ccc5..1e835dcc7d9 100644
--- a/chromium/ui/views/controls/menu/menu_controller.h
+++ b/chromium/ui/views/controls/menu/menu_controller.h
@@ -18,6 +18,7 @@
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-forward.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/platform/platform_event_dispatcher.h"
@@ -186,7 +187,8 @@ class VIEWS_EXPORT MenuController
void OnDragEntered(SubmenuView* source, const ui::DropTargetEvent& event);
int OnDragUpdated(SubmenuView* source, const ui::DropTargetEvent& event);
void OnDragExited(SubmenuView* source);
- int OnPerformDrop(SubmenuView* source, const ui::DropTargetEvent& event);
+ ui::mojom::DragOperation OnPerformDrop(SubmenuView* source,
+ const ui::DropTargetEvent& event);
// Invoked from the scroll buttons of the MenuScrollViewContainer.
void OnDragEnteredScrollButton(SubmenuView* source, bool is_up);
@@ -313,24 +315,26 @@ class VIEWS_EXPORT MenuController
// Used by GetMenuPart to indicate the menu part at a particular location.
struct MenuPart {
// Type of part.
- enum Type { NONE, MENU_ITEM, SCROLL_UP, SCROLL_DOWN };
+ enum class Type { kNone, kMenuItem, kScrollUp, kScrollDown };
- // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP.
- bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; }
+ // Convenience for testing type == kScrollDown or type == kScrollUp.
+ bool is_scroll() const {
+ return type == Type::kScrollDown || type == Type::kScrollUp;
+ }
// Type of part.
- Type type = NONE;
+ Type type = Type::kNone;
- // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise
- // this is NULL.
- // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item
+ // If type is kMenuItem, this is the menu item the mouse is over, otherwise
+ // this is null.
+ // NOTE: if type is kMenuItem and the mouse is not over a valid menu item
// but is over a menu (for example, the mouse is over a separator or
- // empty menu), this is NULL and parent is the menu the mouse was
+ // empty menu), this is null and parent is the menu the mouse was
// clicked on.
MenuItemView* menu = nullptr;
- // If type is MENU_ITEM but the mouse is not over a menu item this is the
- // parent of the menu item the user clicked on. Otherwise this is NULL.
+ // If type is kMenuItem but the mouse is not over a menu item this is the
+ // parent of the menu item the user clicked on. Otherwise this is null.
MenuItemView* parent = nullptr;
// This is the submenu the mouse is over.
diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
index c58a57017a2..383452eca46 100644
--- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
@@ -892,7 +892,7 @@ TEST_F(MenuControllerTest, EventTargeter) {
TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) {
// Run this test only for X11 (either Ozone or non-Ozone).
if (features::IsUsingOzonePlatform() &&
- std::strcmp(ui::OzonePlatform::GetPlatformName(), "x11") != 0) {
+ ui::OzonePlatform::GetPlatformNameForTest() != "x11") {
GTEST_SKIP();
}
@@ -1063,6 +1063,70 @@ TEST_F(MenuControllerTest, VerifyMenuBubblePositionAfterSizeChanges) {
}
}
+// Verifies that the context menu bubble position, MenuPosition::kBubbleBelow,
+// does not shift as items are removed. The menu position will shift it items
+// are added and the menu no longer fits in its previous position.
+TEST_F(MenuControllerTest, VerifyMenuBubbleBelowPositionAfterSizeChanges) {
+ constexpr gfx::Rect kMonitorBounds(0, 0, 500, 500);
+ constexpr gfx::Size kMenuSize(100, 200);
+ const gfx::Insets border_and_shadow_insets =
+ BubbleBorder::GetBorderAndShadowInsets(
+ MenuConfig::instance().touchable_menu_shadow_elevation);
+
+ // Calculate the suitable anchor point to ensure that if the menu shows below
+ // the anchor point, the bottom of the menu should be one pixel off the
+ // bottom of the display. It means that there is insufficient space for the
+ // menu below the anchor.
+ const gfx::Point anchor_point(kMonitorBounds.width() / 2,
+ kMonitorBounds.bottom() + 1 -
+ kMenuSize.height() +
+ border_and_shadow_insets.top());
+
+ MenuBoundsOptions options;
+ options.menu_anchor = MenuAnchorPosition::kBubbleBelow;
+ options.monitor_bounds = kMonitorBounds;
+ options.anchor_bounds = gfx::Rect(anchor_point, gfx::Size());
+
+ // Case 1: There is insufficient space for the menu below `anchor_point` and
+ // there is no cached menu position. The menu should show above the anchor.
+ {
+ options.menu_size = kMenuSize;
+ ASSERT_GT(options.anchor_bounds.y() - border_and_shadow_insets.top() +
+ kMenuSize.height(),
+ kMonitorBounds.bottom());
+ CalculateBubbleMenuBounds(options);
+ EXPECT_EQ(MenuItemView::MenuPosition::kAboveBounds,
+ menu_item()->ActualMenuPosition());
+ }
+
+ // Case 2: There is insufficient space for the menu below `anchor_point`. The
+ // cached position is below the anchor. The menu should show above the anchor
+ // point.
+ {
+ options.menu_position = MenuItemView::MenuPosition::kBelowBounds;
+ CalculateBubbleMenuBounds(options);
+ EXPECT_EQ(MenuItemView::MenuPosition::kAboveBounds,
+ menu_item()->ActualMenuPosition());
+ }
+
+ // Case 3: There is enough space for the menu below `anchor_point`. The cached
+ // menu position is above the anchor. The menu should show above the anchor.
+ {
+ // Shrink the menu size. Verify that there is enough space below the anchor
+ // point now.
+ constexpr gfx::Size kUpdatedSize(kMenuSize.width(), kMenuSize.height() / 2);
+ options.menu_size = kUpdatedSize;
+ EXPECT_LE(options.anchor_bounds.y() - border_and_shadow_insets.top() +
+ kUpdatedSize.height(),
+ kMonitorBounds.bottom());
+
+ options.menu_position = MenuItemView::MenuPosition::kAboveBounds;
+ CalculateBubbleMenuBounds(options);
+ EXPECT_EQ(MenuItemView::MenuPosition::kAboveBounds,
+ menu_item()->ActualMenuPosition());
+ }
+}
+
// Tests that opening the menu and pressing 'Home' selects the first menu item.
TEST_F(MenuControllerTest, FirstSelectedItem) {
SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
@@ -2100,7 +2164,7 @@ TEST_P(MenuControllerTest, TestSubmenuFitsOnScreen) {
MenuItemView* sub_item = menu_item()->GetSubmenu()->GetMenuItemAt(0);
sub_item->AppendMenuItem(11, base::ASCIIToUTF16("Subitem.One"));
- const int menu_width = MenuConfig::instance().touchable_menu_width;
+ const int menu_width = MenuConfig::instance().touchable_menu_min_width;
const gfx::Size parent_size(menu_width, menu_width);
const gfx::Size parent_size_wide(menu_width * 2, menu_width);
diff --git a/chromium/ui/views/controls/menu/menu_delegate.cc b/chromium/ui/views/controls/menu/menu_delegate.cc
index 9bc2a66b339..f9dad0e796c 100644
--- a/chromium/ui/views/controls/menu/menu_delegate.cc
+++ b/chromium/ui/views/controls/menu/menu_delegate.cc
@@ -4,6 +4,7 @@
#include "ui/views/controls/menu/menu_delegate.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/events/event.h"
#include "ui/views/controls/menu/menu_config.h"
@@ -90,18 +91,20 @@ bool MenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
return false;
}
-int MenuDelegate::GetDropOperation(MenuItemView* item,
- const ui::DropTargetEvent& event,
- DropPosition* position) {
+ui::mojom::DragOperation MenuDelegate::GetDropOperation(
+ MenuItemView* item,
+ const ui::DropTargetEvent& event,
+ DropPosition* position) {
NOTREACHED() << "If you override CanDrop, you need to override this too";
- return ui::DragDropTypes::DRAG_NONE;
+ return ui::mojom::DragOperation::kNone;
}
-int MenuDelegate::OnPerformDrop(MenuItemView* menu,
- DropPosition position,
- const ui::DropTargetEvent& event) {
+ui::mojom::DragOperation MenuDelegate::OnPerformDrop(
+ MenuItemView* menu,
+ DropPosition position,
+ const ui::DropTargetEvent& event) {
NOTREACHED() << "If you override CanDrop, you need to override this too";
- return ui::DragDropTypes::DRAG_NONE;
+ return ui::mojom::DragOperation::kNone;
}
bool MenuDelegate::CanDrag(MenuItemView* menu) {
diff --git a/chromium/ui/views/controls/menu/menu_delegate.h b/chromium/ui/views/controls/menu/menu_delegate.h
index 158724b4752..d863d6f874a 100644
--- a/chromium/ui/views/controls/menu/menu_delegate.h
+++ b/chromium/ui/views/controls/menu/menu_delegate.h
@@ -11,6 +11,7 @@
#include "base/strings/string16.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-forward.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/font_list.h"
@@ -160,19 +161,21 @@ class VIEWS_EXPORT MenuDelegate {
// is set based on the location of the mouse, reset to specify a different
// position.
//
- // If a drop should not be allowed, returned ui::DragDropTypes::DRAG_NONE.
- virtual int GetDropOperation(MenuItemView* item,
- const ui::DropTargetEvent& event,
- DropPosition* position);
+ // If a drop should not be allowed, returns DragOperation::kNone.
+ virtual ui::mojom::DragOperation GetDropOperation(
+ MenuItemView* item,
+ const ui::DropTargetEvent& event,
+ DropPosition* position);
// Invoked to perform the drop operation. This is ONLY invoked if CanDrop()
// returned true for the parent menu item, and GetDropOperation() returned an
- // operation other than ui::DragDropTypes::DRAG_NONE.
+ // operation other than DragOperation::kNone.
//
// |menu| is the menu the drop occurred on.
- virtual int OnPerformDrop(MenuItemView* menu,
- DropPosition position,
- const ui::DropTargetEvent& event);
+ virtual ui::mojom::DragOperation OnPerformDrop(
+ MenuItemView* menu,
+ DropPosition position,
+ const ui::DropTargetEvent& event);
// Invoked to determine if it is possible for the user to drag the specified
// menu item.
diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc
index 9b3cc9c5f21..865b7dac0b8 100644
--- a/chromium/ui/views/controls/menu/menu_host.cc
+++ b/chromium/ui/views/controls/menu/menu_host.cc
@@ -20,6 +20,7 @@
#include "ui/views/controls/menu/menu_scroll_view_container.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/round_rect_painter.h"
+#include "ui/views/views_delegate.h"
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/widget.h"
@@ -223,6 +224,10 @@ internal::RootView* MenuHost::CreateRootView() {
void MenuHost::OnMouseCaptureLost() {
if (destroying_ || ignore_capture_lost_)
return;
+
+ if (!ViewsDelegate::GetInstance()->ShouldCloseMenuIfMouseCaptureLost())
+ return;
+
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
if (menu_controller && !menu_controller->drag_in_progress())
diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc
index 38bb7252ab7..89f6c657d0a 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view.cc
@@ -19,6 +19,7 @@
#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.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/ui_base_features.h"
@@ -51,6 +52,10 @@
#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
+#if defined(OS_APPLE)
+#include "ui/views/accessibility/view_accessibility.h"
+#endif // defined(OS_APPLE)
+
namespace views {
namespace {
@@ -181,6 +186,8 @@ void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
switch (type_) {
case Type::kSubMenu:
case Type::kActionableSubMenu:
+ // Note: This is neither necessary nor sufficient for macOS. See
+ // CreateSubmenu() for virtual child creation and explanation.
node_data->SetHasPopup(ax::mojom::HasPopup::kMenu);
break;
case Type::kCheckbox:
@@ -205,6 +212,11 @@ void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
ax::mojom::StringAttribute::kKeyShortcuts,
base::UTF16ToUTF8(base::string16(1, mnemonic)));
}
+
+ if (IsTraversableByKeyboard()) {
+ node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
+ IsSelected());
+ }
}
bool MenuItemView::HandleAccessibleAction(const ui::AXActionData& action_data) {
@@ -223,9 +235,33 @@ bool MenuItemView::HandleAccessibleAction(const ui::AXActionData& action_data) {
return true;
}
+View::FocusBehavior MenuItemView::GetFocusBehavior() const {
+ // If the creator/owner of the MenuItemView has explicitly set the focus
+ // behavior to something other than the default NEVER, don't override it.
+ View::FocusBehavior focus_behavior = View::GetFocusBehavior();
+ if (focus_behavior != FocusBehavior::NEVER)
+ return focus_behavior;
+
+ // Some MenuItemView types are presumably never focusable, even by assistive
+ // technologies.
+ if (type_ == Type::kEmpty || type_ == Type::kSeparator)
+ return FocusBehavior::NEVER;
+
+ // The rest of the MenuItemView types are presumably focusable, at least by
+ // assistive technologies. But if they lack presentable information, then
+ // there won't be anything for ATs to convey to the user. Filter those out.
+ if (title_.empty() && secondary_title_.empty() && minor_text_.empty() &&
+ vector_icon_.empty()) {
+ return FocusBehavior::NEVER;
+ }
+
+ return FocusBehavior::ACCESSIBLE_ONLY;
+}
+
// static
bool MenuItemView::IsBubble(MenuAnchorPosition anchor) {
return anchor == MenuAnchorPosition::kBubbleAbove ||
+ anchor == MenuAnchorPosition::kBubbleBelow ||
anchor == MenuAnchorPosition::kBubbleLeft ||
anchor == MenuAnchorPosition::kBubbleRight;
}
@@ -376,6 +412,18 @@ SubmenuView* MenuItemView::CreateSubmenu() {
if (!submenu_) {
submenu_ = new SubmenuView(this);
+#if defined(OS_APPLE)
+ // All MenuItemViews of Type kSubMenu have a respective SubmenuView.
+ // However, in the Views hierarchy, this SubmenuView is not a child of the
+ // MenuItemView. This confuses VoiceOver, because it expects the submenu
+ // itself to be a child of the menu item. To allow VoiceOver to recognize
+ // submenu items, we create a virtual child of type Menu.
+ std::unique_ptr<AXVirtualView> virtual_child =
+ std::make_unique<AXVirtualView>();
+ virtual_child->GetCustomData().role = ax::mojom::Role::kMenu;
+ GetViewAccessibility().AddVirtualChildView(std::move(virtual_child));
+#endif // defined(OS_APPLE)
+
// Initialize the submenu indicator icon (arrow).
submenu_arrow_image_view_ = AddChildView(std::make_unique<ImageView>());
}
@@ -420,7 +468,7 @@ void MenuItemView::SetSelected(bool selected) {
OnPropertyChanged(&selected_, kPropertyEffectsPaint);
}
-PropertyChangedSubscription MenuItemView::AddSelectedChangedCallback(
+base::CallbackListSubscription MenuItemView::AddSelectedChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&selected_, std::move(callback));
}
@@ -565,7 +613,7 @@ const MenuItemView* MenuItemView::GetRootMenuItem() const {
base::char16 MenuItemView::GetMnemonic() {
if (!GetRootMenuItem()->has_mnemonics_ ||
- !MenuConfig::instance().use_mnemonics) {
+ !MenuConfig::instance().use_mnemonics || !may_have_mnemonics()) {
return 0;
}
@@ -736,6 +784,12 @@ bool MenuItemView::ShouldShowNewBadge() const {
return feature_enabled && is_new_;
}
+bool MenuItemView::IsTraversableByKeyboard() const {
+ bool ignore_enabled = ui::AXPlatformNode::GetAccessibilityMode().has_mode(
+ ui::AXMode::kNativeAPIs);
+ return GetVisible() && (ignore_enabled || GetEnabled());
+}
+
MenuItemView::MenuItemView(MenuItemView* parent,
int command,
MenuItemView::Type type) {
@@ -863,7 +917,7 @@ int MenuItemView::GetDrawStringFlags() {
else
flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
- if (GetRootMenuItem()->has_mnemonics_) {
+ if (GetRootMenuItem()->has_mnemonics_ && may_have_mnemonics()) {
if (MenuConfig::instance().show_mnemonics ||
GetRootMenuItem()->show_mnemonics_) {
flags |= gfx::Canvas::SHOW_PREFIX;
@@ -1195,6 +1249,9 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
dimensions.children_width = child_size.width();
const MenuConfig& menu_config = MenuConfig::instance();
+ MenuDelegate::LabelStyle style;
+ GetLabelStyle(&style);
+
if (GetMenuController() && GetMenuController()->use_touchable_layout()) {
dimensions.height = menu_config.touchable_menu_height;
@@ -1205,7 +1262,15 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
if (IsContainer())
return dimensions;
- dimensions.standard_width = menu_config.touchable_menu_width;
+ // Calculate total item width to make sure the current |title_|
+ // has enough room within the context menu.
+ int label_start = GetLabelStartForThisItem();
+ int string_width = gfx::GetStringWidth(title_, style.font_list);
+ int item_width = string_width + label_start + item_right_margin_;
+
+ item_width = std::max(item_width, menu_config.touchable_menu_min_width);
+ item_width = std::min(item_width, menu_config.touchable_menu_max_width);
+ dimensions.standard_width = item_width;
if (icon_view_) {
dimensions.height = icon_view_->GetPreferredSize().height() +
@@ -1214,8 +1279,6 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
return dimensions;
}
- MenuDelegate::LabelStyle style;
- GetLabelStyle(&style);
base::string16 minor_text = GetMinorText();
dimensions.height = child_size.height();
@@ -1304,7 +1367,8 @@ int MenuItemView::GetLabelStartForThisItem() const {
// Touchable items with icons do not respect |label_start_|.
if (GetMenuController() && GetMenuController()->use_touchable_layout() &&
icon_view_) {
- return 2 * config.touchable_item_horizontal_padding + icon_view_->width();
+ return 2 * config.touchable_item_horizontal_padding +
+ icon_view_->GetPreferredSize().width();
}
int label_start = label_start_ + left_icon_margin_ + right_icon_margin_;
diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h
index e0c41896c70..b614f1a2d1d 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.h
+++ b/chromium/ui/views/controls/menu/menu_item_view.h
@@ -123,6 +123,7 @@ class VIEWS_EXPORT MenuItemView : public View {
base::string16 GetTooltipText(const gfx::Point& p) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
+ FocusBehavior GetFocusBehavior() const override;
// Returns the preferred height of menu items. This is only valid when the
// menu is about to be shown.
@@ -239,7 +240,7 @@ class VIEWS_EXPORT MenuItemView : public View {
// Adds a callback subscription associated with the above selected property.
// The callback will be invoked whenever the selected property changes.
- PropertyChangedSubscription AddSelectedChangedCallback(
+ base::CallbackListSubscription AddSelectedChangedCallback(
PropertyChangedCallback callback) WARN_UNUSED_RESULT;
// Sets whether the submenu area of an ACTIONABLE_SUBMENU is selected.
@@ -279,6 +280,11 @@ class VIEWS_EXPORT MenuItemView : public View {
void set_is_new(bool is_new) { is_new_ = is_new; }
bool is_new() const { return is_new_; }
+ void set_may_have_mnemonics(bool may_have_mnemonics) {
+ may_have_mnemonics_ = may_have_mnemonics;
+ }
+ bool may_have_mnemonics() const { return may_have_mnemonics_; }
+
// Paints the menu item.
void OnPaint(gfx::Canvas* canvas) override;
@@ -363,6 +369,10 @@ class VIEWS_EXPORT MenuItemView : public View {
// Takes into account whether the badging feature is enabled.
bool ShouldShowNewBadge() const;
+ // Returns whether keyboard navigation through the menu should stop on this
+ // item.
+ bool IsTraversableByKeyboard() const;
+
protected:
// Creates a MenuItemView. This is used by the various AddXXX methods.
MenuItemView(MenuItemView* parent, int command, Type type);
@@ -528,6 +538,9 @@ class VIEWS_EXPORT MenuItemView : public View {
// a way to highlight a new feature for users.
bool is_new_ = false;
+ // Whether the menu item contains user-created text.
+ bool may_have_mnemonics_ = true;
+
// Submenu, created via CreateSubmenu.
SubmenuView* submenu_ = nullptr;
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 b942a3d978e..cff5dcf02a4 100644
--- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
@@ -16,6 +16,7 @@
#include "ui/gfx/geometry/insets.h"
#include "ui/native_theme/themed_vector_icon.h"
#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/controls/menu/test_menu_item_view.h"
#include "ui/views/test/menu_test_utils.h"
@@ -205,6 +206,67 @@ TEST_F(MenuItemViewUnitTest, NotifiesSelectedChanged) {
EXPECT_FALSE(is_selected);
}
+class TouchableMenuItemViewTest : public ViewsTestBase {
+ public:
+ TouchableMenuItemViewTest() = default;
+ ~TouchableMenuItemViewTest() override = default;
+
+ void SetUp() override {
+ ViewsTestBase::SetUp();
+ widget_ = CreateTestWidget();
+ widget_->Show();
+
+ menu_delegate_ = std::make_unique<test::TestMenuDelegate>();
+ menu_item_view_ = new TestMenuItemView(menu_delegate_.get());
+ menu_runner_ = std::make_unique<MenuRunner>(
+ menu_item_view_, MenuRunner::USE_TOUCHABLE_LAYOUT);
+ menu_runner_->RunMenuAt(widget_.get(), nullptr, gfx::Rect(),
+ MenuAnchorPosition::kTopLeft,
+ ui::MENU_SOURCE_KEYBOARD);
+ }
+
+ void TearDown() override {
+ widget_->CloseNow();
+ ViewsTestBase::TearDown();
+ }
+
+ gfx::Size AppendItemAndGetSize(int i, const base::string16& title) {
+ return menu_item_view_->AppendMenuItem(i, title)->GetPreferredSize();
+ }
+
+ private:
+ std::unique_ptr<test::TestMenuDelegate> menu_delegate_;
+ std::unique_ptr<MenuRunner> menu_runner_;
+ std::unique_ptr<Widget> widget_;
+
+ // Owned by MenuRunner.
+ TestMenuItemView* menu_item_view_ = nullptr;
+};
+
+// Test that touchable menu items are sized to fit the menu item titles within
+// the allowed minimum and maximum width.
+TEST_F(TouchableMenuItemViewTest, MinAndMaxWidth) {
+ const int min_menu_width = MenuConfig::instance().touchable_menu_min_width;
+ const int max_menu_width = MenuConfig::instance().touchable_menu_max_width;
+
+ // Test a title shorter than the minimum width.
+ gfx::Size item1_size =
+ AppendItemAndGetSize(1, base::ASCIIToUTF16("Item1 Short title"));
+ EXPECT_EQ(item1_size.width(), min_menu_width);
+
+ // Test a title which is between the min and max allowed widths.
+ gfx::Size item2_size = AppendItemAndGetSize(
+ 2, base::ASCIIToUTF16("Item2 bigger than min less than max"));
+ EXPECT_GT(item2_size.width(), min_menu_width);
+ EXPECT_LT(item2_size.width(), max_menu_width);
+
+ // Test a title which is longer than the max touchable menu width.
+ gfx::Size item3_size = AppendItemAndGetSize(
+ 3, base::ASCIIToUTF16("Item3 Title that is longer than the maximum "
+ "allowed context menu width"));
+ EXPECT_EQ(item3_size.width(), max_menu_width);
+}
+
class MenuItemViewLayoutTest : public ViewsTestBase {
public:
MenuItemViewLayoutTest() : test_item_(root_menu_.AppendMenuItem(1)) {}
diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc
index b0b1cf9a7a7..5dd4a9bbb8c 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc
@@ -122,6 +122,8 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
if (model->IsAlertedAt(model_index))
menu_item_view->SetAlerted();
menu_item_view->set_is_new(model->IsNewFeatureAt(model_index));
+ menu_item_view->set_may_have_mnemonics(
+ model->MayHaveMnemonicsAt(model_index));
return menu_item_view;
}
diff --git a/chromium/ui/views/controls/menu/menu_runner.cc b/chromium/ui/views/controls/menu/menu_runner.cc
index b13f9fbf7cb..30b3ac414f9 100644
--- a/chromium/ui/views/controls/menu/menu_runner.cc
+++ b/chromium/ui/views/controls/menu/menu_runner.cc
@@ -45,7 +45,7 @@ void MenuRunner::RunMenuAt(Widget* parent,
// the parent widget will not be able to reset its state (it might have mouse
// capture from the mouse down). So we clear its state here.
if (parent && parent->GetRootView())
- parent->GetRootView()->SetMouseHandler(nullptr);
+ parent->GetRootView()->SetMouseAndGestureHandler(nullptr);
if (runner_handler_.get()) {
runner_handler_->RunMenuAt(parent, button_controller, bounds, anchor,
diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
index 214011f187c..9aa4d512daa 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
+++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
@@ -26,90 +26,138 @@
namespace {
+constexpr CGFloat kNativeCheckmarkWidth = 18;
+constexpr CGFloat kNativeMenuItemHeight = 18;
+constexpr CGFloat kIPHDotSize = 6;
+
NSImage* NewTagImage() {
- static NSImage* new_tag = []() {
- // 1. Make the attributed string.
-
- NSString* badge_text = l10n_util::GetNSString(IDS_NEW_BADGE);
-
- // The preferred font is slightly smaller and slightly more bold than the
- // menu font. The size change is required to make it look correct in the
- // badge; we add a small degree of bold to prevent color smearing/blurring
- // due to font smoothing. This ensures readability on all platforms and in
- // both light and dark modes.
- gfx::Font badge_font = gfx::Font(
- new gfx::PlatformFontMac(gfx::PlatformFontMac::SystemFontType::kMenu));
- badge_font =
- badge_font.Derive(views::NewBadge::kNewBadgeFontSizeAdjustment,
- gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM);
-
- NSColor* badge_text_color = skia::SkColorToSRGBNSColor(
- ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
- ui::NativeTheme::kColorId_TextOnProminentButtonColor));
-
- NSDictionary* badge_attrs = @{
- NSFontAttributeName : badge_font.GetNativeFont(),
- NSForegroundColorAttributeName : badge_text_color,
- };
-
- NSMutableAttributedString* badge_attr_string =
- [[NSMutableAttributedString alloc] initWithString:badge_text
- attributes:badge_attrs];
-
- if (base::mac::IsOS10_10()) {
- // The system font for 10.10 is Helvetica Neue, and when used for this
- // "new tag" the letters look cramped. Track it out so that there's some
- // breathing room. There is no tracking attribute, so instead add kerning
- // to all but the last character.
- [badge_attr_string
- addAttribute:NSKernAttributeName
- value:@0.4
- range:NSMakeRange(0, [badge_attr_string length] - 1)];
- }
+ // 1. Make the attributed string.
+
+ NSString* badge_text = l10n_util::GetNSString(IDS_NEW_BADGE);
+
+ // The preferred font is slightly smaller and slightly more bold than the
+ // menu font. The size change is required to make it look correct in the
+ // badge; we add a small degree of bold to prevent color smearing/blurring
+ // due to font smoothing. This ensures readability on all platforms and in
+ // both light and dark modes.
+ gfx::Font badge_font = gfx::Font(
+ new gfx::PlatformFontMac(gfx::PlatformFontMac::SystemFontType::kMenu));
+ badge_font = badge_font.Derive(views::NewBadge::kNewBadgeFontSizeAdjustment,
+ gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM);
+
+ NSColor* badge_text_color = skia::SkColorToSRGBNSColor(
+ ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
+ ui::NativeTheme::kColorId_TextOnProminentButtonColor));
+
+ NSDictionary* badge_attrs = @{
+ NSFontAttributeName : badge_font.GetNativeFont(),
+ NSForegroundColorAttributeName : badge_text_color,
+ };
+
+ NSMutableAttributedString* badge_attr_string =
+ [[NSMutableAttributedString alloc] initWithString:badge_text
+ attributes:badge_attrs];
+
+ // 2. Calculate the size required.
+
+ NSSize badge_size = [badge_attr_string size];
+ badge_size.width = trunc(badge_size.width);
+ badge_size.height = trunc(badge_size.height);
+
+ badge_size.width += 2 * views::NewBadge::kNewBadgeInternalPadding +
+ 2 * views::NewBadge::kNewBadgeHorizontalMargin;
+ badge_size.height += views::NewBadge::kNewBadgeInternalPaddingTopMac;
+
+ // 3. Craft the image.
+
+ return [NSImage
+ imageWithSize:badge_size
+ flipped:NO
+ drawingHandler:^(NSRect dest_rect) {
+ NSRect badge_frame = NSInsetRect(
+ dest_rect, views::NewBadge::kNewBadgeHorizontalMargin, 0);
+ NSBezierPath* rounded_badge_rect = [NSBezierPath
+ bezierPathWithRoundedRect:badge_frame
+ xRadius:views::NewBadge::kNewBadgeCornerRadius
+ yRadius:views::NewBadge::kNewBadgeCornerRadius];
+ NSColor* badge_color = skia::SkColorToSRGBNSColor(
+ ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
+ ui::NativeTheme::kColorId_ProminentButtonColor));
+ [badge_color set];
+ [rounded_badge_rect fill];
+
+ NSPoint badge_text_location = NSMakePoint(
+ NSMinX(badge_frame) + views::NewBadge::kNewBadgeInternalPadding,
+ NSMinY(badge_frame) +
+ views::NewBadge::kNewBadgeInternalPaddingTopMac);
+ [badge_attr_string drawAtPoint:badge_text_location];
+
+ return YES;
+ }];
+}
- // 2. Calculate the size required.
-
- NSSize badge_size = [badge_attr_string size];
- badge_size.width = trunc(badge_size.width);
- badge_size.height = trunc(badge_size.height);
-
- badge_size.width += 2 * views::NewBadge::kNewBadgeInternalPadding +
- 2 * views::NewBadge::kNewBadgeHorizontalMargin;
- badge_size.height += views::NewBadge::kNewBadgeInternalPaddingTopMac;
-
- // 3. Craft the image.
-
- return [[NSImage
- imageWithSize:badge_size
- flipped:NO
- drawingHandler:^(NSRect dest_rect) {
- NSRect badge_frame = NSInsetRect(
- dest_rect, views::NewBadge::kNewBadgeHorizontalMargin, 0);
- NSBezierPath* rounded_badge_rect = [NSBezierPath
- bezierPathWithRoundedRect:badge_frame
- xRadius:views::NewBadge::kNewBadgeCornerRadius
- yRadius:views::NewBadge::kNewBadgeCornerRadius];
- NSColor* badge_color = skia::SkColorToSRGBNSColor(
- ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
- ui::NativeTheme::kColorId_ProminentButtonColor));
- [badge_color set];
- [rounded_badge_rect fill];
-
- NSPoint badge_text_location = NSMakePoint(
- NSMinX(badge_frame) + views::NewBadge::kNewBadgeInternalPadding,
- NSMinY(badge_frame) +
- views::NewBadge::kNewBadgeInternalPaddingTopMac);
- [badge_attr_string drawAtPoint:badge_text_location];
-
- return YES;
- }] retain];
- }();
-
- return new_tag;
+NSImage* IPHDotImage() {
+ // Embed horizontal centering space as NSMenuItem will otherwise left-align
+ // it.
+ return [NSImage
+ imageWithSize:NSMakeSize(2 * kIPHDotSize, kIPHDotSize)
+ flipped:NO
+ drawingHandler:^(NSRect dest_rect) {
+ NSBezierPath* dot_path = [NSBezierPath
+ bezierPathWithOvalInRect:NSMakeRect(kIPHDotSize / 2, 0, kIPHDotSize,
+ kIPHDotSize)];
+ NSColor* dot_color = skia::SkColorToSRGBNSColor(
+ ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
+ ui::NativeTheme::kColorId_ProminentButtonColor));
+ [dot_color set];
+ [dot_path fill];
+
+ return YES;
+ }];
+}
+
+NSMutableAttributedString* MutableAttributedStringForMenuItemTitleString(
+ NSString* string) {
+ // Starting in 10.13, if an attributed string is set as a menu item title,
+ // and NSFontAttributeName is not specified for it, it is automatically
+ // rendered in a font matching other menu items. Prior to then, a menu item
+ // with no specified font is rendered in Helvetica. In addition, while the
+ // documentation says that -[NSFont menuFontOfSize:0] gives the standard
+ // menu font, that doesn't actually match up. Therefore, specify a font that
+ // visually matches.
+ NSDictionary* attrs = nil;
+ if (base::mac::IsAtMostOS10_12())
+ attrs = @{NSFontAttributeName : [NSFont menuFontOfSize:14]};
+
+ return [[[NSMutableAttributedString alloc] initWithString:string
+ attributes:attrs] autorelease];
}
} // namespace
+// --- Private API begin ---
+
+@interface NSCarbonMenuImpl : NSObject
+- (void)highlightItemAtIndex:(NSInteger)index;
+@end
+
+@interface NSMenu ()
+- (NSCarbonMenuImpl*)_menuImpl;
+@end
+
+// --- Private API end ---
+
+// An NSTextAttachmentCell to show the [New] tag on a menu item.
+//
+// /!\ WARNING /!\
+//
+// Do NOT update to the "new in macOS 10.11" API of NSTextAttachment.image until
+// macOS 10.15 is the minimum required macOS for Chromium. Because menus are
+// Carbon-based, the new NSTextAttachment.image API did not function correctly
+// until then. Specifically, in macOS 10.11-10.12, images that use the new API
+// do not appear. In macOS 10.13-10.14, the flipped flag of -[NSImage
+// imageWithSize:flipped:drawingHandler:] is not respected. Only when 10.15 is
+// the minimum required OS can https://crrev.com/c/2572937 be relanded.
@interface NewTagAttachmentCell : NSTextAttachmentCell
@end
@@ -123,7 +171,7 @@ NSImage* NewTagImage() {
}
- (NSPoint)cellBaselineOffset {
- return NSMakePoint(0, views::NewBadge::kNewBadgeBaslineOffsetMac);
+ return NSMakePoint(0, views::NewBadge::kNewBadgeBaselineOffsetMac);
}
- (NSSize)cellSize {
@@ -132,46 +180,65 @@ NSImage* NewTagImage() {
@end
-@interface MenuControllerDelegate : NSObject <MenuControllerCocoaDelegate>
+@interface MenuControllerDelegate : NSObject <MenuControllerCocoaDelegate> {
+ id<NSObject> _menuOpenObserver;
+}
@end
@implementation MenuControllerDelegate
+- (void)dealloc {
+ if (_menuOpenObserver)
+ [[NSNotificationCenter defaultCenter] removeObserver:_menuOpenObserver];
+
+ [super dealloc];
+}
+
- (void)controllerWillAddItem:(NSMenuItem*)menuItem
fromModel:(ui::MenuModel*)model
atIndex:(NSInteger)index {
- static const bool feature_enabled =
+ static const bool newBadgeFeatureEnabled =
base::FeatureList::IsEnabled(views::features::kEnableNewBadgeOnMenuItems);
- if (!feature_enabled || !model->IsNewFeatureAt(index))
- return;
-
- // TODO(avi): When moving to 10.11 as the minimum macOS, switch to using
- // NSTextAttachment's |image| and |bounds| properties and avoid the whole
- // NSTextAttachmentCell subclassing mishegas.
- base::scoped_nsobject<NSTextAttachment> attachment(
- [[NSTextAttachment alloc] init]);
- attachment.get().attachmentCell =
- [[[NewTagAttachmentCell alloc] init] autorelease];
-
- // Starting in 10.13, if an attributed string is set as a menu item title, and
- // NSFontAttributeName is not specified for it, it is automatically rendered
- // in a font matching other menu items. Prior to then, a menu item with no
- // specified font is rendered in Helvetica. In addition, while the
- // documentation says that -[NSFont menuFontOfSize:0] gives the standard menu
- // font, that doesn't actually match up. Therefore, specify a font that
- // visually matches.
- NSDictionary* attrs = nil;
- if (base::mac::IsAtMostOS10_12())
- attrs = @{NSFontAttributeName : [NSFont menuFontOfSize:14]};
-
- base::scoped_nsobject<NSMutableAttributedString> attrTitle(
- [[NSMutableAttributedString alloc] initWithString:menuItem.title
- attributes:attrs]);
- [attrTitle
- appendAttributedString:[NSAttributedString
- attributedStringWithAttachment:attachment]];
+ if (newBadgeFeatureEnabled && model->IsNewFeatureAt(index)) {
+ // /!\ WARNING /!\ Do not update this to use NSTextAttachment.image until
+ // macOS 10.15 is the minimum required OS. See the details on the class
+ // comment above.
+ NSTextAttachment* attachment =
+ [[[NSTextAttachment alloc] init] autorelease];
+ attachment.attachmentCell =
+ [[[NewTagAttachmentCell alloc] init] autorelease];
+
+ NSMutableAttributedString* attrTitle =
+ MutableAttributedStringForMenuItemTitleString(menuItem.title);
+ [attrTitle
+ appendAttributedString:[NSAttributedString
+ attributedStringWithAttachment:attachment]];
+
+ menuItem.attributedTitle = attrTitle;
+ }
- menuItem.attributedTitle = attrTitle;
+ if (model->IsAlertedAt(index)) {
+ NSImage* iphDotImage = IPHDotImage();
+ menuItem.onStateImage = iphDotImage;
+ menuItem.offStateImage = iphDotImage;
+ menuItem.mixedStateImage = iphDotImage;
+
+ DCHECK(!_menuOpenObserver);
+ _menuOpenObserver = [[NSNotificationCenter defaultCenter]
+ addObserverForName:NSMenuDidBeginTrackingNotification
+ object:menuItem.menu
+ queue:nil
+ usingBlock:^(NSNotification* note) {
+ NSMenu* menu = note.object;
+ if ([menu respondsToSelector:@selector(_menuImpl)]) {
+ NSCarbonMenuImpl* menuImpl = [menu _menuImpl];
+ if ([menuImpl respondsToSelector:@selector
+ (highlightItemAtIndex:)]) {
+ [menuImpl highlightItemAtIndex:index];
+ }
+ }
+ }];
+ }
}
@end
@@ -180,9 +247,6 @@ namespace views {
namespace internal {
namespace {
-constexpr CGFloat kNativeCheckmarkWidth = 18;
-constexpr CGFloat kNativeMenuItemHeight = 18;
-
// Returns the first item in |menu_controller|'s menu that will be checked.
NSMenuItem* FirstCheckedItem(MenuControllerCocoa* menu_controller) {
for (NSMenuItem* item in [[menu_controller menu] itemArray]) {
diff --git a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc
index 4771d1d6e85..8e21f427f6a 100644
--- a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc
+++ b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -13,6 +13,7 @@
#include "third_party/skia/include/core/SkPath.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/border.h"
@@ -22,6 +23,7 @@
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/layout/flex_layout.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/round_rect_painter.h"
#include "ui/views/view.h"
@@ -43,11 +45,14 @@ static constexpr int kBorderPaddingDueToRoundedCorners = 1;
class MenuScrollButton : public View {
public:
+ METADATA_HEADER(MenuScrollButton);
MenuScrollButton(SubmenuView* host, bool is_up)
: host_(host),
is_up_(is_up),
// Make our height the same as that of other MenuItemViews.
pref_height_(MenuItemView::pref_menu_height()) {}
+ MenuScrollButton(const MenuScrollButton&) = delete;
+ MenuScrollButton& operator=(const MenuScrollButton&) = delete;
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(MenuConfig::instance().scroll_arrow_height * 2 - 1,
@@ -80,8 +85,9 @@ class MenuScrollButton : public View {
host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_);
}
- int OnPerformDrop(const ui::DropTargetEvent& event) override {
- return ui::DragDropTypes::DRAG_NONE;
+ ui::mojom::DragOperation OnPerformDrop(
+ const ui::DropTargetEvent& event) override {
+ return ui::mojom::DragOperation::kNone;
}
void OnPaint(gfx::Canvas* canvas) override {
@@ -133,10 +139,11 @@ class MenuScrollButton : public View {
// Color for the arrow to scroll.
SkColor arrow_color_;
-
- DISALLOW_COPY_AND_ASSIGN(MenuScrollButton);
};
+BEGIN_METADATA(MenuScrollButton, View)
+END_METADATA
+
} // namespace
// MenuScrollView --------------------------------------------------------------
@@ -151,7 +158,10 @@ class MenuScrollButton : public View {
class MenuScrollViewContainer::MenuScrollView : public View {
public:
+ METADATA_HEADER(MenuScrollView);
explicit MenuScrollView(View* child) { AddChildView(child); }
+ MenuScrollView(const MenuScrollView&) = delete;
+ MenuScrollView& operator=(const MenuScrollView&) = delete;
void ScrollRectToVisible(const gfx::Rect& rect) override {
// NOTE: this assumes we only want to scroll in the y direction.
@@ -177,11 +187,11 @@ class MenuScrollViewContainer::MenuScrollView : public View {
// Returns the contents, which is the SubmenuView.
View* GetContents() { return children().front(); }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MenuScrollView);
};
+BEGIN_METADATA(MenuScrollViewContainer, MenuScrollView, View)
+END_METADATA
+
// MenuScrollViewContainer ----------------------------------------------------
MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView* content_view)
@@ -293,7 +303,7 @@ void MenuScrollViewContainer::CreateDefaultBorder() {
const ui::NativeTheme* native_theme = GetNativeTheme();
bool use_outer_border =
menu_config.use_outer_border ||
- (native_theme && native_theme->UsesHighContrastColors());
+ (native_theme && native_theme->UserHasContrastPreference());
corner_radius_ = menu_config.CornerRadiusForMenu(
content_view_->GetMenuItem()->GetMenuController());
int padding = use_outer_border && corner_radius_ > 0
@@ -327,7 +337,8 @@ void MenuScrollViewContainer::CreateDefaultBorder() {
void MenuScrollViewContainer::CreateBubbleBorder() {
const SkColor color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_MenuBackgroundColor);
- bubble_border_ = new BubbleBorder(arrow_, BubbleBorder::SMALL_SHADOW, color);
+ bubble_border_ =
+ new BubbleBorder(arrow_, BubbleBorder::STANDARD_SHADOW, color);
if (content_view_->GetMenuItem()
->GetMenuController()
->use_touchable_layout()) {
@@ -351,6 +362,7 @@ BubbleBorder::Arrow MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
MenuAnchorPosition anchor) {
switch (anchor) {
case MenuAnchorPosition::kBubbleAbove:
+ case MenuAnchorPosition::kBubbleBelow:
case MenuAnchorPosition::kBubbleLeft:
case MenuAnchorPosition::kBubbleRight:
return BubbleBorder::FLOAT;
diff --git a/chromium/ui/views/controls/menu/menu_separator.cc b/chromium/ui/views/controls/menu/menu_separator.cc
index 539e79934fe..98343f884a3 100644
--- a/chromium/ui/views/controls/menu/menu_separator.cc
+++ b/chromium/ui/views/controls/menu/menu_separator.cc
@@ -6,6 +6,8 @@
#include "build/build_config.h"
#include "third_party/skia/include/core/SkColor.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/canvas.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/menu/menu_config.h"
@@ -101,6 +103,10 @@ void MenuSeparator::SetType(ui::MenuSeparatorType type) {
OnPropertyChanged(&type_, kPropertyEffectsPreferredSizeChanged);
}
+void MenuSeparator::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+ node_data->role = ax::mojom::Role::kSplitter;
+}
+
BEGIN_METADATA(MenuSeparator, View)
ADD_PROPERTY_METADATA(ui::MenuSeparatorType, Type)
END_METADATA
diff --git a/chromium/ui/views/controls/menu/menu_separator.h b/chromium/ui/views/controls/menu/menu_separator.h
index 18566efae79..edf5c249212 100644
--- a/chromium/ui/views/controls/menu/menu_separator.h
+++ b/chromium/ui/views/controls/menu/menu_separator.h
@@ -29,6 +29,8 @@ class VIEWS_EXPORT MenuSeparator : public View {
ui::MenuSeparatorType GetType() const;
void SetType(ui::MenuSeparatorType type);
+ void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+
private:
// The type of the separator.
ui::MenuSeparatorType type_;
diff --git a/chromium/ui/views/controls/menu/menu_types.h b/chromium/ui/views/controls/menu/menu_types.h
index 3554c3fb84b..d00ccf44bcb 100644
--- a/chromium/ui/views/controls/menu/menu_types.h
+++ b/chromium/ui/views/controls/menu/menu_types.h
@@ -18,6 +18,7 @@ enum class MenuAnchorPosition {
kBubbleAbove,
kBubbleLeft,
kBubbleRight,
+ kBubbleBelow,
};
} // namespace views
diff --git a/chromium/ui/views/controls/menu/native_menu_win.cc b/chromium/ui/views/controls/menu/native_menu_win.cc
index 042ecddda20..a28e24a532a 100644
--- a/chromium/ui/views/controls/menu/native_menu_win.cc
+++ b/chromium/ui/views/controls/menu/native_menu_win.cc
@@ -8,6 +8,7 @@
#include "base/check.h"
#include "base/strings/string_util.h"
+#include "base/strings/string_util_win.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_win.h"
@@ -51,12 +52,9 @@ NativeMenuWin::NativeMenuWin(ui::MenuModel* model, HWND system_menu_for)
!system_menu_for),
system_menu_for_(system_menu_for),
first_item_index_(0),
- parent_(nullptr),
- destroyed_flag_(nullptr) {}
+ parent_(nullptr) {}
NativeMenuWin::~NativeMenuWin() {
- if (destroyed_flag_)
- *destroyed_flag_ = true;
items_.clear();
DestroyMenu(menu_);
}
@@ -193,12 +191,13 @@ void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii,
ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
// Strip out any tabs, otherwise they get interpreted as accelerators and can
// lead to weird behavior.
- base::ReplaceSubstringsAfterOffset(&formatted, 0, L"\t", L" ");
+ base::ReplaceSubstringsAfterOffset(&formatted, 0, STRING16_LITERAL("\t"),
+ STRING16_LITERAL(" "));
if (type != ui::MenuModel::TYPE_SUBMENU) {
// Add accelerator details to the label if provided.
ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE);
if (model_->GetAcceleratorAt(model_index, &accelerator)) {
- formatted += L"\t";
+ formatted += STRING16_LITERAL("\t");
formatted += accelerator.GetShortcutText();
}
}
@@ -209,7 +208,7 @@ void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii,
// Give Windows a pointer to the label string.
mii->fMask |= MIIM_STRING;
- mii->dwTypeData = const_cast<wchar_t*>(items_[model_index]->label.c_str());
+ mii->dwTypeData = base::as_writable_wcstr(items_[model_index]->label);
}
void NativeMenuWin::ResetNativeMenu() {
diff --git a/chromium/ui/views/controls/menu/native_menu_win.h b/chromium/ui/views/controls/menu/native_menu_win.h
index 03f00a84ead..01db841cd96 100644
--- a/chromium/ui/views/controls/menu/native_menu_win.h
+++ b/chromium/ui/views/controls/menu/native_menu_win.h
@@ -100,11 +100,6 @@ class VIEWS_EXPORT NativeMenuWin {
// If we're a submenu, this is our parent.
NativeMenuWin* parent_;
- // If non-null the destructor sets this to true. This is set to non-null while
- // the menu is showing. It is used to detect if the menu was deleted while
- // running.
- bool* destroyed_flag_;
-
DISALLOW_COPY_AND_ASSIGN(NativeMenuWin);
};
diff --git a/chromium/ui/views/controls/menu/new_badge.h b/chromium/ui/views/controls/menu/new_badge.h
index 89c0602e703..478c67fb1d0 100644
--- a/chromium/ui/views/controls/menu/new_badge.h
+++ b/chromium/ui/views/controls/menu/new_badge.h
@@ -67,7 +67,7 @@ class VIEWS_EXPORT NewBadge {
static constexpr int kNewBadgeInternalPaddingTopMac = 1;
// The baseline offset of the "new" badge image to the menu text baseline.
- static constexpr int kNewBadgeBaslineOffsetMac = -4;
+ static constexpr int kNewBadgeBaselineOffsetMac = -4;
// The corner radius of the rounded rect for the "new" badge.
static constexpr int kNewBadgeCornerRadius = 3;
diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc
index 253c795739f..548986ec100 100644
--- a/chromium/ui/views/controls/menu/submenu_view.cc
+++ b/chromium/ui/views/controls/menu/submenu_view.cc
@@ -267,7 +267,8 @@ void SubmenuView::OnDragExited() {
parent_menu_item_->GetMenuController()->OnDragExited(this);
}
-int SubmenuView::OnPerformDrop(const ui::DropTargetEvent& event) {
+ui::mojom::DragOperation SubmenuView::OnPerformDrop(
+ const ui::DropTargetEvent& event) {
DCHECK(parent_menu_item_->GetMenuController());
return parent_menu_item_->GetMenuController()->OnPerformDrop(this, event);
}
diff --git a/chromium/ui/views/controls/menu/submenu_view.h b/chromium/ui/views/controls/menu/submenu_view.h
index 5dd7dee536d..d03e5bbbefe 100644
--- a/chromium/ui/views/controls/menu/submenu_view.h
+++ b/chromium/ui/views/controls/menu/submenu_view.h
@@ -88,7 +88,8 @@ class VIEWS_EXPORT SubmenuView : public View,
void OnDragEntered(const ui::DropTargetEvent& event) override;
int OnDragUpdated(const ui::DropTargetEvent& event) override;
void OnDragExited() override;
- int OnPerformDrop(const ui::DropTargetEvent& event) override;
+ ui::mojom::DragOperation OnPerformDrop(
+ const ui::DropTargetEvent& event) override;
// Scrolls on menu item boundaries.
bool OnMouseWheel(const ui::MouseWheelEvent& e) override;
diff --git a/chromium/ui/views/controls/message_box_view.cc b/chromium/ui/views/controls/message_box_view.cc
index ba85f6f7fa2..1c699affa71 100644
--- a/chromium/ui/views/controls/message_box_view.cc
+++ b/chromium/ui/views/controls/message_box_view.cc
@@ -183,10 +183,6 @@ void MessageBoxView::SetLink(const base::string16& text,
ResetLayoutManager();
}
-void MessageBoxView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
- node_data->role = ax::mojom::Role::kAlertDialog;
-}
-
void MessageBoxView::SetInterRowVerticalSpacing(int spacing) {
if (inter_row_vertical_spacing_ == spacing)
return;
@@ -220,8 +216,6 @@ void MessageBoxView::ViewHierarchyChanged(
if (details.child == this && details.is_add) {
if (prompt_field_ && prompt_field_->GetVisible())
prompt_field_->SelectAll(true);
-
- NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
}
}
diff --git a/chromium/ui/views/controls/message_box_view.h b/chromium/ui/views/controls/message_box_view.h
index 8d790f6b389..79ac063b557 100644
--- a/chromium/ui/views/controls/message_box_view.h
+++ b/chromium/ui/views/controls/message_box_view.h
@@ -71,9 +71,6 @@ class VIEWS_EXPORT MessageBoxView : public View {
// Sets the text and the callback of the link. |text| must be non-empty.
void SetLink(const base::string16& text, Link::ClickedCallback callback);
- // View:
- void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
void SetInterRowVerticalSpacing(int spacing);
void SetMessageWidth(int width);
diff --git a/chromium/ui/views/controls/native/native_view_host_mac.mm b/chromium/ui/views/controls/native/native_view_host_mac.mm
index bf60b86ae20..ec20dd5e820 100644
--- a/chromium/ui/views/controls/native/native_view_host_mac.mm
+++ b/chromium/ui/views/controls/native/native_view_host_mac.mm
@@ -155,8 +155,11 @@ void NativeViewHostMac::RemovedFromWidget() {
bool NativeViewHostMac::SetCornerRadii(
const gfx::RoundedCornersF& corner_radii) {
- NOTIMPLEMENTED();
- return false;
+ ui::Layer* layer = GetUiLayer();
+ DCHECK(layer);
+ layer->SetRoundedCornerRadius(corner_radii);
+ layer->SetIsFastRoundedCorner(true);
+ return true;
}
bool NativeViewHostMac::SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) {
diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc
index c0cc92ea180..f553d805f14 100644
--- a/chromium/ui/views/controls/prefix_selector.cc
+++ b/chromium/ui/views/controls/prefix_selector.cc
@@ -13,6 +13,7 @@
#include "base/i18n/case_conversion.h"
#include "base/time/default_tick_clock.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/gfx/range/range.h"
@@ -48,7 +49,9 @@ uint32_t PrefixSelector::ConfirmCompositionText(bool keep_selection) {
void PrefixSelector::ClearCompositionText() {}
-void PrefixSelector::InsertText(const base::string16& text) {
+void PrefixSelector::InsertText(const base::string16& text,
+ InsertTextCursorBehavior cursor_behavior) {
+ // TODO(crbug.com/1155331): Handle |cursor_behavior| correctly.
OnTextInput(text);
}
@@ -163,7 +166,7 @@ bool PrefixSelector::ShouldDoLearning() {
return false;
}
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
bool PrefixSelector::SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
@@ -173,7 +176,7 @@ bool PrefixSelector::SetCompositionFromExistingText(
}
#endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
gfx::Range PrefixSelector::GetAutocorrectRange() const {
NOTIMPLEMENTED_LOG_ONCE();
return gfx::Range();
@@ -184,16 +187,11 @@ gfx::Rect PrefixSelector::GetAutocorrectCharacterBounds() const {
return gfx::Rect();
}
-bool PrefixSelector::SetAutocorrectRange(const base::string16& autocorrect_text,
- const gfx::Range& range) {
- // TODO(crbug.com/1091088) Implement setAutocorrectRange.
+bool PrefixSelector::SetAutocorrectRange(const gfx::Range& range) {
+ // TODO(crbug.com/1091088): Implement SetAutocorrectRange.
NOTIMPLEMENTED_LOG_ONCE();
return false;
}
-
-void PrefixSelector::ClearAutocorrectRange() {
- // TODO(crbug.com/1091088) Implement ClearAutocorrectRange.
-}
#endif
#if defined(OS_WIN)
diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h
index 9df6de750fb..a3df03f5c0f 100644
--- a/chromium/ui/views/controls/prefix_selector.h
+++ b/chromium/ui/views/controls/prefix_selector.h
@@ -16,6 +16,7 @@
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/views/views_export.h"
@@ -46,7 +47,8 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
void SetCompositionText(const ui::CompositionText& composition) override;
uint32_t ConfirmCompositionText(bool keep_selection) override;
void ClearCompositionText() override;
- void InsertText(const base::string16& text) override;
+ void InsertText(const base::string16& text,
+ InsertTextCursorBehavior cursor_behavior) override;
void InsertChar(const ui::KeyEvent& event) override;
ui::TextInputType GetTextInputType() const override;
ui::TextInputMode GetTextInputMode() const override;
@@ -76,18 +78,16 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
ukm::SourceId GetClientSourceForMetrics() const override;
bool ShouldDoLearning() override;
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
bool SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
#endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
gfx::Range GetAutocorrectRange() const override;
gfx::Rect GetAutocorrectCharacterBounds() const override;
- bool SetAutocorrectRange(const base::string16& autocorrect_text,
- const gfx::Range& range) override;
- void ClearAutocorrectRange() override;
+ bool SetAutocorrectRange(const gfx::Range& range) override;
#endif
#if defined(OS_WIN)
diff --git a/chromium/ui/views/controls/prefix_selector_unittest.cc b/chromium/ui/views/controls/prefix_selector_unittest.cc
index 567d2209b08..ac40d186c43 100644
--- a/chromium/ui/views/controls/prefix_selector_unittest.cc
+++ b/chromium/ui/views/controls/prefix_selector_unittest.cc
@@ -63,27 +63,43 @@ class PrefixSelectorTest : public ViewsTestBase {
};
TEST_F(PrefixSelectorTest, PrefixSelect) {
- selector_->InsertText(ASCIIToUTF16("an"));
+ selector_->InsertText(
+ ASCIIToUTF16("an"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(1, delegate_.GetSelectedRow());
// Invoke OnViewBlur() to reset time.
selector_->OnViewBlur();
- selector_->InsertText(ASCIIToUTF16("a"));
+ selector_->InsertText(
+ ASCIIToUTF16("a"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(0, delegate_.GetSelectedRow());
selector_->OnViewBlur();
- selector_->InsertText(ASCIIToUTF16("g"));
+ selector_->InsertText(
+ ASCIIToUTF16("g"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(3, delegate_.GetSelectedRow());
selector_->OnViewBlur();
- selector_->InsertText(ASCIIToUTF16("b"));
- selector_->InsertText(ASCIIToUTF16("a"));
+ selector_->InsertText(
+ ASCIIToUTF16("b"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
+ selector_->InsertText(
+ ASCIIToUTF16("a"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(2, delegate_.GetSelectedRow());
selector_->OnViewBlur();
- selector_->InsertText(ASCIIToUTF16("\t"));
- selector_->InsertText(ASCIIToUTF16("b"));
- selector_->InsertText(ASCIIToUTF16("a"));
+ selector_->InsertText(
+ ASCIIToUTF16("\t"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
+ selector_->InsertText(
+ ASCIIToUTF16("b"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
+ selector_->InsertText(
+ ASCIIToUTF16("a"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(2, delegate_.GetSelectedRow());
}
diff --git a/chromium/ui/views/controls/progress_bar.cc b/chromium/ui/views/controls/progress_bar.cc
index 09e80a9c335..00b38aa71de 100644
--- a/chromium/ui/views/controls/progress_bar.cc
+++ b/chromium/ui/views/controls/progress_bar.cc
@@ -31,13 +31,20 @@ namespace {
// In DP, the amount to round the corners of the progress bar (both bg and
// fg, aka slice).
constexpr int kCornerRadius = 3;
+constexpr int kSmallCornerRadius = 1;
-// Adds a rectangle to the path. The corners will be rounded if there is room.
+// Adds a rectangle to the path. The corners will be rounded with regular corner
+// radius if the progress bar height is larger than the regular corner radius.
+// Otherwise the corners will be rounded with the small corner radius if there
+// is room for it.
void AddPossiblyRoundRectToPath(const gfx::Rect& rectangle,
bool allow_round_corner,
SkPath* path) {
- if (!allow_round_corner || rectangle.height() < kCornerRadius) {
+ if (!allow_round_corner || rectangle.height() < kSmallCornerRadius) {
path->addRect(gfx::RectToSkRect(rectangle));
+ } else if (rectangle.height() < kCornerRadius) {
+ path->addRoundRect(gfx::RectToSkRect(rectangle), kSmallCornerRadius,
+ kSmallCornerRadius);
} else {
path->addRoundRect(gfx::RectToSkRect(rectangle), kCornerRadius,
kCornerRadius);
@@ -257,8 +264,8 @@ void ProgressBar::MaybeNotifyAccessibilityValueChanged() {
}
BEGIN_METADATA(ProgressBar, View)
-ADD_PROPERTY_METADATA(SkColor, ForegroundColor)
-ADD_PROPERTY_METADATA(SkColor, BackgroundColor)
+ADD_PROPERTY_METADATA(SkColor, ForegroundColor, metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(SkColor, BackgroundColor, metadata::SkColorConverter)
END_METADATA
} // namespace views
diff --git a/chromium/ui/views/controls/scroll_view.cc b/chromium/ui/views/controls/scroll_view.cc
index 1911f194535..dd9827ffe0e 100644
--- a/chromium/ui/views/controls/scroll_view.cc
+++ b/chromium/ui/views/controls/scroll_view.cc
@@ -23,6 +23,7 @@
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/focus_ring.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/view.h"
@@ -49,7 +50,10 @@ T CombineScrollOffsets(T x, T y) {
class ScrollCornerView : public View {
public:
+ METADATA_HEADER(ScrollCornerView);
ScrollCornerView() = default;
+ ScrollCornerView(const ScrollCornerView&) = delete;
+ ScrollCornerView& operator=(const ScrollCornerView&) = delete;
void OnPaint(gfx::Canvas* canvas) override {
ui::NativeTheme::ExtraParams ignored;
@@ -57,11 +61,11 @@ class ScrollCornerView : public View {
canvas->sk_canvas(), ui::NativeTheme::kScrollbarCorner,
ui::NativeTheme::kNormal, GetLocalBounds(), ignored);
}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ScrollCornerView);
};
+BEGIN_METADATA(ScrollCornerView, View)
+END_METADATA
+
// Returns true if any descendants of |view| have a layer (not including
// |view|).
bool DoesDescendantHaveLayer(View* view) {
@@ -128,7 +132,10 @@ int AdjustPosition(int current_position,
// Viewport contains the contents View of the ScrollView.
class ScrollView::Viewport : public View {
public:
+ METADATA_HEADER(Viewport);
explicit Viewport(ScrollView* scroll_view) : scroll_view_(scroll_view) {}
+ Viewport(const Viewport&) = delete;
+ Viewport& operator=(const Viewport&) = delete;
~Viewport() override = default;
void ScrollRectToVisible(const gfx::Rect& rect) override {
@@ -159,25 +166,27 @@ class ScrollView::Viewport : public View {
void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) override {
- if (details.is_add && IsContentsViewport() && Contains(details.parent))
+ if (details.is_add && GetIsContentsViewport() && Contains(details.parent))
scroll_view_->UpdateViewportLayerForClipping();
}
void OnChildLayerChanged(View* child) override {
- if (IsContentsViewport())
+ if (GetIsContentsViewport())
scroll_view_->UpdateViewportLayerForClipping();
}
private:
- bool IsContentsViewport() const {
+ bool GetIsContentsViewport() const {
return parent() && scroll_view_->contents_viewport_ == this;
}
ScrollView* scroll_view_;
-
- DISALLOW_COPY_AND_ASSIGN(Viewport);
};
+BEGIN_METADATA(ScrollView, Viewport, View)
+ADD_READONLY_PROPERTY_METADATA(bool, IsContentsViewport)
+END_METADATA
+
ScrollView::ScrollView()
: ScrollView(base::FeatureList::IsEnabled(
::features::kUiCompositorScrollWithLayers)
@@ -365,6 +374,47 @@ void ScrollView::SetDrawOverflowIndicator(bool draw_overflow_indicator) {
OnPropertyChanged(&draw_overflow_indicator_, kPropertyEffectsPaint);
}
+View* ScrollView::SetCustomOverflowIndicator(OverflowIndicatorAlignment side,
+ std::unique_ptr<View> indicator,
+ int thickness,
+ bool fills_opaquely) {
+ if (thickness < 0)
+ thickness = 0;
+
+ if (ScrollsWithLayers()) {
+ indicator->SetPaintToLayer();
+ indicator->layer()->SetFillsBoundsOpaquely(fills_opaquely);
+ }
+
+ View* indicator_ptr = indicator.get();
+ switch (side) {
+ case OverflowIndicatorAlignment::kLeft:
+ more_content_left_ = std::move(indicator);
+ more_content_left_thickness_ = thickness;
+ break;
+ case OverflowIndicatorAlignment::kTop:
+ more_content_top_ = std::move(indicator);
+ more_content_top_thickness_ = thickness;
+ break;
+ case OverflowIndicatorAlignment::kRight:
+ more_content_right_ = std::move(indicator);
+ more_content_right_thickness_ = thickness;
+ break;
+ case OverflowIndicatorAlignment::kBottom:
+ more_content_bottom_ = std::move(indicator);
+ more_content_bottom_thickness_ = thickness;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ UpdateOverflowIndicatorVisibility(CurrentOffset());
+ PositionOverflowIndicators();
+
+ return indicator_ptr;
+}
+
void ScrollView::ClipHeightTo(int min_height, int max_height) {
min_height_ = min_height;
max_height_ = max_height;
@@ -408,6 +458,14 @@ void ScrollView::SetHasFocusIndicator(bool has_focus_indicator) {
OnPropertyChanged(&draw_focus_indicator_, kPropertyEffectsPaint);
}
+void ScrollView::AddScrollViewObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ScrollView::RemoveScrollViewObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
gfx::Size ScrollView::CalculatePreferredSize() const {
if (!is_bounded())
return View::CalculatePreferredSize();
@@ -994,18 +1052,10 @@ gfx::ScrollOffset ScrollView::CurrentOffset() const {
void ScrollView::ScrollToOffset(const gfx::ScrollOffset& offset) {
if (ScrollsWithLayers()) {
contents_->layer()->SetScrollOffset(offset);
-
- // TODO(tapted): Remove this call to OnLayerScrolled(). It's unnecessary,
- // but will only be invoked (asynchronously) when a Compositor is present
- // and commits a frame, which isn't true in some tests.
- // See http://crbug.com/637521.
- OnLayerScrolled(offset, contents_->layer()->element_id());
} else {
contents_->SetPosition(gfx::Point(-offset.x(), -offset.y()));
- ScrollHeader();
}
- UpdateOverflowIndicatorVisibility(offset);
- UpdateScrollBarPositions();
+ OnScrolled(offset);
}
bool ScrollView::ScrollsWithLayers() const {
@@ -1051,10 +1101,18 @@ void ScrollView::EnableViewportLayer() {
UpdateBackground();
}
-void ScrollView::OnLayerScrolled(const gfx::ScrollOffset&,
+void ScrollView::OnLayerScrolled(const gfx::ScrollOffset& current_offset,
const cc::ElementId&) {
+ OnScrolled(current_offset);
+}
+
+void ScrollView::OnScrolled(const gfx::ScrollOffset& offset) {
+ UpdateOverflowIndicatorVisibility(offset);
UpdateScrollBarPositions();
ScrollHeader();
+
+ for (auto& observer : observers_)
+ observer.OnContentsScrolled();
}
void ScrollView::ScrollHeader() {
@@ -1117,16 +1175,23 @@ base::Optional<ui::NativeTheme::ColorId> ScrollView::GetBackgroundThemeColorId()
}
void ScrollView::PositionOverflowIndicators() {
- const gfx::Rect bounds = GetContentsBounds();
- const int x = bounds.x();
- const int y = bounds.y();
- const int w = bounds.width();
- const int h = bounds.height();
- const int t = Separator::kThickness;
- more_content_left_->SetBounds(x, y, t, h);
- more_content_top_->SetBounds(x, y, w, t);
- more_content_right_->SetBounds(bounds.right() - t, y, t, h);
- more_content_bottom_->SetBounds(x, bounds.bottom() - t, w, t);
+ // TODO(https://crbug.com/1166949): Use a layout manager to position these.
+ const gfx::Rect contents_bounds = GetContentsBounds();
+ const int x = contents_bounds.x();
+ const int y = contents_bounds.y();
+ const int w = contents_bounds.width();
+ const int h = contents_bounds.height();
+
+ more_content_left_->SetBoundsRect(
+ gfx::Rect(x, y, more_content_left_thickness_, h));
+ more_content_top_->SetBoundsRect(
+ gfx::Rect(x, y, w, more_content_top_thickness_));
+ more_content_right_->SetBoundsRect(
+ gfx::Rect(contents_bounds.right() - more_content_right_thickness_, y,
+ more_content_right_thickness_, h));
+ more_content_bottom_->SetBoundsRect(
+ gfx::Rect(x, contents_bounds.bottom() - more_content_bottom_thickness_, w,
+ more_content_bottom_thickness_));
}
void ScrollView::UpdateOverflowIndicatorVisibility(
@@ -1137,7 +1202,7 @@ void ScrollView::UpdateOverflowIndicatorVisibility(
draw_overflow_indicator_);
SetControlVisibility(
more_content_bottom_.get(),
- !draw_border_ && vert_sb_->GetVisible() && !horiz_sb_->GetVisible() &&
+ !draw_border_ && IsVerticalScrollEnabled() && !horiz_sb_->GetVisible() &&
offset.y() < vert_sb_->GetMaxPosition() && draw_overflow_indicator_);
SetControlVisibility(more_content_left_.get(),
diff --git a/chromium/ui/views/controls/scroll_view.h b/chromium/ui/views/controls/scroll_view.h
index ec20b217e4a..c7227c266b7 100644
--- a/chromium/ui/views/controls/scroll_view.h
+++ b/chromium/ui/views/controls/scroll_view.h
@@ -27,7 +27,7 @@ namespace test {
class ScrollViewTestApi;
}
-class Separator;
+enum class OverflowIndicatorAlignment { kLeft, kTop, kRight, kBottom };
/////////////////////////////////////////////////////////////////////////////
//
@@ -63,6 +63,12 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
kEnabled
};
+ class Observer {
+ public:
+ // Called when |contents_| scrolled.
+ virtual void OnContentsScrolled() {}
+ };
+
ScrollView();
// Additional constructor for overriding scrolling as defined by
@@ -140,6 +146,11 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
bool GetDrawOverflowIndicator() const { return draw_overflow_indicator_; }
void SetDrawOverflowIndicator(bool draw_overflow_indicator);
+ View* SetCustomOverflowIndicator(OverflowIndicatorAlignment side,
+ std::unique_ptr<View> indicator,
+ int thickness,
+ bool fills_opaquely);
+
// Turns this scroll view into a bounded scroll view, with a fixed height.
// By default, a ScrollView will stretch to fill its outer container.
void ClipHeightTo(int min_height, int max_height);
@@ -166,6 +177,9 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
bool GetHasFocusIndicator() const { return draw_focus_indicator_; }
void SetHasFocusIndicator(bool has_focus_indicator);
+ void AddScrollViewObserver(Observer* observer);
+ void RemoveScrollViewObserver(Observer* observer);
+
// View overrides:
gfx::Size CalculatePreferredSize() const override;
int GetHeightForWidth(int width) const override;
@@ -244,6 +258,9 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
// Callback entrypoint when hosted Layers are scrolled by the Compositor.
void OnLayerScrolled(const gfx::ScrollOffset&, const cc::ElementId&);
+ // Updates accessory elements when |contents_| is scrolled.
+ void OnScrolled(const gfx::ScrollOffset& offset);
+
// Horizontally scrolls the header (if any) to match the contents.
void ScrollHeader();
@@ -279,12 +296,16 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
std::unique_ptr<View> corner_view_;
// Hidden content indicators
- std::unique_ptr<Separator> more_content_left_ = std::make_unique<Separator>();
- std::unique_ptr<Separator> more_content_top_ = std::make_unique<Separator>();
- std::unique_ptr<Separator> more_content_right_ =
- std::make_unique<Separator>();
- std::unique_ptr<Separator> more_content_bottom_ =
- std::make_unique<Separator>();
+ // TODO(https://crbug.com/1166949): Use preferred width/height instead of
+ // thickness members.
+ std::unique_ptr<View> more_content_left_ = std::make_unique<Separator>();
+ int more_content_left_thickness_ = Separator::kThickness;
+ std::unique_ptr<View> more_content_top_ = std::make_unique<Separator>();
+ int more_content_top_thickness_ = Separator::kThickness;
+ std::unique_ptr<View> more_content_right_ = std::make_unique<Separator>();
+ int more_content_right_thickness_ = Separator::kThickness;
+ std::unique_ptr<View> more_content_bottom_ = std::make_unique<Separator>();
+ int more_content_bottom_thickness_ = Separator::kThickness;
// The min and max height for the bounded scroll view. These are negative
// values if the view is not bounded.
@@ -322,6 +343,8 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
// The focus ring for this ScrollView.
FocusRing* focus_ring_ = nullptr;
+ base::ObserverList<Observer>::Unchecked observers_;
+
DISALLOW_COPY_AND_ASSIGN(ScrollView);
};
diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc
index 3eb0d5202c9..2e6aa82878c 100644
--- a/chromium/ui/views/controls/scroll_view_unittest.cc
+++ b/chromium/ui/views/controls/scroll_view_unittest.cc
@@ -25,7 +25,6 @@
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
#include "ui/views/controls/scrollbar/scroll_bar_views.h"
-#include "ui/views/controls/separator.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
@@ -77,16 +76,10 @@ class ScrollViewTestApi {
View* corner_view() { return scroll_view_->corner_view_.get(); }
View* contents_viewport() { return scroll_view_->contents_viewport_; }
- Separator* more_content_left() {
- return scroll_view_->more_content_left_.get();
- }
- Separator* more_content_top() {
- return scroll_view_->more_content_top_.get();
- }
- Separator* more_content_right() {
- return scroll_view_->more_content_right_.get();
- }
- Separator* more_content_bottom() {
+ View* more_content_left() { return scroll_view_->more_content_left_.get(); }
+ View* more_content_top() { return scroll_view_->more_content_top_.get(); }
+ View* more_content_right() { return scroll_view_->more_content_right_.get(); }
+ View* more_content_bottom() {
return scroll_view_->more_content_bottom_.get();
}
@@ -1740,6 +1733,73 @@ TEST_F(ScrollViewTest, VerticalWithHeaderOverflowIndicators) {
EXPECT_FALSE(test_api.more_content_right()->GetVisible());
}
+TEST_F(ScrollViewTest, CustomOverflowIndicator) {
+ const int kWidth = 100;
+ const int kHeight = 100;
+
+ ScrollViewTestApi test_api(scroll_view_.get());
+
+ // Set up with both horizontal and vertical scrolling.
+ auto contents = std::make_unique<FixedView>();
+ contents->SetPreferredSize(gfx::Size(kWidth * 5, kHeight * 5));
+ scroll_view_->SetContents(std::move(contents));
+
+ // Hide both scrollbars so they don't interfere with indicator visibility.
+ scroll_view_->SetHorizontalScrollBarMode(
+ views::ScrollView::ScrollBarMode::kHiddenButEnabled);
+ scroll_view_->SetVerticalScrollBarMode(
+ views::ScrollView::ScrollBarMode::kHiddenButEnabled);
+
+ // Make sure the size is set so the ScrollView is smaller than its contents
+ // in both directions.
+ scroll_view_->SetSize(gfx::Size(kWidth, kHeight));
+
+ // The horizontal and vertical scroll bars should not be visible.
+ CheckScrollbarVisibility(scroll_view_.get(), HORIZONTAL, false);
+ CheckScrollbarVisibility(scroll_view_.get(), VERTICAL, false);
+
+ // Make sure the initial origin is 0,0
+ EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
+
+ // Now scroll the view to someplace in the middle of the scrollable region.
+ int offset_x = kWidth * 2;
+ scroll_view_->ScrollToPosition(test_api.GetScrollBar(HORIZONTAL), offset_x);
+ int offset_y = kHeight * 2;
+ scroll_view_->ScrollToPosition(test_api.GetScrollBar(VERTICAL), offset_y);
+ EXPECT_EQ(gfx::ScrollOffset(offset_x, offset_y), test_api.CurrentOffset());
+
+ // All overflow indicators should be visible.
+ ASSERT_TRUE(test_api.more_content_right()->GetVisible());
+ ASSERT_TRUE(test_api.more_content_bottom()->GetVisible());
+ ASSERT_TRUE(test_api.more_content_left()->GetVisible());
+ ASSERT_TRUE(test_api.more_content_top()->GetVisible());
+
+ // This should be similar to the default separator.
+ View* left_indicator = scroll_view_->SetCustomOverflowIndicator(
+ OverflowIndicatorAlignment::kLeft, std::make_unique<View>(), 1, true);
+ EXPECT_EQ(gfx::Rect(0, 0, 1, 100), left_indicator->bounds());
+ if (left_indicator->layer())
+ EXPECT_TRUE(left_indicator->layer()->fills_bounds_opaquely());
+
+ // A larger, but still reasonable, indicator that is not opaque.
+ View* top_indicator = scroll_view_->SetCustomOverflowIndicator(
+ OverflowIndicatorAlignment::kTop, std::make_unique<View>(), 20, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 20), top_indicator->bounds());
+ if (top_indicator->layer())
+ EXPECT_FALSE(top_indicator->layer()->fills_bounds_opaquely());
+
+ // Negative thickness doesn't make sense. It should be treated like zero.
+ View* right_indicator = scroll_view_->SetCustomOverflowIndicator(
+ OverflowIndicatorAlignment::kRight, std::make_unique<View>(), -1, true);
+ EXPECT_EQ(gfx::Rect(100, 0, 0, 100), right_indicator->bounds());
+
+ // Thicker than the scrollview is strange, but works as you'd expect.
+ View* bottom_indicator = scroll_view_->SetCustomOverflowIndicator(
+ OverflowIndicatorAlignment::kBottom, std::make_unique<View>(), 1000,
+ true);
+ EXPECT_EQ(gfx::Rect(0, -900, 100, 1000), bottom_indicator->bounds());
+}
+
// Ensure ScrollView::Layout succeeds if a disabled scrollbar's overlap style
// does not match the other scrollbar.
TEST_F(ScrollViewTest, IgnoreOverlapWithDisabledHorizontalScroll) {
diff --git a/chromium/ui/views/controls/separator.cc b/chromium/ui/views/controls/separator.cc
index dc2e4e43918..d3d985bb758 100644
--- a/chromium/ui/views/controls/separator.cc
+++ b/chromium/ui/views/controls/separator.cc
@@ -15,6 +15,8 @@
namespace views {
+constexpr int Separator::kThickness;
+
Separator::Separator() = default;
Separator::~Separator() = default;
@@ -95,7 +97,7 @@ void Separator::OnPaint(gfx::Canvas* canvas) {
}
BEGIN_METADATA(Separator, View)
-ADD_PROPERTY_METADATA(SkColor, Color)
+ADD_PROPERTY_METADATA(SkColor, Color, metadata::SkColorConverter)
ADD_PROPERTY_METADATA(int, PreferredHeight)
END_METADATA
diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc
index e36544f90bd..d96abc32f8e 100644
--- a/chromium/ui/views/controls/styled_label_unittest.cc
+++ b/chromium/ui/views/controls/styled_label_unittest.cc
@@ -346,9 +346,11 @@ TEST_F(StyledLabelTest, StyledRangeTextStyleBold) {
InitStyledLabel(bold_text + text);
// Pretend disabled text becomes bold for testing.
- bold_provider.SetFont(
- style::CONTEXT_LABEL, style::STYLE_DISABLED,
- styled()->GetFontList().DeriveWithWeight(gfx::Font::Weight::BOLD));
+ auto details =
+ bold_provider.GetFontDetails(style::CONTEXT_LABEL, style::STYLE_DISABLED);
+ details.weight = gfx::Font::Weight::BOLD;
+ bold_provider.SetFontDetails(style::CONTEXT_LABEL, style::STYLE_DISABLED,
+ details);
StyledLabel::RangeStyleInfo style_info;
style_info.text_style = style::STYLE_DISABLED;
diff --git a/chromium/ui/views/controls/tabbed_pane/DIR_METADATA b/chromium/ui/views/controls/tabbed_pane/DIR_METADATA
new file mode 100644
index 00000000000..d1a66307bfa
--- /dev/null
+++ b/chromium/ui/views/controls/tabbed_pane/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail: {
+ component: "Internals>Views"
+}
diff --git a/chromium/ui/views/controls/tabbed_pane/OWNERS b/chromium/ui/views/controls/tabbed_pane/OWNERS
index dba3e5032df..80d20216a72 100644
--- a/chromium/ui/views/controls/tabbed_pane/OWNERS
+++ b/chromium/ui/views/controls/tabbed_pane/OWNERS
@@ -1,4 +1,2 @@
ellyjones@chromium.org
msw@chromium.org
-
-# COMPONENT: Internals>Views
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 4e43dde3360..9fbfeab4516 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -5,6 +5,7 @@
#include "ui/views/controls/tabbed_pane/tabbed_pane.h"
#include <algorithm>
+#include <string>
#include <utility>
#include "base/check_op.h"
@@ -229,8 +230,8 @@ void Tab::OnStateChanged() {
}
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
- title_->SetFontList(
- rb.GetFontListWithDelta(font_size_delta, gfx::Font::NORMAL, font_weight));
+ title_->SetFontList(rb.GetFontListForDetails(ui::ResourceBundle::FontDetails(
+ std::string(), font_size_delta, font_weight)));
}
void Tab::OnPaint(gfx::Canvas* canvas) {
diff --git a/chromium/ui/views/controls/table/table_header.cc b/chromium/ui/views/controls/table/table_header.cc
index 0933088aa47..113ed48a5e2 100644
--- a/chromium/ui/views/controls/table/table_header.cc
+++ b/chromium/ui/views/controls/table/table_header.cc
@@ -20,6 +20,7 @@
#include "ui/views/background.h"
#include "ui/views/controls/table/table_utils.h"
#include "ui/views/controls/table/table_view.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/native_cursor.h"
namespace views {
@@ -46,8 +47,6 @@ constexpr int kSortIndicatorSize = 8;
} // namespace
// static
-const char TableHeader::kViewClassName[] = "TableHeader";
-// static
const int TableHeader::kHorizontalPadding = 7;
// static
const int TableHeader::kSortIndicatorWidth =
@@ -164,10 +163,6 @@ void TableHeader::OnPaint(gfx::Canvas* canvas) {
}
}
-const char* TableHeader::GetClassName() const {
- return kViewClassName;
-}
-
gfx::Size TableHeader::CalculatePreferredSize() const {
return gfx::Size(1, kVerticalPadding * 2 + font_list_.GetHeight());
}
@@ -335,5 +330,7 @@ int TableHeader::GetResizeColumn(int x) const {
return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ? index
: -1;
}
+BEGIN_METADATA(TableHeader, View)
+END_METADATA
} // namespace views
diff --git a/chromium/ui/views/controls/table/table_header.h b/chromium/ui/views/controls/table/table_header.h
index 66516b1eee2..a79cd242a1e 100644
--- a/chromium/ui/views/controls/table/table_header.h
+++ b/chromium/ui/views/controls/table/table_header.h
@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "ui/gfx/font_list.h"
#include "ui/views/controls/table/table_view.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/view.h"
#include "ui/views/views_export.h"
@@ -18,8 +19,7 @@ namespace views {
// Views used to render the header for the table.
class VIEWS_EXPORT TableHeader : public views::View {
public:
- // Internal class name.
- static const char kViewClassName[];
+ METADATA_HEADER(TableHeader);
// Amount the text is padded on the left/right side.
static const int kHorizontalPadding;
@@ -28,6 +28,8 @@ class VIEWS_EXPORT TableHeader : public views::View {
static const int kSortIndicatorWidth;
explicit TableHeader(TableView* table);
+ TableHeader(const TableHeader&) = delete;
+ TableHeader& operator=(const TableHeader&) = delete;
~TableHeader() override;
const gfx::FontList& font_list() const { return font_list_; }
@@ -37,7 +39,6 @@ class VIEWS_EXPORT TableHeader : public views::View {
// views::View overrides.
void OnPaint(gfx::Canvas* canvas) override;
- const char* GetClassName() const override;
gfx::Size CalculatePreferredSize() const override;
bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
void OnVisibleBoundsChanged() override;
@@ -88,8 +89,6 @@ class VIEWS_EXPORT TableHeader : public views::View {
// If non-null a resize is in progress.
std::unique_ptr<ColumnResizeDetails> resize_details_;
-
- DISALLOW_COPY_AND_ASSIGN(TableHeader);
};
} // namespace views
diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc
index 1b254a0b416..b9bac9d0965 100644
--- a/chromium/ui/views/controls/table/table_view.cc
+++ b/chromium/ui/views/controls/table/table_view.cc
@@ -251,8 +251,25 @@ void TableView::Select(int model_row) {
SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row));
}
+void TableView::SetSelectionAll(bool select) {
+ if (!GetRowCount())
+ return;
+
+ ui::ListSelectionModel selection_model;
+
+ if (select)
+ selection_model.AddIndexRangeToSelection(0, GetRowCount() - 1);
+
+ selection_model.set_anchor(selection_model_.anchor());
+ selection_model.set_active(selection_model_.active());
+
+ SetSelectionModel(std::move(selection_model));
+}
+
int TableView::GetFirstSelectedRow() const {
- return selection_model_.empty() ? -1 : selection_model_.selected_indices()[0];
+ return selection_model_.empty()
+ ? -1
+ : *selection_model_.selected_indices().begin();
}
void TableView::SetColumnVisibility(int id, bool is_visible) {
@@ -470,11 +487,7 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
case ui::VKEY_A:
// control-a selects all.
if (IsCmdOrCtrl(event) && !single_selection_ && GetRowCount()) {
- ui::ListSelectionModel selection_model;
- selection_model.SetSelectedIndex(selection_model_.active());
- for (int i = 0; i < GetRowCount(); ++i)
- selection_model.AddIndexToSelection(i);
- SetSelectionModel(std::move(selection_model));
+ SetSelectionAll(/*select=*/true);
return true;
}
break;
@@ -747,14 +760,18 @@ void TableView::OnItemsRemoved(int start, int length) {
for (int i = 0; i < length; ++i)
selection_model_.DecrementFrom(start);
- // Remove the virtual views that are no longer needed.
- auto& virtual_children = GetViewAccessibility().virtual_children();
- for (int i = start; i < start + length; i++)
- virtual_children[virtual_children.size() - 1]->RemoveFromParentView();
-
+ // Update the `view_to_model_` and `model_to_view_` mappings prior to updating
+ // TableView's virtual children below. We do this because at this point the
+ // table model has changed but the model-view mappings have not yet been
+ // updated to reflect this. `RemoveFromParentView()` below may trigger calls
+ // back into TableView and this would happen before the model-view mappings
+ // have been updated. This can result in memory overflow errors.
+ // See (https://crbug.com/1173373).
SortItemsAndUpdateMapping(/*schedule_paint=*/true);
- PreferredSizeChanged();
- NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, true);
+ if (GetIsSorted()) {
+ DCHECK_EQ(GetRowCount(), int{view_to_model_.size()});
+ DCHECK_EQ(GetRowCount(), int{model_to_view_.size()});
+ }
// If the selection was empty and is no longer empty select the same visual
// index.
@@ -768,6 +785,15 @@ void TableView::OnItemsRemoved(int start, int length) {
if (!selection_model_.empty() && selection_model_.anchor() == -1)
selection_model_.set_anchor(GetFirstSelectedRow());
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
+
+ // Remove the virtual views that are no longer needed.
+ auto& virtual_children = GetViewAccessibility().virtual_children();
+ for (int i = start; i < start + length; i++)
+ virtual_children[virtual_children.size() - 1]->RemoveFromParentView();
+
+ UpdateVirtualAccessibilityChildrenBounds();
+ PreferredSizeChanged();
+ NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, true);
if (observer_)
observer_->OnSelectionChanged();
}
@@ -1092,8 +1118,10 @@ void TableView::SchedulePaintForSelection() {
if (selection_model_.size() == 1) {
const int first_model_row = GetFirstSelectedRow();
SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row)));
- if (first_model_row != selection_model_.active())
- SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_.active())));
+
+ const int active_row = selection_model_.active();
+ if (active_row >= 0 && first_model_row != active_row)
+ SchedulePaintInRect(GetRowBounds(ModelToView(active_row)));
} else if (selection_model_.size() > 1) {
SchedulePaint();
}
diff --git a/chromium/ui/views/controls/table/table_view.h b/chromium/ui/views/controls/table/table_view.h
index e20f57b2b34..962cc76169f 100644
--- a/chromium/ui/views/controls/table/table_view.h
+++ b/chromium/ui/views/controls/table/table_view.h
@@ -138,6 +138,9 @@ class VIEWS_EXPORT TableView : public views::View,
// Selects the specified item, making sure it's visible.
void Select(int model_row);
+ // Selects all items.
+ void SetSelectionAll(bool select);
+
// Returns the first selected row in terms of the model.
int GetFirstSelectedRow() const;
@@ -218,6 +221,11 @@ class VIEWS_EXPORT TableView : public views::View,
// table's virtual accessibility children.
void UpdateVirtualAccessibilityChildrenBounds();
+ // Returns the virtual accessibility view corresponding to the specified cell.
+ // |row| should be a view index, not a model index.
+ // |visible_column_index| indexes into |visible_columns_|.
+ AXVirtualView* GetVirtualAccessibilityCell(int row, int visible_column_index);
+
// View overrides:
void Layout() override;
gfx::Size CalculatePreferredSize() const override;
@@ -381,11 +389,6 @@ class VIEWS_EXPORT TableView : public views::View,
// |row| should be a view index, not a model index.
AXVirtualView* GetVirtualAccessibilityRow(int row);
- // Returns the virtual accessibility view corresponding to the specified cell.
- // |row| should be a view index, not a model index.
- // |visible_column_index| indexes into |visible_columns_|.
- AXVirtualView* GetVirtualAccessibilityCell(int row, int visible_column_index);
-
// Creates a virtual accessibility view that is used to expose information
// about the row at |view_index| to assistive software.
std::unique_ptr<AXVirtualView> CreateRowAccessibilityView(int view_index);
diff --git a/chromium/ui/views/controls/table/table_view_unittest.cc b/chromium/ui/views/controls/table/table_view_unittest.cc
index dfa2affc493..ff2060e1b41 100644
--- a/chromium/ui/views/controls/table/table_view_unittest.cc
+++ b/chromium/ui/views/controls/table/table_view_unittest.cc
@@ -172,6 +172,9 @@ class TestTableModel2 : public ui::TableModel {
// Reorders rows in the model.
void MoveRows(int row_from, int length, int row_to);
+ // Allows overriding the tooltip for testing.
+ void SetTooltip(const base::string16& tooltip);
+
// ui::TableModel:
int RowCount() override;
base::string16 GetText(int row, int column_id) override;
@@ -182,6 +185,8 @@ class TestTableModel2 : public ui::TableModel {
private:
ui::TableModelObserver* observer_ = nullptr;
+ base::Optional<base::string16> tooltip_;
+
// The data.
std::vector<std::vector<int>> rows_;
@@ -264,6 +269,10 @@ void TestTableModel2::MoveRows(int row_from, int length, int row_to) {
observer_->OnItemsMoved(row_from, length, row_to);
}
+void TestTableModel2::SetTooltip(const base::string16& tooltip) {
+ tooltip_ = tooltip;
+}
+
int TestTableModel2::RowCount() {
return static_cast<int>(rows_.size());
}
@@ -273,7 +282,8 @@ base::string16 TestTableModel2::GetText(int row, int column_id) {
}
base::string16 TestTableModel2::GetTooltip(int row) {
- return base::ASCIIToUTF16("Tooltip") + base::NumberToString16(row);
+ return tooltip_ ? *tooltip_
+ : base::ASCIIToUTF16("Tooltip") + base::NumberToString16(row);
}
void TestTableModel2::SetObserver(ui::TableModelObserver* observer) {
@@ -471,10 +481,14 @@ class TableViewTest : public ViewsTestBase,
" selection=";
const ui::ListSelectionModel::SelectedIndices& selection(
model.selected_indices());
- for (size_t i = 0; i < selection.size(); ++i) {
- if (i != 0)
+ bool first = true;
+ for (int index : selection) {
+ if (first) {
+ first = false;
+ } else {
result += " ";
- result += base::NumberToString(selection[i]);
+ }
+ result += base::NumberToString(index);
}
return result;
}
@@ -1295,6 +1309,29 @@ TEST_P(TableViewTest, Selection) {
table_->set_observer(nullptr);
}
+TEST_P(TableViewTest, SelectAll) {
+ TableViewObserverImpl observer;
+ table_->set_observer(&observer);
+
+ // Initially no selection.
+ EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString());
+
+ table_->SetSelectionAll(/*select=*/true);
+ EXPECT_EQ(1, observer.GetChangedCountAndClear());
+ EXPECT_EQ("active=-1 anchor=-1 selection=0 1 2 3", SelectionStateAsString());
+
+ table_->Select(2);
+ EXPECT_EQ(1, observer.GetChangedCountAndClear());
+ EXPECT_EQ("active=2 anchor=2 selection=2", SelectionStateAsString());
+
+ table_->SetSelectionAll(/*select=*/true);
+ EXPECT_EQ(1, observer.GetChangedCountAndClear());
+ EXPECT_EQ("active=2 anchor=2 selection=0 1 2 3", SelectionStateAsString());
+
+ table_->SetSelectionAll(/*select=*/false);
+ EXPECT_EQ("active=2 anchor=2 selection=", SelectionStateAsString());
+}
+
TEST_P(TableViewTest, RemoveUnselectedRows) {
TableViewObserverImpl observer;
table_->set_observer(&observer);
@@ -1897,6 +1934,34 @@ TEST_P(TableViewTest, FocusAfterRemovingAnchor) {
table_->RequestFocus();
}
+// OnItemsRemoved() should ensure view-model mappings are updated in response to
+// the table model change before these view-model mappings are used.
+// Test for (https://crbug.com/1173373).
+TEST_P(TableViewTest, RemovingSortedRowsDoesNotCauseOverflow) {
+ // Ensure the table has a sort descriptor set so that `view_to_model_` and
+ // `model_to_view_` mappings are established and are in descending order. Do
+ // this so the first view row maps to the last model row.
+ table_->ToggleSortOrder(0);
+ table_->ToggleSortOrder(0);
+ ASSERT_TRUE(table_->GetIsSorted());
+ ASSERT_EQ(1u, table_->sort_descriptors().size());
+ EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
+ EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
+ EXPECT_EQ("3 2 1 0", GetViewToModelAsString(table_));
+ EXPECT_EQ("3 2 1 0", GetModelToViewAsString(table_));
+
+ // Removing rows can result in callbacks to GetTooltipText(). Above mappings
+ // will cause TableView to try to access the text for the first view row and
+ // consequently attempt to access the last element in the model via the
+ // `view_to_model_` mapping. This will result in a crash if the view-model
+ // mappings have not been appropriately updated.
+ model_->SetTooltip(base::ASCIIToUTF16(""));
+ model_->RemoveRow(0);
+ model_->RemoveRow(0);
+ model_->RemoveRow(0);
+ model_->RemoveRow(0);
+}
+
namespace {
class RemoveFocusChangeListenerDelegate : public WidgetDelegate {
diff --git a/chromium/ui/views/controls/textarea/textarea.cc b/chromium/ui/views/controls/textarea/textarea.cc
new file mode 100644
index 00000000000..de01539052c
--- /dev/null
+++ b/chromium/ui/views/controls/textarea/textarea.cc
@@ -0,0 +1,114 @@
+// Copyright 2019 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/textarea/textarea.h"
+
+#include "base/logging.h"
+#include "ui/base/ime/text_edit_commands.h"
+#include "ui/events/event.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+
+namespace views {
+
+Textarea::Textarea() {
+ GetRenderText()->SetMultiline(true);
+ GetRenderText()->SetVerticalAlignment(gfx::ALIGN_TOP);
+ GetRenderText()->SetWordWrapBehavior(gfx::WRAP_LONG_WORDS);
+}
+
+size_t Textarea::GetNumLines() {
+ return GetRenderText()->GetNumLines();
+}
+
+bool Textarea::OnMouseWheel(const ui::MouseWheelEvent& event) {
+ GetRenderText()->SetDisplayOffset(GetRenderText()->GetUpdatedDisplayOffset() +
+ gfx::Vector2d(0, event.y_offset()));
+ UpdateCursorViewPosition();
+ SchedulePaint();
+ return true;
+}
+
+Textfield::EditCommandResult Textarea::DoExecuteTextEditCommand(
+ ui::TextEditCommand command) {
+ bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
+ gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
+ gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT;
+
+ switch (command) {
+ case ui::TextEditCommand::MOVE_UP:
+ textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_UP,
+ gfx::SELECTION_NONE);
+ break;
+ case ui::TextEditCommand::MOVE_DOWN:
+ textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_DOWN,
+ gfx::SELECTION_NONE);
+ break;
+ case ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION:
+ textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_UP,
+ gfx::SELECTION_RETAIN);
+ break;
+ case ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION:
+ textfield_model()->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_DOWN,
+ gfx::SELECTION_RETAIN);
+ break;
+ case ui::TextEditCommand::
+ MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION:
+ case ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION:
+ textfield_model()->MoveCursor(gfx::FIELD_BREAK, begin,
+ kPageSelectionBehavior);
+ break;
+ case ui::TextEditCommand::
+ MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION:
+ textfield_model()->MoveCursor(gfx::FIELD_BREAK, begin,
+ kMoveParagraphSelectionBehavior);
+ break;
+ case ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION:
+ case ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION:
+ textfield_model()->MoveCursor(gfx::FIELD_BREAK, end,
+ kPageSelectionBehavior);
+ break;
+ case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION:
+ textfield_model()->MoveCursor(gfx::FIELD_BREAK, end,
+ kMoveParagraphSelectionBehavior);
+ break;
+ default:
+ return Textfield::DoExecuteTextEditCommand(command);
+ }
+
+ // TODO(jongkwon.lee): Return |cursor_changed| with actual value. It's okay
+ // for now because |cursor_changed| is detected afterward in
+ // |Textfield::ExecuteTextEditCommand|.
+ return {false, false};
+}
+
+bool Textarea::PreHandleKeyPressed(const ui::KeyEvent& event) {
+ if (event.key_code() == ui::VKEY_RETURN) {
+ DoInsertChar('\n');
+ return true;
+ }
+ return false;
+}
+
+ui::TextEditCommand Textarea::GetCommandForKeyEvent(const ui::KeyEvent& event) {
+ if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode())
+ return Textfield::GetCommandForKeyEvent(event);
+
+ const bool shift = event.IsShiftDown();
+ switch (event.key_code()) {
+ case ui::VKEY_UP:
+ return shift ? ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::MOVE_UP;
+ case ui::VKEY_DOWN:
+ return shift ? ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::MOVE_DOWN;
+ default:
+ return Textfield::GetCommandForKeyEvent(event);
+ }
+}
+
+BEGIN_METADATA(Textarea, Textfield)
+END_METADATA
+
+} // namespace views
diff --git a/chromium/ui/views/controls/textarea/textarea.h b/chromium/ui/views/controls/textarea/textarea.h
new file mode 100644
index 00000000000..9e0699c12fb
--- /dev/null
+++ b/chromium/ui/views/controls/textarea/textarea.h
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTAREA_TEXTAREA_H_
+#define UI_VIEWS_CONTROLS_TEXTAREA_TEXTAREA_H_
+
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace views {
+
+// A multiline textfield implementation.
+class VIEWS_EXPORT Textarea : public Textfield {
+ public:
+ METADATA_HEADER(Textarea);
+
+ Textarea();
+ ~Textarea() override = default;
+
+ // Returns the number of lines of the text.
+ size_t GetNumLines();
+
+ // Textfield:
+ bool OnMouseWheel(const ui::MouseWheelEvent& event) override;
+
+ protected:
+ // Textfield:
+ Textfield::EditCommandResult DoExecuteTextEditCommand(
+ ui::TextEditCommand command) override;
+ bool PreHandleKeyPressed(const ui::KeyEvent& event) override;
+ ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) override;
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTAREA_TEXTAREA_H_
diff --git a/chromium/ui/views/controls/textarea/textarea_unittest.cc b/chromium/ui/views/controls/textarea/textarea_unittest.cc
new file mode 100644
index 00000000000..a75ccf7c333
--- /dev/null
+++ b/chromium/ui/views/controls/textarea/textarea_unittest.cc
@@ -0,0 +1,299 @@
+// Copyright 2019 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/textarea/textarea.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "ui/events/event.h"
+#include "ui/gfx/render_text.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/textfield/textfield_test_api.h"
+#include "ui/views/controls/textfield/textfield_unittest.h"
+#include "ui/views/style/platform_style.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+const base::char16 kHebrewLetterSamekh = 0x05E1;
+
+} // namespace
+
+namespace views {
+namespace {
+
+class TextareaTest : public test::TextfieldTest {
+ public:
+ TextareaTest() = default;
+ ~TextareaTest() override = default;
+
+ // TextfieldTest:
+ void SetUp() override {
+ TextfieldTest::SetUp();
+
+ ASSERT_FALSE(textarea_);
+ textarea_ = PrepareTextfields(0, std::make_unique<Textarea>(),
+ gfx::Rect(100, 100, 800, 100));
+ }
+
+ protected:
+ void RunMoveUpDownTest(int start_index,
+ ui::KeyboardCode key_code,
+ std::vector<int> expected) {
+ DCHECK(key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
+ textarea_->SetSelectedRange(gfx::Range(start_index));
+ for (size_t i = 0; i < expected.size(); ++i) {
+ SCOPED_TRACE(testing::Message()
+ << (key_code == ui::VKEY_UP ? "MOVE UP " : "MOVE DOWN ")
+ << i + 1 << " times from Range " << start_index);
+ SendKeyEvent(key_code);
+ EXPECT_EQ(gfx::Range(expected[i]), textarea_->GetSelectedRange());
+ }
+ }
+
+ size_t GetCursorLine() const {
+ return test_api_->GetRenderText()->GetLineContainingCaret(
+ textarea_->GetSelectionModel());
+ }
+
+ // TextfieldTest:
+ void SendHomeEvent(bool shift) override {
+ SendKeyEvent(ui::VKEY_HOME, shift, TestingNativeMac());
+ }
+
+ // TextfieldTest:
+ void SendEndEvent(bool shift) override {
+ SendKeyEvent(ui::VKEY_END, shift, TestingNativeMac());
+ }
+
+ Textarea* textarea_ = nullptr;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TextareaTest);
+};
+
+} // namespace
+
+// Disabled when using XKB for crbug.com/1171828.
+#if BUILDFLAG(USE_XKBCOMMON)
+#define MAYBE_InsertNewlineTest DISABLED_InsertNewlineTest
+#else
+#define MAYBE_InsertNewlineTest InsertNewlineTest
+#endif // BUILDFLAG(USE_XKBCOMMON)
+TEST_F(TextareaTest, MAYBE_InsertNewlineTest) {
+ for (size_t i = 0; i < 5; i++) {
+ SendKeyEvent(static_cast<ui::KeyboardCode>(ui::VKEY_A + i));
+ SendKeyEvent(ui::VKEY_RETURN);
+ }
+ EXPECT_STR_EQ("a\nb\nc\nd\ne\n", textarea_->GetText());
+}
+
+TEST_F(TextareaTest, PasteNewlineTest) {
+ const std::string& kText = "abc\n \n";
+ textarea_->SetText(base::ASCIIToUTF16(kText));
+ textarea_->SelectAll(false);
+ textarea_->ExecuteCommand(Textfield::kCopy, 0);
+ textarea_->SetText(base::string16());
+ textarea_->ExecuteCommand(Textfield::kPaste, 0);
+ EXPECT_STR_EQ(kText, textarea_->GetText());
+}
+
+// Re-enable when crbug.com/1163587 is fixed.
+TEST_F(TextareaTest, DISABLED_CursorMovement) {
+ textarea_->SetText(base::ASCIIToUTF16("one\n\ntwo three"));
+
+ // Move Up/Down at the front of the line.
+ RunMoveUpDownTest(0, ui::VKEY_DOWN, {4, 5, 14});
+ RunMoveUpDownTest(5, ui::VKEY_UP, {4, 0, 0});
+
+ // Move Up/Down at the end of the line.
+ RunMoveUpDownTest(3, ui::VKEY_DOWN, {4, 8, 14});
+ RunMoveUpDownTest(14, ui::VKEY_UP, {4, 3, 0});
+
+ // Move Up/Down at the middle position.
+ RunMoveUpDownTest(2, ui::VKEY_DOWN, {4, 7, 14});
+ RunMoveUpDownTest(7, ui::VKEY_UP, {4, 2, 0});
+
+ // Test Home/End key on each lines.
+ textarea_->SetSelectedRange(gfx::Range(2)); // First line.
+ SendHomeEvent(false);
+ EXPECT_EQ(gfx::Range(0), textarea_->GetSelectedRange());
+ SendEndEvent(false);
+ EXPECT_EQ(gfx::Range(3), textarea_->GetSelectedRange());
+ textarea_->SetSelectedRange(gfx::Range(4)); // 2nd line.
+ SendHomeEvent(false);
+ EXPECT_EQ(gfx::Range(4), textarea_->GetSelectedRange());
+ SendEndEvent(false);
+ EXPECT_EQ(gfx::Range(4), textarea_->GetSelectedRange());
+ textarea_->SetSelectedRange(gfx::Range(7)); // 3rd line.
+ SendHomeEvent(false);
+ EXPECT_EQ(gfx::Range(5), textarea_->GetSelectedRange());
+ SendEndEvent(false);
+ EXPECT_EQ(gfx::Range(14), textarea_->GetSelectedRange());
+}
+
+// Ensure cursor view is always inside display rect.
+TEST_F(TextareaTest, CursorViewBounds) {
+ textarea_->SetBounds(0, 0, 100, 31);
+ for (size_t i = 0; i < 10; ++i) {
+ SCOPED_TRACE(base::StringPrintf("VKEY_RETURN %" PRIuS " times", i + 1));
+ SendKeyEvent(ui::VKEY_RETURN);
+ ASSERT_TRUE(textarea_->GetVisibleBounds().Contains(GetCursorViewRect()));
+ ASSERT_FALSE(GetCursorViewRect().size().IsEmpty());
+ }
+
+ for (size_t i = 0; i < 10; ++i) {
+ SCOPED_TRACE(base::StringPrintf("VKEY_UP %" PRIuS " times", i + 1));
+ SendKeyEvent(ui::VKEY_UP);
+ ASSERT_TRUE(textarea_->GetVisibleBounds().Contains(GetCursorViewRect()));
+ ASSERT_FALSE(GetCursorViewRect().size().IsEmpty());
+ }
+}
+
+TEST_F(TextareaTest, LineSelection) {
+ textarea_->SetText(base::ASCIIToUTF16("12\n34567 89"));
+
+ // Place the cursor after "5".
+ textarea_->SetEditableSelectionRange(gfx::Range(6));
+
+ // Select line towards right.
+ SendEndEvent(true);
+ EXPECT_STR_EQ("67 89", textarea_->GetSelectedText());
+
+ // Select line towards left. On Mac, the existing selection should be extended
+ // to cover the whole line.
+ SendHomeEvent(true);
+
+ if (Textarea::kLineSelectionBehavior == gfx::SELECTION_EXTEND)
+ EXPECT_STR_EQ("34567 89", textarea_->GetSelectedText());
+ else
+ EXPECT_STR_EQ("345", textarea_->GetSelectedText());
+
+ EXPECT_TRUE(textarea_->GetSelectedRange().is_reversed());
+
+ // Select line towards right.
+ SendEndEvent(true);
+
+ if (Textarea::kLineSelectionBehavior == gfx::SELECTION_EXTEND)
+ EXPECT_STR_EQ("34567 89", textarea_->GetSelectedText());
+ else
+ EXPECT_STR_EQ("67 89", textarea_->GetSelectedText());
+
+ EXPECT_FALSE(textarea_->GetSelectedRange().is_reversed());
+}
+
+// Disabled on Mac for crbug.com/1171826.
+#if defined(OS_MAC)
+#define MAYBE_MoveUpDownAndModifySelection DISABLED_MoveUpDownAndModifySelection
+#else
+#define MAYBE_MoveUpDownAndModifySelection MoveUpDownAndModifySelection
+#endif // defined(OS_MAC)
+TEST_F(TextareaTest, MAYBE_MoveUpDownAndModifySelection) {
+ textarea_->SetText(base::ASCIIToUTF16("12\n34567 89"));
+ textarea_->SetEditableSelectionRange(gfx::Range(6));
+ EXPECT_EQ(1U, GetCursorLine());
+
+ // Up key should place the cursor after "2" not after newline to place the
+ // cursor on the first line.
+ SendKeyEvent(ui::VKEY_UP);
+ EXPECT_EQ(0U, GetCursorLine());
+ EXPECT_EQ(gfx::Range(2), textarea_->GetSelectedRange());
+
+ // Down key after Up key should select the same range as the previous one.
+ SendKeyEvent(ui::VKEY_DOWN);
+ EXPECT_EQ(1U, GetCursorLine());
+ EXPECT_EQ(gfx::Range(6), textarea_->GetSelectedRange());
+
+ // Shift+Up should select the text to the upper line position including
+ // the newline character.
+ SendKeyEvent(ui::VKEY_UP, true /* shift */, false /* command */);
+ EXPECT_EQ(gfx::Range(6, 2), textarea_->GetSelectedRange());
+
+ // Shift+Down should collapse the selection.
+ SendKeyEvent(ui::VKEY_DOWN, true /* shift */, false /* command */);
+ EXPECT_EQ(gfx::Range(6), textarea_->GetSelectedRange());
+
+ // Shift+Down again should select the text to the end of the last line.
+ SendKeyEvent(ui::VKEY_DOWN, true /* shift */, false /* command */);
+ EXPECT_EQ(gfx::Range(6, 11), textarea_->GetSelectedRange());
+}
+
+TEST_F(TextareaTest, MovePageUpDownAndModifySelection) {
+ textarea_->SetText(base::ASCIIToUTF16("12\n34567 89"));
+ textarea_->SetEditableSelectionRange(gfx::Range(6));
+
+ EXPECT_TRUE(
+ textarea_->IsTextEditCommandEnabled(ui::TextEditCommand::MOVE_PAGE_UP));
+ EXPECT_TRUE(
+ textarea_->IsTextEditCommandEnabled(ui::TextEditCommand::MOVE_PAGE_DOWN));
+ EXPECT_TRUE(textarea_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION));
+ EXPECT_TRUE(textarea_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION));
+
+ test_api_->ExecuteTextEditCommand(ui::TextEditCommand::MOVE_PAGE_UP);
+ EXPECT_EQ(gfx::Range(0), textarea_->GetSelectedRange());
+
+ test_api_->ExecuteTextEditCommand(ui::TextEditCommand::MOVE_PAGE_DOWN);
+ EXPECT_EQ(gfx::Range(11), textarea_->GetSelectedRange());
+
+ textarea_->SetEditableSelectionRange(gfx::Range(6));
+ test_api_->ExecuteTextEditCommand(
+ ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION);
+ EXPECT_EQ(gfx::Range(6, 0), textarea_->GetSelectedRange());
+
+ test_api_->ExecuteTextEditCommand(
+ ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION);
+
+ if (Textarea::kLineSelectionBehavior == gfx::SELECTION_EXTEND)
+ EXPECT_EQ(gfx::Range(0, 11), textarea_->GetSelectedRange());
+ else
+ EXPECT_EQ(gfx::Range(6, 11), textarea_->GetSelectedRange());
+}
+
+// Ensure the textarea breaks the long word and scrolls on overflow.
+TEST_F(TextareaTest, OverflowTest) {
+ const size_t count = 50U;
+ textarea_->SetBounds(0, 0, 60, 40);
+
+ textarea_->SetText(base::string16(count, 'a'));
+ EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds()));
+
+ textarea_->SetText(base::string16(count, kHebrewLetterSamekh));
+ EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds()));
+}
+
+TEST_F(TextareaTest, OverflowInRTLTest) {
+ const size_t count = 50U;
+ textarea_->SetBounds(0, 0, 60, 40);
+ std::string locale = base::i18n::GetConfiguredLocale();
+ base::i18n::SetICUDefaultLocale("he");
+
+ textarea_->SetText(base::string16(count, 'a'));
+ EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds()));
+
+ textarea_->SetText(base::string16(count, kHebrewLetterSamekh));
+ EXPECT_TRUE(GetDisplayRect().Contains(GetCursorBounds()));
+
+ // Reset locale.
+ base::i18n::SetICUDefaultLocale(locale);
+}
+
+TEST_F(TextareaTest, OnBlurTest) {
+ const std::string& kText = "abcdef";
+ textarea_->SetText(base::ASCIIToUTF16(kText));
+
+ SendEndEvent(false);
+ EXPECT_EQ(kText.size(), textarea_->GetCursorPosition());
+
+ // A focus loss should not change the cursor position.
+ textarea_->OnBlur();
+ EXPECT_EQ(kText.size(), textarea_->GetCursorPosition());
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/textfield/DIR_METADATA b/chromium/ui/views/controls/textfield/DIR_METADATA
new file mode 100644
index 00000000000..d1a66307bfa
--- /dev/null
+++ b/chromium/ui/views/controls/textfield/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail: {
+ component: "Internals>Views"
+}
diff --git a/chromium/ui/views/controls/textfield/OWNERS b/chromium/ui/views/controls/textfield/OWNERS
index d0cc058381a..b1b0e2d4660 100644
--- a/chromium/ui/views/controls/textfield/OWNERS
+++ b/chromium/ui/views/controls/textfield/OWNERS
@@ -1,5 +1,3 @@
msw@chromium.org
oshima@chromium.org
pkasting@chromium.org
-
-# COMPONENT: Internals>Views
diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc
index 432332a7fad..246ab8040a8 100644
--- a/chromium/ui/views/controls/textfield/textfield.cc
+++ b/chromium/ui/views/controls/textfield/textfield.cc
@@ -21,12 +21,14 @@
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/default_style.h"
#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/resource/resource_bundle.h"
@@ -68,7 +70,9 @@
#include "base/win/win_util.h"
#endif
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
#include "ui/base/ime/linux/text_edit_command_auralinux.h"
#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
#endif
@@ -77,7 +81,7 @@
#include "ui/base/x/x11_util.h" // nogncheck
#endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/aura/window.h"
#include "ui/wm/core/ime_util_chromeos.h"
#endif
@@ -93,10 +97,16 @@
#include "ui/ozone/public/platform_gl_egl_utility.h"
#endif
-namespace views {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ui/base/ime/chromeos/extension_ime_util.h"
+#include "ui/base/ime/chromeos/input_method_manager.h"
+#endif
+namespace views {
namespace {
+using ::ui::mojom::DragOperation;
+
// An enum giving different model properties unique keys for the
// OnPropertyChanged call.
enum TextfieldPropertyKey {
@@ -110,117 +120,6 @@ enum TextfieldPropertyKey {
kTextfieldSelectedRange,
};
-#if defined(OS_APPLE)
-constexpr gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_EXTEND;
-constexpr gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_CARET;
-constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior =
- gfx::SELECTION_CARET;
-#else
-constexpr gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_RETAIN;
-constexpr gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_RETAIN;
-constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior =
- gfx::SELECTION_RETAIN;
-#endif
-
-// Get the default command for a given key |event|.
-ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) {
- if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode())
- return ui::TextEditCommand::INVALID_COMMAND;
-
- const bool shift = event.IsShiftDown();
- const bool control = event.IsControlDown() || event.IsCommandDown();
- const bool alt = event.IsAltDown() || event.IsAltGrDown();
- switch (event.key_code()) {
- case ui::VKEY_Z:
- if (control && !shift && !alt)
- return ui::TextEditCommand::UNDO;
- return (control && shift && !alt) ? ui::TextEditCommand::REDO
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_Y:
- return (control && !alt) ? ui::TextEditCommand::REDO
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_A:
- return (control && !alt) ? ui::TextEditCommand::SELECT_ALL
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_X:
- return (control && !alt) ? ui::TextEditCommand::CUT
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_C:
- return (control && !alt) ? ui::TextEditCommand::COPY
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_V:
- return (control && !alt) ? ui::TextEditCommand::PASTE
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_RIGHT:
- // Ignore alt+right, which may be a browser navigation shortcut.
- if (alt)
- return ui::TextEditCommand::INVALID_COMMAND;
- if (!shift) {
- return control ? ui::TextEditCommand::MOVE_WORD_RIGHT
- : ui::TextEditCommand::MOVE_RIGHT;
- }
- return control ? ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
- : ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION;
- case ui::VKEY_LEFT:
- // Ignore alt+left, which may be a browser navigation shortcut.
- if (alt)
- return ui::TextEditCommand::INVALID_COMMAND;
- if (!shift) {
- return control ? ui::TextEditCommand::MOVE_WORD_LEFT
- : ui::TextEditCommand::MOVE_LEFT;
- }
- return control ? ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION
- : ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION;
- case ui::VKEY_HOME:
- return shift ? ui::TextEditCommand::
- MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
- : ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE;
- case ui::VKEY_END:
- return shift
- ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
- : ui::TextEditCommand::MOVE_TO_END_OF_LINE;
- case ui::VKEY_UP:
- return shift ? ui::TextEditCommand::
- MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_DOWN:
- return shift
- ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
- : ui::TextEditCommand::INVALID_COMMAND;
- case ui::VKEY_BACK:
- if (!control) {
-#if defined(OS_WIN)
- if (alt)
- return shift ? ui::TextEditCommand::REDO : ui::TextEditCommand::UNDO;
-#endif
- return ui::TextEditCommand::DELETE_BACKWARD;
- }
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
- // Only erase by line break on Linux and ChromeOS.
- if (shift)
- return ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE;
-#endif
- return ui::TextEditCommand::DELETE_WORD_BACKWARD;
- case ui::VKEY_DELETE:
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
- // Only erase by line break on Linux and ChromeOS.
- if (shift && control)
- return ui::TextEditCommand::DELETE_TO_END_OF_LINE;
-#endif
- if (control)
- return ui::TextEditCommand::DELETE_WORD_FORWARD;
- return shift ? ui::TextEditCommand::CUT
- : ui::TextEditCommand::DELETE_FORWARD;
- case ui::VKEY_INSERT:
- if (control && !shift)
- return ui::TextEditCommand::COPY;
- return (shift && !control) ? ui::TextEditCommand::PASTE
- : ui::TextEditCommand::INVALID_COMMAND;
- default:
- return ui::TextEditCommand::INVALID_COMMAND;
- }
-}
-
// Returns the ui::TextEditCommand corresponding to the |command_id| menu
// action. |has_selection| is true if the textfield has an active selection.
// Keep in sync with UpdateContextMenu.
@@ -585,6 +484,7 @@ gfx::HorizontalAlignment Textfield::GetHorizontalAlignment() const {
void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
GetRenderText()->SetHorizontalAlignment(alignment);
+
OnPropertyChanged(&model_ + kTextfieldHorizontalAlignment,
kPropertyEffectsNone);
}
@@ -698,9 +598,15 @@ void Textfield::FitToLocalBounds() {
// beyond their legibility, or enlarging controls dynamically with content.
gfx::Rect bounds = GetLocalBounds();
const gfx::Insets insets = GetInsets();
- // The text will draw with the correct vertical alignment if we don't apply
- // the vertical insets.
- bounds.Inset(insets.left(), 0, insets.right(), 0);
+
+ if (GetRenderText()->multiline()) {
+ bounds.Inset(insets);
+ } else {
+ // The text will draw with the correct vertical alignment if we don't apply
+ // the vertical insets.
+ bounds.Inset(insets.left(), 0, insets.right(), 0);
+ }
+
bounds.set_x(GetMirroredXForRect(bounds));
GetRenderText()->SetDisplayRect(bounds);
UpdateAfterChange(TextChangeType::kNone, true);
@@ -765,7 +671,9 @@ bool Textfield::OnMousePressed(const ui::MouseEvent& event) {
#endif
}
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
if (!handled && !had_focus && event.IsOnlyMiddleMouseButton())
RequestFocusWithPointer(ui::EventPointerType::kMouse);
#endif
@@ -800,6 +708,9 @@ WordLookupClient* Textfield::GetWordLookupClient() {
}
bool Textfield::OnKeyPressed(const ui::KeyEvent& event) {
+ if (PreHandleKeyPressed(event))
+ return true;
+
ui::TextEditCommand edit_command = scheduled_text_edit_command_;
scheduled_text_edit_command_ = ui::TextEditCommand::INVALID_COMMAND;
@@ -812,7 +723,9 @@ bool Textfield::OnKeyPressed(const ui::KeyEvent& event) {
if (!textfield)
return handled;
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
ui::TextEditKeyBindingsDelegateAuraLinux* delegate =
ui::GetTextEditKeyBindingsDelegate();
std::vector<ui::TextEditCommandAuraLinux> commands;
@@ -966,7 +879,9 @@ void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) {
}
bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
// Skip any accelerator handling that conflicts with custom keybindings.
ui::TextEditKeyBindingsDelegateAuraLinux* delegate =
ui::GetTextEditKeyBindingsDelegate();
@@ -1037,13 +952,13 @@ void Textfield::OnDragExited() {
SchedulePaint();
}
-int Textfield::OnPerformDrop(const ui::DropTargetEvent& event) {
+DragOperation Textfield::OnPerformDrop(const ui::DropTargetEvent& event) {
DCHECK(CanDrop(event.data()));
drop_cursor_visible_ = false;
if (controller_) {
- int drag_operation = controller_->OnDrop(event.data());
- if (drag_operation != ui::DragDropTypes::DRAG_NONE)
+ DragOperation drag_operation = controller_->OnDrop(event.data());
+ if (drag_operation != DragOperation::kNone)
return drag_operation;
}
@@ -1074,7 +989,7 @@ int Textfield::OnPerformDrop(const ui::DropTargetEvent& event) {
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(TextChangeType::kUserTriggered, true);
OnAfterUserAction();
- return move ? ui::DragDropTypes::DRAG_MOVE : ui::DragDropTypes::DRAG_COPY;
+ return move ? DragOperation::kMove : DragOperation::kCopy;
}
void Textfield::OnDragDone() {
@@ -1184,15 +1099,16 @@ void Textfield::OnBlur() {
render_text->set_focused(false);
// If necessary, yank the cursor to the logical start of the textfield.
- if (PlatformStyle::kTextfieldScrollsToStartOnFocusChange)
+ if (PlatformStyle::kTextfieldScrollsToStartOnFocusChange &&
+ !render_text->multiline())
model_->MoveCursorTo(gfx::SelectionModel(0, gfx::CURSOR_FORWARD));
if (GetInputMethod()) {
GetInputMethod()->DetachTextInputClient(this);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
wm::RestoreWindowBoundsOnClientFocusLost(
GetNativeView()->GetToplevelWindow());
-#endif // defined(OS_CHROMEOS)
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
StopBlinkingCursor();
cursor_view_->SetVisible(false);
@@ -1276,7 +1192,7 @@ void Textfield::WriteDragDataForView(View* sender,
.context(),
label.size()));
constexpr gfx::Vector2d kOffset(-15, 0);
- gfx::ImageSkia image(gfx::ImageSkiaRep(bitmap, raster_scale));
+ gfx::ImageSkia image = gfx::ImageSkia::CreateFromBitmap(bitmap, raster_scale);
data->provider().SetDragImage(image, kOffset);
if (controller_)
controller_->OnWriteDragData(data);
@@ -1515,7 +1431,8 @@ void Textfield::ClearCompositionText() {
OnAfterUserAction();
}
-void Textfield::InsertText(const base::string16& new_text) {
+void Textfield::InsertText(const base::string16& new_text,
+ InsertTextCursorBehavior cursor_behavior) {
base::string16 filtered_new_text;
std::copy_if(new_text.begin(), new_text.end(),
std::back_inserter(filtered_new_text), IsValidCharToInsert);
@@ -1524,6 +1441,7 @@ void Textfield::InsertText(const base::string16& new_text) {
filtered_new_text.empty())
return;
+ // TODO(crbug.com/1155331): Handle |cursor_behavior| correctly.
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
model_->InsertText(filtered_new_text);
@@ -1686,6 +1604,7 @@ void Textfield::OnInputMethodChanged() {}
bool Textfield::ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection direction) {
+ DCHECK_NE(direction, base::i18n::UNKNOWN_DIRECTION);
// Restore text directionality mode when the indicated direction matches the
// current forced mode; otherwise, force the mode indicated. This helps users
// manage BiDi text layout without getting stuck in forced LTR or RTL modes.
@@ -1696,7 +1615,13 @@ bool Textfield::ChangeTextDirectionAndLayoutAlignment(
const bool modes_match = new_mode == render_text->directionality_mode();
render_text->SetDirectionalityMode(modes_match ? gfx::DIRECTIONALITY_FROM_TEXT
: new_mode);
- SetHorizontalAlignment(default_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
+ // Do not reset horizontal alignment to left/right if it has been set to
+ // center, or if it depends on the text direction and that direction has not
+ // been modified.
+ bool dir_from_text =
+ modes_match && GetHorizontalAlignment() == gfx::ALIGN_TO_HEAD;
+ if (!dir_from_text && GetHorizontalAlignment() != gfx::ALIGN_CENTER)
+ SetHorizontalAlignment(default_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
SchedulePaint();
return true;
}
@@ -1713,10 +1638,10 @@ void Textfield::ExtendSelectionAndDelete(size_t before, size_t after) {
}
void Textfield::EnsureCaretNotInRect(const gfx::Rect& rect_in_screen) {
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
aura::Window* top_level_window = GetNativeView()->GetToplevelWindow();
wm::EnsureWindowNotInRect(top_level_window, rect_in_screen);
-#endif // defined(OS_CHROMEOS)
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
bool Textfield::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
@@ -1796,12 +1721,16 @@ bool Textfield::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
case ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION:
case ui::TextEditCommand::MOVE_UP:
case ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION:
+ case ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT:
+ case ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT:
+ case ui::TextEditCommand::SCROLL_PAGE_DOWN:
+ case ui::TextEditCommand::SCROLL_PAGE_UP:
// On Mac, the textfield should respond to Up/Down arrows keys and
// PageUp/PageDown.
#if defined(OS_APPLE)
return true;
#else
- return false;
+ return GetRenderText()->multiline();
#endif
case ui::TextEditCommand::INSERT_TEXT:
case ui::TextEditCommand::SET_MARK:
@@ -1833,7 +1762,7 @@ bool Textfield::ShouldDoLearning() {
return false;
}
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
// TODO(https://crbug.com/952355): Implement this method to support Korean IME
// reconversion feature on native text fields (e.g. find bar).
bool Textfield::SetCompositionFromExistingText(
@@ -1849,7 +1778,7 @@ bool Textfield::SetCompositionFromExistingText(
}
#endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
gfx::Range Textfield::GetAutocorrectRange() const {
return model_->autocorrect_range();
}
@@ -1868,22 +1797,37 @@ gfx::Rect Textfield::GetAutocorrectCharacterBounds() const {
return rect;
}
-bool Textfield::SetAutocorrectRange(const base::string16& autocorrect_text,
- const gfx::Range& range) {
- base::UmaHistogramEnumeration("InputMethod.Assistive.Autocorrect.Count",
- TextInputClient::SubClass::kTextField);
- return model_->SetAutocorrectRange(autocorrect_text, range);
-}
-
-void Textfield::ClearAutocorrectRange() {
- model_->SetAutocorrectRange(base::string16(), gfx::Range());
+bool Textfield::SetAutocorrectRange(const gfx::Range& range) {
+ if (!range.is_empty()) {
+ base::UmaHistogramEnumeration("InputMethod.Assistive.Autocorrect.Count",
+ TextInputClient::SubClass::kTextField);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ auto* input_method_manager =
+ chromeos::input_method::InputMethodManager::Get();
+ if (input_method_manager &&
+ chromeos::extension_ime_util::IsExperimentalMultilingual(
+ input_method_manager->GetActiveIMEState()
+ ->GetCurrentInputMethod()
+ .id())) {
+ base::UmaHistogramEnumeration(
+ "InputMethod.MultilingualExperiment.Autocorrect.Count",
+ TextInputClient::SubClass::kTextField);
+ }
+#endif
+ }
+ return model_->SetAutocorrectRange(range);
}
#endif
#if defined(OS_WIN)
void Textfield::GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
- base::Optional<gfx::Rect>* selection_bounds) {}
+ base::Optional<gfx::Rect>* selection_bounds) {
+ gfx::Rect origin = GetContentsBounds();
+ ConvertRectToScreen(this, &origin);
+ *control_bounds = origin;
+}
// TODO(https://crbug.com/952355): Implement this method once TSF supports
// reconversion features on native text fields.
@@ -1925,6 +1869,30 @@ base::string16 Textfield::GetSelectionClipboardText() const {
void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
DestroyTouchSelection();
+ // We only execute the commands enabled in Textfield::IsTextEditCommandEnabled
+ // below. Hence don't do a virtual IsTextEditCommandEnabled call.
+ if (!IsTextEditCommandEnabled(command))
+ return;
+
+ OnBeforeUserAction();
+
+ gfx::SelectionModel selection_model = GetSelectionModel();
+ bool text_changed, cursor_changed;
+ std::tie(text_changed, cursor_changed) = DoExecuteTextEditCommand(command);
+
+ cursor_changed |= (GetSelectionModel() != selection_model);
+ if (cursor_changed && HasSelection())
+ UpdateSelectionClipboard();
+ UpdateAfterChange(
+ text_changed ? TextChangeType::kUserTriggered : TextChangeType::kNone,
+ cursor_changed);
+ OnAfterUserAction();
+}
+
+Textfield::EditCommandResult Textfield::DoExecuteTextEditCommand(
+ ui::TextEditCommand command) {
+ bool changed = false;
+ bool cursor_changed = false;
bool add_to_kill_buffer = false;
base::AutoReset<bool> show_rejection_ui(&show_rejection_ui_if_any_, true);
@@ -1947,48 +1915,40 @@ void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
break;
}
- // We only execute the commands enabled in Textfield::IsTextEditCommandEnabled
- // below. Hence don't do a virtual IsTextEditCommandEnabled call.
- if (!IsTextEditCommandEnabled(command))
- return;
-
- bool changed = false;
bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT;
- gfx::SelectionModel selection_model = GetSelectionModel();
- OnBeforeUserAction();
switch (command) {
case ui::TextEditCommand::DELETE_BACKWARD:
- changed = model_->Backspace(add_to_kill_buffer);
+ changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_FORWARD:
- changed = model_->Delete(add_to_kill_buffer);
+ changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE:
model_->MoveCursor(gfx::LINE_BREAK, begin, gfx::SELECTION_RETAIN);
- changed = model_->Backspace(add_to_kill_buffer);
+ changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH:
model_->MoveCursor(gfx::FIELD_BREAK, begin, gfx::SELECTION_RETAIN);
- changed = model_->Backspace(add_to_kill_buffer);
+ changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_END_OF_LINE:
model_->MoveCursor(gfx::LINE_BREAK, end, gfx::SELECTION_RETAIN);
- changed = model_->Delete(add_to_kill_buffer);
+ changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH:
model_->MoveCursor(gfx::FIELD_BREAK, end, gfx::SELECTION_RETAIN);
- changed = model_->Delete(add_to_kill_buffer);
+ changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_WORD_BACKWARD:
model_->MoveCursor(gfx::WORD_BREAK, begin, gfx::SELECTION_RETAIN);
- changed = model_->Backspace(add_to_kill_buffer);
+ changed = cursor_changed = model_->Backspace(add_to_kill_buffer);
break;
case ui::TextEditCommand::DELETE_WORD_FORWARD:
model_->MoveCursor(gfx::WORD_BREAK, end, gfx::SELECTION_RETAIN);
- changed = model_->Delete(add_to_kill_buffer);
+ changed = cursor_changed = model_->Delete(add_to_kill_buffer);
break;
case ui::TextEditCommand::MOVE_BACKWARD:
model_->MoveCursor(gfx::CHARACTER_BREAK, begin, gfx::SELECTION_NONE);
@@ -2025,6 +1985,8 @@ void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH:
case ui::TextEditCommand::MOVE_UP:
case ui::TextEditCommand::MOVE_PAGE_UP:
+ case ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT:
+ case ui::TextEditCommand::SCROLL_PAGE_UP:
model_->MoveCursor(gfx::FIELD_BREAK, begin, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
@@ -2047,6 +2009,8 @@ void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
case ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH:
case ui::TextEditCommand::MOVE_DOWN:
case ui::TextEditCommand::MOVE_PAGE_DOWN:
+ case ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT:
+ case ui::TextEditCommand::SCROLL_PAGE_DOWN:
model_->MoveCursor(gfx::FIELD_BREAK, end, gfx::SELECTION_NONE);
break;
case ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
@@ -2097,28 +2061,28 @@ void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
kWordSelectionBehavior);
break;
case ui::TextEditCommand::UNDO:
- changed = model_->Undo();
+ changed = cursor_changed = model_->Undo();
break;
case ui::TextEditCommand::REDO:
- changed = model_->Redo();
+ changed = cursor_changed = model_->Redo();
break;
case ui::TextEditCommand::CUT:
- changed = Cut();
+ changed = cursor_changed = Cut();
break;
case ui::TextEditCommand::COPY:
Copy();
break;
case ui::TextEditCommand::PASTE:
- changed = Paste();
+ changed = cursor_changed = Paste();
break;
case ui::TextEditCommand::SELECT_ALL:
SelectAll(false);
break;
case ui::TextEditCommand::TRANSPOSE:
- changed = model_->Transpose();
+ changed = cursor_changed = model_->Transpose();
break;
case ui::TextEditCommand::YANK:
- changed = model_->Yank();
+ changed = cursor_changed = model_->Yank();
break;
case ui::TextEditCommand::INSERT_TEXT:
case ui::TextEditCommand::SET_MARK:
@@ -2128,14 +2092,7 @@ void Textfield::ExecuteTextEditCommand(ui::TextEditCommand command) {
break;
}
- const auto text_change_type =
- changed ? TextChangeType::kUserTriggered : TextChangeType::kNone;
- const bool cursor_changed =
- changed || (GetSelectionModel() != selection_model);
- if (cursor_changed && HasSelection())
- UpdateSelectionClipboard();
- UpdateAfterChange(text_change_type, cursor_changed);
- OnAfterUserAction();
+ return {changed, cursor_changed};
}
void Textfield::OffsetDoubleClickWord(int offset) {
@@ -2185,12 +2142,157 @@ void Textfield::RequestFocusForGesture(const ui::GestureEventDetails& details) {
ShowVirtualKeyboardIfEnabled();
}
-views::PropertyChangedSubscription Textfield::AddTextChangedCallback(
+base::CallbackListSubscription Textfield::AddTextChangedCallback(
views::PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&model_ + kTextfieldText,
std::move(callback));
}
+bool Textfield::PreHandleKeyPressed(const ui::KeyEvent& event) {
+ return false;
+}
+
+ui::TextEditCommand Textfield::GetCommandForKeyEvent(
+ const ui::KeyEvent& event) {
+ if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode())
+ return ui::TextEditCommand::INVALID_COMMAND;
+
+ const bool shift = event.IsShiftDown();
+#if defined(OS_APPLE)
+ const bool command = event.IsCommandDown();
+#endif
+ const bool control = event.IsControlDown() || event.IsCommandDown();
+ const bool alt = event.IsAltDown() || event.IsAltGrDown();
+ switch (event.key_code()) {
+ case ui::VKEY_Z:
+ if (control && !shift && !alt)
+ return ui::TextEditCommand::UNDO;
+ return (control && shift && !alt) ? ui::TextEditCommand::REDO
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_Y:
+ return (control && !alt) ? ui::TextEditCommand::REDO
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_A:
+ return (control && !alt) ? ui::TextEditCommand::SELECT_ALL
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_X:
+ return (control && !alt) ? ui::TextEditCommand::CUT
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_C:
+ return (control && !alt) ? ui::TextEditCommand::COPY
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_V:
+ return (control && !alt) ? ui::TextEditCommand::PASTE
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_RIGHT:
+ // Ignore alt+right, which may be a browser navigation shortcut.
+ if (alt)
+ return ui::TextEditCommand::INVALID_COMMAND;
+ if (!shift) {
+ return control ? ui::TextEditCommand::MOVE_WORD_RIGHT
+ : ui::TextEditCommand::MOVE_RIGHT;
+ }
+ return control ? ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION;
+ case ui::VKEY_LEFT:
+ // Ignore alt+left, which may be a browser navigation shortcut.
+ if (alt)
+ return ui::TextEditCommand::INVALID_COMMAND;
+ if (!shift) {
+ return control ? ui::TextEditCommand::MOVE_WORD_LEFT
+ : ui::TextEditCommand::MOVE_LEFT;
+ }
+ return control ? ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION;
+ case ui::VKEY_HOME:
+ if (shift) {
+ return ui::TextEditCommand::
+ MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION;
+ }
+#if defined(OS_APPLE)
+ return ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT;
+#else
+ return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE;
+#endif
+ case ui::VKEY_END:
+ if (shift)
+ return ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION;
+#if defined(OS_APPLE)
+ return ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT;
+#else
+ return ui::TextEditCommand::MOVE_TO_END_OF_LINE;
+#endif
+ case ui::VKEY_UP:
+#if defined(OS_APPLE)
+ if (control && shift) {
+ return ui::TextEditCommand::
+ MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION;
+ }
+ if (command)
+ return ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT;
+ return shift ? ui::TextEditCommand::
+ MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::MOVE_UP;
+#else
+ return shift ? ui::TextEditCommand::
+ MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::INVALID_COMMAND;
+#endif
+ case ui::VKEY_DOWN:
+#if defined(OS_APPLE)
+ if (control && shift) {
+ return ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION;
+ }
+ if (command)
+ return ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT;
+ return shift
+ ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::MOVE_DOWN;
+#else
+ return shift
+ ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION
+ : ui::TextEditCommand::INVALID_COMMAND;
+#endif
+ case ui::VKEY_BACK:
+ if (!control) {
+#if defined(OS_WIN)
+ if (alt)
+ return shift ? ui::TextEditCommand::REDO : ui::TextEditCommand::UNDO;
+#endif
+ return ui::TextEditCommand::DELETE_BACKWARD;
+ }
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ // Only erase by line break on Linux and ChromeOS.
+ if (shift)
+ return ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE;
+#endif
+ return ui::TextEditCommand::DELETE_WORD_BACKWARD;
+ case ui::VKEY_DELETE:
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ // Only erase by line break on Linux and ChromeOS.
+ if (shift && control)
+ return ui::TextEditCommand::DELETE_TO_END_OF_LINE;
+#endif
+ if (control)
+ return ui::TextEditCommand::DELETE_WORD_FORWARD;
+ return shift ? ui::TextEditCommand::CUT
+ : ui::TextEditCommand::DELETE_FORWARD;
+ case ui::VKEY_INSERT:
+ if (control && !shift)
+ return ui::TextEditCommand::COPY;
+ return (shift && !control) ? ui::TextEditCommand::PASTE
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_PRIOR:
+ return control ? ui::TextEditCommand::SCROLL_PAGE_UP
+ : ui::TextEditCommand::INVALID_COMMAND;
+ case ui::VKEY_NEXT:
+ return control ? ui::TextEditCommand::SCROLL_PAGE_DOWN
+ : ui::TextEditCommand::INVALID_COMMAND;
+ default:
+ return ui::TextEditCommand::INVALID_COMMAND;
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// Textfield, private:
@@ -2256,7 +2358,9 @@ bool Textfield::PasteSelectionClipboard() {
}
void Textfield::UpdateSelectionClipboard() {
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
if (text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD) {
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kSelection)
.WriteText(GetSelectedText());
@@ -2558,10 +2662,12 @@ ADD_PROPERTY_METADATA(bool, ReadOnly)
ADD_PROPERTY_METADATA(base::string16, Text)
ADD_PROPERTY_METADATA(ui::TextInputType, TextInputType)
ADD_PROPERTY_METADATA(int, TextInputFlags)
-ADD_PROPERTY_METADATA(SkColor, TextColor)
-ADD_PROPERTY_METADATA(SkColor, SelectionTextColor)
-ADD_PROPERTY_METADATA(SkColor, BackgroundColor)
-ADD_PROPERTY_METADATA(SkColor, SelectionBackgroundColor)
+ADD_PROPERTY_METADATA(SkColor, TextColor, metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(SkColor, SelectionTextColor, metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(SkColor, BackgroundColor, metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(SkColor,
+ SelectionBackgroundColor,
+ metadata::SkColorConverter)
ADD_PROPERTY_METADATA(bool, CursorEnabled)
ADD_PROPERTY_METADATA(base::string16, PlaceholderText)
ADD_PROPERTY_METADATA(bool, Invalid)
diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h
index 3e3b731dbb4..fe833a15e8e 100644
--- a/chromium/ui/views/controls/textfield/textfield.h
+++ b/chromium/ui/views/controls/textfield/textfield.h
@@ -11,6 +11,7 @@
#include <memory>
#include <set>
#include <string>
+#include <utility>
#if defined(OS_WIN)
#include <vector>
@@ -25,6 +26,7 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/ime/text_edit_commands.h"
#include "ui/base/ime/text_input_client.h"
@@ -84,6 +86,29 @@ class VIEWS_EXPORT Textfield : public View,
kLastCommandId = kSelectAll,
};
+#if defined(OS_APPLE)
+ static constexpr gfx::SelectionBehavior kLineSelectionBehavior =
+ gfx::SELECTION_EXTEND;
+ static constexpr gfx::SelectionBehavior kWordSelectionBehavior =
+ gfx::SELECTION_CARET;
+ static constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior =
+ gfx::SELECTION_CARET;
+ static constexpr gfx::SelectionBehavior kPageSelectionBehavior =
+ gfx::SELECTION_EXTEND;
+#else
+ static constexpr gfx::SelectionBehavior kLineSelectionBehavior =
+ gfx::SELECTION_RETAIN;
+ static constexpr gfx::SelectionBehavior kWordSelectionBehavior =
+ gfx::SELECTION_RETAIN;
+ static constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior =
+ gfx::SELECTION_RETAIN;
+ static constexpr gfx::SelectionBehavior kPageSelectionBehavior =
+ gfx::SELECTION_RETAIN;
+#endif
+
+ // Pair of |text_changed|, |cursor_changed|.
+ using EditCommandResult = std::pair<bool, bool>;
+
// Returns the text cursor blink time, or 0 for no blinking.
static base::TimeDelta GetCaretBlinkInterval();
@@ -312,7 +337,8 @@ class VIEWS_EXPORT Textfield : public View,
bool CanDrop(const ui::OSExchangeData& data) override;
int OnDragUpdated(const ui::DropTargetEvent& event) override;
void OnDragExited() override;
- int OnPerformDrop(const ui::DropTargetEvent& event) override;
+ ui::mojom::DragOperation OnPerformDrop(
+ const ui::DropTargetEvent& event) override;
void OnDragDone() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
@@ -377,7 +403,8 @@ class VIEWS_EXPORT Textfield : public View,
void SetCompositionText(const ui::CompositionText& composition) override;
uint32_t ConfirmCompositionText(bool keep_selection) override;
void ClearCompositionText() override;
- void InsertText(const base::string16& text) override;
+ void InsertText(const base::string16& text,
+ InsertTextCursorBehavior cursor_behavior) override;
void InsertChar(const ui::KeyEvent& event) override;
ui::TextInputType GetTextInputType() const override;
ui::TextInputMode GetTextInputMode() const override;
@@ -409,18 +436,16 @@ class VIEWS_EXPORT Textfield : public View,
// 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)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
bool SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
#endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
gfx::Range GetAutocorrectRange() const override;
gfx::Rect GetAutocorrectCharacterBounds() const override;
- bool SetAutocorrectRange(const base::string16& autocorrect_text,
- const gfx::Range& range) override;
- void ClearAutocorrectRange() override;
+ bool SetAutocorrectRange(const gfx::Range& range) override;
#endif
#if defined(OS_WIN)
@@ -433,10 +458,12 @@ class VIEWS_EXPORT Textfield : public View,
bool is_composition_committed) override;
#endif
- views::PropertyChangedSubscription AddTextChangedCallback(
+ base::CallbackListSubscription AddTextChangedCallback(
views::PropertyChangedCallback callback) WARN_UNUSED_RESULT;
protected:
+ TextfieldModel* textfield_model() { return model_.get(); }
+
// Inserts or appends a character in response to an IME operation.
virtual void DoInsertChar(base::char16 ch);
@@ -474,6 +501,20 @@ class VIEWS_EXPORT Textfield : public View,
// gesture event.
void RequestFocusForGesture(const ui::GestureEventDetails& details);
+ virtual Textfield::EditCommandResult DoExecuteTextEditCommand(
+ ui::TextEditCommand command);
+
+ // Handles key press event ahead of OnKeyPressed(). This is used for Textarea
+ // to handle the return key. Use TextfieldController::HandleKeyEvent to
+ // intercept the key event in other cases.
+ virtual bool PreHandleKeyPressed(const ui::KeyEvent& event);
+
+ // Get the default command for a given key |event|.
+ virtual ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event);
+
+ // Update the cursor position in the text field.
+ void UpdateCursorViewPosition();
+
private:
friend class TextfieldTestApi;
@@ -527,9 +568,6 @@ class VIEWS_EXPORT Textfield : public View,
// A callback function to periodically update the cursor node_data.
void UpdateCursorVisibility();
- // Update the cursor position in the text field.
- void UpdateCursorViewPosition();
-
// Gets the style::TextStyle that should be used.
int GetTextStyle() const;
@@ -735,7 +773,7 @@ class VIEWS_EXPORT Textfield : public View,
gfx::Insets extra_insets_ = gfx::Insets();
// Holds the subscription object for the enabled changed callback.
- PropertyChangedSubscription enabled_changed_subscription_ =
+ base::CallbackListSubscription enabled_changed_subscription_ =
AddEnabledChangedCallback(
base::BindRepeating(&Textfield::OnEnabledChanged,
base::Unretained(this)));
diff --git a/chromium/ui/views/controls/textfield/textfield_controller.cc b/chromium/ui/views/controls/textfield/textfield_controller.cc
index bf5f964c3e0..da6260083cb 100644
--- a/chromium/ui/views/controls/textfield/textfield_controller.cc
+++ b/chromium/ui/views/controls/textfield/textfield_controller.cc
@@ -4,7 +4,7 @@
#include "ui/views/controls/textfield/textfield_controller.h"
-#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/events/event.h"
namespace views {
@@ -25,8 +25,9 @@ bool TextfieldController::HandleGestureEvent(
return false;
}
-int TextfieldController::OnDrop(const ui::OSExchangeData& data) {
- return ui::DragDropTypes::DRAG_NONE;
+ui::mojom::DragOperation TextfieldController::OnDrop(
+ const ui::OSExchangeData& data) {
+ return ui::mojom::DragOperation::kNone;
}
} // namespace views
diff --git a/chromium/ui/views/controls/textfield/textfield_controller.h b/chromium/ui/views/controls/textfield/textfield_controller.h
index 3010b3fe9dd..5ad5b589299 100644
--- a/chromium/ui/views/controls/textfield/textfield_controller.h
+++ b/chromium/ui/views/controls/textfield/textfield_controller.h
@@ -10,6 +10,7 @@
#include "base/strings/string16.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/clipboard_format_type.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-forward.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/views/views_export.h"
@@ -81,9 +82,9 @@ class VIEWS_EXPORT TextfieldController {
// Called when a drop of dragged data happens on the textfield. This method is
// called before regular handling of the drop. If this returns a drag
- // operation other than |ui::DragDropTypes::DRAG_NONE|, regular handling is
+ // operation other than `ui::mojom::DragOperation::kNone`, regular handling is
// skipped.
- virtual int OnDrop(const ui::OSExchangeData& data);
+ virtual ui::mojom::DragOperation OnDrop(const ui::OSExchangeData& data);
// Gives the controller a chance to modify the context menu contents.
virtual void UpdateContextMenu(ui::SimpleMenuModel* menu_contents) {}
diff --git a/chromium/ui/views/controls/textfield/textfield_model.cc b/chromium/ui/views/controls/textfield/textfield_model.cc
index 24514cb8e02..ba182dde811 100644
--- a/chromium/ui/views/controls/textfield/textfield_model.cc
+++ b/chromium/ui/views/controls/textfield/textfield_model.cc
@@ -13,6 +13,7 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
+#include "build/chromeos_buildflags.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/range/range.h"
@@ -362,7 +363,7 @@ void SelectRangeInCompositionText(gfx::RenderText* render_text,
DCHECK(range.IsValid());
uint32_t start = range.GetMin();
uint32_t end = range.GetMax();
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
// Swap |start| and |end| so that GetCaretBounds() can always return the same
// value during conversion.
// TODO(yusukes): Check if this works for other platforms. If it is, use this
@@ -625,6 +626,11 @@ bool TextfieldModel::Paste() {
if (text.empty())
return false;
+ if (render_text()->multiline()) {
+ InsertTextInternal(text, false);
+ return true;
+ }
+
// Leading/trailing whitespace is often selected accidentally, and is rarely
// critical to include (e.g. when pasting into a find bar). Trim it. By
// contrast, whitespace in the middle of the string may need exact
@@ -753,41 +759,13 @@ void TextfieldModel::SetCompositionText(
}
}
-#if defined(OS_CHROMEOS)
-bool TextfieldModel::SetAutocorrectRange(const base::string16& autocorrect_text,
- const gfx::Range& autocorrect_range) {
- // Clears autocorrect range if text is empty.
- if (autocorrect_text.empty() || autocorrect_range == gfx::Range()) {
- autocorrect_range_ = gfx::Range();
- original_text_ = base::EmptyString16();
- } else {
- // TODO(crbug.com/1108170): Use original text to create the Undo window.
- base::string16 current_text =
- render_text_->GetTextFromRange(autocorrect_range);
- // current text should always be valid.
- if (current_text.empty())
- return false;
-
- original_text_ = std::move(current_text);
- uint32_t autocorrect_range_start = autocorrect_range.start();
-
- // TODO(crbug.com/1108170): Update the autocorrect range when the
- // composition changes for ChromeOS. The current autocorrect_range_ does not
- // get updated when composition changes or more text is committed.
- autocorrect_range_ =
- gfx::Range(autocorrect_range_start,
- autocorrect_text.length() + autocorrect_range_start);
-
- base::string16 before_text = render_text_->GetTextFromRange(
- gfx::Range(0, autocorrect_range.start()));
- base::string16 after_text = render_text_->GetTextFromRange(gfx::Range(
- autocorrect_range.end(),
- std::max(autocorrect_range.end(),
- static_cast<uint32_t>(render_text_->text().length()))));
- base::string16 new_text =
- before_text.append(autocorrect_text).append(after_text);
- SetRenderTextText(new_text);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+bool TextfieldModel::SetAutocorrectRange(const gfx::Range& range) {
+ // TODO(crbug.com/1108170): Add an underline to |range|.
+ if (range.GetMax() > render_text()->text().length()) {
+ return false;
}
+ autocorrect_range_ = range;
return true;
}
#endif
diff --git a/chromium/ui/views/controls/textfield/textfield_model.h b/chromium/ui/views/controls/textfield/textfield_model.h
index 2d581a86104..77700891f91 100644
--- a/chromium/ui/views/controls/textfield/textfield_model.h
+++ b/chromium/ui/views/controls/textfield/textfield_model.h
@@ -14,6 +14,7 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/strings/string16.h"
+#include "build/chromeos_buildflags.h"
#include "ui/base/ime/composition_text.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/text_constants.h"
@@ -39,6 +40,7 @@ enum class MergeType {
namespace test {
class BridgedNativeWidgetTest;
+class TextfieldTest;
} // namespace test
// A model that represents text content for a views::Textfield.
@@ -233,15 +235,14 @@ class VIEWS_EXPORT TextfieldModel {
// composition text.
void SetCompositionText(const ui::CompositionText& composition);
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
// Return the text range corresponding to the autocorrected text.
const gfx::Range& autocorrect_range() const { return autocorrect_range_; }
- // Replace the text in the specified range with the autocorrect text and
- // store necessary metadata (The size of the new text + the original text)
- // to be able to undo this change if needed.
- bool SetAutocorrectRange(const base::string16& autocorrect_text,
- const gfx::Range& range);
+ // Sets the autocorrect range to |range|. If |range| is empty, then the
+ // autocorrect range is cleared. Returns true if the range was set or cleared
+ // successfully.
+ bool SetAutocorrectRange(const gfx::Range& range);
#endif
// Puts the text in the specified range into composition mode.
@@ -270,7 +271,7 @@ class VIEWS_EXPORT TextfieldModel {
friend class internal::Edit;
friend class test::BridgedNativeWidgetTest;
friend class TextfieldModelTest;
- friend class TextfieldTest;
+ friend class test::TextfieldTest;
FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_BasicTest);
FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_CutCopyPasteTest);
@@ -335,12 +336,8 @@ class VIEWS_EXPORT TextfieldModel {
gfx::Range composition_range_;
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
gfx::Range autocorrect_range_;
- // Original text is the text that was replaced by the autocorrect feature.
- // This should be restored if the Undo button corresponding to the Autocorrect
- // window is pressed.
- base::string16 original_text_;
#endif
// The list of Edits. The oldest Edits are at the front of the list, and the
diff --git a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
index f3919eece4a..2e22fa6f7d1 100644
--- a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
@@ -15,6 +15,7 @@
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
@@ -1299,7 +1300,7 @@ TEST_F(TextfieldModelTest, CompositionTextTest) {
model.SetCompositionText(composition);
EXPECT_TRUE(model.HasCompositionText());
EXPECT_TRUE(model.HasSelection());
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
// |composition.selection| is ignored because SetCompositionText checks
// if a thick underline exists first.
EXPECT_EQ(gfx::Range(5, 7), model.render_text()->selection());
@@ -1340,7 +1341,7 @@ TEST_F(TextfieldModelTest, CompositionTextTest) {
model.SetCompositionText(composition);
EXPECT_STR_EQ("1234567890678", model.text());
EXPECT_TRUE(model.HasSelection());
-#if !defined(OS_CHROMEOS)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
EXPECT_EQ(gfx::Range(10, 11), model.render_text()->selection());
EXPECT_EQ(11U, model.render_text()->cursor_position());
#else
diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc
index 42d1f0f7cad..aa8e109a607 100644
--- a/chromium/ui/views/controls/textfield/textfield_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc
@@ -2,30 +2,31 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_unittest.h"
#include <stddef.h>
#include <stdint.h>
#include <set>
#include <string>
-#include <utility>
#include <vector>
#include "base/command_line.h"
-#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/i18n/rtl.h"
#include "base/pickle.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
+#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
-#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/clipboard/test/test_clipboard.h"
#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/init/input_method_factory.h"
@@ -40,20 +41,17 @@
#include "ui/events/event_processor.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
-#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/test/keyboard_layout.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/render_text_test_api.h"
#include "ui/strings/grit/ui_strings.h"
-#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/controls/textfield/textfield_model.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/test/test_views_delegate.h"
-#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
@@ -64,11 +62,13 @@
#include "base/win/windows_version.h"
#endif
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
#endif
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/aura/window.h"
#include "ui/wm/core/ime_util_chromeos.h"
#endif
@@ -82,18 +82,79 @@ using base::ASCIIToUTF16;
using base::UTF8ToUTF16;
using base::WideToUTF16;
-#define EXPECT_STR_EQ(ascii, utf16) EXPECT_EQ(ASCIIToUTF16(ascii), utf16)
+namespace views {
+namespace test {
-namespace {
+const ui::EventType kFocusEvent =
+ base::FeatureList::IsEnabled(features::kTextfieldFocusOnTapUp)
+ ? ui::ET_GESTURE_TAP
+ : ui::ET_GESTURE_TAP_DOWN;
const base::char16 kHebrewLetterSamekh = 0x05E1;
+// Convenience to make constructing a GestureEvent simpler.
+class GestureEventForTest : public ui::GestureEvent {
+ public:
+ GestureEventForTest(int x, int y, ui::GestureEventDetails details)
+ : GestureEvent(x, y, ui::EF_NONE, base::TimeTicks(), details) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GestureEventForTest);
+};
+
+// This controller will happily destroy the target field passed on
+// construction when a key event is triggered.
+class TextfieldDestroyerController : public TextfieldController {
+ public:
+ explicit TextfieldDestroyerController(Textfield* target) : target_(target) {
+ target_->set_controller(this);
+ }
+
+ Textfield* target() { return target_.get(); }
+
+ // TextfieldController:
+ bool HandleKeyEvent(Textfield* sender,
+ const ui::KeyEvent& key_event) override {
+ if (target_)
+ target_->OnBlur();
+ target_.reset();
+ return false;
+ }
+
+ private:
+ std::unique_ptr<Textfield> target_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextfieldDestroyerController);
+};
+
+// Class that focuses a textfield when it sees a KeyDown event.
+class TextfieldFocuser : public View {
+ public:
+ explicit TextfieldFocuser(Textfield* textfield) : textfield_(textfield) {
+ SetFocusBehavior(FocusBehavior::ALWAYS);
+ }
+
+ void set_consume(bool consume) { consume_ = consume; }
+
+ // View:
+ bool OnKeyPressed(const ui::KeyEvent& event) override {
+ textfield_->RequestFocus();
+ return consume_;
+ }
+
+ private:
+ bool consume_ = true;
+ Textfield* textfield_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextfieldFocuser);
+};
+
class MockInputMethod : public ui::InputMethodBase {
public:
MockInputMethod();
~MockInputMethod() override;
- // Overridden from InputMethod:
+ // InputMethod:
ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* key) override;
void OnTextInputTypeChanged(const ui::TextInputClient* client) override;
void OnCaretBoundsChanged(const ui::TextInputClient* client) override {}
@@ -117,7 +178,6 @@ class MockInputMethod : public ui::InputMethodBase {
void SetCompositionTextForNextKey(const ui::CompositionText& composition);
void SetResultTextForNextKey(const base::string16& result);
- int count_show_virtual_keyboard_ = 0;
private:
// Overridden from InputMethodBase.
@@ -147,6 +207,8 @@ class MockInputMethod : public ui::InputMethodBase {
bool text_input_type_changed_ = false;
bool cancel_composition_called_ = false;
+ int count_show_virtual_keyboard_ = 0;
+
DISALLOW_COPY_AND_ASSIGN(MockInputMethod);
};
@@ -191,7 +253,9 @@ ui::EventDispatchDetails MockInputMethod::DispatchKeyEvent(ui::KeyEvent* key) {
if (client) {
if (handled) {
if (result_text_.length())
- client->InsertText(result_text_);
+ client->InsertText(result_text_,
+ ui::TextInputClient::InsertTextCursorBehavior::
+ kMoveCursorAfterText);
if (composition_.text.length())
client->SetCompositionText(composition_);
else
@@ -268,8 +332,9 @@ void MockInputMethod::ClearComposition() {
class TestTextfield : public views::Textfield {
public:
TestTextfield() = default;
+ ~TestTextfield() override = default;
- // ui::TextInputClient overrides:
+ // ui::TextInputClient:
void InsertChar(const ui::KeyEvent& e) override {
views::Textfield::InsertChar(e);
#if defined(OS_APPLE)
@@ -298,7 +363,7 @@ class TestTextfield : public views::Textfield {
}
private:
- // views::View override:
+ // views::View:
void OnKeyEvent(ui::KeyEvent* event) override {
key_received_ = true;
event_flags_ = event->flags();
@@ -328,256 +393,198 @@ class TestTextfield : public views::Textfield {
DISALLOW_COPY_AND_ASSIGN(TestTextfield);
};
-// Convenience to make constructing a GestureEvent simpler.
-class GestureEventForTest : public ui::GestureEvent {
- public:
- GestureEventForTest(int x, int y, ui::GestureEventDetails details)
- : GestureEvent(x, y, 0, base::TimeTicks(), details) {}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(GestureEventForTest);
-};
-
-// This controller will happily destroy the target textfield passed on
-// construction when a key event is triggered.
-class TextfieldDestroyerController : public views::TextfieldController {
- public:
- explicit TextfieldDestroyerController(views::Textfield* target)
- : target_(target) {
- target_->set_controller(this);
- }
-
- views::Textfield* target() { return target_.get(); }
-
- // views::TextfieldController:
- bool HandleKeyEvent(views::Textfield* sender,
- const ui::KeyEvent& key_event) override {
- if (target_)
- target_->OnBlur();
- target_.reset();
- return false;
- }
-
- private:
- std::unique_ptr<views::Textfield> target_;
-};
-
-// Class that focuses a textfield when it sees a KeyDown event.
-class TextfieldFocuser : public views::View {
- public:
- explicit TextfieldFocuser(views::Textfield* textfield)
- : textfield_(textfield) {
- SetFocusBehavior(FocusBehavior::ALWAYS);
- }
+TextfieldTest::TextfieldTest() {
+ input_method_ = new MockInputMethod();
+ ui::SetUpInputMethodForTesting(input_method_);
+}
- void set_consume(bool consume) { consume_ = consume; }
+TextfieldTest::~TextfieldTest() = default;
- // View:
- bool OnKeyPressed(const ui::KeyEvent& event) override {
- textfield_->RequestFocus();
- return consume_;
- }
+void TextfieldTest::SetUp() {
+ // OS clipboard is a global resource, which causes flakiness when unit tests
+ // run in parallel. So, use a per-instance test clipboard.
+ ui::Clipboard::SetClipboardForCurrentThread(
+ std::make_unique<ui::TestClipboard>());
+ ViewsTestBase::SetUp();
+}
- private:
- bool consume_ = true;
- views::Textfield* textfield_;
+void TextfieldTest::TearDown() {
+ if (widget_)
+ widget_->Close();
+ // Clear kill buffer used for "Yank" text editing command so that no state
+ // persists between tests.
+ TextfieldModel::ClearKillBuffer();
+ ViewsTestBase::TearDown();
+}
- DISALLOW_COPY_AND_ASSIGN(TextfieldFocuser);
-};
+ui::ClipboardBuffer TextfieldTest::GetAndResetCopiedToClipboard() {
+ return std::exchange(copied_to_clipboard_, ui::ClipboardBuffer::kMaxValue);
+}
-base::string16 GetClipboardText(ui::ClipboardBuffer clipboard_buffer) {
+base::string16 TextfieldTest::GetClipboardText(
+ ui::ClipboardBuffer clipboard_buffer) {
base::string16 text;
ui::Clipboard::GetForCurrentThread()->ReadText(
clipboard_buffer, /* data_dst = */ nullptr, &text);
return text;
}
-void SetClipboardText(ui::ClipboardBuffer clipboard_buffer,
- const std::string& text) {
+void TextfieldTest::SetClipboardText(ui::ClipboardBuffer clipboard_buffer,
+ const std::string& text) {
ui::ScopedClipboardWriter(clipboard_buffer).WriteText(ASCIIToUTF16(text));
}
-} // namespace
-
-namespace views {
-
-class TextfieldTest : public ViewsTestBase, public TextfieldController {
- public:
- TextfieldTest() {
- input_method_ = new MockInputMethod();
- ui::SetUpInputMethodForTesting(input_method_);
- }
-
- // ::testing::Test:
- void TearDown() override {
- if (widget_)
- widget_->Close();
- // Clear kill buffer used for "Yank" text editing command so that no state
- // persists between tests.
- TextfieldModel::ClearKillBuffer();
- ViewsTestBase::TearDown();
- }
+void TextfieldTest::ContentsChanged(Textfield* sender,
+ const base::string16& new_contents) {
+ // Paste calls TextfieldController::ContentsChanged() explicitly even if the
+ // paste action did not change the content. So |new_contents| may match
+ // |last_contents_|. For more info, see http://crbug.com/79002
+ last_contents_ = new_contents;
+}
- ui::ClipboardBuffer GetAndResetCopiedToClipboard() {
- ui::ClipboardBuffer clipboard_buffer = copied_to_clipboard_;
- copied_to_clipboard_ = ui::ClipboardBuffer::kMaxValue;
- return clipboard_buffer;
- }
+void TextfieldTest::OnBeforeUserAction(Textfield* sender) {
+ ++on_before_user_action_;
+}
- // TextfieldController:
- void ContentsChanged(Textfield* sender,
- const base::string16& new_contents) override {
- // Paste calls TextfieldController::ContentsChanged() explicitly even if the
- // paste action did not change the content. So |new_contents| may match
- // |last_contents_|. For more info, see http://crbug.com/79002
- last_contents_ = new_contents;
- }
+void TextfieldTest::OnAfterUserAction(Textfield* sender) {
+ ++on_after_user_action_;
+}
- void OnBeforeUserAction(Textfield* sender) override {
- ++on_before_user_action_;
- }
+void TextfieldTest::OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_type) {
+ copied_to_clipboard_ = clipboard_type;
+}
- void OnAfterUserAction(Textfield* sender) override {
- ++on_after_user_action_;
- }
+void TextfieldTest::InitTextfield(int count) {
+ ASSERT_FALSE(textfield_);
+ textfield_ = PrepareTextfields(count, std::make_unique<TestTextfield>(),
+ gfx::Rect(100, 100, 100, 100));
+}
- void OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_buffer) override {
- copied_to_clipboard_ = clipboard_buffer;
+void TextfieldTest::PrepareTextfieldsInternal(int count,
+ Textfield* textfield,
+ View* container,
+ gfx::Rect bounds) {
+ input_method_->SetDelegate(
+ test::WidgetTest::GetInputMethodDelegateForWidget(widget_.get()));
+
+ textfield->set_controller(this);
+ textfield->SetBoundsRect(bounds);
+ textfield->SetID(1);
+ test_api_ = std::make_unique<TextfieldTestApi>(textfield);
+
+ for (int i = 1; i < count; ++i) {
+ Textfield* textfield =
+ container->AddChildView(std::make_unique<Textfield>());
+ textfield->SetID(i + 1);
}
- void InitTextfield() { InitTextfields(1); }
-
- void InitTextfields(int count) {
- ASSERT_FALSE(textfield_);
- textfield_ = new TestTextfield();
- textfield_->set_controller(this);
- widget_ = new Widget();
-
- // The widget type must be an activatable type, and we don't want to worry
- // about the non-client view, which leaves just TYPE_WINDOW_FRAMELESS.
- Widget::InitParams params =
- CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
-
- params.bounds = gfx::Rect(100, 100, 100, 100);
- widget_->Init(std::move(params));
- input_method_->SetDelegate(
- test::WidgetTest::GetInputMethodDelegateForWidget(widget_));
- View* container = widget_->SetContentsView(std::make_unique<View>());
- container->AddChildView(textfield_);
- textfield_->SetBoundsRect(params.bounds);
- textfield_->SetID(1);
- test_api_ = std::make_unique<TextfieldTestApi>(textfield_);
-
- for (int i = 1; i < count; i++) {
- Textfield* textfield = new Textfield();
- container->AddChildView(textfield);
- textfield->SetID(i + 1);
- }
+ model_ = test_api_->model();
+ model_->ClearEditHistory();
- model_ = test_api_->model();
- model_->ClearEditHistory();
+ // Since the window type is activatable, showing the widget will also
+ // activate it. Calling Activate directly is insufficient, since that does
+ // not also _focus_ an aura::Window (i.e. using the FocusClient). Both the
+ // widget and the textfield must have focus to properly handle input.
+ widget_->Show();
+ textfield->RequestFocus();
- // Since the window type is activatable, showing the widget will also
- // activate it. Calling Activate directly is insufficient, since that does
- // not also _focus_ an aura::Window (i.e. using the FocusClient). Both the
- // widget and the textfield must have focus to properly handle input.
- widget_->Show();
- textfield_->RequestFocus();
+ event_generator_ =
+ std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_.get()));
+ event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW);
+ event_target_ = textfield;
+}
- event_generator_ =
- std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_));
- event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW);
- }
- ui::MenuModel* GetContextMenuModel() {
- test_api_->UpdateContextMenu();
- return test_api_->context_menu_contents();
- }
+ui::MenuModel* TextfieldTest::GetContextMenuModel() {
+ test_api_->UpdateContextMenu();
+ return test_api_->context_menu_contents();
+}
- // True if native Mac keystrokes should be used (to avoid ifdef litter).
- bool TestingNativeMac() {
+bool TextfieldTest::TestingNativeMac() const {
#if defined(OS_APPLE)
- return true;
+ return true;
#else
- return false;
+ return false;
#endif
- }
+}
- bool TestingNativeCrOs() const {
-#if defined(OS_CHROMEOS)
- return true;
+bool TextfieldTest::TestingNativeCrOs() const {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ return true;
#else
- return false;
-#endif // defined(OS_CHROMEOS)
- }
+ return false;
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+}
- protected:
- void SendKeyPress(ui::KeyboardCode key_code, int flags) {
- event_generator_->PressKey(key_code, flags);
- }
+void TextfieldTest::SendKeyPress(ui::KeyboardCode key_code, int flags) {
+ event_generator_->PressKey(key_code, flags);
+}
- void SendKeyEvent(ui::KeyboardCode key_code,
- bool alt,
- bool shift,
- bool control_or_command,
- bool caps_lock) {
- bool control = control_or_command;
- bool command = false;
-
- // By default, swap control and command for native events on Mac. This
- // handles most cases.
- if (TestingNativeMac())
- std::swap(control, command);
-
- int flags =
- (shift ? ui::EF_SHIFT_DOWN : 0) | (control ? ui::EF_CONTROL_DOWN : 0) |
- (alt ? ui::EF_ALT_DOWN : 0) | (command ? ui::EF_COMMAND_DOWN : 0) |
- (caps_lock ? ui::EF_CAPS_LOCK_ON : 0);
-
- SendKeyPress(key_code, flags);
- }
+void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code,
+ bool alt,
+ bool shift,
+ bool control_or_command,
+ bool caps_lock) {
+ bool control = control_or_command;
+ bool command = false;
- void SendKeyEvent(ui::KeyboardCode key_code,
- bool shift,
- bool control_or_command) {
- SendKeyEvent(key_code, false, shift, control_or_command, false);
- }
+ // By default, swap control and command for native events on Mac. This
+ // handles most cases.
+ if (TestingNativeMac())
+ std::swap(control, command);
- void SendKeyEvent(ui::KeyboardCode key_code) {
- SendKeyEvent(key_code, false, false);
- }
+ int flags =
+ (shift ? ui::EF_SHIFT_DOWN : 0) | (control ? ui::EF_CONTROL_DOWN : 0) |
+ (alt ? ui::EF_ALT_DOWN : 0) | (command ? ui::EF_COMMAND_DOWN : 0) |
+ (caps_lock ? ui::EF_CAPS_LOCK_ON : 0);
- void SendKeyEvent(base::char16 ch) { SendKeyEvent(ch, ui::EF_NONE, false); }
+ SendKeyPress(key_code, flags);
+}
- void SendKeyEvent(base::char16 ch, int flags) {
- SendKeyEvent(ch, flags, false);
- }
+void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code,
+ bool shift,
+ bool control_or_command) {
+ SendKeyEvent(key_code, false, shift, control_or_command, false);
+}
+
+void TextfieldTest::SendKeyEvent(ui::KeyboardCode key_code) {
+ SendKeyEvent(key_code, false, false);
+}
+
+void TextfieldTest::SendKeyEvent(base::char16 ch) {
+ SendKeyEvent(ch, ui::EF_NONE, false);
+}
+
+void TextfieldTest::SendKeyEvent(base::char16 ch, int flags) {
+ SendKeyEvent(ch, flags, false);
+}
- void SendKeyEvent(base::char16 ch, int flags, bool from_vk) {
- if (ch < 0x80) {
- ui::KeyboardCode code =
- ch == ' ' ? ui::VKEY_SPACE
- : static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a');
- SendKeyPress(code, flags);
- } else {
- // For unicode characters, assume they come from IME rather than the
- // keyboard. So they are dispatched directly to the input method. But on
- // Mac, key events don't pass through InputMethod. Hence they are
- // dispatched regularly.
- ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, ui::DomCode::NONE, flags);
- if (from_vk) {
- ui::Event::Properties properties;
- properties[ui::kPropertyFromVK] =
- std::vector<uint8_t>(ui::kPropertyFromVKSize);
- event.SetProperties(properties);
- }
+void TextfieldTest::SendKeyEvent(base::char16 ch, int flags, bool from_vk) {
+ if (ch < 0x80) {
+ ui::KeyboardCode code =
+ ch == ' ' ? ui::VKEY_SPACE
+ : static_cast<ui::KeyboardCode>(ui::VKEY_A + ch - 'a');
+ SendKeyPress(code, flags);
+ } else {
+ // For unicode characters, assume they come from IME rather than the
+ // keyboard. So they are dispatched directly to the input method. But on
+ // Mac, key events don't pass through InputMethod. Hence they are
+ // dispatched regularly.
+ ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, ui::DomCode::NONE, flags);
+ if (from_vk) {
+ ui::Event::Properties properties;
+ properties[ui::kPropertyFromVK] =
+ std::vector<uint8_t>(ui::kPropertyFromVKSize);
+ event.SetProperties(properties);
+ }
#if defined(OS_APPLE)
- event_generator_->Dispatch(&event);
+ event_generator_->Dispatch(&event);
#else
- input_method_->DispatchKeyEvent(&event);
+ input_method_->DispatchKeyEvent(&event);
#endif
- }
}
+}
+void TextfieldTest::DispatchMockInputMethodKeyEvent() {
// Send a key to trigger MockInputMethod::DispatchKeyEvent(). Note the
// specific VKEY isn't used (MockInputMethod will mock a ui::VKEY_PROCESSKEY
// whenever it has a test composition). However, on Mac, it can't be a letter
@@ -585,244 +592,220 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
// and don't have a meaningful ui::KeyEvent that would trigger
// DispatchKeyEvent(). It also can't be VKEY_ENTER, since those key events may
// need to be suppressed when interacting with real system IME.
- void DispatchMockInputMethodKeyEvent() { SendKeyEvent(ui::VKEY_INSERT); }
-
- // Sends a platform-specific move (and select) to the logical start of line.
- // Eg. this should move (and select) to the right end of line for RTL text.
- void SendHomeEvent(bool shift) {
- if (TestingNativeMac()) {
- // [NSResponder moveToBeginningOfLine:] is the correct way to do this on
- // Mac, but that doesn't have a default key binding. Since
- // views::Textfield doesn't currently support multiple lines, the same
- // effect can be achieved by Cmd+Up which maps to
- // [NSResponder moveToBeginningOfDocument:].
- SendKeyEvent(ui::VKEY_UP, shift /* shift */, true /* command */);
- return;
- }
- SendKeyEvent(ui::VKEY_HOME, shift /* shift */, false /* control */);
- }
+ SendKeyEvent(ui::VKEY_INSERT);
+}
- // Sends a platform-specific move (and select) to the logical end of line.
- void SendEndEvent(bool shift) {
- if (TestingNativeMac()) {
- SendKeyEvent(ui::VKEY_DOWN, shift, true); // Cmd+Down.
- return;
- }
- SendKeyEvent(ui::VKEY_END, shift, false);
+// Sends a platform-specific move (and select) to the logical start of line.
+// Eg. this should move (and select) to the right end of line for RTL text.
+void TextfieldTest::SendHomeEvent(bool shift) {
+ if (TestingNativeMac()) {
+ // [NSResponder moveToBeginningOfLine:] is the correct way to do this on
+ // Mac, but that doesn't have a default key binding. Since
+ // views::Textfield doesn't currently support multiple lines, the same
+ // effect can be achieved by Cmd+Up which maps to
+ // [NSResponder moveToBeginningOfDocument:].
+ SendKeyEvent(ui::VKEY_UP, shift /* shift */, true /* command */);
+ return;
}
+ SendKeyEvent(ui::VKEY_HOME, shift /* shift */, false /* control */);
+}
- // Sends {delete, move, select} word {forward, backward}.
- void SendWordEvent(ui::KeyboardCode key, bool shift) {
- bool alt = false;
- bool control = true;
- bool caps = false;
- if (TestingNativeMac()) {
- // Use Alt+Left/Right/Backspace on native Mac.
- alt = true;
- control = false;
- }
- SendKeyEvent(key, alt, shift, control, caps);
+// Sends a platform-specific move (and select) to the logical end of line.
+void TextfieldTest::SendEndEvent(bool shift) {
+ if (TestingNativeMac()) {
+ SendKeyEvent(ui::VKEY_DOWN, shift, true); // Cmd+Down.
+ return;
}
+ SendKeyEvent(ui::VKEY_END, shift, false);
+}
- // Sends Shift+Delete if supported, otherwise Cmd+X again.
- void SendAlternateCut() {
- if (TestingNativeMac())
- SendKeyEvent(ui::VKEY_X, false, true);
- else
- SendKeyEvent(ui::VKEY_DELETE, true, false);
+// Sends {delete, move, select} word {forward, backward}.
+void TextfieldTest::SendWordEvent(ui::KeyboardCode key, bool shift) {
+ bool alt = false;
+ bool control = true;
+ bool caps = false;
+ if (TestingNativeMac()) {
+ // Use Alt+Left/Right/Backspace on native Mac.
+ alt = true;
+ control = false;
}
+ SendKeyEvent(key, alt, shift, control, caps);
+}
- // Sends Ctrl+Insert if supported, otherwise Cmd+C again.
- void SendAlternateCopy() {
- if (TestingNativeMac())
- SendKeyEvent(ui::VKEY_C, false, true);
- else
- SendKeyEvent(ui::VKEY_INSERT, false, true);
- }
+// Sends Shift+Delete if supported, otherwise Cmd+X again.
+void TextfieldTest::SendAlternateCut() {
+ if (TestingNativeMac())
+ SendKeyEvent(ui::VKEY_X, false, true);
+ else
+ SendKeyEvent(ui::VKEY_DELETE, true, false);
+}
- // Sends Shift+Insert if supported, otherwise Cmd+V again.
- void SendAlternatePaste() {
- if (TestingNativeMac())
- SendKeyEvent(ui::VKEY_V, false, true);
- else
- SendKeyEvent(ui::VKEY_INSERT, true, false);
- }
+// Sends Ctrl+Insert if supported, otherwise Cmd+C again.
+void TextfieldTest::SendAlternateCopy() {
+ if (TestingNativeMac())
+ SendKeyEvent(ui::VKEY_C, false, true);
+ else
+ SendKeyEvent(ui::VKEY_INSERT, false, true);
+}
- View* GetFocusedView() {
- return widget_->GetFocusManager()->GetFocusedView();
- }
+// Sends Shift+Insert if supported, otherwise Cmd+V again.
+void TextfieldTest::SendAlternatePaste() {
+ if (TestingNativeMac())
+ SendKeyEvent(ui::VKEY_V, false, true);
+ else
+ SendKeyEvent(ui::VKEY_INSERT, true, false);
+}
- int GetCursorPositionX(int cursor_pos) {
- return test_api_->GetRenderText()
- ->GetCursorBounds(gfx::SelectionModel(cursor_pos, gfx::CURSOR_FORWARD),
- false)
- .x();
- }
+View* TextfieldTest::GetFocusedView() {
+ return widget_->GetFocusManager()->GetFocusedView();
+}
- int GetCursorYForTesting() {
- return test_api_->GetRenderText()->GetLineOffset(0).y() + 1;
- }
+int TextfieldTest::GetCursorPositionX(int cursor_pos) {
+ return test_api_->GetRenderText()
+ ->GetCursorBounds(gfx::SelectionModel(cursor_pos, gfx::CURSOR_FORWARD),
+ false)
+ .x();
+}
- // Get the current cursor bounds.
- gfx::Rect GetCursorBounds() {
- return test_api_->GetRenderText()->GetUpdatedCursorBounds();
- }
+int TextfieldTest::GetCursorYForTesting() {
+ return test_api_->GetRenderText()->GetLineOffset(0).y() + 1;
+}
- // Get the cursor bounds of |sel|.
- gfx::Rect GetCursorBounds(const gfx::SelectionModel& sel) {
- return test_api_->GetRenderText()->GetCursorBounds(sel, true);
- }
+gfx::Rect TextfieldTest::GetCursorBounds() {
+ return test_api_->GetRenderText()->GetUpdatedCursorBounds();
+}
- gfx::Rect GetDisplayRect() {
- return test_api_->GetRenderText()->display_rect();
- }
+// Gets the cursor bounds of |sel|.
+gfx::Rect TextfieldTest::GetCursorBounds(const gfx::SelectionModel& sel) {
+ return test_api_->GetRenderText()->GetCursorBounds(sel, true);
+}
+
+gfx::Rect TextfieldTest::GetDisplayRect() {
+ return test_api_->GetRenderText()->display_rect();
+}
+
+gfx::Rect TextfieldTest::GetCursorViewRect() {
+ return test_api_->GetCursorViewRect();
+}
- // Mouse click on the point whose x-axis is |bound|'s x plus |x_offset| and
- // y-axis is in the middle of |bound|'s vertical range.
- void MouseClick(const gfx::Rect bound, int x_offset) {
- gfx::Point point(bound.x() + x_offset, bound.y() + bound.height() / 2);
- ui::MouseEvent click(ui::ET_MOUSE_PRESSED, point, point,
+// Performs a mouse click on the point whose x-axis is |bound|'s x plus
+// |x_offset| and y-axis is in the middle of |bound|'s vertical range.
+void TextfieldTest::MouseClick(const gfx::Rect bound, int x_offset) {
+ gfx::Point point(bound.x() + x_offset, bound.y() + bound.height() / 2);
+ ui::MouseEvent click(ui::ET_MOUSE_PRESSED, point, point,
+ ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_LEFT_MOUSE_BUTTON);
+ event_target_->OnMousePressed(click);
+ ui::MouseEvent release(ui::ET_MOUSE_RELEASED, point, point,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
- textfield_->OnMousePressed(click);
- ui::MouseEvent release(ui::ET_MOUSE_RELEASED, point, point,
- ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
- ui::EF_LEFT_MOUSE_BUTTON);
- textfield_->OnMouseReleased(release);
- }
+ event_target_->OnMouseReleased(release);
+}
- // This is to avoid double/triple click.
- void NonClientMouseClick() {
- ui::MouseEvent click(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+// This is to avoid double/triple click.
+void TextfieldTest::NonClientMouseClick() {
+ ui::MouseEvent click(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+ ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_NON_CLIENT,
+ ui::EF_LEFT_MOUSE_BUTTON);
+ event_target_->OnMousePressed(click);
+ ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_NON_CLIENT,
ui::EF_LEFT_MOUSE_BUTTON);
- textfield_->OnMousePressed(click);
- ui::MouseEvent release(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
- ui::EventTimeForNow(),
- ui::EF_LEFT_MOUSE_BUTTON | ui::EF_IS_NON_CLIENT,
- ui::EF_LEFT_MOUSE_BUTTON);
- textfield_->OnMouseReleased(release);
- }
+ event_target_->OnMouseReleased(release);
+}
- void VerifyTextfieldContextMenuContents(bool textfield_has_selection,
- bool can_undo,
- ui::MenuModel* menu) {
- const auto& text = textfield_->GetText();
- const bool is_all_selected =
- !text.empty() &&
- textfield_->GetSelectedRange().length() == text.length();
+void TextfieldTest::VerifyTextfieldContextMenuContents(
+ bool textfield_has_selection,
+ bool can_undo,
+ ui::MenuModel* menu) {
+ const auto& text = textfield_->GetText();
+ const bool is_all_selected =
+ !text.empty() && textfield_->GetSelectedRange().length() == text.length();
- int menu_index = 0;
+ int menu_index = 0;
#if defined(OS_APPLE)
- if (textfield_has_selection) {
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Look Up "Selection" */));
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
- }
-#endif
-
- if (ui::IsEmojiPanelSupported()) {
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */));
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
- }
-
- EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */));
- EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
- EXPECT_EQ(textfield_has_selection,
- menu->IsEnabledAt(menu_index++ /* CUT */));
- EXPECT_EQ(textfield_has_selection,
- menu->IsEnabledAt(menu_index++ /* COPY */));
- EXPECT_NE(GetClipboardText(ui::ClipboardBuffer::kCopyPaste).empty(),
- menu->IsEnabledAt(menu_index++ /* PASTE */));
- EXPECT_EQ(textfield_has_selection,
- menu->IsEnabledAt(menu_index++ /* DELETE */));
+ if (textfield_has_selection) {
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Look Up "Selection" */));
EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
- EXPECT_EQ(!is_all_selected,
- menu->IsEnabledAt(menu_index++ /* SELECT ALL */));
- }
-
- void PressMouseButton(ui::EventFlags mouse_button_flags) {
- ui::MouseEvent press(ui::ET_MOUSE_PRESSED, mouse_position_, mouse_position_,
- ui::EventTimeForNow(), mouse_button_flags,
- mouse_button_flags);
- textfield_->OnMousePressed(press);
- }
-
- void ReleaseMouseButton(ui::EventFlags mouse_button_flags) {
- ui::MouseEvent release(ui::ET_MOUSE_RELEASED, mouse_position_,
- mouse_position_, ui::EventTimeForNow(),
- mouse_button_flags, mouse_button_flags);
- textfield_->OnMouseReleased(release);
- }
-
- void PressLeftMouseButton() { PressMouseButton(ui::EF_LEFT_MOUSE_BUTTON); }
-
- void ReleaseLeftMouseButton() {
- ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON);
- }
-
- void ClickLeftMouseButton() {
- PressLeftMouseButton();
- ReleaseLeftMouseButton();
- }
-
- void ClickRightMouseButton() {
- PressMouseButton(ui::EF_RIGHT_MOUSE_BUTTON);
- ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON);
}
+#endif
- void DragMouseTo(const gfx::Point& where) {
- mouse_position_ = where;
- ui::MouseEvent drag(ui::ET_MOUSE_DRAGGED, where, where,
- ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0);
- textfield_->OnMouseDragged(drag);
+ if (ui::IsEmojiPanelSupported()) {
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */));
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
}
- // Textfield does not listen to OnMouseMoved, so this function does not send
- // an event when it updates the cursor position.
- void MoveMouseTo(const gfx::Point& where) { mouse_position_ = where; }
-
- // Tap on the textfield.
- void TapAtCursor(ui::EventPointerType pointer_type) {
- ui::GestureEventDetails tap_down_details(ui::ET_GESTURE_TAP_DOWN);
- tap_down_details.set_primary_pointer_type(pointer_type);
- GestureEventForTest tap_down(GetCursorPositionX(0), 0, tap_down_details);
- textfield_->OnGestureEvent(&tap_down);
+ EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */));
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
+ EXPECT_EQ(textfield_has_selection, menu->IsEnabledAt(menu_index++ /* CUT */));
+ EXPECT_EQ(textfield_has_selection,
+ menu->IsEnabledAt(menu_index++ /* COPY */));
+ EXPECT_NE(GetClipboardText(ui::ClipboardBuffer::kCopyPaste).empty(),
+ menu->IsEnabledAt(menu_index++ /* PASTE */));
+ EXPECT_EQ(textfield_has_selection,
+ menu->IsEnabledAt(menu_index++ /* DELETE */));
+ EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
+ EXPECT_EQ(!is_all_selected, menu->IsEnabledAt(menu_index++ /* SELECT ALL */));
+}
- ui::GestureEventDetails tap_up_details(ui::ET_GESTURE_TAP);
- tap_up_details.set_primary_pointer_type(pointer_type);
- GestureEventForTest tap_up(GetCursorPositionX(0), 0, tap_up_details);
- textfield_->OnGestureEvent(&tap_up);
- }
+void TextfieldTest::PressMouseButton(ui::EventFlags mouse_button_flags) {
+ ui::MouseEvent press(ui::ET_MOUSE_PRESSED, mouse_position_, mouse_position_,
+ ui::EventTimeForNow(), mouse_button_flags,
+ mouse_button_flags);
+ event_target_->OnMousePressed(press);
+}
- // We need widget to populate wrapper class.
- Widget* widget_ = nullptr;
+void TextfieldTest::ReleaseMouseButton(ui::EventFlags mouse_button_flags) {
+ ui::MouseEvent release(ui::ET_MOUSE_RELEASED, mouse_position_,
+ mouse_position_, ui::EventTimeForNow(),
+ mouse_button_flags, mouse_button_flags);
+ event_target_->OnMouseReleased(release);
+}
- TestTextfield* textfield_ = nullptr;
- std::unique_ptr<TextfieldTestApi> test_api_;
- TextfieldModel* model_ = nullptr;
+void TextfieldTest::PressLeftMouseButton() {
+ PressMouseButton(ui::EF_LEFT_MOUSE_BUTTON);
+}
- // The string from Controller::ContentsChanged callback.
- base::string16 last_contents_;
+void TextfieldTest::ReleaseLeftMouseButton() {
+ ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON);
+}
- // For testing input method related behaviors.
- MockInputMethod* input_method_ = nullptr;
+void TextfieldTest::ClickLeftMouseButton() {
+ PressLeftMouseButton();
+ ReleaseLeftMouseButton();
+}
- // Indicates how many times OnBeforeUserAction() is called.
- int on_before_user_action_ = 0;
+void TextfieldTest::ClickRightMouseButton() {
+ PressMouseButton(ui::EF_RIGHT_MOUSE_BUTTON);
+ ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON);
+}
- // Indicates how many times OnAfterUserAction() is called.
- int on_after_user_action_ = 0;
+void TextfieldTest::DragMouseTo(const gfx::Point& where) {
+ mouse_position_ = where;
+ ui::MouseEvent drag(ui::ET_MOUSE_DRAGGED, where, where, ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, 0);
+ event_target_->OnMouseDragged(drag);
+}
- // Position of the mouse for synthetic mouse events.
- gfx::Point mouse_position_;
- ui::ClipboardBuffer copied_to_clipboard_ = ui::ClipboardBuffer::kMaxValue;
- std::unique_ptr<ui::test::EventGenerator> event_generator_;
+void TextfieldTest::MoveMouseTo(const gfx::Point& where) {
+ mouse_position_ = where;
+}
- private:
- DISALLOW_COPY_AND_ASSIGN(TextfieldTest);
-};
+// Taps on the textfield.
+void TextfieldTest::TapAtCursor(ui::EventPointerType pointer_type) {
+ ui::GestureEventDetails tap_down_details(ui::ET_GESTURE_TAP_DOWN);
+ tap_down_details.set_primary_pointer_type(pointer_type);
+ GestureEventForTest tap_down(GetCursorPositionX(0), 0, tap_down_details);
+ textfield_->OnGestureEvent(&tap_down);
+
+ ui::GestureEventDetails tap_up_details(ui::ET_GESTURE_TAP);
+ tap_up_details.set_primary_pointer_type(pointer_type);
+ GestureEventForTest tap_up(GetCursorPositionX(0), 0, tap_up_details);
+ textfield_->OnGestureEvent(&tap_up);
+}
TEST_F(TextfieldTest, ModelChangesTest) {
InitTextfield();
@@ -1297,16 +1280,16 @@ TEST_F(TextfieldTest, ModifySelectionWithMultipleSelections) {
TEST_F(TextfieldTest, InsertionDeletionTest) {
// Insert a test string in a textfield.
InitTextfield();
- for (size_t i = 0; i < 10; i++)
+ for (size_t i = 0; i < 10; ++i)
SendKeyEvent(static_cast<ui::KeyboardCode>(ui::VKEY_A + i));
EXPECT_STR_EQ("abcdefghij", textfield_->GetText());
// Test the delete and backspace keys.
textfield_->SetSelectedRange(gfx::Range(5));
- for (size_t i = 0; i < 3; i++)
+ for (size_t i = 0; i < 3; ++i)
SendKeyEvent(ui::VKEY_BACK);
EXPECT_STR_EQ("abfghij", textfield_->GetText());
- for (size_t i = 0; i < 3; i++)
+ for (size_t i = 0; i < 3; ++i)
SendKeyEvent(ui::VKEY_DELETE);
EXPECT_STR_EQ("abij", textfield_->GetText());
@@ -1586,7 +1569,9 @@ TEST_F(TextfieldTest, OnKeyPress) {
TEST_F(TextfieldTest, OnKeyPressBinding) {
InitTextfield();
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
// Install a TextEditKeyBindingsDelegateAuraLinux that does nothing.
class TestDelegate : public ui::TextEditKeyBindingsDelegateAuraLinux {
public:
@@ -1624,7 +1609,9 @@ TEST_F(TextfieldTest, OnKeyPressBinding) {
EXPECT_STR_EQ("a", textfield_->GetText());
textfield_->clear();
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
ui::SetTextEditKeyBindingsDelegate(nullptr);
#endif
}
@@ -1734,7 +1721,7 @@ TEST_F(TextfieldTest, ShouldShowCursor) {
}
TEST_F(TextfieldTest, FocusTraversalTest) {
- InitTextfields(3);
+ InitTextfield(3);
textfield_->RequestFocus();
EXPECT_EQ(1, GetFocusedView()->GetID());
@@ -1966,7 +1953,7 @@ TEST_F(TextfieldTest, DragAndDrop_AcceptDrop) {
ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE);
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE,
textfield_->OnDragUpdated(drop));
- EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, textfield_->OnPerformDrop(drop));
+ EXPECT_EQ(ui::mojom::DragOperation::kCopy, textfield_->OnPerformDrop(drop));
EXPECT_STR_EQ("hello string world", textfield_->GetText());
// Ensure that textfields do not accept non-OSExchangeData::STRING types.
@@ -2059,7 +2046,7 @@ TEST_F(TextfieldTest, DragAndDrop_ToTheRight) {
EXPECT_TRUE(textfield_->CanDrop(data));
ui::DropTargetEvent drop_a(data, kDropPoint, kDropPoint, operations);
EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a));
- EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnPerformDrop(drop_a));
+ EXPECT_EQ(ui::mojom::DragOperation::kMove, textfield_->OnPerformDrop(drop_a));
EXPECT_STR_EQ("h welloorld", textfield_->GetText());
textfield_->OnDragDone();
@@ -2110,7 +2097,7 @@ TEST_F(TextfieldTest, DragAndDrop_ToTheLeft) {
gfx::PointF drop_point(GetCursorPositionX(1), cursor_y);
ui::DropTargetEvent drop_a(data, drop_point, drop_point, operations);
EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a));
- EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnPerformDrop(drop_a));
+ EXPECT_EQ(ui::mojom::DragOperation::kMove, textfield_->OnPerformDrop(drop_a));
EXPECT_STR_EQ("h worlellod", textfield_->GetText());
textfield_->OnDragDone();
@@ -2453,7 +2440,7 @@ TEST_F(TextfieldTest, RedoWithCtrlY) {
#if defined(OS_APPLE)
TEST_F(TextfieldTest, Yank) {
- InitTextfields(2);
+ InitTextfield(2);
textfield_->SetText(ASCIIToUTF16("abcdef"));
textfield_->SetSelectedRange(gfx::Range(2, 4));
@@ -2996,7 +2983,7 @@ TEST_F(TextfieldTest, CommitEmptyComposingTextTest) {
EXPECT_EQ(composed_text_length, static_cast<uint32_t>(0));
}
-#if defined(OS_WIN) || defined(OS_CHROMEOS)
+#if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH)
// SetCompositionFromExistingText is only available on Windows and Chrome OS.
TEST_F(TextfieldTest, SetCompositionFromExistingTextTest) {
InitTextfield();
@@ -3081,121 +3068,55 @@ TEST_F(TextfieldTest, GetCompositionCharacterBounds_ComplexText) {
// - rects[6] == rects[7]
}
-#if defined(OS_CHROMEOS)
-TEST_F(TextfieldTest, SetAutocorrectRangeText) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(TextfieldTest, SetAutocorrectRange) {
InitTextfield();
- ui::CompositionText composition;
- composition.text = UTF8ToUTF16("Initial txt");
- textfield_->SetCompositionText(composition);
- textfield_->SetAutocorrectRange(ASCIIToUTF16("text replacement"),
- gfx::Range(8, 11));
+ textfield_->SetText(ASCIIToUTF16("abc def ghi"));
+ textfield_->SetAutocorrectRange(gfx::Range(4, 7));
gfx::Range autocorrect_range = textfield_->GetAutocorrectRange();
- EXPECT_EQ(autocorrect_range, gfx::Range(8, 24));
-
- base::string16 text;
- textfield_->GetTextFromRange(gfx::Range(0, 24), &text);
- EXPECT_EQ(text, UTF8ToUTF16("Initial text replacement"));
-}
-
-TEST_F(TextfieldTest, SetAutocorrectRangeExplicitlySet) {
- InitTextfield();
- textfield_->InsertText(UTF8ToUTF16("Initial txt"));
- textfield_->SetAutocorrectRange(ASCIIToUTF16("text replacement"),
- gfx::Range(8, 11));
-
- gfx::Range autocorrectRange = textfield_->GetAutocorrectRange();
- EXPECT_EQ(autocorrectRange, gfx::Range(8, 24));
-
- base::string16 text;
- textfield_->GetTextFromRange(gfx::Range(0, 24), &text);
- EXPECT_EQ(text, UTF8ToUTF16("Initial text replacement"));
+ EXPECT_EQ(autocorrect_range, gfx::Range(4, 7));
}
TEST_F(TextfieldTest, DoesNotSetAutocorrectRangeWhenRangeGivenIsInvalid) {
InitTextfield();
- ui::CompositionText composition;
- composition.text = UTF8ToUTF16("Initial");
- textfield_->SetCompositionText(composition);
-
- EXPECT_FALSE(textfield_->SetAutocorrectRange(ASCIIToUTF16("text replacement"),
- gfx::Range(8, 11)));
- EXPECT_EQ(gfx::Range(0, 0), textfield_->GetAutocorrectRange());
- gfx::Range range;
- textfield_->GetTextRange(&range);
- base::string16 text;
- textfield_->GetTextFromRange(range, &text);
- EXPECT_EQ(composition.text, text);
-}
-
-TEST_F(TextfieldTest,
- ClearsAutocorrectRangeWhenSetAutocorrectRangeWithEmptyText) {
- InitTextfield();
-
- ui::CompositionText composition;
- composition.text = UTF8ToUTF16("Initial");
- textfield_->SetCompositionText(composition);
+ textfield_->SetText(ASCIIToUTF16("abc"));
- EXPECT_TRUE(
- textfield_->SetAutocorrectRange(base::EmptyString16(), gfx::Range(0, 2)));
- EXPECT_EQ(gfx::Range(0, 0), textfield_->GetAutocorrectRange());
- gfx::Range range;
- textfield_->GetTextRange(&range);
- base::string16 text;
- textfield_->GetTextFromRange(range, &text);
- EXPECT_EQ(composition.text, text);
+ EXPECT_FALSE(textfield_->SetAutocorrectRange(gfx::Range(8, 11)));
+ EXPECT_TRUE(textfield_->GetAutocorrectRange().is_empty());
}
TEST_F(TextfieldTest,
ClearsAutocorrectRangeWhenSetAutocorrectRangeWithEmptyRange) {
InitTextfield();
- ui::CompositionText composition;
- composition.text = UTF8ToUTF16("Initial");
- textfield_->SetCompositionText(composition);
-
- EXPECT_TRUE(
- textfield_->SetAutocorrectRange(UTF8ToUTF16("Test"), gfx::Range(0, 0)));
- EXPECT_EQ(gfx::Range(0, 0), textfield_->GetAutocorrectRange());
- gfx::Range range;
- textfield_->GetTextRange(&range);
- base::string16 text;
- textfield_->GetTextFromRange(range, &text);
- EXPECT_EQ(composition.text, text);
-}
-
-TEST_F(TextfieldTest, ClearAutocorrectRange) {
- InitTextfield();
- textfield_->InsertText(UTF8ToUTF16("Initial txt"));
- textfield_->SetAutocorrectRange(ASCIIToUTF16("text replacement"),
- gfx::Range(8, 11));
-
- EXPECT_EQ(textfield_->GetText(), UTF8ToUTF16("Initial text replacement"));
- EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range(8, 24));
-
- textfield_->ClearAutocorrectRange();
+ textfield_->SetText(ASCIIToUTF16("abc"));
- EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range());
+ EXPECT_TRUE(textfield_->SetAutocorrectRange(gfx::Range()));
+ EXPECT_TRUE(textfield_->GetAutocorrectRange().is_empty());
}
TEST_F(TextfieldTest, GetAutocorrectCharacterBoundsTest) {
InitTextfield();
- textfield_->InsertText(UTF8ToUTF16("hello placeholder text"));
- textfield_->SetAutocorrectRange(ASCIIToUTF16("longlonglongtext"),
- gfx::Range(3, 10));
+ textfield_->InsertText(
+ UTF8ToUTF16("hello placeholder text"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
+ textfield_->SetAutocorrectRange(gfx::Range(3, 10));
- EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range(3, 19));
+ EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range(3, 10));
gfx::Rect rect_for_long_text = textfield_->GetAutocorrectCharacterBounds();
// Clear the text
textfield_->DeleteRange(gfx::Range(0, 99));
- textfield_->InsertText(UTF8ToUTF16("hello placeholder text"));
- textfield_->SetAutocorrectRange(ASCIIToUTF16("short"), gfx::Range(3, 10));
+ textfield_->InsertText(
+ UTF8ToUTF16("hello placeholder text"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
+ textfield_->SetAutocorrectRange(gfx::Range(3, 8));
EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range(3, 8));
@@ -3214,7 +3135,7 @@ TEST_F(TextfieldTest, GetAutocorrectCharacterBoundsTest) {
// TODO(crbug.com/1108170): Add a test to check that when the composition /
// surrounding text is updated, the AutocorrectRange is updated accordingly.
-#endif // OS_CHROMEOS
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// The word we select by double clicking should remain selected regardless of
// where we drag the mouse afterwards without releasing the left button.
@@ -3241,7 +3162,9 @@ TEST_F(TextfieldTest, KeepInitiallySelectedWord) {
EXPECT_EQ(gfx::Range(7, 0), textfield_->GetSelectedRange());
}
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_F(TextfieldTest, SelectionClipboard) {
InitTextfield();
textfield_->SetText(ASCIIToUTF16("0123"));
@@ -3386,7 +3309,7 @@ TEST_F(TextfieldTest, SelectionClipboard) {
// Verify that the selection clipboard is not updated for selections on a
// password textfield.
TEST_F(TextfieldTest, SelectionClipboard_Password) {
- InitTextfields(2);
+ InitTextfield(2);
textfield_->SetText(ASCIIToUTF16("abcd"));
// Select-all should update the selection clipboard for a non-password
@@ -3501,13 +3424,13 @@ TEST_F(TextfieldTest, SetAccessibleNameNotifiesAccessibilityEvent) {
EXPECT_EQ(test_tooltip_text, ASCIIToUTF16(name));
}
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
// Check that when accessibility virtual keyboard is enabled, windows are
// shifted up when focused and restored when focus is lost.
TEST_F(TextfieldTest, VirtualKeyboardFocusEnsureCaretNotInRect) {
InitTextfield();
- aura::Window* root_window = GetRootWindow(widget_);
+ aura::Window* root_window = GetRootWindow(widget_.get());
int keyboard_height = 200;
gfx::Rect root_bounds = root_window->bounds();
gfx::Rect orig_widget_bounds = gfx::Rect(0, 300, 400, 200);
@@ -3535,7 +3458,7 @@ TEST_F(TextfieldTest, VirtualKeyboardFocusEnsureCaretNotInRect) {
// Window should be restored.
EXPECT_EQ(widget_->GetNativeView()->bounds(), orig_widget_bounds);
}
-#endif // defined(OS_CHROMEOS)
+#endif // BUILDFLAG(IS_CHROMEOS_ASH)
class TextfieldTouchSelectionTest : public TextfieldTest {
protected:
@@ -3566,7 +3489,7 @@ class TextfieldTouchSelectionTest : public TextfieldTest {
};
// Touch selection and dragging currently only works for chromeos.
-#if defined(OS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(TextfieldTouchSelectionTest, TouchSelectionAndDraggingTest) {
InitTextfield();
textfield_->SetText(ASCIIToUTF16("hello world"));
@@ -3802,17 +3725,13 @@ TEST_F(TextfieldTest, TextfieldBoundsChangeTest) {
// Verify that after creating a new Textfield, the Textfield doesn't
// automatically receive focus and the text cursor is not visible.
TEST_F(TextfieldTest, TextfieldInitialization) {
- TestTextfield* new_textfield = new TestTextfield();
- new_textfield->set_controller(this);
- 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));
+ std::unique_ptr<Widget> widget = CreateTestWidget();
View* container = widget->SetContentsView(std::make_unique<View>());
- container->AddChildView(new_textfield);
- new_textfield->SetBoundsRect(params.bounds);
+ TestTextfield* new_textfield =
+ container->AddChildView(std::make_unique<TestTextfield>());
+ new_textfield->set_controller(this);
+ new_textfield->SetBoundsRect(gfx::Rect(100, 100, 100, 100));
new_textfield->SetID(1);
test_api_ = std::make_unique<TextfieldTestApi>(new_textfield);
widget->Show();
@@ -4070,6 +3989,7 @@ TEST_F(TextfieldTest, FocusReasonMultipleEvents) {
EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE,
textfield_->GetFocusReason());
+ // Pen tap, followed by a touch tap.
TapAtCursor(ui::EventPointerType::kPen);
TapAtCursor(ui::EventPointerType::kTouch);
EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN,
@@ -4123,6 +4043,32 @@ TEST_F(TextfieldTest, ChangeTextDirectionAndLayoutAlignmentTest) {
base::i18n::TextDirection::LEFT_TO_RIGHT);
EXPECT_EQ(textfield_->GetHorizontalAlignment(),
gfx::HorizontalAlignment::ALIGN_LEFT);
+
+ // If the text is center-aligned, only the text direction should change.
+ textfield_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ textfield_->ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection::RIGHT_TO_LEFT);
+ EXPECT_EQ(textfield_->GetTextDirection(),
+ base::i18n::TextDirection::RIGHT_TO_LEFT);
+ EXPECT_EQ(textfield_->GetHorizontalAlignment(),
+ gfx::HorizontalAlignment::ALIGN_CENTER);
+
+ // If the text is aligned to the text direction, its alignment should change
+ // iff the text direction changes. We test both scenarios.
+ auto dir = base::i18n::TextDirection::RIGHT_TO_LEFT;
+ auto opposite_dir = base::i18n::TextDirection::LEFT_TO_RIGHT;
+ EXPECT_EQ(textfield_->GetTextDirection(), dir);
+ textfield_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
+ textfield_->ChangeTextDirectionAndLayoutAlignment(opposite_dir);
+ EXPECT_EQ(textfield_->GetTextDirection(), opposite_dir);
+ EXPECT_NE(textfield_->GetHorizontalAlignment(), gfx::ALIGN_TO_HEAD);
+
+ dir = base::i18n::TextDirection::LEFT_TO_RIGHT;
+ EXPECT_EQ(textfield_->GetTextDirection(), dir);
+ textfield_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
+ textfield_->ChangeTextDirectionAndLayoutAlignment(dir);
+ EXPECT_EQ(textfield_->GetTextDirection(), dir);
+ EXPECT_EQ(textfield_->GetHorizontalAlignment(), gfx::ALIGN_TO_HEAD);
}
TEST_F(TextfieldTest, TextChangedCallbackTest) {
@@ -4154,9 +4100,53 @@ TEST_F(TextfieldTest, TextChangedCallbackTest) {
TEST_F(TextfieldTest, InsertInvalidCharsTest) {
InitTextfield();
- textfield_->InsertText(ASCIIToUTF16("\babc\ndef\t"));
+ textfield_->InsertText(
+ ASCIIToUTF16("\babc\ndef\t"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ(textfield_->GetText(), ASCIIToUTF16("abcdef"));
}
+TEST_F(TextfieldTest, ScrollCommands) {
+ InitTextfield();
+
+ // Scroll commands are only available on Mac.
+#if defined(OS_APPLE)
+ textfield_->SetText(ASCIIToUTF16("12 34567 89"));
+ textfield_->SetEditableSelectionRange(gfx::Range(6));
+
+ EXPECT_TRUE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_PAGE_UP));
+ EXPECT_TRUE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_PAGE_DOWN));
+ EXPECT_TRUE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT));
+ EXPECT_TRUE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT));
+
+ test_api_->ExecuteTextEditCommand(ui::TextEditCommand::SCROLL_PAGE_UP);
+ EXPECT_EQ(textfield_->GetCursorPosition(), 0u);
+
+ test_api_->ExecuteTextEditCommand(ui::TextEditCommand::SCROLL_PAGE_DOWN);
+ EXPECT_EQ(textfield_->GetCursorPosition(), 11u);
+
+ test_api_->ExecuteTextEditCommand(
+ ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT);
+ EXPECT_EQ(textfield_->GetCursorPosition(), 0u);
+
+ test_api_->ExecuteTextEditCommand(
+ ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT);
+ EXPECT_EQ(textfield_->GetCursorPosition(), 11u);
+#else
+ EXPECT_FALSE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_PAGE_UP));
+ EXPECT_FALSE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_PAGE_DOWN));
+ EXPECT_FALSE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_TO_BEGINNING_OF_DOCUMENT));
+ EXPECT_FALSE(textfield_->IsTextEditCommandEnabled(
+ ui::TextEditCommand::SCROLL_TO_END_OF_DOCUMENT));
+#endif
+}
+} // namespace test
} // namespace views
diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.h b/chromium/ui/views/controls/textfield/textfield_unittest.h
new file mode 100644
index 00000000000..daea3478371
--- /dev/null
+++ b/chromium/ui/views/controls/textfield/textfield_unittest.h
@@ -0,0 +1,184 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_UNITTEST_H_
+#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_UNITTEST_H_
+
+#include "ui/views/controls/textfield/textfield.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/events/event_constants.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/test/views_test_base.h"
+
+#define EXPECT_STR_EQ(ascii, utf16) EXPECT_EQ(base::ASCIIToUTF16(ascii), utf16)
+
+namespace ui {
+namespace test {
+class EventGenerator;
+}
+} // namespace ui
+
+namespace views {
+
+class TextfieldTestApi;
+
+namespace test {
+
+class MockInputMethod;
+class TestTextfield;
+
+class TextfieldTest : public ViewsTestBase, public TextfieldController {
+ public:
+ TextfieldTest();
+ ~TextfieldTest() override;
+
+ // ViewsTestBase:
+ void SetUp() override;
+ void TearDown() override;
+
+ ui::ClipboardBuffer GetAndResetCopiedToClipboard();
+ base::string16 GetClipboardText(ui::ClipboardBuffer type);
+ void SetClipboardText(ui::ClipboardBuffer type, const std::string& text);
+
+ // TextfieldController:
+ void ContentsChanged(Textfield* sender,
+ const base::string16& new_contents) override;
+ void OnBeforeUserAction(Textfield* sender) override;
+ void OnAfterUserAction(Textfield* sender) override;
+ void OnAfterCutOrCopy(ui::ClipboardBuffer clipboard_type) override;
+
+ void InitTextfield(int count = 1);
+ ui::MenuModel* GetContextMenuModel();
+
+ bool TestingNativeMac() const;
+ bool TestingNativeCrOs() const;
+
+ template <typename T>
+ T* PrepareTextfields(int count,
+ std::unique_ptr<T> textfield_owned,
+ gfx::Rect bounds) {
+ widget_ = CreateTestWidget();
+ widget_->SetBounds(bounds);
+
+ View* container = widget_->SetContentsView(std::make_unique<View>());
+ T* textfield = container->AddChildView(std::move(textfield_owned));
+
+ PrepareTextfieldsInternal(count, textfield, container, bounds);
+
+ return textfield;
+ }
+
+ protected:
+ void PrepareTextfieldsInternal(int count,
+ Textfield* textfield,
+ View* view,
+ gfx::Rect bounds);
+
+ void SendKeyPress(ui::KeyboardCode key_code, int flags);
+ void SendKeyEvent(ui::KeyboardCode key_code,
+ bool alt,
+ bool shift,
+ bool control_or_command,
+ bool caps_lock);
+ void SendKeyEvent(ui::KeyboardCode key_code,
+ bool shift,
+ bool control_or_command);
+ void SendKeyEvent(ui::KeyboardCode key_code);
+ void SendKeyEvent(base::char16 ch);
+ void SendKeyEvent(base::char16 ch, int flags);
+ void SendKeyEvent(base::char16 ch, int flags, bool from_vk);
+ void DispatchMockInputMethodKeyEvent();
+
+ // Sends a platform-specific move (and select) to the logical start of line.
+ // Eg. this should move (and select) to the right end of line for RTL text.
+ virtual void SendHomeEvent(bool shift);
+
+ // Sends a platform-specific move (and select) to the logical end of line.
+ virtual void SendEndEvent(bool shift);
+
+ // Sends {delete, move, select} word {forward, backward}.
+ void SendWordEvent(ui::KeyboardCode key, bool shift);
+
+ // Sends Shift+Delete if supported, otherwise Cmd+X again.
+ void SendAlternateCut();
+
+ // Sends Ctrl+Insert if supported, otherwise Cmd+C again.
+ void SendAlternateCopy();
+
+ // Sends Shift+Insert if supported, otherwise Cmd+V again.
+ void SendAlternatePaste();
+
+ View* GetFocusedView();
+ int GetCursorPositionX(int cursor_pos);
+ int GetCursorYForTesting();
+
+ // Get the current cursor bounds.
+ gfx::Rect GetCursorBounds();
+
+ // Get the cursor bounds of |sel|.
+ gfx::Rect GetCursorBounds(const gfx::SelectionModel& sel);
+
+ gfx::Rect GetDisplayRect();
+ gfx::Rect GetCursorViewRect();
+
+ // Mouse click on the point whose x-axis is |bound|'s x plus |x_offset| and
+ // y-axis is in the middle of |bound|'s vertical range.
+ void MouseClick(const gfx::Rect bound, int x_offset);
+
+ // This is to avoid double/triple click.
+ void NonClientMouseClick();
+
+ void VerifyTextfieldContextMenuContents(bool textfield_has_selection,
+ bool can_undo,
+ ui::MenuModel* menu);
+ void PressMouseButton(ui::EventFlags mouse_button_flags);
+ void ReleaseMouseButton(ui::EventFlags mouse_button_flags);
+ void PressLeftMouseButton();
+ void ReleaseLeftMouseButton();
+ void ClickLeftMouseButton();
+ void ClickRightMouseButton();
+ void DragMouseTo(const gfx::Point& where);
+
+ // Textfield does not listen to OnMouseMoved, so this function does not send
+ // an event when it updates the cursor position.
+ void MoveMouseTo(const gfx::Point& where);
+ void TapAtCursor(ui::EventPointerType pointer_type);
+
+ // We need widget to populate wrapper class.
+ std::unique_ptr<Widget> widget_ = nullptr;
+
+ TestTextfield* textfield_ = nullptr;
+ std::unique_ptr<TextfieldTestApi> test_api_;
+ TextfieldModel* model_ = nullptr;
+
+ // The string from Controller::ContentsChanged callback.
+ base::string16 last_contents_;
+
+ // For testing input method related behaviors.
+ MockInputMethod* input_method_ = nullptr;
+
+ // Indicates how many times OnBeforeUserAction() is called.
+ int on_before_user_action_ = 0;
+
+ // Indicates how many times OnAfterUserAction() is called.
+ int on_after_user_action_ = 0;
+
+ // Position of the mouse for synthetic mouse events.
+ gfx::Point mouse_position_;
+
+ ui::ClipboardBuffer copied_to_clipboard_ = ui::ClipboardBuffer::kMaxValue;
+ std::unique_ptr<ui::test::EventGenerator> event_generator_;
+ View* event_target_ = nullptr;
+};
+
+} // namespace test
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_UNITTEST_H_
diff --git a/chromium/ui/views/controls/theme_tracking_image_view.cc b/chromium/ui/views/controls/theme_tracking_image_view.cc
new file mode 100644
index 00000000000..18895f6609c
--- /dev/null
+++ b/chromium/ui/views/controls/theme_tracking_image_view.cc
@@ -0,0 +1,36 @@
+// Copyright 2021 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/theme_tracking_image_view.h"
+
+#include "ui/gfx/color_utils.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
+
+namespace views {
+
+ThemeTrackingImageView::ThemeTrackingImageView(
+ const gfx::ImageSkia& light_image,
+ const gfx::ImageSkia& dark_image,
+ const base::RepeatingCallback<SkColor()>& get_background_color_callback)
+ : light_image_(light_image),
+ dark_image_(dark_image),
+ get_background_color_callback_(get_background_color_callback) {
+ DCHECK_EQ(light_image.size(), dark_image.size());
+ SetImage(light_image);
+}
+
+ThemeTrackingImageView::~ThemeTrackingImageView() = default;
+
+void ThemeTrackingImageView::OnThemeChanged() {
+ ImageView::OnThemeChanged();
+ SetImage(color_utils::IsDark(get_background_color_callback_.Run())
+ ? dark_image_
+ : light_image_);
+}
+
+BEGIN_METADATA(ThemeTrackingImageView, views::ImageView)
+END_METADATA
+
+} // namespace views
diff --git a/chromium/ui/views/controls/theme_tracking_image_view.h b/chromium/ui/views/controls/theme_tracking_image_view.h
new file mode 100644
index 00000000000..7275019b0df
--- /dev/null
+++ b/chromium/ui/views/controls/theme_tracking_image_view.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_THEME_TRACKING_IMAGE_VIEW_H_
+#define UI_VIEWS_CONTROLS_THEME_TRACKING_IMAGE_VIEW_H_
+
+#include "ui/views/controls/image_view.h"
+
+namespace views {
+
+// An ImageView that displays either |light_image| or |dark_image| based on the
+// current background color returned by |get_background_color_callback|. Tracks
+// theme changes so the image is always the correct version. |light_image| and
+// |dark_image| must be of the same size. The |light_image| is set by default
+// upon construction.
+class VIEWS_EXPORT ThemeTrackingImageView : public ImageView {
+ public:
+ METADATA_HEADER(ThemeTrackingImageView);
+ ThemeTrackingImageView(
+ const gfx::ImageSkia& light_image,
+ const gfx::ImageSkia& dark_image,
+ const base::RepeatingCallback<SkColor()>& get_background_color_callback);
+ ThemeTrackingImageView(const ThemeTrackingImageView&) = delete;
+ ThemeTrackingImageView& operator=(const ThemeTrackingImageView&) = delete;
+ ~ThemeTrackingImageView() override;
+
+ // ImageView:
+ void OnThemeChanged() override;
+
+ private:
+ // The underlying light and dark mode images.
+ gfx::ImageSkia light_image_;
+ gfx::ImageSkia dark_image_;
+
+ base::RepeatingCallback<SkColor()> get_background_color_callback_;
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_THEME_TRACKING_IMAGE_VIEW_H_
diff --git a/chromium/ui/views/controls/tree/tree_view_unittest.cc b/chromium/ui/views/controls/tree/tree_view_unittest.cc
index 0173210e0d1..839e7150a69 100644
--- a/chromium/ui/views/controls/tree/tree_view_unittest.cc
+++ b/chromium/ui/views/controls/tree/tree_view_unittest.cc
@@ -942,21 +942,29 @@ TEST_F(TreeViewTest, ExpandOrSelectChild) {
TEST_F(TreeViewTest, SelectOnKeyStroke) {
tree_->SetModel(&model_);
tree_->ExpandAll(model_.GetRoot());
- selector()->InsertText(ASCIIToUTF16("b"));
+ selector()->InsertText(
+ ASCIIToUTF16("b"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ("b", GetSelectedNodeTitle());
EXPECT_EQ("b", GetSelectedAccessibilityViewName());
- selector()->InsertText(ASCIIToUTF16("1"));
+ selector()->InsertText(
+ ASCIIToUTF16("1"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ("b1", GetSelectedNodeTitle());
EXPECT_EQ("b1", GetSelectedAccessibilityViewName());
// Invoke OnViewBlur() to reset time.
selector()->OnViewBlur();
- selector()->InsertText(ASCIIToUTF16("z"));
+ selector()->InsertText(
+ ASCIIToUTF16("z"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ("b1", GetSelectedNodeTitle());
EXPECT_EQ("b1", GetSelectedAccessibilityViewName());
selector()->OnViewBlur();
- selector()->InsertText(ASCIIToUTF16("a"));
+ selector()->InsertText(
+ ASCIIToUTF16("a"),
+ ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
EXPECT_EQ("a", GetSelectedNodeTitle());
EXPECT_EQ("a", GetSelectedAccessibilityViewName());
}
@@ -1068,6 +1076,7 @@ TEST_F(TreeViewTest, OnFocusAccessibilityEvents) {
// (in this case, the root node).
ClearAccessibilityEvents();
tree_->RequestFocus();
+ EXPECT_TRUE(tree_->HasFocus());
EXPECT_EQ((AccessibilityEventsVector{std::make_pair(
GetRootAccessibilityView(), ax::mojom::Event::kFocus)}),
accessibility_events());
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 a2c0df01350..531ebe33d19 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
@@ -85,7 +85,7 @@ bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
return command_id == IDS_CONTENT_CONTEXT_EMOJI;
}
-#if !defined(OS_APPLE)
+#if !defined(OS_APPLE) && !BUILDFLAG(IS_CHROMEOS_ASH)
// static
std::unique_ptr<ViewsTextServicesContextMenu>
ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
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 2318cf80d6b..4651047e6b7 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
@@ -6,13 +6,16 @@
#define UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
#include "ui/views/controls/views_text_services_context_menu.h"
+#include "ui/views/views_export.h"
namespace views {
// 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 {
+class VIEWS_EXPORT ViewsTextServicesContextMenuBase
+ : public ViewsTextServicesContextMenu {
public:
ViewsTextServicesContextMenuBase(ui::SimpleMenuModel* menu,
Textfield* client);
@@ -31,7 +34,7 @@ class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu {
bool SupportsCommand(int command_id) const override;
protected:
-#if defined(OS_APPLE)
+#if defined(OS_APPLE) || BUILDFLAG(IS_CHROMEOS_ASH)
Textfield* client() { return client_; }
const Textfield* client() const { return client_; }
#endif
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_chromeos.cc b/chromium/ui/views/controls/views_text_services_context_menu_chromeos.cc
new file mode 100644
index 00000000000..5a2d082aeef
--- /dev/null
+++ b/chromium/ui/views/controls/views_text_services_context_menu_chromeos.cc
@@ -0,0 +1,79 @@
+// Copyright 2021 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_chromeos.h"
+
+#include <utility>
+
+#include "base/no_destructor.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
+
+namespace views {
+
+namespace {
+
+using ImplFactory = ViewsTextServicesContextMenuChromeos::ImplFactory;
+ImplFactory& GetImplFactory() {
+ static base::NoDestructor<ImplFactory> impl_factory;
+ return *impl_factory;
+}
+
+} // namespace
+
+// static
+void ViewsTextServicesContextMenuChromeos::SetImplFactory(
+ ImplFactory impl_factory) {
+ GetImplFactory() = std::move(impl_factory);
+}
+
+ViewsTextServicesContextMenuChromeos::ViewsTextServicesContextMenuChromeos(
+ ui::SimpleMenuModel* menu,
+ Textfield* client) {
+ auto& impl_factory = GetImplFactory();
+
+ // In unit tests, `impl_factory` may not be set. Use
+ // `ViewTextServicesContextMenuBase` in that case.
+ if (impl_factory)
+ impl_ = impl_factory.Run(menu, client);
+ else
+ impl_ = std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
+}
+
+ViewsTextServicesContextMenuChromeos::~ViewsTextServicesContextMenuChromeos() =
+ default;
+
+bool ViewsTextServicesContextMenuChromeos::GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) const {
+ return impl_->GetAcceleratorForCommandId(command_id, accelerator);
+}
+
+bool ViewsTextServicesContextMenuChromeos::IsCommandIdChecked(
+ int command_id) const {
+ return impl_->IsCommandIdChecked(command_id);
+}
+
+bool ViewsTextServicesContextMenuChromeos::IsCommandIdEnabled(
+ int command_id) const {
+ return impl_->IsCommandIdEnabled(command_id);
+}
+
+void ViewsTextServicesContextMenuChromeos::ExecuteCommand(int command_id,
+ int event_flags) {
+ return impl_->ExecuteCommand(command_id, event_flags);
+}
+
+bool ViewsTextServicesContextMenuChromeos::SupportsCommand(
+ int command_id) const {
+ return impl_->IsCommandIdEnabled(command_id);
+}
+
+// static
+std::unique_ptr<ViewsTextServicesContextMenu>
+ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
+ Textfield* client) {
+ return std::make_unique<ViewsTextServicesContextMenuChromeos>(menu, client);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_chromeos.h b/chromium/ui/views/controls/views_text_services_context_menu_chromeos.h
new file mode 100644
index 00000000000..4dfd7d05d66
--- /dev/null
+++ b/chromium/ui/views/controls/views_text_services_context_menu_chromeos.h
@@ -0,0 +1,51 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_CHROMEOS_H_
+#define UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_CHROMEOS_H_
+
+#include <memory>
+
+#include "ui/views/controls/views_text_services_context_menu.h"
+#include "ui/views/views_export.h"
+
+namespace views {
+
+// This class is used to add and handle text service items in the text context
+// menu under the CrOS environment.
+class VIEWS_EXPORT ViewsTextServicesContextMenuChromeos
+ : public ViewsTextServicesContextMenu {
+ public:
+ using ImplFactory = base::RepeatingCallback<std::unique_ptr<
+ ViewsTextServicesContextMenu>(ui::SimpleMenuModel*, Textfield*)>;
+
+ // Injects the method to construct `impl_`.
+ static void SetImplFactory(ImplFactory factory);
+
+ ViewsTextServicesContextMenuChromeos(ui::SimpleMenuModel* menu,
+ Textfield* client);
+ ViewsTextServicesContextMenuChromeos(
+ const ViewsTextServicesContextMenuChromeos&) = delete;
+ ViewsTextServicesContextMenuChromeos& operator=(
+ const ViewsTextServicesContextMenuChromeos&) = delete;
+ ~ViewsTextServicesContextMenuChromeos() override;
+
+ // ViewsTextServicesContextMenu:
+ bool GetAcceleratorForCommandId(int command_id,
+ ui::Accelerator* accelerator) const override;
+ 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;
+
+ private:
+ // CrOS functionality must be provided by the embedder, so requests are
+ // forwarded to this concrete object, whose construction can be controlled by
+ // `SetImplFactory()`.
+ std::unique_ptr<ViewsTextServicesContextMenu> impl_;
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_CHROMEOS_H_
diff --git a/chromium/ui/views/controls/webview/OWNERS b/chromium/ui/views/controls/webview/OWNERS
index d2e56866d7a..1356a8b6333 100644
--- a/chromium/ui/views/controls/webview/OWNERS
+++ b/chromium/ui/views/controls/webview/OWNERS
@@ -2,5 +2,4 @@ sadrul@chromium.org
sky@chromium.org
# For "EmbedFullscreenWidgetMode" changes. (Component: UI>Browser>TabCapture)
-miu@chromium.org
mfoltz@chromium.org
diff --git a/chromium/ui/views/controls/webview/web_contents_set_background_color.cc b/chromium/ui/views/controls/webview/web_contents_set_background_color.cc
index 2a9ff959e44..0cf575a7876 100644
--- a/chromium/ui/views/controls/webview/web_contents_set_background_color.cc
+++ b/chromium/ui/views/controls/webview/web_contents_set_background_color.cc
@@ -5,7 +5,7 @@
#include "ui/views/controls/webview/web_contents_set_background_color.h"
#include "base/memory/ptr_util.h"
-#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
@@ -33,24 +33,13 @@ WebContentsSetBackgroundColor::WebContentsSetBackgroundColor(
WebContentsSetBackgroundColor::~WebContentsSetBackgroundColor() = default;
-void WebContentsSetBackgroundColor::RenderViewReady() {
- web_contents()
- ->GetMainFrame()
- ->GetRenderViewHost()
- ->GetWidget()
- ->GetView()
- ->SetBackgroundColor(color_);
-}
-
-void WebContentsSetBackgroundColor::RenderViewCreated(
- content::RenderViewHost* render_view_host) {
- render_view_host->GetWidget()->GetView()->SetBackgroundColor(color_);
-}
-
-void WebContentsSetBackgroundColor::RenderViewHostChanged(
- content::RenderViewHost* old_host,
- content::RenderViewHost* new_host) {
- new_host->GetWidget()->GetView()->SetBackgroundColor(color_);
+void WebContentsSetBackgroundColor::RenderFrameCreated(
+ content::RenderFrameHost* render_frame_host) {
+ // We set the background color just on the main frame's widget. Other frames
+ // that are local roots would have a widget of their own, but their background
+ // colors are part of, and controlled by, the webpage.
+ if (!render_frame_host->GetParent())
+ render_frame_host->GetView()->SetBackgroundColor(color_);
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsSetBackgroundColor)
diff --git a/chromium/ui/views/controls/webview/web_contents_set_background_color.h b/chromium/ui/views/controls/webview/web_contents_set_background_color.h
index 9bac23b4e8a..140ffa41f24 100644
--- a/chromium/ui/views/controls/webview/web_contents_set_background_color.h
+++ b/chromium/ui/views/controls/webview/web_contents_set_background_color.h
@@ -32,10 +32,7 @@ class WebContentsSetBackgroundColor
SkColor color);
// content::WebContentsObserver:
- void RenderViewReady() override;
- void RenderViewCreated(content::RenderViewHost* render_view_host) override;
- void RenderViewHostChanged(content::RenderViewHost* old_host,
- content::RenderViewHost* new_host) override;
+ void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
SkColor color_;
diff --git a/chromium/ui/views/controls/webview/web_dialog_view.cc b/chromium/ui/views/controls/webview/web_dialog_view.cc
index 39a58363e71..d5730f11198 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view.cc
+++ b/chromium/ui/views/controls/webview/web_dialog_view.cc
@@ -20,6 +20,7 @@
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/fill_layout.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
@@ -69,6 +70,9 @@ void ObservableWebView::ResetDelegate() {
delegate_ = nullptr;
}
+BEGIN_METADATA(ObservableWebView, WebView)
+END_METADATA
+
////////////////////////////////////////////////////////////////////////////////
// WebDialogView, public:
@@ -473,4 +477,8 @@ void WebDialogView::InitDialog() {
web_view_->LoadInitialURL(GetDialogContentURL());
}
+BEGIN_METADATA(WebDialogView, ClientView)
+ADD_READONLY_PROPERTY_METADATA(ObservableWebView*, WebView);
+END_METADATA
+
} // namespace views
diff --git a/chromium/ui/views/controls/webview/web_dialog_view.h b/chromium/ui/views/controls/webview/web_dialog_view.h
index 97142bc886e..d4cb57f0f04 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view.h
+++ b/chromium/ui/views/controls/webview/web_dialog_view.h
@@ -17,6 +17,7 @@
#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/controls/webview/webview_export.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/client_view.h"
#include "ui/web_dialogs/web_dialog_delegate.h"
@@ -33,8 +34,12 @@ namespace views {
// A kind of webview that can notify its delegate when its content is ready.
class ObservableWebView : public WebView {
public:
+ METADATA_HEADER(ObservableWebView);
+
ObservableWebView(content::BrowserContext* browser_context,
ui::WebDialogDelegate* delegate);
+ ObservableWebView(const ObservableWebView&) = delete;
+ ObservableWebView& operator=(const ObservableWebView&) = delete;
~ObservableWebView() override;
// content::WebContentsObserver
@@ -51,8 +56,6 @@ class ObservableWebView : public WebView {
private:
ui::WebDialogDelegate* delegate_;
-
- DISALLOW_COPY_AND_ASSIGN(ObservableWebView);
};
////////////////////////////////////////////////////////////////////////////////
@@ -73,12 +76,16 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
public ui::WebDialogDelegate,
public WidgetDelegate {
public:
+ METADATA_HEADER(WebDialogView);
+
// |handler| must not be nullptr.
// |use_dialog_frame| indicates whether to use dialog frame view for non
// client frame view.
WebDialogView(content::BrowserContext* context,
ui::WebDialogDelegate* delegate,
std::unique_ptr<WebContentsHandler> handler);
+ WebDialogView(const WebDialogView&) = delete;
+ WebDialogView& operator=(const WebDialogView&) = delete;
~WebDialogView() override;
content::WebContents* web_contents();
@@ -170,6 +177,9 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
// Initializes the contents of the dialog.
void InitDialog();
+ // Accessor used by metadata only.
+ ObservableWebView* GetWebView() const { return web_view_; }
+
// This view is a delegate to the HTML content since it needs to get notified
// about when the dialog is closing. For all other actions (besides dialog
// closing) we delegate to the creator of this view, which we keep track of
@@ -205,8 +215,6 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
bool disable_url_load_for_test_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(WebDialogView);
};
} // namespace views
diff --git a/chromium/ui/views/controls/webview/web_dialog_view_unittest.cc b/chromium/ui/views/controls/webview/web_dialog_view_unittest.cc
index 004f97daf2a..352ffe730c3 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view_unittest.cc
+++ b/chromium/ui/views/controls/webview/web_dialog_view_unittest.cc
@@ -19,6 +19,7 @@
#include "content/test/test_web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/web_dialogs/test/test_web_contents_handler.h"
@@ -184,4 +185,8 @@ TEST_F(WebDialogViewUnitTest, ObservableWebViewOnWebDialogViewClosed) {
web_view()->ResourceLoadComplete(rfh, request_id, resource_load_info);
}
+TEST_F(WebDialogViewUnitTest, MetadataTest) {
+ test::TestViewMetadata(web_dialog_view());
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc
index 44a8fc6ef9d..2ade47b0077 100644
--- a/chromium/ui/views/controls/webview/webview.cc
+++ b/chromium/ui/views/controls/webview/webview.cc
@@ -13,7 +13,6 @@
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_process_host.h"
-#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "ipc/ipc_message.h"
@@ -90,13 +89,15 @@ void WebView::SetWebContents(content::WebContents* replacement) {
DetachWebContentsNativeView();
WebContentsObserver::Observe(replacement);
// web_contents() now returns |replacement| from here onwards.
- UpdateCrashedOverlayView();
if (wc_owner_.get() != replacement)
wc_owner_.reset();
AttachWebContentsNativeView();
- NotifyAccessibilityWebContentsChanged();
- MaybeEnableAutoResize();
+ if (replacement && replacement->GetMainFrame()->IsRenderFrameCreated()) {
+ SetUpNewMainFrame(replacement->GetMainFrame());
+ } else {
+ LostMainFrame();
+ }
}
content::BrowserContext* WebView::GetBrowserContext() {
@@ -124,7 +125,8 @@ void WebView::EnableSizingFromWebContents(const gfx::Size& min_size,
DCHECK(!max_size.IsEmpty());
min_size_ = min_size;
max_size_ = max_size;
- MaybeEnableAutoResize();
+ if (web_contents() && web_contents()->GetMainFrame()->IsRenderFrameCreated())
+ MaybeEnableAutoResize(web_contents()->GetMainFrame());
}
void WebView::SetCrashedOverlayView(View* crashed_overlay_view) {
@@ -298,31 +300,37 @@ void WebView::OnAXModeAdded(ui::AXMode mode) {
////////////////////////////////////////////////////////////////////////////////
// WebView, content::WebContentsObserver implementation:
-void WebView::RenderViewCreated(content::RenderViewHost* render_view_host) {
- MaybeEnableAutoResize();
-}
-
-void WebView::RenderViewReady() {
- UpdateCrashedOverlayView();
- NotifyAccessibilityWebContentsChanged();
-}
+void WebView::RenderFrameCreated(content::RenderFrameHost* render_frame_host) {
+ // Only handle the initial main frame, not speculative ones.
+ if (render_frame_host != web_contents()->GetMainFrame())
+ return;
-void WebView::RenderViewDeleted(content::RenderViewHost* render_view_host) {
- UpdateCrashedOverlayView();
- NotifyAccessibilityWebContentsChanged();
+ SetUpNewMainFrame(render_frame_host);
}
-void WebView::RenderViewHostChanged(content::RenderViewHost* old_host,
- content::RenderViewHost* new_host) {
- MaybeEnableAutoResize();
+void WebView::RenderFrameDeleted(content::RenderFrameHost* render_frame_host) {
+ // Only handle the active main frame, not speculative ones.
+ if (render_frame_host != web_contents()->GetMainFrame())
+ return;
- if (HasFocus())
- OnFocus();
- NotifyAccessibilityWebContentsChanged();
+ LostMainFrame();
}
-void WebView::WebContentsDestroyed() {
- NotifyAccessibilityWebContentsChanged();
+void WebView::RenderFrameHostChanged(content::RenderFrameHost* old_host,
+ content::RenderFrameHost* new_host) {
+ // Since we skipped speculative main frames in RenderFrameCreated, we must
+ // watch for them being swapped in by watching for RenderFrameHostChanged().
+ if (new_host != web_contents()->GetMainFrame())
+ return;
+ // Ignore the initial main frame host, as there's no renderer frame for it
+ // yet. If the DCHECK fires, then we would need to handle the initial main
+ // frame when it its renderer frame is created.
+ if (!old_host) {
+ DCHECK(!new_host->IsRenderFrameCreated());
+ return;
+ }
+
+ SetUpNewMainFrame(new_host);
}
void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen,
@@ -338,11 +346,6 @@ void WebView::OnWebContentsFocused(
RequestFocus();
}
-void WebView::RenderProcessGone(base::TerminationStatus status) {
- UpdateCrashedOverlayView();
- NotifyAccessibilityWebContentsChanged();
-}
-
void WebView::AXTreeIDForMainFrameHasChanged() {
NotifyAccessibilityWebContentsChanged();
}
@@ -425,15 +428,23 @@ std::unique_ptr<content::WebContents> WebView::CreateWebContents(
return contents;
}
-void WebView::MaybeEnableAutoResize() {
- if (max_size_.IsEmpty() || !web_contents() ||
- !web_contents()->GetRenderWidgetHostView()) {
- return;
- }
+void WebView::SetUpNewMainFrame(content::RenderFrameHost* frame_host) {
+ MaybeEnableAutoResize(frame_host);
+ UpdateCrashedOverlayView();
+ NotifyAccessibilityWebContentsChanged();
+ if (HasFocus())
+ OnFocus();
+}
+
+void WebView::LostMainFrame() {
+ UpdateCrashedOverlayView();
+ NotifyAccessibilityWebContentsChanged();
+}
- content::RenderWidgetHostView* render_widget_host_view =
- web_contents()->GetRenderWidgetHostView();
- render_widget_host_view->EnableAutoResize(min_size_, max_size_);
+void WebView::MaybeEnableAutoResize(content::RenderFrameHost* frame_host) {
+ DCHECK(frame_host->IsRenderFrameCreated());
+ if (!max_size_.IsEmpty())
+ frame_host->GetView()->EnableAutoResize(min_size_, max_size_);
}
BEGIN_METADATA(WebView, View)
diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h
index 9604515c2d5..94e25679eab 100644
--- a/chromium/ui/views/controls/webview/webview.h
+++ b/chromium/ui/views/controls/webview/webview.h
@@ -138,12 +138,10 @@ class WEBVIEW_EXPORT WebView : public View,
void AddedToWidget() override;
// Overridden from content::WebContentsObserver:
- void RenderViewCreated(content::RenderViewHost* render_view_host) override;
- void RenderViewReady() override;
- void RenderViewDeleted(content::RenderViewHost* render_view_host) override;
- void RenderViewHostChanged(content::RenderViewHost* old_host,
- content::RenderViewHost* new_host) override;
- void WebContentsDestroyed() override;
+ void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
+ void RenderFrameHostChanged(content::RenderFrameHost* old_host,
+ content::RenderFrameHost* new_host) override;
void DidToggleFullscreenModeForTab(bool entered_fullscreen,
bool will_cause_resize) override;
// Workaround for MSVC++ linker bug/feature that requires
@@ -153,7 +151,6 @@ class WEBVIEW_EXPORT WebView : public View,
void OnBadMessageReceived(const IPC::Message& message) override {}
void OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) override;
- void RenderProcessGone(base::TerminationStatus status) override;
void AXTreeIDForMainFrameHasChanged() override;
// Override from ui::AXModeObserver
@@ -167,10 +164,16 @@ class WEBVIEW_EXPORT WebView : public View,
void UpdateCrashedOverlayView();
void NotifyAccessibilityWebContentsChanged();
- // Registers for ResizeDueToAutoResize() notifications from the
+ // Called when the main frame in the renderer becomes present.
+ void SetUpNewMainFrame(content::RenderFrameHost* frame_host);
+ // Called when the main frame in the renderer is no longer present.
+ void LostMainFrame();
+
+ // Registers for ResizeDueToAutoResize() notifications from `frame_host`'s
// RenderWidgetHostView whenever it is created or changes, if
- // EnableSizingFromWebContents() has been called.
- void MaybeEnableAutoResize();
+ // EnableSizingFromWebContents() has been called. This should only be called
+ // for main frames; other frames can not have auto resize set.
+ void MaybeEnableAutoResize(content::RenderFrameHost* frame_host);
// Create a regular or test web contents (based on whether we're running
// in a unit test or not).
diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc
index d407fb1dd0e..39005facc44 100644
--- a/chromium/ui/views/controls/webview/webview_unittest.cc
+++ b/chromium/ui/views/controls/webview/webview_unittest.cc
@@ -191,6 +191,11 @@ class WebViewUnitTest : public views::test::WidgetTest {
content::WebContents::CreateParams(browser_context_.get()));
}
+ std::unique_ptr<content::WebContents> CreateTestWebContents() const {
+ return content::WebContentsTester::CreateTestWebContents(
+ browser_context_.get(), /*site_instnace=*/nullptr);
+ }
+
private:
std::unique_ptr<content::RenderViewHostTestEnabler> rvh_enabler_;
std::unique_ptr<content::TestBrowserContext> browser_context_;
@@ -321,7 +326,11 @@ TEST_F(WebViewUnitTest, DetachedWebViewDestructor) {
// Test that the specified crashed overlay view is shown when a WebContents
// is in a crashed state.
TEST_F(WebViewUnitTest, CrashedOverlayView) {
- const std::unique_ptr<content::WebContents> web_contents(CreateWebContents());
+ const std::unique_ptr<content::WebContents> web_contents =
+ CreateTestWebContents();
+ content::WebContentsTester* tester =
+ content::WebContentsTester::For(web_contents.get());
+
std::unique_ptr<WebView> web_view(
new WebView(web_contents->GetBrowserContext()));
View* contents_view = top_level_widget()->GetContentsView();
@@ -335,18 +344,21 @@ TEST_F(WebViewUnitTest, CrashedOverlayView) {
// Normally when a renderer crashes, the WebView will learn about it
// automatically via WebContentsObserver. Since this is a test
// WebContents, simulate that by calling SetIsCrashed and then
- // explicitly calling RenderViewDeleted on the WebView to trigger it
+ // explicitly calling RenderFrameDeleted on the WebView to trigger it
// to swap in the crashed overlay view.
- web_contents->SetIsCrashed(base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
+ tester->SetIsCrashed(base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
EXPECT_TRUE(web_contents->IsCrashed());
static_cast<content::WebContentsObserver*>(web_view.get())
- ->RenderViewDeleted(nullptr);
+ ->RenderFrameDeleted(web_contents->GetMainFrame());
EXPECT_TRUE(crashed_overlay_view->IsDrawn());
}
// Test that a crashed overlay view isn't deleted if it's owned by client.
TEST_F(WebViewUnitTest, CrashedOverlayViewOwnedbyClient) {
- const std::unique_ptr<content::WebContents> web_contents(CreateWebContents());
+ const std::unique_ptr<content::WebContents> web_contents =
+ CreateTestWebContents();
+ content::WebContentsTester* tester =
+ content::WebContentsTester::For(web_contents.get());
std::unique_ptr<WebView> web_view(
new WebView(web_contents->GetBrowserContext()));
View* contents_view = top_level_widget()->GetContentsView();
@@ -359,10 +371,10 @@ TEST_F(WebViewUnitTest, CrashedOverlayViewOwnedbyClient) {
EXPECT_FALSE(crashed_overlay_view->IsDrawn());
// Simulate a renderer crash (see above).
- web_contents->SetIsCrashed(base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
+ tester->SetIsCrashed(base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
EXPECT_TRUE(web_contents->IsCrashed());
static_cast<content::WebContentsObserver*>(web_view.get())
- ->RenderViewDeleted(nullptr);
+ ->RenderFrameDeleted(web_contents->GetMainFrame());
EXPECT_TRUE(crashed_overlay_view->IsDrawn());
web_view->SetCrashedOverlayView(nullptr);