summaryrefslogtreecommitdiff
path: root/chromium/ui/views/controls
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-29 10:46:47 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-11-02 12:02:10 +0000
commit99677208ff3b216fdfec551fbe548da5520cd6fb (patch)
tree476a4865c10320249360e859d8fdd3e01833b03a /chromium/ui/views/controls
parentc30a6232df03e1efbd9f3b226777b07e087a1122 (diff)
downloadqtwebengine-chromium-99677208ff3b216fdfec551fbe548da5520cd6fb.tar.gz
BASELINE: Update Chromium to 86.0.4240.124
Change-Id: Ide0ff151e94cd665ae6521a446995d34a9d1d644 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/views/controls')
-rw-r--r--chromium/ui/views/controls/base_control_test_widget.cc40
-rw-r--r--chromium/ui/views/controls/base_control_test_widget.h41
-rw-r--r--chromium/ui/views/controls/button/button.cc100
-rw-r--r--chromium/ui/views/controls/button/button.h46
-rw-r--r--chromium/ui/views/controls/button/button_controller.cc20
-rw-r--r--chromium/ui/views/controls/button/button_controller.h1
-rw-r--r--chromium/ui/views/controls/button/button_observer.h28
-rw-r--r--chromium/ui/views/controls/button/button_unittest.cc249
-rw-r--r--chromium/ui/views/controls/button/checkbox.cc36
-rw-r--r--chromium/ui/views/controls/button/checkbox.h6
-rw-r--r--chromium/ui/views/controls/button/checkbox_unittest.cc3
-rw-r--r--chromium/ui/views/controls/button/image_button.cc22
-rw-r--r--chromium/ui/views/controls/button/image_button.h13
-rw-r--r--chromium/ui/views/controls/button/image_button_unittest.cc12
-rw-r--r--chromium/ui/views/controls/button/label_button.cc193
-rw-r--r--chromium/ui/views/controls/button/label_button.h48
-rw-r--r--chromium/ui/views/controls/button/label_button_label.cc4
-rw-r--r--chromium/ui/views/controls/button/label_button_label.h4
-rw-r--r--chromium/ui/views/controls/button/label_button_label_unittest.cc2
-rw-r--r--chromium/ui/views/controls/button/label_button_unittest.cc43
-rw-r--r--chromium/ui/views/controls/button/md_text_button.cc107
-rw-r--r--chromium/ui/views/controls/button/md_text_button.h14
-rw-r--r--chromium/ui/views/controls/button/md_text_button_unittest.cc57
-rw-r--r--chromium/ui/views/controls/button/menu_button.cc4
-rw-r--r--chromium/ui/views/controls/button/menu_button.h6
-rw-r--r--chromium/ui/views/controls/button/menu_button_controller.cc51
-rw-r--r--chromium/ui/views/controls/button/menu_button_controller.h7
-rw-r--r--chromium/ui/views/controls/button/menu_button_unittest.cc67
-rw-r--r--chromium/ui/views/controls/button/radio_button.h3
-rw-r--r--chromium/ui/views/controls/button/toggle_button.cc5
-rw-r--r--chromium/ui/views/controls/button/toggle_button.h3
-rw-r--r--chromium/ui/views/controls/button/toggle_button_unittest.cc3
-rw-r--r--chromium/ui/views/controls/combobox/combobox.cc55
-rw-r--r--chromium/ui/views/controls/combobox/combobox.h17
-rw-r--r--chromium/ui/views/controls/combobox/combobox_unittest.cc129
-rw-r--r--chromium/ui/views/controls/combobox/empty_combobox_model.cc30
-rw-r--r--chromium/ui/views/controls/combobox/empty_combobox_model.h30
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.cc28
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.h35
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox_listener.h28
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc114
-rw-r--r--chromium/ui/views/controls/focusable_border.cc1
-rw-r--r--chromium/ui/views/controls/highlight_path_generator.cc17
-rw-r--r--chromium/ui/views/controls/highlight_path_generator.h27
-rw-r--r--chromium/ui/views/controls/image_view.cc1
-rw-r--r--chromium/ui/views/controls/image_view_unittest.cc19
-rw-r--r--chromium/ui/views/controls/label.cc106
-rw-r--r--chromium/ui/views/controls/label.h22
-rw-r--r--chromium/ui/views/controls/label_unittest.cc104
-rw-r--r--chromium/ui/views/controls/link.cc2
-rw-r--r--chromium/ui/views/controls/link.h2
-rw-r--r--chromium/ui/views/controls/link_unittest.cc82
-rw-r--r--chromium/ui/views/controls/menu/menu_config.h24
-rw-r--r--chromium/ui/views/controls/menu/menu_config_mac.mm4
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.cc87
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.h11
-rw-r--r--chromium/ui/views/controls/menu/menu_controller_unittest.cc226
-rw-r--r--chromium/ui/views/controls/menu/menu_host.cc21
-rw-r--r--chromium/ui/views/controls/menu/menu_host.h2
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.cc84
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.h13
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl.cc30
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h4
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm170
-rw-r--r--chromium/ui/views/controls/menu/menu_runner_unittest.cc13
-rw-r--r--chromium/ui/views/controls/menu/menu_separator.cc13
-rw-r--r--chromium/ui/views/controls/menu/menu_separator.h10
-rw-r--r--chromium/ui/views/controls/menu/menu_separator_unittest.cc35
-rw-r--r--chromium/ui/views/controls/menu/submenu_view.cc9
-rw-r--r--chromium/ui/views/controls/menu/submenu_view_unittest.cc6
-rw-r--r--chromium/ui/views/controls/menu/test_menu_item_view.cc2
-rw-r--r--chromium/ui/views/controls/native/native_view_host_mac_unittest.mm13
-rw-r--r--chromium/ui/views/controls/prefix_selector.cc18
-rw-r--r--chromium/ui/views/controls/prefix_selector.h5
-rw-r--r--chromium/ui/views/controls/resize_area_unittest.cc6
-rw-r--r--chromium/ui/views/controls/scroll_view.cc69
-rw-r--r--chromium/ui/views/controls/scroll_view.h2
-rw-r--r--chromium/ui/views/controls/scroll_view_unittest.cc12
-rw-r--r--chromium/ui/views/controls/scrollbar/base_scroll_bar_button.cc6
-rw-r--r--chromium/ui/views/controls/scrollbar/base_scroll_bar_button.h7
-rw-r--r--chromium/ui/views/controls/scrollbar/base_scroll_bar_button_unittest.cc134
-rw-r--r--chromium/ui/views/controls/scrollbar/scroll_bar.cc12
-rw-r--r--chromium/ui/views/controls/scrollbar/scroll_bar.h3
-rw-r--r--chromium/ui/views/controls/scrollbar/scroll_bar_views.cc4
-rw-r--r--chromium/ui/views/controls/scrollbar/scrollbar_unittest.cc94
-rw-r--r--chromium/ui/views/controls/slider.cc6
-rw-r--r--chromium/ui/views/controls/slider.h2
-rw-r--r--chromium/ui/views/controls/slider_unittest.cc24
-rw-r--r--chromium/ui/views/controls/styled_label.cc47
-rw-r--r--chromium/ui/views/controls/styled_label.h17
-rw-r--r--chromium/ui/views/controls/styled_label_unittest.cc12
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc8
-rw-r--r--chromium/ui/views/controls/table/table_view.cc10
-rw-r--r--chromium/ui/views/controls/table/table_view_unittest.cc4
-rw-r--r--chromium/ui/views/controls/textfield/textfield.cc160
-rw-r--r--chromium/ui/views/controls/textfield/textfield.h36
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model.cc49
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model.h25
-rw-r--r--chromium/ui/views/controls/textfield/textfield_model_unittest.cc64
-rw-r--r--chromium/ui/views/controls/textfield/textfield_test_api.cc8
-rw-r--r--chromium/ui/views/controls/textfield/textfield_test_api.h3
-rw-r--r--chromium/ui/views/controls/textfield/textfield_unittest.cc391
-rw-r--r--chromium/ui/views/controls/tree/tree_view.cc4
-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.cc4
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.h2
-rw-r--r--chromium/ui/views/controls/webview/BUILD.gn8
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view.cc42
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view.h11
-rw-r--r--chromium/ui/views/controls/webview/web_dialog_view_unittest.cc2
-rw-r--r--chromium/ui/views/controls/webview/webview.cc33
-rw-r--r--chromium/ui/views/controls/webview/webview.h17
-rw-r--r--chromium/ui/views/controls/webview/webview_unittest.cc19
113 files changed, 2979 insertions, 1282 deletions
diff --git a/chromium/ui/views/controls/base_control_test_widget.cc b/chromium/ui/views/controls/base_control_test_widget.cc
new file mode 100644
index 00000000000..edad02693bd
--- /dev/null
+++ b/chromium/ui/views/controls/base_control_test_widget.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/base_control_test_widget.h"
+
+#include <memory>
+#include <utility>
+
+namespace views {
+
+namespace test {
+
+BaseControlTestWidget::BaseControlTestWidget() = default;
+BaseControlTestWidget::~BaseControlTestWidget() = default;
+
+void BaseControlTestWidget::SetUp() {
+ ViewsTestBase::SetUp();
+
+ widget_ = std::make_unique<Widget>();
+ Widget::InitParams params =
+ CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.bounds = gfx::Rect(200, 200);
+ widget_->Init(std::move(params));
+ auto* container = widget_->SetContentsView(std::make_unique<View>());
+
+ CreateWidgetContent(container);
+
+ widget_->Show();
+}
+
+void BaseControlTestWidget::TearDown() {
+ widget_.reset();
+ ViewsTestBase::TearDown();
+}
+
+void BaseControlTestWidget::CreateWidgetContent(View* container) {}
+
+} // namespace test
+} // namespace views
diff --git a/chromium/ui/views/controls/base_control_test_widget.h b/chromium/ui/views/controls/base_control_test_widget.h
new file mode 100644
index 00000000000..4a8686ca7e2
--- /dev/null
+++ b/chromium/ui/views/controls/base_control_test_widget.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_BASE_CONTROL_TEST_WIDGET_H_
+#define UI_VIEWS_CONTROLS_BASE_CONTROL_TEST_WIDGET_H_
+
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/unique_widget_ptr.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_utils.h"
+
+namespace views {
+
+namespace test {
+
+class BaseControlTestWidget : public ViewsTestBase {
+ public:
+ BaseControlTestWidget();
+ BaseControlTestWidget(const BaseControlTestWidget&) = delete;
+ BaseControlTestWidget& operator=(const BaseControlTestWidget&) = delete;
+ ~BaseControlTestWidget() override;
+
+ // ViewsTestBase:
+ void SetUp() override;
+ void TearDown() override;
+
+ protected:
+ virtual void CreateWidgetContent(View* container);
+
+ Widget* widget() { return widget_.get(); }
+
+ private:
+ UniqueWidgetPtr widget_;
+};
+
+} // namespace test
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_BASE_CONTROL_TEST_WIDGET_H_
diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc
index 5bb670ce30a..f930117e231 100644
--- a/chromium/ui/views/controls/button/button.cc
+++ b/chromium/ui/views/controls/button/button.cc
@@ -20,7 +20,6 @@
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/controls/button/button_controller_delegate.h"
-#include "ui/views/controls/button/button_observer.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
@@ -30,7 +29,6 @@
#include "ui/views/controls/focus_ring.h"
#include "ui/views/painter.h"
#include "ui/views/style/platform_style.h"
-#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/aura/client/capture_client.h"
@@ -45,33 +43,6 @@ DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsButtonProperty, false)
} // namespace
-////////////////////////////////////////////////////////////////////////////////
-// WidgetObserverButtonBridge:
-Button::WidgetObserverButtonBridge::WidgetObserverButtonBridge(Button* button)
- : owner_(button) {
- DCHECK(button->GetWidget());
- button->GetWidget()->AddObserver(this);
-}
-
-Button::WidgetObserverButtonBridge::~WidgetObserverButtonBridge() {
- if (owner_)
- owner_->GetWidget()->RemoveObserver(this);
- CHECK(!IsInObserverList());
-}
-
-void Button::WidgetObserverButtonBridge::OnWidgetPaintAsActiveChanged(
- Widget* widget,
- bool paint_as_active) {
- owner_->WidgetPaintAsActiveChanged(widget, paint_as_active);
-}
-
-void Button::WidgetObserverButtonBridge::OnWidgetDestroying(Widget* widget) {
- widget->RemoveObserver(this);
- owner_ = nullptr;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// ButtonControllerDelegate:
Button::DefaultButtonControllerDelegate::DefaultButtonControllerDelegate(
Button* button)
: ButtonControllerDelegate(button) {}
@@ -120,8 +91,6 @@ bool Button::DefaultButtonControllerDelegate::InDrag() {
return button()->InDrag();
}
-////////////////////////////////////////////////////////////////////////////////
-
// static
constexpr Button::ButtonState Button::kButtonStates[STATE_COUNT];
@@ -154,13 +123,10 @@ Button::ButtonState Button::GetButtonStateFrom(ui::NativeTheme::State state) {
return Button::STATE_NORMAL;
}
-////////////////////////////////////////////////////////////////////////////////
-// Button, public:
-
Button::~Button() = default;
void Button::SetFocusForPlatform() {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// On Mac, buttons are focusable only in full keyboard access mode.
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
#else
@@ -174,6 +140,7 @@ void Button::SetTooltipText(const base::string16& tooltip_text) {
tooltip_text_ = tooltip_text;
OnSetTooltipText(tooltip_text);
TooltipTextChanged();
+ NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
}
void Button::SetAccessibleName(const base::string16& name) {
@@ -185,6 +152,10 @@ const base::string16& Button::GetAccessibleName() const {
return accessible_name_.empty() ? tooltip_text_ : accessible_name_;
}
+Button::ButtonState Button::GetState() const {
+ return state_;
+}
+
void Button::SetState(ButtonState state) {
if (state == state_)
return;
@@ -211,15 +182,7 @@ void Button::SetState(ButtonState state) {
ButtonState old_state = state_;
state_ = state;
StateChanged(old_state);
- SchedulePaint();
-}
-
-Button::ButtonState Button::GetVisualState() const {
- if (PlatformStyle::kInactiveWidgetControlsAppearDisabled && GetWidget() &&
- !GetWidget()->ShouldPaintAsActive()) {
- return STATE_DISABLED;
- }
- return state();
+ OnPropertyChanged(&state_, kPropertyEffectsPaint);
}
void Button::StartThrobbing(int cycles_til_stop) {
@@ -275,22 +238,21 @@ void Button::SetHighlighted(bool bubble_visible) {
AnimateInkDrop(bubble_visible ? views::InkDropState::ACTIVATED
: views::InkDropState::DEACTIVATED,
nullptr);
- for (ButtonObserver& observer : button_observers_)
- observer.OnHighlightChanged(this, bubble_visible);
}
-void Button::AddButtonObserver(ButtonObserver* observer) {
- button_observers_.AddObserver(observer);
-}
-
-void Button::RemoveButtonObserver(ButtonObserver* observer) {
- button_observers_.RemoveObserver(observer);
+PropertyChangedSubscription Button::AddStateChangedCallback(
+ PropertyChangedCallback callback) {
+ return AddPropertyChangedCallback(&state_, std::move(callback));
}
Button::KeyClickAction Button::GetKeyClickActionForEvent(
const ui::KeyEvent& event) {
if (event.key_code() == ui::VKEY_SPACE)
return PlatformStyle::kKeyClickActionOnSpace;
+ // Note that default buttons also have VKEY_RETURN installed as an accelerator
+ // in LabelButton::SetIsDefault(). On platforms where
+ // PlatformStyle::kReturnClicksFocusedControl, the logic here will take
+ // precedence over that.
if (event.key_code() == ui::VKEY_RETURN &&
PlatformStyle::kReturnClicksFocusedControl)
return KeyClickAction::kOnKeyPress;
@@ -328,9 +290,6 @@ gfx::Point Button::GetMenuPosition() const {
return menu_position;
}
-////////////////////////////////////////////////////////////////////////////////
-// Button, View overrides:
-
bool Button::OnMousePressed(const ui::MouseEvent& event) {
return button_controller_->OnMousePressed(event);
}
@@ -507,15 +466,6 @@ void Button::OnBlur() {
SchedulePaint();
}
-void Button::AddedToWidget() {
- if (PlatformStyle::kInactiveWidgetControlsAppearDisabled)
- widget_observer_ = std::make_unique<WidgetObserverButtonBridge>(this);
-}
-
-void Button::RemovedFromWidget() {
- widget_observer_.reset();
-}
-
std::unique_ptr<InkDrop> Button::CreateInkDrop() {
std::unique_ptr<InkDrop> ink_drop = InkDropHostView::CreateInkDrop();
ink_drop->SetShowHighlightOnFocus(!focus_ring_);
@@ -526,16 +476,10 @@ SkColor Button::GetInkDropBaseColor() const {
return ink_drop_base_color_;
}
-////////////////////////////////////////////////////////////////////////////////
-// Button, gfx::AnimationDelegate implementation:
-
void Button::AnimationProgressed(const gfx::Animation* animation) {
SchedulePaint();
}
-////////////////////////////////////////////////////////////////////////////////
-// Button, protected:
-
Button::Button(ButtonListener* listener)
: AnimationDelegateViews(this),
listener_(listener),
@@ -579,11 +523,7 @@ void Button::OnClickCanceled(const ui::Event& event) {
void Button::OnSetTooltipText(const base::string16& tooltip_text) {}
-void Button::StateChanged(ButtonState old_state) {
- button_controller_->OnStateChanged(old_state);
- for (ButtonObserver& observer : button_observers_)
- observer.OnStateChanged(this, old_state);
-}
+void Button::StateChanged(ButtonState old_state) {}
bool Button::IsTriggerableEvent(const ui::Event& event) {
return button_controller_->IsTriggerableEvent(event);
@@ -637,12 +577,16 @@ void Button::OnEnabledChanged() {
}
}
-void Button::WidgetPaintAsActiveChanged(Widget* widget, bool paint_as_active) {
- StateChanged(state());
-}
+DEFINE_ENUM_CONVERTERS(
+ Button::ButtonState,
+ {Button::STATE_NORMAL, base::ASCIIToUTF16("STATE_NORMAL")},
+ {Button::STATE_HOVERED, base::ASCIIToUTF16("STATE_HOVERED")},
+ {Button::STATE_PRESSED, base::ASCIIToUTF16("STATE_PRESSED")},
+ {Button::STATE_DISABLED, base::ASCIIToUTF16("STATE_DISABLED")})
BEGIN_METADATA(Button)
METADATA_PARENT_CLASS(InkDropHostView)
+ADD_PROPERTY_METADATA(Button, ButtonState, State)
END_METADATA()
} // namespace views
diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h
index 6411eb3514b..1095469f066 100644
--- a/chromium/ui/views/controls/button/button.h
+++ b/chromium/ui/views/controls/button/button.h
@@ -19,7 +19,6 @@
#include "ui/views/controls/button/button_controller_delegate.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/painter.h"
-#include "ui/views/widget/widget_observer.h"
namespace views {
namespace test {
@@ -28,7 +27,6 @@ class ButtonTestApi;
class Button;
class ButtonController;
-class ButtonObserver;
class Event;
// An interface implemented by an object to let it know that a button was
@@ -107,19 +105,18 @@ class VIEWS_EXPORT Button : public InkDropHostView,
int tag() const { return tag_; }
void set_tag(int tag) { tag_ = tag; }
+ void set_listener(ButtonListener* listener) { listener_ = listener; }
+
void SetAccessibleName(const base::string16& name);
const base::string16& GetAccessibleName() const;
// Get/sets the current display state of the button.
- ButtonState state() const { return state_; }
+ ButtonState GetState() const;
// Clients passing in STATE_DISABLED should consider calling
// SetEnabled(false) instead because the enabled flag can affect other things
// like event dispatching, focus traversals, etc. Calling SetEnabled(false)
// will also set the state of |this| to STATE_DISABLED.
void SetState(ButtonState state);
- // Returns the visual appearance state of the button. This takes into account
- // both the button's display state and the state of the containing widget.
- ButtonState GetVisualState() const;
// Starts throbbing. See HoverAnimation for a description of cycles_til_stop.
// This method does nothing if |animate_on_state_change_| is false.
@@ -141,7 +138,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
void set_request_focus_on_press(bool value) {
// On Mac, buttons should not request focus on a mouse press. Hence keep the
// default value i.e. false.
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
request_focus_on_press_ = value;
#endif
}
@@ -180,8 +177,8 @@ class VIEWS_EXPORT Button : public InkDropHostView,
// Highlights the ink drop for the button.
void SetHighlighted(bool bubble_visible);
- void AddButtonObserver(ButtonObserver* observer);
- void RemoveButtonObserver(ButtonObserver* observer);
+ PropertyChangedSubscription AddStateChangedCallback(
+ PropertyChangedCallback callback);
// Overridden from View:
bool OnMousePressed(const ui::MouseEvent& event) override;
@@ -209,8 +206,6 @@ class VIEWS_EXPORT Button : public InkDropHostView,
const ViewHierarchyChangedDetails& details) override;
void OnFocus() override;
void OnBlur() override;
- void AddedToWidget() override;
- void RemovedFromWidget() override;
// Overridden from InkDropHostView:
std::unique_ptr<InkDrop> CreateInkDrop() override;
@@ -237,7 +232,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
// Construct the Button with a Listener. The listener can be null. This can be
// true of buttons that don't have a listener - e.g. menubuttons where there's
// no default action and checkboxes.
- explicit Button(ButtonListener* listener);
+ explicit Button(ButtonListener* listener = nullptr);
// Called when the button has been clicked or tapped and should request focus
// if necessary.
@@ -301,31 +296,8 @@ class VIEWS_EXPORT Button : public InkDropHostView,
friend class test::ButtonTestApi;
FRIEND_TEST_ALL_PREFIXES(BlueButtonTest, Border);
- // Bridge class to allow Button to observe a Widget without being a
- // WidgetObserver. This is desirable because many Button subclasses are
- // themselves WidgetObservers, and if Button is a WidgetObserver, any change
- // to its WidgetObserver overrides requires updating all the subclasses as
- // well.
- class WidgetObserverButtonBridge : public WidgetObserver {
- public:
- explicit WidgetObserverButtonBridge(Button* owner);
- ~WidgetObserverButtonBridge() override;
-
- // WidgetObserver:
- void OnWidgetPaintAsActiveChanged(Widget* widget,
- bool paint_as_active) override;
- void OnWidgetDestroying(Widget* widget) override;
-
- private:
- Button* owner_;
-
- DISALLOW_COPY_AND_ASSIGN(WidgetObserverButtonBridge);
- };
-
void OnEnabledChanged();
- void WidgetPaintAsActiveChanged(Widget* widget, bool active);
-
// The text shown in a tooltip.
base::string16 tooltip_text_;
@@ -372,8 +344,6 @@ class VIEWS_EXPORT Button : public InkDropHostView,
std::unique_ptr<Painter> focus_painter_;
- std::unique_ptr<WidgetObserverButtonBridge> widget_observer_;
-
// ButtonController is responsible for handling events sent to the Button and
// related state changes from the events.
// TODO(cyan): Make sure all state changes are handled within
@@ -384,8 +354,6 @@ class VIEWS_EXPORT Button : public InkDropHostView,
AddEnabledChangedCallback(base::BindRepeating(&Button::OnEnabledChanged,
base::Unretained(this)))};
- base::ObserverList<ButtonObserver> button_observers_;
-
DISALLOW_COPY_AND_ASSIGN(Button);
};
diff --git a/chromium/ui/views/controls/button/button_controller.cc b/chromium/ui/views/controls/button/button_controller.cc
index f639cf93c55..4e90ed44342 100644
--- a/chromium/ui/views/controls/button/button_controller.cc
+++ b/chromium/ui/views/controls/button/button_controller.cc
@@ -21,9 +21,9 @@ ButtonController::ButtonController(
ButtonController::~ButtonController() = default;
bool ButtonController::OnMousePressed(const ui::MouseEvent& event) {
- if (button_->state() == Button::STATE_DISABLED)
+ if (button_->GetState() == Button::STATE_DISABLED)
return true;
- if (button_->state() != Button::STATE_PRESSED &&
+ if (button_->GetState() != Button::STATE_PRESSED &&
button_controller_delegate_->ShouldEnterPushedState(event) &&
button_->HitTestPoint(event.location())) {
button_->SetState(Button::STATE_PRESSED);
@@ -40,7 +40,7 @@ bool ButtonController::OnMousePressed(const ui::MouseEvent& event) {
}
void ButtonController::OnMouseReleased(const ui::MouseEvent& event) {
- if (button_->state() != Button::STATE_DISABLED) {
+ if (button_->GetState() != Button::STATE_DISABLED) {
if (!button_->HitTestPoint(event.location())) {
button_->SetState(Button::STATE_NORMAL);
} else {
@@ -59,7 +59,7 @@ void ButtonController::OnMouseReleased(const ui::MouseEvent& event) {
}
void ButtonController::OnMouseMoved(const ui::MouseEvent& event) {
- if (button_->state() != Button::STATE_DISABLED) {
+ if (button_->GetState() != Button::STATE_DISABLED) {
button_->SetState(button_->HitTestPoint(event.location())
? Button::STATE_HOVERED
: Button::STATE_NORMAL);
@@ -67,19 +67,19 @@ void ButtonController::OnMouseMoved(const ui::MouseEvent& event) {
}
void ButtonController::OnMouseEntered(const ui::MouseEvent& event) {
- if (button_->state() != Button::STATE_DISABLED)
+ if (button_->GetState() != Button::STATE_DISABLED)
button_->SetState(Button::STATE_HOVERED);
}
void ButtonController::OnMouseExited(const ui::MouseEvent& event) {
// Starting a drag results in a MouseExited, we need to ignore it.
- if (button_->state() != Button::STATE_DISABLED &&
+ if (button_->GetState() != Button::STATE_DISABLED &&
!button_controller_delegate_->InDrag())
button_->SetState(Button::STATE_NORMAL);
}
bool ButtonController::OnKeyPressed(const ui::KeyEvent& event) {
- if (button_->state() == Button::STATE_DISABLED)
+ if (button_->GetState() == Button::STATE_DISABLED)
return false;
switch (button_->GetKeyClickActionForEvent(event)) {
@@ -104,7 +104,7 @@ bool ButtonController::OnKeyPressed(const ui::KeyEvent& event) {
}
bool ButtonController::OnKeyReleased(const ui::KeyEvent& event) {
- const bool click_button = button_->state() == Button::STATE_PRESSED &&
+ const bool click_button = button_->GetState() == Button::STATE_PRESSED &&
button_->GetKeyClickActionForEvent(event) ==
Button::KeyClickAction::kOnKeyRelease;
if (!click_button)
@@ -116,7 +116,7 @@ bool ButtonController::OnKeyReleased(const ui::KeyEvent& event) {
}
void ButtonController::OnGestureEvent(ui::GestureEvent* event) {
- if (button_->state() == Button::STATE_DISABLED)
+ if (button_->GetState() == Button::STATE_DISABLED)
return;
if (event->type() == ui::ET_GESTURE_TAP &&
@@ -139,8 +139,6 @@ void ButtonController::OnGestureEvent(ui::GestureEvent* event) {
void ButtonController::UpdateAccessibleNodeData(ui::AXNodeData* node_data) {}
-void ButtonController::OnStateChanged(Button::ButtonState old_state) {}
-
bool ButtonController::IsTriggerableEvent(const ui::Event& event) {
return event.type() == ui::ET_GESTURE_TAP_DOWN ||
event.type() == ui::ET_GESTURE_TAP ||
diff --git a/chromium/ui/views/controls/button/button_controller.h b/chromium/ui/views/controls/button/button_controller.h
index db509f674e2..042bb513ea6 100644
--- a/chromium/ui/views/controls/button/button_controller.h
+++ b/chromium/ui/views/controls/button/button_controller.h
@@ -50,7 +50,6 @@ class VIEWS_EXPORT ButtonController {
virtual void UpdateAccessibleNodeData(ui::AXNodeData* node_data);
// Methods that parallel respective methods in Button:
- virtual void OnStateChanged(Button::ButtonState old_state);
virtual bool IsTriggerableEvent(const ui::Event& event);
protected:
diff --git a/chromium/ui/views/controls/button/button_observer.h b/chromium/ui/views/controls/button/button_observer.h
deleted file mode 100644
index 56efa5d3cff..00000000000
--- a/chromium/ui/views/controls/button/button_observer.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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_BUTTON_BUTTON_OBSERVER_H_
-#define UI_VIEWS_CONTROLS_BUTTON_BUTTON_OBSERVER_H_
-
-#include "base/observer_list_types.h"
-#include "ui/views/controls/button/button.h"
-#include "ui/views/views_export.h"
-
-namespace views {
-
-class VIEWS_EXPORT ButtonObserver : public base::CheckedObserver {
- public:
- virtual void OnHighlightChanged(views::Button* observed_button,
- bool highlighted) {}
-
- virtual void OnStateChanged(views::Button* observed_button,
- views::Button::ButtonState old_state) {}
-
- protected:
- ~ButtonObserver() override = default;
-};
-
-} // namespace views
-
-#endif // UI_VIEWS_CONTROLS_BUTTON_BUTTON_OBSERVER_H_
diff --git a/chromium/ui/views/controls/button/button_unittest.cc b/chromium/ui/views/controls/button/button_unittest.cc
index 5850b215f25..d57aa9a2d27 100644
--- a/chromium/ui/views/controls/button/button_unittest.cc
+++ b/chromium/ui/views/controls/button/button_unittest.cc
@@ -5,6 +5,7 @@
#include "ui/views/controls/button/button.h"
#include <memory>
+#include <string>
#include <utility>
#include "base/bind.h"
@@ -14,6 +15,8 @@
#include "base/run_loop.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
#include "ui/base/layout.h"
#include "ui/display/screen.h"
#include "ui/events/event_utils.h"
@@ -25,7 +28,6 @@
#include "ui/views/animation/test/test_ink_drop_host.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/button/button_controller.h"
-#include "ui/views/controls/button/button_observer.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
@@ -35,6 +37,7 @@
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/style/platform_style.h"
+#include "ui/views/test/test_ax_event_observer.h"
#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"
@@ -104,6 +107,7 @@ class TestButton : public Button, public ButtonListener {
bool canceled() { return canceled_; }
int ink_drop_layer_add_count() { return ink_drop_layer_add_count_; }
int ink_drop_layer_remove_count() { return ink_drop_layer_remove_count_; }
+ ButtonListener* listener() const { return listener_; }
void set_custom_key_click_action(KeyClickAction custom_key_click_action) {
custom_key_click_action_ = custom_key_click_action;
@@ -136,45 +140,57 @@ class TestButton : public Button, public ButtonListener {
DISALLOW_COPY_AND_ASSIGN(TestButton);
};
-class TestButtonObserver : public ButtonObserver {
+class TestButtonObserver {
public:
- TestButtonObserver() = default;
- ~TestButtonObserver() override = default;
-
- void OnHighlightChanged(views::Button* observed_button,
- bool highlighted) override {
- observed_button_ = observed_button;
- highlighted_ = highlighted;
- }
-
- void OnStateChanged(views::Button* observed_button,
- views::Button::ButtonState old_state) override {
- observed_button_ = observed_button;
- state_changed_ = true;
+ explicit TestButtonObserver(Button* button) {
+ highlighted_changed_subscription_ =
+ button->AddHighlightedChangedCallback(base::BindRepeating(
+ [](TestButtonObserver* obs) { obs->highlighted_changed_ = true; },
+ base::Unretained(this)));
+ state_changed_subscription_ =
+ button->AddStateChangedCallback(base::BindRepeating(
+ [](TestButtonObserver* obs) { obs->state_changed_ = true; },
+ base::Unretained(this)));
}
+ ~TestButtonObserver() = default;
void Reset() {
- observed_button_ = nullptr;
- highlighted_ = false;
+ highlighted_changed_ = false;
state_changed_ = false;
}
- views::Button* observed_button() { return observed_button_; }
- bool highlighted() const { return highlighted_; }
+ bool highlighted_changed() const { return highlighted_changed_; }
bool state_changed() const { return state_changed_; }
private:
- views::Button* observed_button_ = nullptr;
- bool highlighted_ = false;
+ bool highlighted_changed_ = false;
bool state_changed_ = false;
- private:
+ PropertyChangedSubscription highlighted_changed_subscription_;
+ PropertyChangedSubscription state_changed_subscription_;
+
DISALLOW_COPY_AND_ASSIGN(TestButtonObserver);
};
+class TestButtonListener : public ButtonListener {
+ public:
+ void ButtonPressed(Button* sender, const ui::Event& event) override {
+ pressed_ = true;
+ sender_ = sender;
+ }
+
+ bool pressed() const { return pressed_; }
+ Button* sender() const { return sender_; }
+
+ private:
+ bool pressed_ = false;
+ Button* sender_ = nullptr;
+};
+
TestInkDrop* AddTestInkDrop(TestButton* button) {
auto owned_ink_drop = std::make_unique<TestInkDrop>();
TestInkDrop* ink_drop = owned_ink_drop.get();
+ button->SetInkDropMode(InkDropHostView::InkDropMode::ON);
InkDropHostViewTestApi(button).SetInkDrop(std::move(owned_ink_drop));
return ink_drop;
}
@@ -207,10 +223,6 @@ class ButtonTest : public ViewsTestBase {
}
void TearDown() override {
- if (button_observer_)
- button_->RemoveButtonObserver(button_observer_.get());
-
- button_observer_.reset();
widget_.reset();
ViewsTestBase::TearDown();
@@ -222,16 +234,10 @@ class ButtonTest : public ViewsTestBase {
return AddTestInkDrop(button_);
}
- void CreateButtonWithRealInkDrop() {
- button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
- InkDropHostViewTestApi(button_).SetInkDrop(
- std::make_unique<InkDropImpl>(button_, button_->size()));
- }
-
void CreateButtonWithObserver() {
button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
- button_observer_ = std::make_unique<TestButtonObserver>();
- button_->AddButtonObserver(button_observer_.get());
+ button_->SetInkDropMode(InkDropHostView::InkDropMode::ON);
+ button_observer_ = std::make_unique<TestButtonObserver>(button_);
}
protected:
@@ -248,7 +254,6 @@ class ButtonTest : public ViewsTestBase {
TestButton* button_;
std::unique_ptr<TestButtonObserver> button_observer_;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
-
DISALLOW_COPY_AND_ASSIGN(ButtonTest);
};
@@ -261,22 +266,22 @@ TEST_F(ButtonTest, MetadataTest) {
TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
event_generator()->ReleaseLeftButton();
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button()->SetEnabled(false);
- EXPECT_EQ(Button::STATE_DISABLED, button()->state());
+ EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
button()->SetEnabled(true);
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button()->SetVisible(false);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(true);
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
#if defined(USE_AURA)
{
@@ -291,22 +296,22 @@ TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
second_widget.GetNativeWindow()->SetCapture();
button()->SetEnabled(false);
- EXPECT_EQ(Button::STATE_DISABLED, button()->state());
+ EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
button()->SetEnabled(true);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(false);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(true);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
#endif
// Disabling cursor events occurs for touch events and the Ash magnifier. There
// is no touch on desktop Mac. Tracked in http://crbug.com/445520.
-#if !defined(OS_MACOSX) || defined(USE_AURA)
+#if !defined(OS_APPLE) || defined(USE_AURA)
aura::test::TestCursorClient cursor_client(GetRootWindow(widget()));
// In Aura views, no new hover effects are invoked if mouse events
@@ -314,17 +319,17 @@ TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
cursor_client.DisableMouseEvents();
button()->SetEnabled(false);
- EXPECT_EQ(Button::STATE_DISABLED, button()->state());
+ EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
button()->SetEnabled(true);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(false);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(true);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
-#endif // !defined(OS_MACOSX) || defined(USE_AURA)
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
+#endif // !defined(OS_APPLE) || defined(USE_AURA)
}
// Tests that the hover state is preserved during a view hierarchy update of a
@@ -332,11 +337,11 @@ TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
TEST_F(ButtonTest, HoverStatePreservedOnDescendantViewHierarchyChange) {
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
Label* child = new Label(base::string16());
button()->AddChildView(child);
delete child;
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
}
// Tests the different types of NotifyActions.
@@ -347,13 +352,13 @@ TEST_F(ButtonTest, NotifyAction) {
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_FALSE(button()->pressed());
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
EXPECT_TRUE(button()->pressed());
// Set the notify action to its listener on mouse press.
@@ -363,7 +368,7 @@ TEST_F(ButtonTest, NotifyAction) {
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_TRUE(button()->pressed());
// The button should no longer notify on mouse release.
@@ -371,7 +376,7 @@ TEST_F(ButtonTest, NotifyAction) {
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
EXPECT_FALSE(button()->pressed());
}
@@ -410,7 +415,7 @@ TEST_F(ButtonTest, NotifyActionNoClick) {
}
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
-#if !defined(OS_MACOSX) || defined(USE_AURA)
+#if !defined(OS_APPLE) || defined(USE_AURA)
namespace {
@@ -426,16 +431,16 @@ void PerformGesture(Button* button, ui::EventType event_type) {
TEST_F(ButtonTest, GestureEventsSetState) {
aura::test::TestCursorClient cursor_client(GetRootWindow(widget()));
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
PerformGesture(button(), ui::ET_GESTURE_TAP_DOWN);
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
PerformGesture(button(), ui::ET_GESTURE_SHOW_PRESS);
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
PerformGesture(button(), ui::ET_GESTURE_TAP_CANCEL);
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
// Tests that if the button was disabled in its button press handler, gesture
@@ -445,12 +450,12 @@ TEST_F(ButtonTest, GestureEventsRespectDisabledState) {
button()->set_on_button_pressed_handler(base::BindRepeating(
[](TestButton* button) { button->SetEnabled(false); }, button()));
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
event_generator()->GestureTapAt(button()->GetBoundsInScreen().CenterPoint());
- EXPECT_EQ(Button::STATE_DISABLED, button()->state());
+ EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
}
-#endif // !defined(OS_MACOSX) || defined(USE_AURA)
+#endif // !defined(OS_APPLE) || defined(USE_AURA)
// Ensure subclasses of Button are correctly recognized as Button.
TEST_F(ButtonTest, AsButton) {
@@ -459,7 +464,7 @@ TEST_F(ButtonTest, AsButton) {
LabelButton label_button(nullptr, text);
EXPECT_TRUE(Button::AsButton(&label_button));
- ImageButton image_button(nullptr);
+ ImageButton image_button;
EXPECT_TRUE(Button::AsButton(&image_button));
Checkbox checkbox(text);
@@ -468,16 +473,16 @@ TEST_F(ButtonTest, AsButton) {
RadioButton radio_button(text, 0);
EXPECT_TRUE(Button::AsButton(&radio_button));
- MenuButton menu_button(text, nullptr);
+ MenuButton menu_button(nullptr, text);
EXPECT_TRUE(Button::AsButton(&menu_button));
- ToggleButton toggle_button(nullptr);
+ ToggleButton toggle_button;
EXPECT_TRUE(Button::AsButton(&toggle_button));
Label label;
EXPECT_FALSE(Button::AsButton(&label));
- Link link(text);
+ Link link;
EXPECT_FALSE(Button::AsButton(&link));
Textfield textfield;
@@ -508,13 +513,13 @@ TEST_F(ButtonTest, CaptureLossHidesInkDrop) {
event_generator()->PressLeftButton();
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
- EXPECT_EQ(Button::ButtonState::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::ButtonState::STATE_PRESSED, button()->GetState());
SetDraggedView(button());
widget()->SetCapture(button());
widget()->ReleaseCapture();
SetDraggedView(nullptr);
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
- EXPECT_EQ(Button::ButtonState::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::ButtonState::STATE_NORMAL, button()->GetState());
}
TEST_F(ButtonTest, HideInkDropWhenShowingContextMenu) {
@@ -748,6 +753,28 @@ TEST_F(ButtonTest, InkDropStaysHiddenWhileDragging) {
SetDraggedView(nullptr);
}
+// Ensure ButtonListener is dynamically settable.
+TEST_F(ButtonTest, SetListener) {
+ gfx::Point center(10, 10);
+ ButtonListener* old_listener = button()->listener();
+ auto listener = std::make_unique<TestButtonListener>();
+
+ button()->set_listener(listener.get());
+ EXPECT_EQ(listener.get(), button()->listener());
+
+ button()->OnMousePressed(ui::MouseEvent(
+ ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+ // Default button controller notifies listener at mouse release.
+ button()->OnMouseReleased(ui::MouseEvent(
+ ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+ EXPECT_TRUE(listener->pressed());
+ EXPECT_EQ(button(), listener->sender());
+
+ button()->set_listener(old_listener);
+}
+
// VisibilityTestButton tests to see if an ink drop or a layer has been added to
// the button at any point during the visibility state changes of its Widget.
class VisibilityTestButton : public TestButton {
@@ -806,23 +833,23 @@ TEST_F(ButtonTest, ActionOnSpace) {
ui::KeyEvent space_press(ui::ET_KEY_PRESSED, ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_TRUE(button()->OnKeyPressed(space_press));
-#if defined(OS_MACOSX)
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+#if defined(OS_APPLE)
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
#else
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_FALSE(button()->pressed());
#endif
ui::KeyEvent space_release(ui::ET_KEY_RELEASED, ui::VKEY_SPACE, ui::EF_NONE);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_FALSE(button()->OnKeyReleased(space_release));
#else
EXPECT_TRUE(button()->OnKeyReleased(space_release));
#endif
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
}
@@ -837,13 +864,13 @@ TEST_F(ButtonTest, ActionOnReturn) {
ui::KeyEvent return_press(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_FALSE(button()->OnKeyPressed(return_press));
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_FALSE(button()->pressed());
#else
EXPECT_TRUE(button()->OnKeyPressed(return_press));
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
#endif
@@ -864,7 +891,7 @@ TEST_F(ButtonTest, CustomActionOnKeyPressedEvent) {
ui::KeyEvent control_press(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL, ui::EF_NONE);
EXPECT_TRUE(button()->OnKeyPressed(control_press));
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
ui::KeyEvent control_release(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL,
@@ -872,57 +899,79 @@ TEST_F(ButtonTest, CustomActionOnKeyPressedEvent) {
EXPECT_FALSE(button()->OnKeyReleased(control_release));
}
-// Verifies that ButtonObserver is notified when the button activition highlight
-// state is changed. Also verifies the |observed_button| and |highlighted|
-// passed to observer are correct.
+// Verifies that button activation highlight state changes trigger property
+// change callbacks.
TEST_F(ButtonTest, ChangingHighlightStateNotifiesListener) {
CreateButtonWithObserver();
- EXPECT_FALSE(button_observer()->highlighted());
+ EXPECT_FALSE(button_observer()->highlighted_changed());
+ EXPECT_FALSE(button()->GetHighlighted());
button()->SetHighlighted(/*bubble_visible=*/true);
- EXPECT_EQ(button_observer()->observed_button(), button());
- EXPECT_TRUE(button_observer()->highlighted());
+ EXPECT_TRUE(button_observer()->highlighted_changed());
+ EXPECT_TRUE(button()->GetHighlighted());
+
+ button_observer()->Reset();
+ EXPECT_FALSE(button_observer()->highlighted_changed());
+ EXPECT_TRUE(button()->GetHighlighted());
button()->SetHighlighted(/*bubble_visible=*/false);
- EXPECT_EQ(button_observer()->observed_button(), button());
- EXPECT_FALSE(button_observer()->highlighted());
+ EXPECT_TRUE(button_observer()->highlighted_changed());
+ EXPECT_FALSE(button()->GetHighlighted());
}
-// Verifies that ButtonObserver is notified when the button state is changed,
-// and that the |observed_button| is passed to observer correctly.
+// Verifies that button state changes trigger property change callbacks.
TEST_F(ButtonTest, ClickingButtonNotifiesObserverOfStateChanges) {
CreateButtonWithObserver();
+ EXPECT_FALSE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
- EXPECT_EQ(button_observer()->observed_button(), button());
EXPECT_TRUE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
button_observer()->Reset();
- EXPECT_EQ(button_observer()->observed_button(), nullptr);
EXPECT_FALSE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
event_generator()->ReleaseLeftButton();
- EXPECT_EQ(button_observer()->observed_button(), button());
EXPECT_TRUE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
}
-// Verifies the ButtonObserver is notified whenever Button::SetState() is
-// called directly.
+// Verifies that direct calls to Button::SetState() trigger property change
+// callbacks.
TEST_F(ButtonTest, SetStateNotifiesObserver) {
CreateButtonWithObserver();
+ EXPECT_FALSE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
- button()->SetState(Button::ButtonState::STATE_HOVERED);
- EXPECT_EQ(button_observer()->observed_button(), button());
+ button()->SetState(Button::STATE_HOVERED);
EXPECT_TRUE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button_observer()->Reset();
- EXPECT_EQ(button_observer()->observed_button(), nullptr);
EXPECT_FALSE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
- button()->SetState(Button::ButtonState::STATE_NORMAL);
- EXPECT_EQ(button_observer()->observed_button(), button());
+ button()->SetState(Button::STATE_NORMAL);
EXPECT_TRUE(button_observer()->state_changed());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
+}
+
+// Verifies setting the tooltip text will call NotifyAccessibilityEvent.
+TEST_F(ButtonTest, SetTooltipTextNotifiesAccessibilityEvent) {
+ base::string16 test_tooltip_text = base::ASCIIToUTF16("Test Tooltip Text");
+ test::TestAXEventObserver observer;
+ EXPECT_EQ(0, observer.text_changed_event_count());
+ button()->SetTooltipText(test_tooltip_text);
+ EXPECT_EQ(1, observer.text_changed_event_count());
+ EXPECT_EQ(test_tooltip_text, button()->GetTooltipText(gfx::Point()));
+ ui::AXNodeData data;
+ button()->GetAccessibleNodeData(&data);
+ const std::string& name =
+ data.GetStringAttribute(ax::mojom::StringAttribute::kName);
+ EXPECT_EQ(test_tooltip_text, base::ASCIIToUTF16(name));
}
} // namespace views
diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc
index 06050cc2479..7281bfb9bf3 100644
--- a/chromium/ui/views/controls/button/checkbox.cc
+++ b/chromium/ui/views/controls/button/checkbox.cc
@@ -132,6 +132,24 @@ void Checkbox::GetAccessibleNodeData(ui::AXNodeData* node_data) {
}
}
+gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
+ int icon_state = 0;
+ if (GetChecked())
+ icon_state |= IconState::CHECKED;
+ if (for_state != STATE_DISABLED)
+ icon_state |= IconState::ENABLED;
+ return gfx::CreateVectorIcon(GetVectorIcon(), 16,
+ GetIconImageColor(icon_state));
+}
+
+std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const {
+ std::unique_ptr<LabelButtonBorder> border =
+ LabelButton::CreateDefaultBorder();
+ border->set_insets(
+ LayoutProvider::Get()->GetInsetsMetric(INSETS_CHECKBOX_RADIO_BUTTON));
+ return border;
+}
+
void Checkbox::OnThemeChanged() {
LabelButton::OnThemeChanged();
UpdateImage();
@@ -156,24 +174,6 @@ SkColor Checkbox::GetInkDropBaseColor() const {
return GetIconImageColor(IconState::ENABLED);
}
-gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const {
- int icon_state = 0;
- if (GetChecked())
- icon_state |= IconState::CHECKED;
- if (for_state != STATE_DISABLED)
- icon_state |= IconState::ENABLED;
- return gfx::CreateVectorIcon(GetVectorIcon(), 16,
- GetIconImageColor(icon_state));
-}
-
-std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const {
- std::unique_ptr<LabelButtonBorder> border =
- LabelButton::CreateDefaultBorder();
- border->set_insets(
- LayoutProvider::Get()->GetInsetsMetric(INSETS_CHECKBOX_RADIO_BUTTON));
- return border;
-}
-
SkPath Checkbox::GetFocusRingPath() const {
SkPath path;
gfx::Rect bounds = image()->GetMirroredBounds();
diff --git a/chromium/ui/views/controls/button/checkbox.h b/chromium/ui/views/controls/button/checkbox.h
index 2b8e2a28372..44b7daa6e3a 100644
--- a/chromium/ui/views/controls/button/checkbox.h
+++ b/chromium/ui/views/controls/button/checkbox.h
@@ -28,7 +28,7 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
METADATA_HEADER(Checkbox);
// |force_md| forces MD even when --secondary-ui-md flag is not set.
- explicit Checkbox(const base::string16& label,
+ explicit Checkbox(const base::string16& label = base::string16(),
ButtonListener* listener = nullptr);
~Checkbox() override;
@@ -50,6 +50,8 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
// LabelButton:
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+ gfx::ImageSkia GetImage(ButtonState for_state) const override;
+ std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override;
protected:
// Bitmask constants for GetIconImageColor.
@@ -60,8 +62,6 @@ class VIEWS_EXPORT Checkbox : public LabelButton {
std::unique_ptr<InkDrop> CreateInkDrop() override;
std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
SkColor GetInkDropBaseColor() const override;
- gfx::ImageSkia GetImage(ButtonState for_state) const override;
- std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override;
// Returns the path to draw the focus ring around for this Checkbox.
virtual SkPath GetFocusRingPath() const;
diff --git a/chromium/ui/views/controls/button/checkbox_unittest.cc b/chromium/ui/views/controls/button/checkbox_unittest.cc
index a468b3d7212..bb554d2ec77 100644
--- a/chromium/ui/views/controls/button/checkbox_unittest.cc
+++ b/chromium/ui/views/controls/button/checkbox_unittest.cc
@@ -32,8 +32,7 @@ class CheckboxTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- checkbox_ =
- widget_->SetContentsView(std::make_unique<Checkbox>(base::string16()));
+ checkbox_ = widget_->SetContentsView(std::make_unique<Checkbox>());
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/button/image_button.cc b/chromium/ui/views/controls/button/image_button.cc
index e026cd2fd95..5e3087cd429 100644
--- a/chromium/ui/views/controls/button/image_button.cc
+++ b/chromium/ui/views/controls/button/image_button.cc
@@ -172,7 +172,7 @@ gfx::ImageSkia ImageButton::GetImageToPaint() {
images_[STATE_NORMAL], images_[STATE_HOVERED],
hover_animation().GetCurrentValue());
} else {
- img = images_[state()];
+ img = images_[GetState()];
}
return !img.isNull() ? img : images_[STATE_NORMAL];
@@ -233,7 +233,7 @@ void ToggleImageButton::SetToggledImage(ButtonState image_state,
const gfx::ImageSkia* image) {
if (toggled_) {
images_[image_state] = image ? *image : gfx::ImageSkia();
- if (state() == image_state)
+ if (GetState() == image_state)
SchedulePaint();
} else {
alternate_images_[image_state] = image ? *image : gfx::ImageSkia();
@@ -244,6 +244,10 @@ void ToggleImageButton::SetToggledTooltipText(const base::string16& tooltip) {
toggled_tooltip_text_ = tooltip;
}
+void ToggleImageButton::SetToggledAccessibleName(const base::string16& name) {
+ toggled_accessible_name_ = name;
+}
+
////////////////////////////////////////////////////////////////////////////////
// ToggleImageButton, ImageButton overrides:
@@ -260,7 +264,7 @@ void ToggleImageButton::SetImage(ButtonState image_state,
alternate_images_[image_state] = image;
} else {
images_[image_state] = image;
- if (state() == image_state)
+ if (GetState() == image_state)
SchedulePaint();
}
PreferredSizeChanged();
@@ -277,7 +281,13 @@ base::string16 ToggleImageButton::GetTooltipText(const gfx::Point& p) const {
void ToggleImageButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
ImageButton::GetAccessibleNodeData(node_data);
- node_data->SetName(GetTooltipText(gfx::Point()));
+ if (!toggled_)
+ return;
+
+ if (!toggled_accessible_name_.empty())
+ node_data->SetName(toggled_accessible_name_);
+ else if (!toggled_tooltip_text_.empty())
+ node_data->SetName(toggled_tooltip_text_);
// Use the visual pressed image as a cue for making this control into an
// accessible toggle button.
@@ -289,10 +299,6 @@ void ToggleImageButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
}
}
-bool ToggleImageButton::toggled_for_testing() const {
- return toggled_;
-}
-
DEFINE_ENUM_CONVERTERS(ImageButton::HorizontalAlignment,
{ImageButton::HorizontalAlignment::ALIGN_LEFT,
base::ASCIIToUTF16("ALIGN_LEFT")},
diff --git a/chromium/ui/views/controls/button/image_button.h b/chromium/ui/views/controls/button/image_button.h
index 81ad539d661..0f19b6c84f6 100644
--- a/chromium/ui/views/controls/button/image_button.h
+++ b/chromium/ui/views/controls/button/image_button.h
@@ -29,7 +29,7 @@ class VIEWS_EXPORT ImageButton : public Button {
// An enum describing the vertical alignment of images on Buttons.
enum VerticalAlignment { ALIGN_TOP = 0, ALIGN_MIDDLE, ALIGN_BOTTOM };
- explicit ImageButton(ButtonListener* listener);
+ explicit ImageButton(ButtonListener* listener = nullptr);
~ImageButton() override;
// Returns the image for a given |state|.
@@ -122,6 +122,8 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
explicit ToggleImageButton(ButtonListener* listener);
~ToggleImageButton() override;
+ bool toggled() const { return toggled_; }
+
// Change the toggled state.
void SetToggled(bool toggled);
@@ -133,6 +135,9 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
// Set the tooltip text displayed when the button is toggled.
void SetToggledTooltipText(const base::string16& tooltip);
+ // Set the accessible text used when the button is toggled.
+ void SetToggledAccessibleName(const base::string16& name);
+
// Overridden from ImageButton:
const gfx::ImageSkia& GetImage(ButtonState state) const override;
void SetImage(ButtonState state, const gfx::ImageSkia& image) override;
@@ -141,8 +146,6 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
base::string16 GetTooltipText(const gfx::Point& p) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
- bool toggled_for_testing() const;
-
private:
// The parent class's images_ member is used for the current images,
// and this array is used to hold the alternative images.
@@ -156,6 +159,10 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton {
// this one is shown when toggled.
base::string16 toggled_tooltip_text_;
+ // The parent class's accessibility data is used when not toggled, and this
+ // one is used when toggled.
+ base::string16 toggled_accessible_name_;
+
DISALLOW_COPY_AND_ASSIGN(ToggleImageButton);
};
diff --git a/chromium/ui/views/controls/button/image_button_unittest.cc b/chromium/ui/views/controls/button/image_button_unittest.cc
index 9b372f638a6..e1872623d63 100644
--- a/chromium/ui/views/controls/button/image_button_unittest.cc
+++ b/chromium/ui/views/controls/button/image_button_unittest.cc
@@ -40,7 +40,7 @@ namespace views {
using ImageButtonTest = ViewsTestBase;
TEST_F(ImageButtonTest, Basics) {
- ImageButton button(nullptr);
+ ImageButton button;
// Our image to paint starts empty.
EXPECT_TRUE(button.GetImageToPaint().isNull());
@@ -88,7 +88,7 @@ TEST_F(ImageButtonTest, Basics) {
}
TEST_F(ImageButtonTest, SetAndGetImage) {
- ImageButton button(nullptr);
+ ImageButton button;
// Images start as null.
EXPECT_TRUE(button.GetImage(Button::STATE_NORMAL).isNull());
@@ -114,7 +114,7 @@ TEST_F(ImageButtonTest, SetAndGetImage) {
}
TEST_F(ImageButtonTest, ImagePositionWithBorder) {
- ImageButton button(nullptr);
+ ImageButton button;
gfx::ImageSkia image = CreateTestImage(20, 30);
button.SetImage(Button::STATE_NORMAL, &image);
@@ -143,7 +143,7 @@ TEST_F(ImageButtonTest, ImagePositionWithBorder) {
}
TEST_F(ImageButtonTest, LeftAlignedMirrored) {
- ImageButton button(nullptr);
+ ImageButton button;
gfx::ImageSkia image = CreateTestImage(20, 30);
button.SetImage(Button::STATE_NORMAL, &image);
button.SetBounds(0, 0, 50, 30);
@@ -156,7 +156,7 @@ TEST_F(ImageButtonTest, LeftAlignedMirrored) {
}
TEST_F(ImageButtonTest, RightAlignedMirrored) {
- ImageButton button(nullptr);
+ ImageButton button;
gfx::ImageSkia image = CreateTestImage(20, 30);
button.SetImage(Button::STATE_NORMAL, &image);
button.SetBounds(0, 0, 50, 30);
@@ -171,7 +171,7 @@ TEST_F(ImageButtonTest, RightAlignedMirrored) {
TEST_F(ImageButtonTest, PreferredSizeInvalidation) {
Parent parent;
- ImageButton button(nullptr);
+ ImageButton button;
gfx::ImageSkia first_image = CreateTestImage(20, 30);
gfx::ImageSkia second_image = CreateTestImage(50, 50);
button.SetImage(Button::STATE_NORMAL, &first_image);
diff --git a/chromium/ui/views/controls/button/label_button.cc b/chromium/ui/views/controls/button/label_button.cc
index 3aef54f32cd..77c7f6830f9 100644
--- a/chromium/ui/views/controls/button/label_button.cc
+++ b/chromium/ui/views/controls/button/label_button.cc
@@ -20,6 +20,7 @@
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/native_theme/native_theme.h"
+#include "ui/native_theme/themed_vector_icon.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/label_button_border.h"
@@ -45,8 +46,8 @@ LabelButton::LabelButton(ButtonListener* listener,
image_ = AddChildView(std::make_unique<ImageView>());
image_->set_can_process_events_within_subtree(false);
- label_ =
- AddChildView(std::make_unique<LabelButtonLabel>(text, button_context));
+ label_ = AddChildView(
+ std::make_unique<internal::LabelButtonLabel>(text, button_context));
label_->SetAutoColorReadabilityEnabled(false);
label_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
@@ -57,14 +58,36 @@ LabelButton::LabelButton(ButtonListener* listener,
LabelButton::~LabelButton() = default;
gfx::ImageSkia LabelButton::GetImage(ButtonState for_state) const {
- if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
- return button_state_images_[STATE_NORMAL];
- return button_state_images_[for_state];
+ for_state = ImageStateForState(for_state);
+
+ const auto& image_model = button_state_image_models_[for_state];
+ if (image_model.IsImage())
+ return image_model.GetImage().AsImageSkia();
+
+ if (image_model.IsVectorIcon()) {
+ return ui::ThemedVectorIcon(image_model.GetVectorIcon())
+ .GetImageSkia(GetNativeTheme());
+ }
+
+ return gfx::ImageSkia();
}
void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
- button_state_images_[for_state] = image;
- UpdateImage();
+ SetImageModel(for_state, ui::ImageModel::FromImageSkia(image));
+}
+
+void LabelButton::SetImageModel(ButtonState for_state,
+ const ui::ImageModel& image_model) {
+ if (button_state_image_models_[for_state] == image_model)
+ return;
+
+ const auto old_image_state = ImageStateForState(GetVisualState());
+
+ button_state_image_models_[for_state] = image_model;
+
+ if (for_state == old_image_state ||
+ for_state == ImageStateForState(GetVisualState()))
+ UpdateImage();
}
const base::string16& LabelButton::GetText() const {
@@ -89,7 +112,7 @@ void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
button_state_colors_[for_state] = color;
if (for_state == STATE_DISABLED)
label_->SetDisabledColor(color);
- else if (for_state == state())
+ else if (for_state == GetState())
label_->SetEnabledColor(color);
explicitly_set_colors_[for_state] = true;
}
@@ -106,6 +129,10 @@ void LabelButton::SetEnabledTextColors(base::Optional<SkColor> color) {
ResetColorsFromNativeTheme();
}
+SkColor LabelButton::GetCurrentTextColor() const {
+ return label_->GetEnabledColor();
+}
+
void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
label_->SetShadows(shadows);
}
@@ -138,8 +165,7 @@ void LabelButton::SetMinSize(const gfx::Size& min_size) {
if (GetMinSize() == min_size)
return;
min_size_ = min_size;
- ResetCachedPreferredSize();
- OnPropertyChanged(&min_size_, kPropertyEffectsNone);
+ OnPropertyChanged(&min_size_, kPropertyEffectsPreferredSizeChanged);
}
gfx::Size LabelButton::GetMaxSize() const {
@@ -150,8 +176,7 @@ void LabelButton::SetMaxSize(const gfx::Size& max_size) {
if (GetMaxSize() == max_size)
return;
max_size_ = max_size;
- ResetCachedPreferredSize();
- OnPropertyChanged(&max_size_, kPropertyEffectsNone);
+ OnPropertyChanged(&max_size_, kPropertyEffectsPreferredSizeChanged);
}
bool LabelButton::GetIsDefault() const {
@@ -163,6 +188,11 @@ void LabelButton::SetIsDefault(bool is_default) {
if (GetIsDefault() == is_default)
return;
is_default_ = is_default;
+
+ // The default button has an accelerator for VKEY_RETURN, which clicks it.
+ // Note that if PlatformStyle::kReturnClicksFocusedControl is true and another
+ // button is focused, that button's VKEY_RETURN handler will take precedence.
+ // See Button::GetKeyClickActionForEvent().
ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
if (is_default)
AddAccelerator(accel);
@@ -179,8 +209,8 @@ void LabelButton::SetImageLabelSpacing(int spacing) {
if (GetImageLabelSpacing() == spacing)
return;
image_label_spacing_ = spacing;
- ResetCachedPreferredSize();
- OnPropertyChanged(&image_label_spacing_, kPropertyEffectsLayout);
+ OnPropertyChanged(&image_label_spacing_,
+ kPropertyEffectsPreferredSizeChanged);
}
bool LabelButton::GetImageCentered() const {
@@ -203,7 +233,6 @@ std::unique_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
void LabelButton::SetBorder(std::unique_ptr<Border> border) {
border_is_themed_border_ = false;
View::SetBorder(std::move(border));
- ResetCachedPreferredSize();
}
void LabelButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
@@ -212,34 +241,27 @@ void LabelButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
}
gfx::Size LabelButton::CalculatePreferredSize() const {
- // Cache the computed size, as recomputing it is an expensive operation.
- if (!cached_preferred_size_) {
- gfx::Size size = GetUnclampedSizeWithoutLabel();
-
- // Disregard label in the preferred size if the button is shrinking down to
- // show no label soon.
- if (!shrinking_down_label_) {
- const gfx::Size preferred_label_size = label_->GetPreferredSize();
- size.Enlarge(preferred_label_size.width(), 0);
-
- // Increase the height of the label (with insets) if larger.
- size.set_height(std::max(
- preferred_label_size.height() + GetInsets().height(), size.height()));
- }
-
- size.SetToMax(GetMinSize());
+ gfx::Size size = GetUnclampedSizeWithoutLabel();
+
+ // Account for the label only when the button is not shrinking down to hide
+ // the label entirely.
+ if (!shrinking_down_label_) {
+ const gfx::Size preferred_label_size = label_->GetPreferredSize();
+ size.Enlarge(preferred_label_size.width(), 0);
+ size.SetToMax(
+ gfx::Size(0, preferred_label_size.height() + GetInsets().height()));
+ }
- // Clamp size to max size (if valid).
- const gfx::Size max_size = GetMaxSize();
- if (max_size.width() > 0)
- size.set_width(std::min(max_size.width(), size.width()));
- if (max_size.height() > 0)
- size.set_height(std::min(max_size.height(), size.height()));
+ size.SetToMax(GetMinSize());
- cached_preferred_size_ = size;
- }
+ // Clamp size to max size (if valid).
+ const gfx::Size max_size = GetMaxSize();
+ if (max_size.width() > 0)
+ size.set_width(std::min(max_size.width(), size.width()));
+ if (max_size.height() > 0)
+ size.set_height(std::min(max_size.height(), size.height()));
- return cached_preferred_size_.value();
+ return size;
}
gfx::Size LabelButton::GetMinimumSize() const {
@@ -372,7 +394,7 @@ gfx::Rect LabelButton::GetThemePaintRect() const {
ui::NativeTheme::State LabelButton::GetThemeState(
ui::NativeTheme::ExtraParams* params) const {
GetExtraParams(params);
- switch (state()) {
+ switch (GetState()) {
case STATE_NORMAL:
return ui::NativeTheme::kNormal;
case STATE_HOVERED:
@@ -382,7 +404,7 @@ ui::NativeTheme::State LabelButton::GetThemeState(
case STATE_DISABLED:
return ui::NativeTheme::kDisabled;
case STATE_COUNT:
- NOTREACHED() << "Unknown state: " << state();
+ NOTREACHED();
}
return ui::NativeTheme::kNormal;
}
@@ -405,16 +427,6 @@ ui::NativeTheme::State LabelButton::GetForegroundThemeState(
void LabelButton::UpdateImage() {
image_->SetImage(GetImage(GetVisualState()));
- ResetCachedPreferredSize();
-}
-
-void LabelButton::UpdateThemedBorder() {
- // Don't override borders set by others.
- if (!border_is_themed_border_)
- return;
-
- SetBorder(PlatformStyle::CreateThemedLabelButtonBorder(this));
- border_is_themed_border_ = true;
}
void LabelButton::AddLayerBeneathView(ui::Layer* new_layer) {
@@ -451,16 +463,25 @@ PropertyEffects LabelButton::UpdateStyleToIndicateDefaultStatus() {
label_->SetFontList(GetIsDefault() ? cached_default_button_font_list_
: cached_normal_font_list_);
ResetLabelEnabledColor();
- return kPropertyEffectsLayout;
+ return kPropertyEffectsPreferredSizeChanged;
}
void LabelButton::ChildPreferredSizeChanged(View* child) {
PreferredSizeChanged();
}
-void LabelButton::PreferredSizeChanged() {
- ResetCachedPreferredSize();
- Button::PreferredSizeChanged();
+void LabelButton::AddedToWidget() {
+ if (PlatformStyle::kInactiveWidgetControlsAppearDisabled) {
+ paint_as_active_subscription_ =
+ GetWidget()->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
+ &LabelButton::VisualStateChanged, base::Unretained(this)));
+ // Set the initial state correctly.
+ VisualStateChanged();
+ }
+}
+
+void LabelButton::RemovedFromWidget() {
+ paint_as_active_subscription_.reset();
}
void LabelButton::OnFocus() {
@@ -478,23 +499,19 @@ void LabelButton::OnBlur() {
void LabelButton::OnThemeChanged() {
Button::OnThemeChanged();
ResetColorsFromNativeTheme();
- UpdateThemedBorder();
+ UpdateImage();
+ if (border_is_themed_border_)
+ View::SetBorder(PlatformStyle::CreateThemedLabelButtonBorder(this));
ResetLabelEnabledColor();
- // Invalidate the layout to pickup the new insets from the border.
- InvalidateLayout();
// The entire button has to be repainted here, since the native theme can
// define the tint for the entire background/border/focus ring.
SchedulePaint();
}
void LabelButton::StateChanged(ButtonState old_state) {
- const gfx::Size previous_image_size(image_->GetPreferredSize());
- UpdateImage();
- ResetLabelEnabledColor();
- label_->SetEnabled(state() != STATE_DISABLED);
- if (image_->GetPreferredSize() != previous_image_size)
- InvalidateLayout();
Button::StateChanged(old_state);
+ ResetLabelEnabledColor();
+ VisualStateChanged();
}
void LabelButton::SetTextInternal(const base::string16& text) {
@@ -502,32 +519,26 @@ void LabelButton::SetTextInternal(const base::string16& text) {
label_->SetText(text);
// Setting text cancels ShrinkDownThenClearText().
- if (shrinking_down_label_) {
- shrinking_down_label_ = false;
- PreferredSizeChanged();
- }
+ const auto effects = shrinking_down_label_
+ ? kPropertyEffectsPreferredSizeChanged
+ : kPropertyEffectsNone;
+ shrinking_down_label_ = false;
// TODO(pkasting): Remove this and forward callback subscriptions to the
// underlying label property when Label is converted to properties.
- OnPropertyChanged(label_, kPropertyEffectsNone);
+ OnPropertyChanged(label_, effects);
}
void LabelButton::ClearTextIfShrunkDown() {
- if (!cached_preferred_size_)
- CalculatePreferredSize();
- if (shrinking_down_label_ && width() <= cached_preferred_size_->width() &&
- height() <= cached_preferred_size_->height()) {
+ const gfx::Size preferred_size = GetPreferredSize();
+ if (shrinking_down_label_ && width() <= preferred_size.width() &&
+ height() <= preferred_size.height()) {
// Once the button shrinks down to its preferred size (that disregards the
// current text), we finish the operation by clearing the text.
- shrinking_down_label_ = false;
SetText(base::string16());
}
}
-void LabelButton::ResetCachedPreferredSize() {
- cached_preferred_size_ = base::nullopt;
-}
-
gfx::Size LabelButton::GetUnclampedSizeWithoutLabel() const {
const gfx::Size image_size = image_->GetPreferredSize();
gfx::Size size = image_size;
@@ -545,6 +556,20 @@ gfx::Size LabelButton::GetUnclampedSizeWithoutLabel() const {
return size;
}
+Button::ButtonState LabelButton::GetVisualState() const {
+ const auto* widget = GetWidget();
+ if (PlatformStyle::kInactiveWidgetControlsAppearDisabled && widget &&
+ widget->CanActivate() && !widget->ShouldPaintAsActive())
+ return STATE_DISABLED;
+ return GetState();
+}
+
+void LabelButton::VisualStateChanged() {
+ UpdateImage();
+ UpdateBackgroundColor();
+ label_->SetEnabled(GetVisualState() != STATE_DISABLED);
+}
+
void LabelButton::ResetColorsFromNativeTheme() {
const ui::NativeTheme* theme = GetNativeTheme();
// Since this is a LabelButton, use the label colors.
@@ -567,11 +592,17 @@ void LabelButton::ResetColorsFromNativeTheme() {
}
void LabelButton::ResetLabelEnabledColor() {
- const SkColor color = button_state_colors_[state()];
- if (state() != STATE_DISABLED && label_->GetEnabledColor() != color)
+ const SkColor color = button_state_colors_[GetState()];
+ if (GetState() != STATE_DISABLED && label_->GetEnabledColor() != color)
label_->SetEnabledColor(color);
}
+Button::ButtonState LabelButton::ImageStateForState(
+ ButtonState for_state) const {
+ return button_state_image_models_[for_state].IsEmpty() ? STATE_NORMAL
+ : for_state;
+}
+
BEGIN_METADATA(LabelButton)
METADATA_PARENT_CLASS(Button)
ADD_PROPERTY_METADATA(LabelButton, base::string16, Text)
diff --git a/chromium/ui/views/controls/button/label_button.h b/chromium/ui/views/controls/button/label_button.h
index ac348aa7dd4..45692ab8b7c 100644
--- a/chromium/ui/views/controls/button/label_button.h
+++ b/chromium/ui/views/controls/button/label_button.h
@@ -21,6 +21,7 @@
#include "ui/views/layout/layout_provider.h"
#include "ui/views/native_theme_delegate.h"
#include "ui/views/style/typography.h"
+#include "ui/views/widget/widget.h"
namespace views {
@@ -35,15 +36,17 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// Creates a LabelButton with ButtonPressed() events sent to |listener| and
// label |text|. |button_context| is a value from views::style::TextContext
// and determines the appearance of |text|.
- LabelButton(ButtonListener* listener,
- const base::string16& text,
- int button_context = style::CONTEXT_BUTTON);
+ explicit LabelButton(ButtonListener* listener = nullptr,
+ const base::string16& text = base::string16(),
+ int button_context = style::CONTEXT_BUTTON);
~LabelButton() override;
// Gets or sets the image shown for the specified button state.
// GetImage returns the image for STATE_NORMAL if the state's image is empty.
virtual gfx::ImageSkia GetImage(ButtonState for_state) const;
+ // TODO(http://crbug.com/1100034) prefer SetImageModel over SetImage().
void SetImage(ButtonState for_state, const gfx::ImageSkia& image);
+ void SetImageModel(ButtonState for_state, const ui::ImageModel& image_model);
// Gets or sets the text shown on the button.
const base::string16& GetText() const;
@@ -64,6 +67,9 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// Sets the text colors shown for the non-disabled states to |color|.
virtual void SetEnabledTextColors(base::Optional<SkColor> color);
+ // Gets the current state text color.
+ SkColor GetCurrentTextColor() const;
+
// Sets drop shadows underneath the text.
void SetTextShadows(const gfx::ShadowValues& shadows);
@@ -145,9 +151,13 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// Updates the image view to contain the appropriate button state image.
void UpdateImage();
- // Updates the border as per the NativeTheme, unless a different border was
- // set with SetBorder.
- void UpdateThemedBorder();
+ // Updates the background color, if the background color is state-sensitive.
+ virtual void UpdateBackgroundColor() {}
+
+ // Returns the current visual appearance of the button. This takes into
+ // account both the button's underlying state and the state of the containing
+ // widget.
+ ButtonState GetVisualState() const;
// Fills |params| with information about the button.
virtual void GetExtraParams(ui::NativeTheme::ExtraParams* params) const;
@@ -158,7 +168,8 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// Button:
void ChildPreferredSizeChanged(View* child) override;
- void PreferredSizeChanged() override;
+ void AddedToWidget() override;
+ void RemovedFromWidget() override;
void OnFocus() override;
void OnBlur() override;
void OnThemeChanged() override;
@@ -169,9 +180,6 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
void ClearTextIfShrunkDown();
- // Resets |cached_preferred_size_|.
- void ResetCachedPreferredSize();
-
// Gets the preferred size (without respecting min_size_ or max_size_), but
// does not account for the label. This is shared between GetHeightForWidth
// and CalculatePreferredSize. GetHeightForWidth will subtract the width
@@ -181,6 +189,10 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// height as total height, and clamp to min/max sizes as appropriate.
gfx::Size GetUnclampedSizeWithoutLabel() const;
+ // Updates the portions of the object that might change in response to a
+ // change in the value returned by GetVisualState().
+ void VisualStateChanged();
+
// Resets colors from the NativeTheme, explicitly set colors are unchanged.
void ResetColorsFromNativeTheme();
@@ -189,9 +201,13 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
// correct for the current background.
void ResetLabelEnabledColor();
+ // Returns the state whose image is shown for |for_state|, by falling back to
+ // STATE_NORMAL when |for_state|'s image is empty.
+ ButtonState ImageStateForState(ButtonState for_state) const;
+
// The image and label shown in the button.
ImageView* image_;
- LabelButtonLabel* label_;
+ internal::LabelButtonLabel* label_;
// A separate view is necessary to hold the ink drop layer so that it can
// be stacked below |image_| and on top of |label_|, without resorting to
@@ -203,8 +219,8 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
gfx::FontList cached_normal_font_list_;
gfx::FontList cached_default_button_font_list_;
- // The images and colors for each button state.
- gfx::ImageSkia button_state_images_[STATE_COUNT] = {};
+ // The image models and colors for each button state.
+ ui::ImageModel button_state_image_models_[STATE_COUNT] = {};
SkColor button_state_colors_[STATE_COUNT] = {};
// Used to track whether SetTextColor() has been invoked.
@@ -214,9 +230,6 @@ class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
gfx::Size min_size_;
gfx::Size max_size_;
- // Cache the last computed preferred size.
- mutable base::Optional<gfx::Size> cached_preferred_size_;
-
// A flag indicating that this button should not include the label in its
// desired size. Furthermore, once the bounds of the button adapt to this
// desired size, the text in the label should get cleared.
@@ -245,6 +258,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_;
+
DISALLOW_COPY_AND_ASSIGN(LabelButton);
};
diff --git a/chromium/ui/views/controls/button/label_button_label.cc b/chromium/ui/views/controls/button/label_button_label.cc
index 7e40c80e5a2..a1ec3993631 100644
--- a/chromium/ui/views/controls/button/label_button_label.cc
+++ b/chromium/ui/views/controls/button/label_button_label.cc
@@ -6,6 +6,8 @@
namespace views {
+namespace internal {
+
LabelButtonLabel::LabelButtonLabel(const base::string16& text, int text_context)
: Label(text, text_context, style::STYLE_PRIMARY) {}
@@ -42,4 +44,6 @@ void LabelButtonLabel::SetColorForEnableState() {
}
}
+} // 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 49d88b6023c..88ab37127f1 100644
--- a/chromium/ui/views/controls/button/label_button_label.h
+++ b/chromium/ui/views/controls/button/label_button_label.h
@@ -16,6 +16,8 @@
namespace views {
+namespace internal {
+
// A Label subclass that can be disabled. This is only used internally for
// views::LabelButton.
class VIEWS_EXPORT LabelButtonLabel : public Label {
@@ -48,6 +50,8 @@ class VIEWS_EXPORT LabelButtonLabel : public Label {
DISALLOW_COPY_AND_ASSIGN(LabelButtonLabel);
};
+} // namespace internal
+
} // namespace views
#endif // UI_VIEWS_CONTROLS_BUTTON_LABEL_BUTTON_LABEL_H_
diff --git a/chromium/ui/views/controls/button/label_button_label_unittest.cc b/chromium/ui/views/controls/button/label_button_label_unittest.cc
index 8e880de4eaf..f42bbf7bfe8 100644
--- a/chromium/ui/views/controls/button/label_button_label_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_label_unittest.cc
@@ -36,7 +36,7 @@ class TestNativeTheme : public ui::NativeThemeBase {
// LabelButtonLabel subclass that reports its text color whenever a paint is
// scheduled.
-class TestLabel : public LabelButtonLabel {
+class TestLabel : public internal::LabelButtonLabel {
public:
explicit TestLabel(SkColor* last_color)
: LabelButtonLabel(base::string16(), views::style::CONTEXT_BUTTON),
diff --git a/chromium/ui/views/controls/button/label_button_unittest.cc b/chromium/ui/views/controls/button/label_button_unittest.cc
index 38443891fff..88953b032f0 100644
--- a/chromium/ui/views/controls/button/label_button_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_unittest.cc
@@ -93,11 +93,15 @@ class LabelButtonTest : public test::WidgetTest {
// used (which could be derived from the Widget's NativeTheme).
test_widget_ = CreateTopLevelPlatformWidget();
+ // Ensure the Widget is active, since LabelButton appearance in inactive
+ // Windows is platform-dependent.
+ test_widget_->Show();
+
// The test code below is not prepared to handle dark mode.
test_widget_->GetNativeTheme()->set_use_dark_colors(false);
- button_ = new TestLabelButton;
- test_widget_->GetContentsView()->AddChildView(button_);
+ button_ = test_widget_->GetContentsView()->AddChildView(
+ std::make_unique<TestLabelButton>());
// Establish the expected text colors for testing changes due to state.
themed_normal_text_color_ = button_->GetNativeTheme()->GetSystemColor(
@@ -107,7 +111,8 @@ class LabelButtonTest : public test::WidgetTest {
// NativeTheme and use a hardcoded black or (on Mac) have a NativeTheme that
// reliably returns black.
styled_normal_text_color_ = SK_ColorBLACK;
-#if defined(OS_LINUX) && BUILDFLAG(ENABLE_DESKTOP_AURA)
+#if (defined(OS_LINUX) || defined(OS_CHROMEOS)) && \
+ BUILDFLAG(ENABLE_DESKTOP_AURA)
// The Linux theme provides a non-black highlight text color, but it's not
// used for styled buttons.
styled_highlight_text_color_ = styled_normal_text_color_ =
@@ -154,7 +159,7 @@ TEST_F(LabelButtonTest, Init) {
ax::mojom::StringAttribute::kName));
EXPECT_FALSE(button.GetIsDefault());
- EXPECT_EQ(Button::STATE_NORMAL, button.state());
+ EXPECT_EQ(Button::STATE_NORMAL, button.GetState());
EXPECT_EQ(button.image()->parent(), &button);
EXPECT_EQ(button.label()->parent(), &button);
@@ -686,6 +691,36 @@ TEST_F(LabelButtonTest, ImageOrLabelGetClipped) {
EXPECT_GE(button_->label()->height(), image_size);
}
+TEST_F(LabelButtonTest, UpdateImageAfterSettingImageModel) {
+ auto is_showing_image = [&](const gfx::ImageSkia& image) {
+ return button_->image()->GetImage().BackedBySameObjectAs(image);
+ };
+
+ auto normal_image = CreateTestImage(16, 16);
+ button_->SetImageModel(Button::STATE_NORMAL,
+ ui::ImageModel::FromImageSkia(normal_image));
+ EXPECT_TRUE(is_showing_image(normal_image));
+
+ // When the button has no specific disabled image, changing the normal image
+ // while the button is disabled should update the currently-visible image.
+ normal_image = CreateTestImage(16, 16);
+ button_->SetState(Button::STATE_DISABLED);
+ button_->SetImageModel(Button::STATE_NORMAL,
+ ui::ImageModel::FromImageSkia(normal_image));
+ EXPECT_TRUE(is_showing_image(normal_image));
+
+ // Any specific disabled image should take precedence over the normal image.
+ auto disabled_image = CreateTestImage(16, 16);
+ button_->SetImageModel(Button::STATE_DISABLED,
+ ui::ImageModel::FromImageSkia(disabled_image));
+ EXPECT_TRUE(is_showing_image(disabled_image));
+
+ // Removing the disabled image should result in falling back to the normal
+ // image again.
+ button_->SetImageModel(Button::STATE_DISABLED, ui::ImageModel());
+ EXPECT_TRUE(is_showing_image(normal_image));
+}
+
// Test fixture for a LabelButton that has an ink drop configured.
class InkDropLabelButtonTest : public ViewsTestBase {
public:
diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc
index 79c42e7b9ac..53b978caed4 100644
--- a/chromium/ui/views/controls/button/md_text_button.cc
+++ b/chromium/ui/views/controls/button/md_text_button.cc
@@ -30,15 +30,32 @@
namespace views {
-// static
-std::unique_ptr<MdTextButton> MdTextButton::Create(ButtonListener* listener,
- const base::string16& text,
- int button_context) {
- auto button = base::WrapUnique<MdTextButton>(
- new MdTextButton(listener, button_context));
- button->SetText(text);
-
- return button;
+MdTextButton::MdTextButton(ButtonListener* listener,
+ const base::string16& text,
+ int button_context)
+ : LabelButton(listener, text, button_context) {
+ SetInkDropMode(InkDropMode::ON);
+ set_has_ink_drop_action_on_click(true);
+ set_show_ink_drop_when_hot_tracked(true);
+ SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(EMPHASIS_LOW));
+ SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ SetFocusForPlatform();
+
+ const int minimum_width = LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH);
+ SetMinSize(gfx::Size(minimum_width, 0));
+ SetInstallFocusRingOnFocus(true);
+ label()->SetAutoColorReadabilityEnabled(false);
+ set_request_focus_on_press(false);
+ set_animate_on_state_change(true);
+
+ // Paint to a layer so that the canvas is snapped to pixel boundaries (useful
+ // for fractional DSF).
+ SetPaintToLayer();
+ layer()->SetFillsBoundsOpaquely(false);
+
+ // Call this to calculate the border given text.
+ UpdatePadding();
}
MdTextButton::~MdTextButton() = default;
@@ -111,7 +128,7 @@ std::unique_ptr<views::InkDropHighlight> MdTextButton::CreateInkDropHighlight()
is_prominent_
? ui::NativeTheme::kColorId_ProminentButtonInkDropShadowColor
: ui::NativeTheme::kColorId_ButtonInkDropShadowColor;
- if (state() == STATE_HOVERED) {
+ if (GetState() == STATE_HOVERED) {
fill_color_id = is_prominent_
? ui::NativeTheme::kColorId_ProminentButtonHoverColor
: ui::NativeTheme::kColorId_ButtonHoverColor;
@@ -158,29 +175,6 @@ PropertyEffects MdTextButton::UpdateStyleToIndicateDefaultStatus() {
return kPropertyEffectsNone;
}
-MdTextButton::MdTextButton(ButtonListener* listener, int button_context)
- : LabelButton(listener, base::string16(), button_context) {
- SetInkDropMode(InkDropMode::ON);
- set_has_ink_drop_action_on_click(true);
- set_show_ink_drop_when_hot_tracked(true);
- SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(EMPHASIS_LOW));
- SetHorizontalAlignment(gfx::ALIGN_CENTER);
- SetFocusForPlatform();
- const int minimum_width = LayoutProvider::Get()->GetDistanceMetric(
- DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH);
- SetMinSize(gfx::Size(minimum_width, 0));
- SetInstallFocusRingOnFocus(true);
- label()->SetAutoColorReadabilityEnabled(false);
- set_request_focus_on_press(false);
-
- set_animate_on_state_change(true);
-
- // Paint to a layer so that the canvas is snapped to pixel boundaries (useful
- // for fractional DSF).
- SetPaintToLayer();
- layer()->SetFillsBoundsOpaquely(false);
-}
-
void MdTextButton::UpdatePadding() {
// Don't use font-based padding when there's no text visible.
if (GetText().empty()) {
@@ -227,27 +221,31 @@ gfx::Insets MdTextButton::CalculateDefaultPadding() const {
horizontal_padding);
}
-void MdTextButton::UpdateColors() {
- bool is_disabled = state() == STATE_DISABLED;
+void MdTextButton::UpdateTextColor() {
+ if (explicitly_set_normal_color())
+ return;
+
SkColor enabled_text_color =
style::GetColor(*this, label()->GetTextContext(),
is_prominent_ ? style::STYLE_DIALOG_BUTTON_DEFAULT
: style::STYLE_PRIMARY);
- if (!explicitly_set_normal_color()) {
- const auto colors = explicitly_set_colors();
- LabelButton::SetEnabledTextColors(enabled_text_color);
- // Disabled buttons need the disabled color explicitly set.
- // This ensures that label()->GetEnabledColor() returns the correct color as
- // the basis for calculating the stroke color. enabled_text_color isn't used
- // since a descendant could have overridden the label enabled color.
- if (is_disabled) {
- LabelButton::SetTextColor(
- STATE_DISABLED, style::GetColor(*this, label()->GetTextContext(),
- style::STYLE_DISABLED));
- }
- set_explicitly_set_colors(colors);
+
+ const auto colors = explicitly_set_colors();
+ LabelButton::SetEnabledTextColors(enabled_text_color);
+ // Disabled buttons need the disabled color explicitly set.
+ // This ensures that label()->GetEnabledColor() returns the correct color as
+ // the basis for calculating the stroke color. enabled_text_color isn't used
+ // since a descendant could have overridden the label enabled color.
+ if (GetState() == STATE_DISABLED) {
+ LabelButton::SetTextColor(STATE_DISABLED,
+ style::GetColor(*this, label()->GetTextContext(),
+ style::STYLE_DISABLED));
}
+ set_explicitly_set_colors(colors);
+}
+void MdTextButton::UpdateBackgroundColor() {
+ bool is_disabled = GetVisualState() == STATE_DISABLED;
ui::NativeTheme* theme = GetNativeTheme();
SkColor bg_color =
theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonColor);
@@ -264,7 +262,7 @@ void MdTextButton::UpdateColors() {
}
}
- if (state() == STATE_PRESSED) {
+ if (GetState() == STATE_PRESSED) {
bg_color = theme->GetSystemButtonPressedColor(bg_color);
}
@@ -272,14 +270,19 @@ void MdTextButton::UpdateColors() {
if (is_prominent_) {
stroke_color = SK_ColorTRANSPARENT;
} else {
- stroke_color = SkColorSetA(
- theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonBorderColor),
- is_disabled ? 0x43 : SK_AlphaOPAQUE);
+ stroke_color = theme->GetSystemColor(
+ is_disabled ? ui::NativeTheme::kColorId_DisabledButtonBorderColor
+ : ui::NativeTheme::kColorId_ButtonBorderColor);
}
SetBackground(
CreateBackgroundFromPainter(Painter::CreateRoundRectWith1PxBorderPainter(
bg_color, stroke_color, corner_radius_)));
+}
+
+void MdTextButton::UpdateColors() {
+ UpdateTextColor();
+ UpdateBackgroundColor();
SchedulePaint();
}
diff --git a/chromium/ui/views/controls/button/md_text_button.h b/chromium/ui/views/controls/button/md_text_button.h
index 1d293a70166..81b342becf7 100644
--- a/chromium/ui/views/controls/button/md_text_button.h
+++ b/chromium/ui/views/controls/button/md_text_button.h
@@ -19,10 +19,9 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
public:
METADATA_HEADER(MdTextButton);
- static std::unique_ptr<MdTextButton> Create(
- ButtonListener* listener,
- const base::string16& text,
- int button_context = style::CONTEXT_BUTTON_MD);
+ explicit MdTextButton(ButtonListener* listener = nullptr,
+ const base::string16& text = base::string16(),
+ int button_context = style::CONTEXT_BUTTON_MD);
~MdTextButton() override;
@@ -57,13 +56,14 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
void OnFocus() override;
void OnBlur() override;
- MdTextButton(ButtonListener* listener, int button_context);
-
private:
void UpdatePadding();
- void UpdateColors();
gfx::Insets CalculateDefaultPadding() const;
+ void UpdateTextColor();
+ void UpdateBackgroundColor() override;
+ void UpdateColors();
+
// True if this button uses prominent styling (blue fill, etc.).
bool is_prominent_ = false;
diff --git a/chromium/ui/views/controls/button/md_text_button_unittest.cc b/chromium/ui/views/controls/button/md_text_button_unittest.cc
index 5fe5d9c1bf1..4013de90dab 100644
--- a/chromium/ui/views/controls/button/md_text_button_unittest.cc
+++ b/chromium/ui/views/controls/button/md_text_button_unittest.cc
@@ -4,7 +4,11 @@
#include "ui/views/controls/button/md_text_button.h"
+#include "ui/views/background.h"
+#include "ui/views/style/platform_style.h"
+#include "ui/views/test/views_drawing_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/test/widget_test.h"
namespace views {
@@ -13,7 +17,7 @@ using MdTextButtonTest = ViewsTestBase;
TEST_F(MdTextButtonTest, CustomPadding) {
const base::string16 text = base::ASCIIToUTF16("abc");
std::unique_ptr<MdTextButton> button =
- MdTextButton::Create(nullptr, text, views::style::CONTEXT_BUTTON_MD);
+ std::make_unique<MdTextButton>(nullptr, text);
const gfx::Insets custom_padding(10, 20);
ASSERT_NE(button->GetInsets(), custom_padding);
@@ -22,4 +26,55 @@ TEST_F(MdTextButtonTest, CustomPadding) {
EXPECT_EQ(button->GetInsets(), custom_padding);
}
+TEST_F(MdTextButtonTest, BackgroundColorChangesWithWidgetActivation) {
+ // Test whether the button's background color changes when its containing
+ // widget's activation changes.
+ if (!PlatformStyle::kInactiveWidgetControlsAppearDisabled)
+ GTEST_SKIP() << "Button colors do not change with widget activation here.";
+
+ std::unique_ptr<Widget> widget = CreateTestWidget();
+ auto* button = widget->SetContentsView(
+ std::make_unique<MdTextButton>(nullptr, base::ASCIIToUTF16("button")));
+ button->SetProminent(true);
+ button->SetBounds(0, 0, 70, 20);
+ widget->LayoutRootViewIfNecessary();
+
+ const ui::NativeTheme* native_theme = button->GetNativeTheme();
+
+ test::WidgetTest::SimulateNativeActivate(widget.get());
+ EXPECT_TRUE(widget->IsActive());
+ SkBitmap active_bitmap = views::test::PaintViewToBitmap(button);
+
+ auto background_color = [button](const SkBitmap& bitmap) {
+ // The very edge of the bitmap contains the button's border, which we aren't
+ // interested in here. Instead, grab a pixel that is inset by the button's
+ // corner radius from the top-left point to avoid the border.
+ //
+ // It would make a bit more sense to inset by the border thickness or
+ // something, but MdTextButton doesn't expose (or even know) that value
+ // without some major abstraction violation.
+ int corner_radius = button->GetCornerRadius();
+ return bitmap.getColor(corner_radius, corner_radius);
+ };
+
+ EXPECT_EQ(background_color(active_bitmap),
+ native_theme->GetSystemColor(
+ ui::NativeTheme::kColorId_ProminentButtonColor));
+
+ // It would be neat to also check the text color here, but the label's text
+ // ends up drawn on top of the background with antialiasing, which means there
+ // aren't any pixels that are actually *exactly*
+ // kColorId_TextOnProminentButtonColor. Bummer.
+
+ // Activate another widget to cause the original widget to deactivate.
+ std::unique_ptr<Widget> other_widget = CreateTestWidget();
+ test::WidgetTest::SimulateNativeActivate(other_widget.get());
+ EXPECT_FALSE(widget->IsActive());
+ SkBitmap inactive_bitmap = views::test::PaintViewToBitmap(button);
+
+ EXPECT_EQ(background_color(inactive_bitmap),
+ native_theme->GetSystemColor(
+ ui::NativeTheme::kColorId_ProminentButtonDisabledColor));
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/button/menu_button.cc b/chromium/ui/views/controls/button/menu_button.cc
index c62097f0cad..a708a841a82 100644
--- a/chromium/ui/views/controls/button/menu_button.cc
+++ b/chromium/ui/views/controls/button/menu_button.cc
@@ -13,8 +13,8 @@
namespace views {
-MenuButton::MenuButton(const base::string16& text,
- ButtonListener* button_listener,
+MenuButton::MenuButton(ButtonListener* button_listener,
+ const base::string16& text,
int button_context)
: LabelButton(nullptr, text, button_context) {
SetHorizontalAlignment(gfx::ALIGN_LEFT);
diff --git a/chromium/ui/views/controls/button/menu_button.h b/chromium/ui/views/controls/button/menu_button.h
index 7f64f44f851..18edea530ca 100644
--- a/chromium/ui/views/controls/button/menu_button.h
+++ b/chromium/ui/views/controls/button/menu_button.h
@@ -26,9 +26,9 @@ class VIEWS_EXPORT MenuButton : public LabelButton {
METADATA_HEADER(MenuButton);
// Create a Button.
- MenuButton(const base::string16& text,
- ButtonListener* button_listener,
- int button_context = style::CONTEXT_BUTTON);
+ explicit MenuButton(ButtonListener* button_listener = nullptr,
+ const base::string16& text = base::string16(),
+ int button_context = style::CONTEXT_BUTTON);
~MenuButton() override;
MenuButtonController* button_controller() const {
diff --git a/chromium/ui/views/controls/button/menu_button_controller.cc b/chromium/ui/views/controls/button/menu_button_controller.cc
index 9c50cbfe8e1..70c3cc31f77 100644
--- a/chromium/ui/views/controls/button/menu_button_controller.cc
+++ b/chromium/ui/views/controls/button/menu_button_controller.cc
@@ -100,7 +100,7 @@ bool MenuButtonController::OnMousePressed(const ui::MouseEvent& event) {
if (button()->request_focus_on_press())
button()->RequestFocus();
- if (button()->state() != Button::STATE_DISABLED &&
+ if (button()->GetState() != Button::STATE_DISABLED &&
button()->HitTestPoint(event.location()) && IsTriggerableEvent(event)) {
return Activate(&event);
}
@@ -112,7 +112,7 @@ bool MenuButtonController::OnMousePressed(const ui::MouseEvent& event) {
}
void MenuButtonController::OnMouseReleased(const ui::MouseEvent& event) {
- if (button()->state() != Button::STATE_DISABLED &&
+ if (button()->GetState() != Button::STATE_DISABLED &&
delegate()->IsTriggerableEvent(event) &&
button()->HitTestPoint(event.location()) && !delegate()->InDrag()) {
Activate(&event);
@@ -180,35 +180,20 @@ void MenuButtonController::UpdateAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen);
}
-void MenuButtonController::OnStateChanged(LabelButton::ButtonState old_state) {
- // State change occurs in IncrementPressedLocked() and
- // DecrementPressedLocked().
- if (pressed_lock_count_ != 0) {
- // The button's state was changed while it was supposed to be locked in a
- // pressed state. This shouldn't happen, but conceivably could if a caller
- // tries to switch from enabled to disabled or vice versa while the button
- // is pressed.
- if (button()->state() == Button::STATE_NORMAL)
- should_disable_after_press_ = false;
- else if (button()->state() == Button::STATE_DISABLED)
- should_disable_after_press_ = true;
- }
-}
-
bool MenuButtonController::IsTriggerableEvent(const ui::Event& event) {
return ButtonController::IsTriggerableEvent(event) &&
IsTriggerableEventType(event) && is_intentional_menu_trigger_;
}
void MenuButtonController::OnGestureEvent(ui::GestureEvent* event) {
- if (button()->state() != Button::STATE_DISABLED) {
+ if (button()->GetState() != Button::STATE_DISABLED) {
auto ref = weak_factory_.GetWeakPtr();
if (delegate()->IsTriggerableEvent(*event) && !Activate(event)) {
// When |Activate()| returns |false|, it means the click was handled by
// a button listener and has handled the gesture event. So, there is no
// need to further process the gesture event here. However, if the
// listener didn't run menu code, we should make sure to reset our state.
- if (ref && button()->state() == Button::STATE_HOVERED)
+ if (ref && button()->GetState() == Button::STATE_HOVERED)
button()->SetState(Button::STATE_NORMAL);
return;
@@ -217,7 +202,7 @@ void MenuButtonController::OnGestureEvent(ui::GestureEvent* event) {
event->SetHandled();
if (pressed_lock_count_ == 0)
button()->SetState(Button::STATE_HOVERED);
- } else if (button()->state() == Button::STATE_HOVERED &&
+ } else if (button()->GetState() == Button::STATE_HOVERED &&
(event->type() == ui::ET_GESTURE_TAP_CANCEL ||
event->type() == ui::ET_GESTURE_END) &&
pressed_lock_count_ == 0) {
@@ -308,9 +293,15 @@ void MenuButtonController::IncrementPressedLocked(
const ui::LocatedEvent* event) {
++pressed_lock_count_;
if (increment_pressed_lock_called_)
- *(increment_pressed_lock_called_) = true;
- should_disable_after_press_ = button()->state() == Button::STATE_DISABLED;
- if (button()->state() != Button::STATE_PRESSED) {
+ *increment_pressed_lock_called_ = true;
+ if (!state_changed_subscription_) {
+ state_changed_subscription_ =
+ button()->AddStateChangedCallback(base::BindRepeating(
+ &MenuButtonController::OnButtonStateChangedWhilePressedLocked,
+ base::Unretained(this)));
+ }
+ should_disable_after_press_ = button()->GetState() == Button::STATE_DISABLED;
+ if (button()->GetState() != Button::STATE_PRESSED) {
if (snap_ink_drop_to_activated)
delegate()->GetInkDrop()->SnapToActivated();
else
@@ -327,6 +318,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();
LabelButton::ButtonState desired_state = Button::STATE_NORMAL;
if (should_disable_after_press_) {
desired_state = Button::STATE_DISABLED;
@@ -340,9 +332,20 @@ void MenuButtonController::DecrementPressedLocked() {
button()->SetState(desired_state);
// The widget may be null during shutdown. If so, it doesn't make sense to
// try to add an ink drop effect.
- if (button()->GetWidget() && button()->state() != Button::STATE_PRESSED)
+ if (button()->GetWidget() && button()->GetState() != Button::STATE_PRESSED)
button()->AnimateInkDrop(InkDropState::DEACTIVATED, nullptr /* event */);
}
}
+void MenuButtonController::OnButtonStateChangedWhilePressedLocked() {
+ // The button's state was changed while it was supposed to be locked in a
+ // pressed state. This shouldn't happen, but conceivably could if a caller
+ // tries to switch from enabled to disabled or vice versa while the button is
+ // pressed.
+ if (button()->GetState() == Button::STATE_NORMAL)
+ should_disable_after_press_ = false;
+ else if (button()->GetState() == Button::STATE_DISABLED)
+ should_disable_after_press_ = true;
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/button/menu_button_controller.h b/chromium/ui/views/controls/button/menu_button_controller.h
index 35b8bf30a22..addaa2d3836 100644
--- a/chromium/ui/views/controls/button/menu_button_controller.h
+++ b/chromium/ui/views/controls/button/menu_button_controller.h
@@ -55,7 +55,6 @@ class VIEWS_EXPORT MenuButtonController : public ButtonController {
bool OnKeyReleased(const ui::KeyEvent& event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void UpdateAccessibleNodeData(ui::AXNodeData* node_data) override;
- void OnStateChanged(Button::ButtonState old_state) override;
bool IsTriggerableEvent(const ui::Event& event) override;
// Calls TakeLock with is_sibling_menu_show as false and a nullptr to the
@@ -88,6 +87,9 @@ class VIEWS_EXPORT MenuButtonController : public ButtonController {
void DecrementPressedLocked();
+ // Called if the button state changes while pressed lock is engaged.
+ void OnButtonStateChangedWhilePressedLocked();
+
// Our listener. Not owned.
ButtonListener* const listener_;
@@ -113,6 +115,9 @@ class VIEWS_EXPORT MenuButtonController : public ButtonController {
// we programmatically show a menu on a disabled button.
bool should_disable_after_press_ = false;
+ // Subscribes to state changes on the button while pressed lock is engaged.
+ views::PropertyChangedSubscription state_changed_subscription_;
+
base::WeakPtrFactory<MenuButtonController> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(MenuButtonController);
diff --git a/chromium/ui/views/controls/button/menu_button_unittest.cc b/chromium/ui/views/controls/button/menu_button_unittest.cc
index e3e70954c85..f7c870780bb 100644
--- a/chromium/ui/views/controls/button/menu_button_unittest.cc
+++ b/chromium/ui/views/controls/button/menu_button_unittest.cc
@@ -26,6 +26,7 @@
#if defined(USE_AURA)
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/drag_drop_client_observer.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
#endif
@@ -41,7 +42,7 @@ using test::TestInkDrop;
class TestMenuButton : public MenuButton {
public:
explicit TestMenuButton(ButtonListener* button_listener)
- : MenuButton(base::string16(ASCIIToUTF16("button")), button_listener) {}
+ : MenuButton(button_listener, base::string16(ASCIIToUTF16("button"))) {}
~TestMenuButton() override = default;
@@ -135,7 +136,7 @@ class TestButtonListener : public ButtonListener {
last_sender_ = sender;
Button* button = Button::AsButton(sender);
DCHECK(button);
- last_sender_state_ = button->state();
+ last_sender_state_ = button->GetState();
last_event_type_ = event.type();
}
@@ -146,7 +147,7 @@ class TestButtonListener : public ButtonListener {
}
Button* last_sender() { return last_sender_; }
- Button::ButtonState last_sender_state() { return last_sender_state_; }
+ Button::ButtonState last_sender_GetState() { return last_sender_state_; }
ui::EventType last_event_type() { return last_event_type_; }
private:
@@ -228,7 +229,7 @@ class TestDragDropClient : public aura::client::DragDropClient,
aura::Window* source_window,
const gfx::Point& screen_location,
int operation,
- ui::DragDropTypes::DragEventSource source) override;
+ ui::mojom::DragEventSource source) override;
void DragCancel() override;
bool IsDragDropInProgress() override;
void AddObserver(aura::client::DragDropClientObserver* observer) override {}
@@ -258,7 +259,7 @@ int TestDragDropClient::StartDragAndDrop(
aura::Window* source_window,
const gfx::Point& screen_location,
int operation,
- ui::DragDropTypes::DragEventSource source) {
+ ui::mojom::DragEventSource source) {
if (IsDragDropInProgress())
return ui::DragDropTypes::DRAG_NONE;
drag_in_progress_ = true;
@@ -300,7 +301,7 @@ class TestShowSiblingButtonListener : public ButtonListener {
// The MenuButton itself doesn't set the PRESSED state during Activate() or
// ButtonPressed(). That should be handled by the MenuController or,
// if no menu is shown, the listener.
- EXPECT_EQ(Button::STATE_HOVERED, source->state());
+ EXPECT_EQ(Button::STATE_HOVERED, source->GetState());
}
private:
@@ -318,7 +319,7 @@ TEST_F(MenuButtonTest, ActivateDropDownOnMouseClick) {
// Check that MenuButton has notified the listener, while it was in pressed
// state.
EXPECT_EQ(button(), button_listener.last_sender());
- EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_state());
+ EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_GetState());
}
TEST_F(MenuButtonTest, ActivateOnKeyPress) {
@@ -369,7 +370,7 @@ TEST_F(MenuButtonTest, InkDropCenterSetFromClickWithPressedLock) {
MenuButtonController::PressedLock pressed_lock(button()->button_controller(),
false, &click_event);
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_EQ(
click_point,
InkDropHostViewTestApi(button()).GetInkDropCenterBasedOnLastEvent());
@@ -381,58 +382,58 @@ TEST_F(MenuButtonTest, ButtonStateForMenuButtonsWithPressedLocks) {
// Move the mouse over the button; the button should be in a hovered state.
generator()->MoveMouseTo(gfx::Point(10, 10));
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
// Introduce a PressedLock, which should make the button pressed.
std::unique_ptr<MenuButtonController::PressedLock> pressed_lock1(
new MenuButtonController::PressedLock(button()->button_controller()));
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
// Even if we move the mouse outside of the button, it should remain pressed.
generator()->MoveMouseTo(gfx::Point(300, 10));
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
// Creating a new lock should obviously keep the button pressed.
std::unique_ptr<MenuButtonController::PressedLock> pressed_lock2(
new MenuButtonController::PressedLock(button()->button_controller()));
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
// The button should remain pressed while any locks are active.
pressed_lock1.reset();
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
// Resetting the final lock should return the button's state to normal...
pressed_lock2.reset();
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
// ...And it should respond to mouse movement again.
generator()->MoveMouseTo(gfx::Point(10, 10));
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
// Test that the button returns to the appropriate state after the press; if
// the mouse ends over the button, the button should be hovered.
pressed_lock1 = button()->button_controller()->TakeLock();
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
pressed_lock1.reset();
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
// If the button is disabled before the pressed lock, it should be disabled
// after the pressed lock.
button()->SetState(Button::STATE_DISABLED);
pressed_lock1 = button()->button_controller()->TakeLock();
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
pressed_lock1.reset();
- EXPECT_EQ(Button::STATE_DISABLED, button()->state());
+ EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
generator()->MoveMouseTo(gfx::Point(300, 10));
// Edge case: the button is disabled, a pressed lock is added, and then the
// button is re-enabled. It should be enabled after the lock is removed.
pressed_lock1 = button()->button_controller()->TakeLock();
- EXPECT_EQ(Button::STATE_PRESSED, button()->state());
+ EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
button()->SetState(Button::STATE_NORMAL);
pressed_lock1.reset();
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
// Test that if a sibling menu is shown, the original menu button releases its
@@ -443,7 +444,7 @@ TEST_F(MenuButtonTest, PressedStateWithSiblingMenu) {
// Move the mouse over the button; the button should be in a hovered state.
generator()->MoveMouseTo(gfx::Point(10, 10));
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
generator()->ClickLeftButton();
// Test is continued in TestShowSiblingButtonListener::ButtonPressed().
}
@@ -461,7 +462,7 @@ TEST_F(MenuButtonTest, DraggableMenuButtonActivatesOnRelease) {
generator()->ReleaseLeftButton();
EXPECT_EQ(button(), button_listener.last_sender());
- EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_state());
+ EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_GetState());
}
TEST_F(MenuButtonTest, InkDropStateForMenuButtonActivationsWithoutListener) {
@@ -570,14 +571,14 @@ TEST_F(MenuButtonTest, DraggableMenuButtonDoesNotActivateOnDrag) {
generator()->DragMouseBy(10, 0);
EXPECT_EQ(nullptr, button_listener.last_sender());
- EXPECT_EQ(Button::STATE_NORMAL, button_listener.last_sender_state());
+ EXPECT_EQ(Button::STATE_NORMAL, button_listener.last_sender_GetState());
button()->RemovePreTargetHandler(&drag_client);
}
#endif // USE_AURA
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
-#if !defined(OS_MACOSX) || defined(USE_AURA)
+#if !defined(OS_APPLE) || defined(USE_AURA)
// Tests if the listener is notified correctly when a gesture tap happens on a
// MenuButton that has a ButtonListener.
@@ -595,10 +596,10 @@ TEST_F(MenuButtonTest, ActivateDropDownOnGestureTap) {
// Check that MenuButton has notified the listener, while it was in pressed
// state.
EXPECT_EQ(button(), button_listener.last_sender());
- EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_state());
+ EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_GetState());
// The button should go back to it's normal state since the gesture ended.
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
// Tests that the button enters a hovered state upon a tap down, before becoming
@@ -607,10 +608,10 @@ TEST_F(MenuButtonTest, TouchFeedbackDuringTap) {
TestButtonListener button_listener;
CreateMenuButtonWithButtonListener(&button_listener);
generator()->PressTouch();
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
generator()->ReleaseTouch();
- EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_state());
+ EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_GetState());
}
// Tests that a move event that exits the button returns it to the normal state,
@@ -619,15 +620,15 @@ TEST_F(MenuButtonTest, TouchFeedbackDuringTapCancel) {
TestButtonListener button_listener;
CreateMenuButtonWithButtonListener(&button_listener);
generator()->PressTouch();
- EXPECT_EQ(Button::STATE_HOVERED, button()->state());
+ EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
generator()->MoveTouch(gfx::Point(10, 30));
generator()->ReleaseTouch();
- EXPECT_EQ(Button::STATE_NORMAL, button()->state());
+ EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_EQ(nullptr, button_listener.last_sender());
}
-#endif // !defined(OS_MACOSX) || defined(USE_AURA)
+#endif // !defined(OS_APPLE) || defined(USE_AURA)
TEST_F(MenuButtonTest, InkDropHoverWhenShowingMenu) {
PressStateButtonListener button_listener(false);
@@ -674,7 +675,7 @@ TEST_F(MenuButtonTest,
class DestroyButtonInGestureListener : public ButtonListener {
public:
DestroyButtonInGestureListener() {
- menu_button_ = std::make_unique<MenuButton>(base::string16(), this);
+ menu_button_ = std::make_unique<MenuButton>();
}
~DestroyButtonInGestureListener() override = default;
diff --git a/chromium/ui/views/controls/button/radio_button.h b/chromium/ui/views/controls/button/radio_button.h
index 3bb6d4a5fe3..c3c61b5b343 100644
--- a/chromium/ui/views/controls/button/radio_button.h
+++ b/chromium/ui/views/controls/button/radio_button.h
@@ -18,7 +18,8 @@ class VIEWS_EXPORT RadioButton : public Checkbox {
public:
METADATA_HEADER(RadioButton);
- RadioButton(const base::string16& label, int group_id);
+ explicit RadioButton(const base::string16& label = base::string16(),
+ int group_id = 0);
~RadioButton() override;
// Overridden from View:
diff --git a/chromium/ui/views/controls/button/toggle_button.cc b/chromium/ui/views/controls/button/toggle_button.cc
index bf88433d005..0c840504a97 100644
--- a/chromium/ui/views/controls/button/toggle_button.cc
+++ b/chromium/ui/views/controls/button/toggle_button.cc
@@ -19,7 +19,6 @@
#include "ui/gfx/shadow_value.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/animation/ink_drop_impl.h"
-#include "ui/views/animation/ink_drop_mask.h"
#include "ui/views/animation/ink_drop_ripple.h"
#include "ui/views/border.h"
#include "ui/views/controls/highlight_path_generator.h"
@@ -330,10 +329,6 @@ std::unique_ptr<InkDrop> ToggleButton::CreateInkDrop() {
return std::move(ink_drop);
}
-std::unique_ptr<InkDropMask> ToggleButton::CreateInkDropMask() const {
- return nullptr;
-}
-
std::unique_ptr<InkDropRipple> ToggleButton::CreateInkDropRipple() const {
gfx::Rect rect = thumb_view_->GetLocalBounds();
rect.Inset(-ThumbView::GetShadowOutsets());
diff --git a/chromium/ui/views/controls/button/toggle_button.h b/chromium/ui/views/controls/button/toggle_button.h
index 0106206f543..cd0cadc71e2 100644
--- a/chromium/ui/views/controls/button/toggle_button.h
+++ b/chromium/ui/views/controls/button/toggle_button.h
@@ -20,7 +20,7 @@ class VIEWS_EXPORT ToggleButton : public Button {
public:
METADATA_HEADER(ToggleButton);
- explicit ToggleButton(ButtonListener* listener);
+ explicit ToggleButton(ButtonListener* listener = nullptr);
~ToggleButton() override;
// AnimateIsOn() animates the state change to |is_on|; SetIsOn() doesn't.
@@ -72,7 +72,6 @@ class VIEWS_EXPORT ToggleButton : public Button {
void AddInkDropLayer(ui::Layer* ink_drop_layer) override;
void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override;
std::unique_ptr<InkDrop> CreateInkDrop() override;
- std::unique_ptr<InkDropMask> CreateInkDropMask() const override;
std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override;
SkColor GetInkDropBaseColor() const override;
diff --git a/chromium/ui/views/controls/button/toggle_button_unittest.cc b/chromium/ui/views/controls/button/toggle_button_unittest.cc
index 24e5d6a84b3..576ca185af0 100644
--- a/chromium/ui/views/controls/button/toggle_button_unittest.cc
+++ b/chromium/ui/views/controls/button/toggle_button_unittest.cc
@@ -19,8 +19,7 @@ namespace views {
class TestToggleButton : public ToggleButton {
public:
- explicit TestToggleButton(int* counter)
- : ToggleButton(nullptr), counter_(counter) {}
+ explicit TestToggleButton(int* counter) : counter_(counter) {}
~TestToggleButton() override {
// Calling SetInkDropMode() in this subclass allows this class's
// implementation of RemoveInkDropLayer() to be called. The same
diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc
index 72903d758de..0583ec98721 100644
--- a/chromium/ui/views/controls/combobox/combobox.cc
+++ b/chromium/ui/views/controls/combobox/combobox.cc
@@ -32,6 +32,7 @@
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/controls/combobox/combobox_util.h"
+#include "ui/views/controls/combobox/empty_combobox_model.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/menu/menu_config.h"
@@ -79,7 +80,7 @@ class TransparentButton : public Button {
~TransparentButton() override = default;
bool OnMousePressed(const ui::MouseEvent& mouse_event) override {
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// On Mac, comboboxes do not take focus on mouse click, but on other
// platforms they do.
parent()->RequestFocus();
@@ -111,7 +112,7 @@ class TransparentButton : public Button {
DISALLOW_COPY_AND_ASSIGN(TransparentButton);
};
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Returns the next or previous valid index (depending on |increment|'s value).
// Skips separator or disabled indices. Returns -1 if there is no valid adjacent
// index.
@@ -230,6 +231,9 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
////////////////////////////////////////////////////////////////////////////////
// Combobox, public:
+Combobox::Combobox(int text_context, int text_style)
+ : Combobox(std::make_unique<internal::EmptyComboboxModel>()) {}
+
Combobox::Combobox(std::unique_ptr<ui::ComboboxModel> model,
int text_context,
int text_style)
@@ -238,18 +242,11 @@ Combobox::Combobox(std::unique_ptr<ui::ComboboxModel> model,
}
Combobox::Combobox(ui::ComboboxModel* model, int text_context, int text_style)
- : model_(model),
- text_context_(text_context),
+ : text_context_(text_context),
text_style_(text_style),
- listener_(nullptr),
- selected_index_(model_->GetDefaultIndex()),
- invalid_(false),
- menu_model_(new ComboboxMenuModel(this, model)),
- arrow_button_(new TransparentButton(this)),
- size_to_largest_label_(true) {
- observer_.Add(model_);
- OnComboboxModelChanged(model_);
-#if defined(OS_MACOSX)
+ arrow_button_(new TransparentButton(this)) {
+ SetModel(model);
+#if defined(OS_APPLE)
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
#else
SetFocusBehavior(FocusBehavior::ALWAYS);
@@ -302,6 +299,28 @@ bool Combobox::SelectValue(const base::string16& value) {
return false;
}
+void Combobox::SetOwnedModel(std::unique_ptr<ui::ComboboxModel> model) {
+ // The swap keeps the outgoing model alive for SetModel().
+ owned_model_.swap(model);
+ SetModel(owned_model_.get());
+}
+
+void Combobox::SetModel(ui::ComboboxModel* model) {
+ DCHECK(model) << "After construction, the model must not be null.";
+
+ if (model_)
+ observer_.Remove(model_);
+
+ model_ = model;
+
+ if (model_) {
+ menu_model_ = std::make_unique<ComboboxMenuModel>(this, model_);
+ observer_.Add(model_);
+ selected_index_ = model_->GetDefaultIndex();
+ OnComboboxModelChanged(model_);
+ }
+}
+
void Combobox::SetTooltipText(const base::string16& tooltip_text) {
arrow_button_->SetTooltipText(tooltip_text);
if (accessible_name_.empty())
@@ -402,7 +421,7 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
bool show_menu = false;
int new_index = kNoSelection;
switch (e.key_code()) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
case ui::VKEY_DOWN:
case ui::VKEY_UP:
case ui::VKEY_SPACE:
@@ -448,7 +467,7 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
case ui::VKEY_SPACE:
show_menu = true;
break;
-#endif // OS_MACOSX
+#endif // OS_APPLE
default:
return false;
}
@@ -576,7 +595,9 @@ void Combobox::PaintIconAndText(gfx::Canvas* canvas) {
gfx::ImageSkia icon_skia =
GetImageSkiaFromImageModel(&icon, GetNativeTheme());
int icon_y = y + (contents_height - icon_skia.height()) / 2;
- canvas->DrawImageInt(icon_skia, x, icon_y);
+ gfx::Rect icon_bounds(x, icon_y, icon_skia.width(), icon_skia.height());
+ AdjustBoundsForRTLUI(&icon_bounds);
+ canvas->DrawImageInt(icon_skia, icon_bounds.x(), icon_bounds.y());
x += icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_LABEL_HORIZONTAL);
}
@@ -622,7 +643,7 @@ void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
gfx::Rect bounds(menu_position, lb.size());
- Button::ButtonState original_state = arrow_button_->state();
+ Button::ButtonState original_state = arrow_button_->GetState();
arrow_button_->SetState(Button::STATE_PRESSED);
// Allow |menu_runner_| to be set by the testing API, but if this method is
diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h
index 91ad60c9a14..cd3a714bd0d 100644
--- a/chromium/ui/views/controls/combobox/combobox.h
+++ b/chromium/ui/views/controls/combobox/combobox.h
@@ -47,6 +47,10 @@ class VIEWS_EXPORT Combobox : public View,
static constexpr int kDefaultComboboxTextContext = style::CONTEXT_BUTTON;
static constexpr int kDefaultComboboxTextStyle = style::STYLE_PRIMARY;
+ // A combobox with an empty model.
+ explicit Combobox(int text_context = kDefaultComboboxTextContext,
+ int text_style = kDefaultComboboxTextStyle);
+
// |model| is owned by the combobox when using this constructor.
explicit Combobox(std::unique_ptr<ui::ComboboxModel> model,
int text_context = kDefaultComboboxTextContext,
@@ -70,6 +74,9 @@ class VIEWS_EXPORT Combobox : public View,
// the found index and returns true. Otherwise simply noops and returns false.
bool SelectValue(const base::string16& value);
+ void SetOwnedModel(std::unique_ptr<ui::ComboboxModel> model);
+ void SetModel(ui::ComboboxModel* model);
+
ui::ComboboxModel* model() const { return model_; }
// Set the tooltip text, and the accessible name if it is currently empty.
@@ -153,7 +160,7 @@ class VIEWS_EXPORT Combobox : public View,
std::unique_ptr<ui::ComboboxModel> owned_model_;
// Reference to our model, which may be owned or not.
- ui::ComboboxModel* model_;
+ ui::ComboboxModel* model_ = nullptr;
// Typography context for the text written in the combobox and the options
// shown in the drop-down menu.
@@ -164,13 +171,13 @@ class VIEWS_EXPORT Combobox : public View,
const int text_style_;
// Our listener. Not owned. Notified when the selected index change.
- ComboboxListener* listener_;
+ ComboboxListener* listener_ = nullptr;
// The current selected index; -1 and means no selection.
- int selected_index_;
+ int selected_index_ = -1;
// True when the selection is visually denoted as invalid.
- bool invalid_;
+ bool invalid_ = false;
// The accessible name of this combobox.
base::string16 accessible_name_;
@@ -204,7 +211,7 @@ class VIEWS_EXPORT Combobox : public View,
// When true, the size of contents is defined by the selected label.
// Otherwise, it's defined by the widest label in the menu. If this is set to
// true, the parent view must relayout in ChildPreferredSizeChanged().
- bool size_to_largest_label_;
+ bool size_to_largest_label_ = true;
// The focus ring for this Combobox.
FocusRing* focus_ring_ = nullptr;
diff --git a/chromium/ui/views/controls/combobox/combobox_unittest.cc b/chromium/ui/views/controls/combobox/combobox_unittest.cc
index b081a0b7408..1581b5f6b5f 100644
--- a/chromium/ui/views/controls/combobox/combobox_unittest.cc
+++ b/chromium/ui/views/controls/combobox/combobox_unittest.cc
@@ -11,10 +11,12 @@
#include <vector>
#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/models/combobox_model.h"
@@ -30,6 +32,7 @@
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/combobox_test_api.h"
+#include "ui/views/test/test_ax_event_observer.h"
#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/unique_widget_ptr.h"
@@ -280,7 +283,7 @@ class ComboboxTest : public ViewsTestBase {
DISALLOW_COPY_AND_ASSIGN(ComboboxTest);
};
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Tests whether the various Mac specific keyboard shortcuts invoke the dropdown
// menu or not.
TEST_F(ComboboxTest, KeyTestMac) {
@@ -354,7 +357,7 @@ TEST_F(ComboboxTest, DisabilityTest) {
// On Mac, key events can't change the currently selected index directly for a
// combobox.
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Tests the behavior of various keyboard shortcuts on the currently selected
// index.
@@ -499,7 +502,7 @@ TEST_F(ComboboxTest, SkipMultipleSeparatorsAtEnd) {
PressKey(ui::VKEY_END);
EXPECT_EQ(6, combobox_->GetSelectedIndex());
}
-#endif // !OS_MACOSX
+#endif // !OS_APPLE
TEST_F(ComboboxTest, GetTextForRowTest) {
std::set<int> separators;
@@ -802,7 +805,7 @@ TEST_F(ComboboxTest, MenuModel) {
EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR,
menu_model->GetTypeAt(kSeparatorIndex));
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Comboboxes on Mac should have checkmarks, with the selected item checked,
EXPECT_EQ(ui::MenuModel::TYPE_CHECK, menu_model->GetTypeAt(0));
EXPECT_EQ(ui::MenuModel::TYPE_CHECK, menu_model->GetTypeAt(1));
@@ -823,4 +826,122 @@ TEST_F(ComboboxTest, MenuModel) {
EXPECT_TRUE(menu_model->IsVisibleAt(0));
}
+// Verifies setting the tooltip text will call NotifyAccessibilityEvent.
+TEST_F(ComboboxTest, SetTooltipTextNotifiesAccessibilityEvent) {
+ InitCombobox(nullptr);
+ base::string16 test_tooltip_text = ASCIIToUTF16("Test Tooltip Text");
+ test::TestAXEventObserver observer;
+ EXPECT_EQ(0, observer.text_changed_event_count());
+ combobox_->SetTooltipText(test_tooltip_text);
+ EXPECT_EQ(1, observer.text_changed_event_count());
+ EXPECT_EQ(test_tooltip_text, combobox_->GetAccessibleName());
+ ui::AXNodeData data;
+ combobox_->GetAccessibleNodeData(&data);
+ const std::string& name =
+ data.GetStringAttribute(ax::mojom::StringAttribute::kName);
+ EXPECT_EQ(test_tooltip_text, ASCIIToUTF16(name));
+}
+
+namespace {
+
+using ComboboxDefaultTest = ViewsTestBase;
+
+class ConfigurableComboboxModel final : public ui::ComboboxModel {
+ public:
+ explicit ConfigurableComboboxModel(bool* destroyed = nullptr)
+ : destroyed_(destroyed) {
+ if (destroyed_)
+ *destroyed_ = false;
+ }
+ ConfigurableComboboxModel(ConfigurableComboboxModel&) = delete;
+ ConfigurableComboboxModel& operator=(const ConfigurableComboboxModel&) =
+ delete;
+ ~ConfigurableComboboxModel() override {
+ if (destroyed_)
+ *destroyed_ = true;
+ }
+
+ // ui::ComboboxModel:
+ int GetItemCount() const override { return item_count_; }
+ base::string16 GetItemAt(int index) const override {
+ DCHECK_LT(index, item_count_);
+ return base::NumberToString16(index);
+ }
+ int GetDefaultIndex() const override { return default_index_; }
+
+ void SetItemCount(int item_count) { item_count_ = item_count; }
+
+ void SetDefaultIndex(int default_index) { default_index_ = default_index; }
+
+ private:
+ bool* const destroyed_;
+ int item_count_ = 0;
+ int default_index_ = -1;
+};
+
+} // namespace
+
+TEST_F(ComboboxDefaultTest, Default) {
+ auto combobox = std::make_unique<Combobox>();
+ EXPECT_EQ(0, combobox->GetRowCount());
+ EXPECT_EQ(-1, combobox->GetSelectedRow());
+}
+
+TEST_F(ComboboxDefaultTest, SetModel) {
+ bool destroyed = false;
+ std::unique_ptr<ConfigurableComboboxModel> model =
+ std::make_unique<ConfigurableComboboxModel>(&destroyed);
+ model->SetItemCount(42);
+ model->SetDefaultIndex(27);
+ {
+ auto combobox = std::make_unique<Combobox>();
+ combobox->SetModel(model.get());
+ EXPECT_EQ(42, combobox->GetRowCount());
+ EXPECT_EQ(27, combobox->GetSelectedRow());
+ }
+ EXPECT_FALSE(destroyed);
+}
+
+TEST_F(ComboboxDefaultTest, SetOwnedModel) {
+ bool destroyed = false;
+ std::unique_ptr<ConfigurableComboboxModel> model =
+ std::make_unique<ConfigurableComboboxModel>(&destroyed);
+ model->SetItemCount(42);
+ model->SetDefaultIndex(27);
+ {
+ auto combobox = std::make_unique<Combobox>();
+ combobox->SetOwnedModel(std::move(model));
+ EXPECT_EQ(42, combobox->GetRowCount());
+ EXPECT_EQ(27, combobox->GetSelectedRow());
+ }
+ EXPECT_TRUE(destroyed);
+}
+
+TEST_F(ComboboxDefaultTest, SetModelOverwriteOwned) {
+ bool destroyed = false;
+ std::unique_ptr<ConfigurableComboboxModel> model =
+ std::make_unique<ConfigurableComboboxModel>(&destroyed);
+ auto combobox = std::make_unique<Combobox>();
+ combobox->SetModel(model.get());
+ ASSERT_FALSE(destroyed);
+ combobox->SetOwnedModel(std::make_unique<ConfigurableComboboxModel>());
+ EXPECT_FALSE(destroyed);
+}
+
+TEST_F(ComboboxDefaultTest, SetOwnedModelOverwriteOwned) {
+ bool destroyed_first = false;
+ bool destroyed_second = false;
+ {
+ auto combobox = std::make_unique<Combobox>();
+ combobox->SetOwnedModel(
+ std::make_unique<ConfigurableComboboxModel>(&destroyed_first));
+ ASSERT_FALSE(destroyed_first);
+ combobox->SetOwnedModel(
+ std::make_unique<ConfigurableComboboxModel>(&destroyed_second));
+ EXPECT_TRUE(destroyed_first);
+ ASSERT_FALSE(destroyed_second);
+ }
+ EXPECT_TRUE(destroyed_second);
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/combobox/empty_combobox_model.cc b/chromium/ui/views/controls/combobox/empty_combobox_model.cc
new file mode 100644
index 00000000000..f6b9bdbbb70
--- /dev/null
+++ b/chromium/ui/views/controls/combobox/empty_combobox_model.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/combobox/empty_combobox_model.h"
+
+#include "base/notreached.h"
+#include "base/strings/string16.h"
+
+namespace views {
+namespace internal {
+
+EmptyComboboxModel::EmptyComboboxModel() = default;
+EmptyComboboxModel::~EmptyComboboxModel() = default;
+
+int EmptyComboboxModel::GetItemCount() const {
+ return 0;
+}
+
+base::string16 EmptyComboboxModel::GetItemAt(int index) const {
+ NOTREACHED();
+ return base::string16();
+}
+
+int EmptyComboboxModel::GetDefaultIndex() const {
+ return -1;
+}
+
+} // namespace internal
+} // namespace views
diff --git a/chromium/ui/views/controls/combobox/empty_combobox_model.h b/chromium/ui/views/controls/combobox/empty_combobox_model.h
new file mode 100644
index 00000000000..66793212d1d
--- /dev/null
+++ b/chromium/ui/views/controls/combobox/empty_combobox_model.h
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_COMBOBOX_EMPTY_COMBOBOX_MODEL_H_
+#define UI_VIEWS_CONTROLS_COMBOBOX_EMPTY_COMBOBOX_MODEL_H_
+
+#include "ui/base/models/combobox_model.h"
+
+namespace views {
+namespace internal {
+
+// An empty model for a combo box.
+class EmptyComboboxModel final : public ui::ComboboxModel {
+ public:
+ EmptyComboboxModel();
+ EmptyComboboxModel(EmptyComboboxModel&) = delete;
+ EmptyComboboxModel& operator=(const EmptyComboboxModel&) = delete;
+ ~EmptyComboboxModel() override;
+
+ // ui::ComboboxModel:
+ int GetItemCount() const override;
+ base::string16 GetItemAt(int index) const override;
+ int GetDefaultIndex() const override;
+};
+
+} // namespace internal
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_COMBOBOX_EMPTY_COMBOBOX_MODEL_H_
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
index b4c14a33505..6073e099014 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -5,7 +5,6 @@
#include "ui/views/controls/editable_combobox/editable_combobox.h"
#include <memory>
-#include <utility>
#include <vector>
#include "base/bind.h"
@@ -45,7 +44,7 @@
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/controls/combobox/combobox_util.h"
-#include "ui/views/controls/editable_combobox/editable_combobox_listener.h"
+#include "ui/views/controls/combobox/empty_combobox_model.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/menu_types.h"
@@ -303,6 +302,9 @@ class EditableCombobox::EditableComboboxPreTargetHandler
DISALLOW_COPY_AND_ASSIGN(EditableComboboxPreTargetHandler);
};
+EditableCombobox::EditableCombobox()
+ : EditableCombobox(std::make_unique<internal::EmptyComboboxModel>()) {}
+
EditableCombobox::EditableCombobox(
std::unique_ptr<ui::ComboboxModel> combobox_model,
const bool filter_on_edit,
@@ -312,16 +314,13 @@ EditableCombobox::EditableCombobox(
const int text_style,
const bool display_arrow)
: textfield_(new Textfield()),
- combobox_model_(std::move(combobox_model)),
- menu_model_(
- std::make_unique<EditableComboboxMenuModel>(this,
- combobox_model_.get(),
- filter_on_edit,
- show_on_empty)),
text_context_(text_context),
text_style_(text_style),
type_(type),
+ filter_on_edit_(filter_on_edit),
+ show_on_empty_(show_on_empty),
showing_password_text_(type != Type::kPassword) {
+ SetModel(std::move(combobox_model));
observer_.Add(textfield_);
textfield_->set_controller(this);
textfield_->SetFontList(GetFontList());
@@ -343,6 +342,13 @@ EditableCombobox::~EditableCombobox() {
textfield_->set_controller(nullptr);
}
+void EditableCombobox::SetModel(std::unique_ptr<ui::ComboboxModel> model) {
+ CloseMenu();
+ combobox_model_.swap(model);
+ menu_model_ = std::make_unique<EditableComboboxMenuModel>(
+ this, combobox_model_.get(), filter_on_edit_, show_on_empty_);
+}
+
const base::string16& EditableCombobox::GetText() const {
return textfield_->GetText();
}
@@ -475,15 +481,15 @@ void EditableCombobox::OnItemSelected(int index) {
void EditableCombobox::HandleNewContent(const base::string16& new_content) {
DCHECK(GetText() == new_content);
- // We notify |listener_| before updating |menu_model_|'s items shown. This
+ // We notify |callback_| before updating |menu_model_|'s items shown. This
// gives the user a chance to modify the ComboboxModel beforehand if they wish
// to do so.
// We disable UpdateItemsShown while we notify the listener in case it
// modifies the ComboboxModel, then calls OnComboboxModelChanged and thus
// UpdateItemsShown. That way UpdateItemsShown doesn't do its work twice.
- if (listener_) {
+ if (!content_changed_callback_.is_null()) {
menu_model_->DisableUpdateItemsShown();
- listener_->OnContentChanged(this);
+ content_changed_callback_.Run();
menu_model_->EnableUpdateItemsShown();
}
menu_model_->UpdateItemsShown();
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.h b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
index e4a0d3c3698..fdd5882bab5 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
@@ -6,7 +6,9 @@
#define UI_VIEWS_CONTROLS_EDITABLE_COMBOBOX_EDITABLE_COMBOBOX_H_
#include <memory>
+#include <utility>
+#include "base/callback.h"
#include "base/macros.h"
#include "base/scoped_observer.h"
#include "base/strings/string16.h"
@@ -32,7 +34,6 @@ class Event;
namespace views {
class EditableComboboxMenuModel;
-class EditableComboboxListener;
class EditableComboboxPreTargetHandler;
class MenuRunner;
class Textfield;
@@ -55,6 +56,8 @@ class VIEWS_EXPORT EditableCombobox
static constexpr int kDefaultTextContext = style::CONTEXT_BUTTON;
static constexpr int kDefaultTextStyle = style::STYLE_PRIMARY;
+ EditableCombobox();
+
// |combobox_model|: The ComboboxModel that gives us the items to show in the
// menu.
// |filter_on_edit|: Whether to only show the items that are case-insensitive
@@ -65,24 +68,25 @@ class VIEWS_EXPORT EditableCombobox
// |text_context| and |text_style|: Together these indicate the font to use.
// |display_arrow|: Whether to display an arrow in the combobox to indicate
// that there is a drop-down list.
- EditableCombobox(std::unique_ptr<ui::ComboboxModel> combobox_model,
- bool filter_on_edit,
- bool show_on_empty,
- Type type = Type::kRegular,
- int text_context = kDefaultTextContext,
- int text_style = kDefaultTextStyle,
- bool display_arrow = true);
+ explicit EditableCombobox(std::unique_ptr<ui::ComboboxModel> combobox_model,
+ bool filter_on_edit = false,
+ bool show_on_empty = true,
+ Type type = Type::kRegular,
+ int text_context = kDefaultTextContext,
+ int text_style = kDefaultTextStyle,
+ bool display_arrow = true);
~EditableCombobox() override;
+ void SetModel(std::unique_ptr<ui::ComboboxModel> model);
+
const base::string16& GetText() const;
void SetText(const base::string16& text);
const gfx::FontList& GetFontList() const;
- // Sets the listener that we will call when a selection is made.
- void set_listener(EditableComboboxListener* listener) {
- listener_ = listener;
+ void set_callback(base::RepeatingClosure callback) {
+ content_changed_callback_ = std::move(callback);
}
// Selects the specified logical text range for the textfield.
@@ -167,11 +171,16 @@ class VIEWS_EXPORT EditableCombobox
const Type type_;
+ // Whether to adapt the items shown to the textfield content.
+ bool filter_on_edit_;
+
+ // Whether to show options when the textfield is empty.
+ bool show_on_empty_;
+
// Set while the drop-down is showing.
std::unique_ptr<MenuRunner> menu_runner_;
- // Our listener. Not owned. Notified when the selected index changes.
- EditableComboboxListener* listener_ = nullptr;
+ base::RepeatingClosure content_changed_callback_;
// Whether we are currently showing the passwords for type
// Type::kPassword.
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox_listener.h b/chromium/ui/views/controls/editable_combobox/editable_combobox_listener.h
deleted file mode 100644
index e5545974a4c..00000000000
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox_listener.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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_EDITABLE_COMBOBOX_EDITABLE_COMBOBOX_LISTENER_H_
-#define UI_VIEWS_CONTROLS_EDITABLE_COMBOBOX_EDITABLE_COMBOBOX_LISTENER_H_
-
-#include "ui/views/views_export.h"
-
-namespace views {
-
-class EditableCombobox;
-
-// Interface used to notify consumers when something interesting happens to an
-// EditableCombobox.
-class VIEWS_EXPORT EditableComboboxListener {
- public:
- // Called when the content of the main textfield changes, either as the user
- // types or after selecting an option from the menu.
- virtual void OnContentChanged(EditableCombobox* editable_combobox) = 0;
-
- protected:
- virtual ~EditableComboboxListener() = default;
-};
-
-} // namespace views
-
-#endif // UI_VIEWS_CONTROLS_EDITABLE_COMBOBOX_EDITABLE_COMBOBOX_LISTENER_H_
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
index 550aa40005b..5e56c1d8ce1 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
@@ -9,6 +9,7 @@
#include <utility>
#include <vector>
+#include "base/callback.h"
#include "base/macros.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
@@ -28,7 +29,6 @@
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/render_text.h"
#include "ui/views/context_menu_controller.h"
-#include "ui/views/controls/editable_combobox/editable_combobox_listener.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/menu_test_utils.h"
@@ -43,22 +43,6 @@ namespace {
using base::ASCIIToUTF16;
using views::test::WaitForMenuClosureAnimation;
-class DummyListener : public EditableComboboxListener {
- public:
- DummyListener() = default;
- ~DummyListener() override = default;
- void OnContentChanged(EditableCombobox* editable_combobox) override {
- change_count_++;
- }
-
- int change_count() const { return change_count_; }
-
- private:
- int change_count_ = 0;
-
- DISALLOW_COPY_AND_ASSIGN(DummyListener);
-};
-
// No-op test double of a ContextMenuController
class TestContextMenuController : public ContextMenuController {
public:
@@ -116,6 +100,9 @@ class EditableComboboxTest : public ViewsTestBase {
bool shift = false,
bool ctrl_cmd = false);
+ int change_count() const { return change_count_; }
+ void OnContentChanged() { ++change_count_; }
+
// The widget where the control will appear.
Widget* widget_ = nullptr;
@@ -128,8 +115,7 @@ class EditableComboboxTest : public ViewsTestBase {
// scenarios.
View* parent_of_combobox_ = nullptr;
- // Listener for our EditableCombobox.
- std::unique_ptr<DummyListener> listener_;
+ int change_count_ = 0;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
@@ -168,8 +154,8 @@ void EditableComboboxTest::InitEditableCombobox(
combobox_ =
new EditableCombobox(std::make_unique<ui::SimpleComboboxModel>(items),
filter_on_edit, show_on_empty, type);
- listener_ = std::make_unique<DummyListener>();
- combobox_->set_listener(listener_.get());
+ combobox_->set_callback(base::BindRepeating(
+ &EditableComboboxTest::OnContentChanged, base::Unretained(this)));
combobox_->SetID(2);
dummy_focusable_view_ = new View();
dummy_focusable_view_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
@@ -195,7 +181,7 @@ void EditableComboboxTest::InitWidget() {
container->AddChildView(dummy_focusable_view_);
widget_->Show();
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// The event loop needs to be flushed here, otherwise in various tests:
// 1. The actual showing of the native window backing the widget gets delayed
// until a spin of the event loop.
@@ -267,7 +253,7 @@ void EditableComboboxTest::SendKeyEvent(ui::KeyboardCode key_code,
const bool alt,
const bool shift,
const bool ctrl_cmd) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
bool command = ctrl_cmd;
bool control = false;
#else
@@ -404,7 +390,7 @@ TEST_F(EditableComboboxTest, EndOrHomeMovesToBeginningOrEndOfText) {
EXPECT_EQ(ASCIIToUTF16("xabcy"), combobox_->GetText());
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
TEST_F(EditableComboboxTest, AltLeftOrRightMovesToNextWords) {
InitEditableCombobox();
@@ -714,30 +700,30 @@ TEST_F(EditableComboboxTest, DontShowOnEmpty) {
ASSERT_EQ(ASCIIToUTF16("item1"), combobox_->GetItemForTest(1));
}
-TEST_F(EditableComboboxTest, NoFilteringNotifiesListener) {
+TEST_F(EditableComboboxTest, NoFilteringNotifiesCallback) {
std::vector<base::string16> items = {ASCIIToUTF16("item0"),
ASCIIToUTF16("item1")};
InitEditableCombobox(items, /*filter_on_edit=*/false, /*show_on_empty=*/true);
- ASSERT_EQ(0, listener_->change_count());
+ ASSERT_EQ(0, change_count());
combobox_->SetText(ASCIIToUTF16("a"));
- ASSERT_EQ(1, listener_->change_count());
+ ASSERT_EQ(1, change_count());
combobox_->SetText(ASCIIToUTF16("ab"));
- ASSERT_EQ(2, listener_->change_count());
+ ASSERT_EQ(2, change_count());
}
-TEST_F(EditableComboboxTest, FilteringNotifiesListener) {
+TEST_F(EditableComboboxTest, FilteringNotifiesCallback) {
std::vector<base::string16> items = {ASCIIToUTF16("item0"),
ASCIIToUTF16("item1")};
InitEditableCombobox(items, /*filter_on_edit=*/true, /*show_on_empty=*/true);
- ASSERT_EQ(0, listener_->change_count());
+ ASSERT_EQ(0, change_count());
combobox_->SetText(ASCIIToUTF16("i"));
- ASSERT_EQ(1, listener_->change_count());
+ ASSERT_EQ(1, change_count());
combobox_->SetText(ASCIIToUTF16("ix"));
- ASSERT_EQ(2, listener_->change_count());
+ ASSERT_EQ(2, change_count());
combobox_->SetText(ASCIIToUTF16("ixy"));
- ASSERT_EQ(3, listener_->change_count());
+ ASSERT_EQ(3, change_count());
}
TEST_F(EditableComboboxTest, PasswordCanBeHiddenAndRevealed) {
@@ -843,5 +829,67 @@ TEST_F(EditableComboboxTest, NoCrashWithoutWidget) {
combobox->RevealPasswords(true);
}
+using EditableComboboxDefaultTest = ViewsTestBase;
+
+class ConfigurableComboboxModel final : public ui::ComboboxModel {
+ public:
+ explicit ConfigurableComboboxModel(bool* destroyed = nullptr)
+ : destroyed_(destroyed) {
+ if (destroyed_)
+ *destroyed_ = false;
+ }
+ ConfigurableComboboxModel(ConfigurableComboboxModel&) = delete;
+ ConfigurableComboboxModel& operator=(const ConfigurableComboboxModel&) =
+ delete;
+ ~ConfigurableComboboxModel() override {
+ if (destroyed_)
+ *destroyed_ = true;
+ }
+
+ // ui::ComboboxModel:
+ int GetItemCount() const override { return item_count_; }
+ base::string16 GetItemAt(int index) const override {
+ DCHECK_LT(index, item_count_);
+ return base::NumberToString16(index);
+ }
+
+ void SetItemCount(int item_count) { item_count_ = item_count; }
+
+ private:
+ bool* const destroyed_;
+ int item_count_ = 0;
+};
+
} // namespace
+
+TEST_F(EditableComboboxDefaultTest, Default) {
+ auto combobox = std::make_unique<EditableCombobox>();
+ EXPECT_EQ(0, combobox->GetItemCountForTest());
+}
+
+TEST_F(EditableComboboxDefaultTest, SetModel) {
+ std::unique_ptr<ConfigurableComboboxModel> model =
+ std::make_unique<ConfigurableComboboxModel>();
+ model->SetItemCount(42);
+ auto combobox = std::make_unique<EditableCombobox>();
+ combobox->SetModel(std::move(model));
+ EXPECT_EQ(42, combobox->GetItemCountForTest());
+}
+
+TEST_F(EditableComboboxDefaultTest, SetModelOverwrite) {
+ bool destroyed_first = false;
+ bool destroyed_second = false;
+ {
+ auto combobox = std::make_unique<EditableCombobox>();
+ combobox->SetModel(
+ std::make_unique<ConfigurableComboboxModel>(&destroyed_first));
+ ASSERT_FALSE(destroyed_first);
+ combobox->SetModel(
+ std::make_unique<ConfigurableComboboxModel>(&destroyed_second));
+ EXPECT_TRUE(destroyed_first);
+ ASSERT_FALSE(destroyed_second);
+ }
+ EXPECT_TRUE(destroyed_second);
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/focusable_border.cc b/chromium/ui/views/controls/focusable_border.cc
index bb8f04bfdbb..d71c9e1f839 100644
--- a/chromium/ui/views/controls/focusable_border.cc
+++ b/chromium/ui/views/controls/focusable_border.cc
@@ -10,7 +10,6 @@
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
-#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/skia_util.h"
#include "ui/native_theme/native_theme.h"
diff --git a/chromium/ui/views/controls/highlight_path_generator.cc b/chromium/ui/views/controls/highlight_path_generator.cc
index 01920dd775d..7899b1affc0 100644
--- a/chromium/ui/views/controls/highlight_path_generator.cc
+++ b/chromium/ui/views/controls/highlight_path_generator.cc
@@ -51,8 +51,11 @@ base::Optional<gfx::RRectF> HighlightPathGenerator::GetRoundRect(
base::Optional<gfx::RRectF> HighlightPathGenerator::GetRoundRect(
const View* view) {
- gfx::Rect bounds(view->GetLocalBounds());
+ gfx::Rect bounds =
+ use_contents_bounds_ ? view->GetContentsBounds() : view->GetLocalBounds();
bounds.Inset(insets_);
+ if (use_mirrored_rect_)
+ bounds = view->GetMirroredRect(bounds);
return GetRoundRect(gfx::RectF(bounds));
}
@@ -99,13 +102,11 @@ void InstallCircleHighlightPathGenerator(View* view,
view, std::make_unique<CircleHighlightPathGenerator>(insets));
}
-SkPath PillHighlightPathGenerator::GetHighlightPath(const View* view) {
- const SkRect rect = gfx::RectToSkRect(view->GetLocalBounds());
- const SkScalar corner_radius =
- SkScalarHalf(std::min(rect.width(), rect.height()));
-
- return SkPath().addRoundRect(gfx::RectToSkRect(view->GetLocalBounds()),
- corner_radius, corner_radius);
+base::Optional<gfx::RRectF> PillHighlightPathGenerator::GetRoundRect(
+ const gfx::RectF& rect) {
+ gfx::RectF bounds = rect;
+ const float corner_radius = std::min(bounds.width(), bounds.height()) / 2.f;
+ return gfx::RRectF(bounds, corner_radius);
}
void InstallPillHighlightPathGenerator(View* view) {
diff --git a/chromium/ui/views/controls/highlight_path_generator.h b/chromium/ui/views/controls/highlight_path_generator.h
index e64d0d48b1b..f5415715209 100644
--- a/chromium/ui/views/controls/highlight_path_generator.h
+++ b/chromium/ui/views/controls/highlight_path_generator.h
@@ -26,8 +26,6 @@ class View;
// effects.
class VIEWS_EXPORT HighlightPathGenerator {
public:
- // TODO(http://crbug.com/1056490): Remove this constructor in favor of the one
- // that takes |insets|.
HighlightPathGenerator();
explicit HighlightPathGenerator(const gfx::Insets& insets);
virtual ~HighlightPathGenerator();
@@ -50,8 +48,27 @@ class VIEWS_EXPORT HighlightPathGenerator {
virtual base::Optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect);
base::Optional<gfx::RRectF> GetRoundRect(const View* view);
+ void set_use_contents_bounds(bool use_contents_bounds) {
+ use_contents_bounds_ = use_contents_bounds;
+ }
+
+ void set_use_mirrored_rect(bool use_mirrored_rect) {
+ use_mirrored_rect_ = use_mirrored_rect;
+ }
+
private:
const gfx::Insets insets_;
+
+ // When set uses the view's content bounds instead of its local bounds.
+ // TODO(http://crbug.com/1056490): Investigate removing this and seeing if all
+ // ink drops / focus rings should use the content bounds.
+ bool use_contents_bounds_ = false;
+
+ // When set uses the mirror rect in RTL. This should not be needed for focus
+ // rings paths as they handle RTL themselves.
+ // TODO(http://crbug.com/1056490): Investigate moving FocusRing RTL to this
+ // class and removing this bool.
+ bool use_mirrored_rect_ = false;
};
// Sets a highlight path that is empty. This is used for ink drops that want to
@@ -114,15 +131,11 @@ class VIEWS_EXPORT PillHighlightPathGenerator : public HighlightPathGenerator {
delete;
// HighlightPathGenerator:
- SkPath GetHighlightPath(const View* view) override;
+ base::Optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override;
};
void VIEWS_EXPORT InstallPillHighlightPathGenerator(View* view);
-// TODO(http://crbug.com/1056490): Investigate if we can make |radius| optional
-// for FixedSizeCircleHighlightPathGenerator and
-// RoundRectHighlightPathGenerator, and combine them with
-// CircleHighlightPathGenerator and PillHighlightPathGenerator respectively.
// Sets a centered fixed-size circular highlight path.
class VIEWS_EXPORT FixedSizeCircleHighlightPathGenerator
: public HighlightPathGenerator {
diff --git a/chromium/ui/views/controls/image_view.cc b/chromium/ui/views/controls/image_view.cc
index 4ca005dbdc9..840bb74c64e 100644
--- a/chromium/ui/views/controls/image_view.cc
+++ b/chromium/ui/views/controls/image_view.cc
@@ -99,6 +99,7 @@ void ImageView::SetAccessibleName(const base::string16& accessible_name) {
accessible_name_ = accessible_name;
OnPropertyChanged(&accessible_name_, kPropertyEffectsNone);
+ NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
}
const base::string16& ImageView::GetAccessibleName() const {
diff --git a/chromium/ui/views/controls/image_view_unittest.cc b/chromium/ui/views/controls/image_view_unittest.cc
index f6b9ee1c3ba..e5d94793918 100644
--- a/chromium/ui/views/controls/image_view_unittest.cc
+++ b/chromium/ui/views/controls/image_view_unittest.cc
@@ -5,18 +5,22 @@
#include "ui/views/controls/image_view.h"
#include <memory>
+#include <string>
#include <utility>
#include "base/i18n/rtl.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.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/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/border.h"
#include "ui/views/layout/box_layout.h"
+#include "ui/views/test/test_ax_event_observer.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
@@ -144,6 +148,21 @@ TEST_P(ImageViewTest, ImageOriginForCustomViewBounds) {
EXPECT_EQ(image_view_bounds, image_view()->bounds());
}
+// Verifies setting the accessible name will be call NotifyAccessibilityEvent.
+TEST_P(ImageViewTest, SetAccessibleNameNotifiesAccessibilityEvent) {
+ base::string16 test_tooltip_text = base::ASCIIToUTF16("Test Tooltip Text");
+ test::TestAXEventObserver observer;
+ EXPECT_EQ(0, observer.text_changed_event_count());
+ image_view()->SetAccessibleName(test_tooltip_text);
+ EXPECT_EQ(1, observer.text_changed_event_count());
+ EXPECT_EQ(test_tooltip_text, image_view()->GetAccessibleName());
+ ui::AXNodeData data;
+ image_view()->GetAccessibleNodeData(&data);
+ const std::string& name =
+ data.GetStringAttribute(ax::mojom::StringAttribute::kName);
+ EXPECT_EQ(test_tooltip_text, base::ASCIIToUTF16(name));
+}
+
INSTANTIATE_TEST_SUITE_P(All,
ImageViewTest,
::testing::Values(Axis::kHorizontal, Axis::kVertical));
diff --git a/chromium/ui/views/controls/label.cc b/chromium/ui/views/controls/label.cc
index febce15ff86..d0b00369f1a 100644
--- a/chromium/ui/views/controls/label.cc
+++ b/chromium/ui/views/controls/label.cc
@@ -70,7 +70,6 @@ Label::Label(const base::string16& text,
text_style_(text_style),
context_menu_contents_(this) {
Init(text, style::GetFont(text_context, text_style), directionality_mode);
- SetLineHeight(style::GetLineHeight(text_context, text_style));
}
Label::Label(const base::string16& text, const CustomFont& font)
@@ -89,7 +88,8 @@ const gfx::FontList& Label::GetDefaultFontList() {
void Label::SetFontList(const gfx::FontList& font_list) {
full_text_->SetFontList(font_list);
- ResetLayout();
+ ClearDisplayText();
+ PreferredSizeChanged();
}
const base::string16& Label::GetText() const {
@@ -100,7 +100,9 @@ void Label::SetText(const base::string16& new_text) {
if (new_text == GetText())
return;
full_text_->SetText(new_text);
- OnPropertyChanged(&full_text_ + kLabelText, kPropertyEffectsLayout);
+ ClearDisplayText();
+ OnPropertyChanged(&full_text_ + kLabelText,
+ kPropertyEffectsPreferredSizeChanged);
stored_selection_range_ = gfx::Range::InvalidRange();
}
@@ -117,6 +119,8 @@ 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?
UpdateColorsFromTheme();
OnPropertyChanged(&text_style_, kPropertyEffectsPreferredSizeChanged);
}
@@ -130,6 +134,7 @@ void Label::SetAutoColorReadabilityEnabled(
if (auto_color_readability_enabled_ == auto_color_readability_enabled)
return;
auto_color_readability_enabled_ = auto_color_readability_enabled;
+ RecalculateColors();
OnPropertyChanged(&auto_color_readability_enabled_, kPropertyEffectsPaint);
}
@@ -142,6 +147,7 @@ void Label::SetEnabledColor(SkColor color) {
return;
requested_enabled_color_ = color;
enabled_color_set_ = true;
+ RecalculateColors();
OnPropertyChanged(&requested_enabled_color_, kPropertyEffectsPaint);
}
@@ -154,6 +160,7 @@ void Label::SetBackgroundColor(SkColor color) {
return;
background_color_ = color;
background_color_set_ = true;
+ RecalculateColors();
OnPropertyChanged(&background_color_, kPropertyEffectsPaint);
}
@@ -166,6 +173,7 @@ void Label::SetSelectionTextColor(SkColor color) {
return;
requested_selection_text_color_ = color;
selection_text_color_set_ = true;
+ RecalculateColors();
OnPropertyChanged(&requested_selection_text_color_, kPropertyEffectsPaint);
}
@@ -178,6 +186,7 @@ void Label::SetSelectionBackgroundColor(SkColor color) {
return;
selection_background_color_ = color;
selection_background_color_set_ = true;
+ RecalculateColors();
OnPropertyChanged(&selection_background_color_, kPropertyEffectsPaint);
}
@@ -189,7 +198,9 @@ void Label::SetShadows(const gfx::ShadowValues& shadows) {
if (full_text_->shadows() == shadows)
return;
full_text_->set_shadows(shadows);
- OnPropertyChanged(&full_text_ + kLabelShadows, kPropertyEffectsLayout);
+ ClearDisplayText();
+ OnPropertyChanged(&full_text_ + kLabelShadows,
+ kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetSubpixelRenderingEnabled() const {
@@ -200,6 +211,7 @@ void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
if (subpixel_rendering_enabled_ == subpixel_rendering_enabled)
return;
subpixel_rendering_enabled_ = subpixel_rendering_enabled;
+ ApplyTextColors();
OnPropertyChanged(&subpixel_rendering_enabled_, kPropertyEffectsPaint);
}
@@ -212,8 +224,9 @@ void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
if (GetHorizontalAlignment() == alignment)
return;
full_text_->SetHorizontalAlignment(alignment);
+ ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelHorizontalAlignment,
- kPropertyEffectsLayout);
+ kPropertyEffectsPaint);
}
gfx::VerticalAlignment Label::GetVerticalAlignment() const {
@@ -224,20 +237,27 @@ void Label::SetVerticalAlignment(gfx::VerticalAlignment alignment) {
if (GetVerticalAlignment() == alignment)
return;
full_text_->SetVerticalAlignment(alignment);
- // TODO(dfried): consider if this should be kPropertyEffectsPaint instead.
+ ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelVerticalAlignment,
- kPropertyEffectsLayout);
+ kPropertyEffectsPaint);
}
int Label::GetLineHeight() const {
- return full_text_->min_line_height();
+ // TODO(pkasting): If we can replace SetFontList() with context/style setter
+ // calls, we can eliminate the reference to font_list().GetHeight() here.
+ return line_height_.value_or(
+ std::max(style::GetLineHeight(text_context_, text_style_),
+ font_list().GetHeight()));
}
-void Label::SetLineHeight(int height) {
- if (GetLineHeight() == height)
+void Label::SetLineHeight(int line_height) {
+ if (line_height_ == line_height)
return;
- full_text_->SetMinLineHeight(height);
- OnPropertyChanged(&full_text_ + kLabelLineHeight, kPropertyEffectsLayout);
+ line_height_ = line_height;
+ full_text_->SetMinLineHeight(line_height);
+ ClearDisplayText();
+ OnPropertyChanged(&full_text_ + kLabelLineHeight,
+ kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetMultiLine() const {
@@ -251,7 +271,8 @@ void Label::SetMultiLine(bool multi_line) {
return;
multi_line_ = multi_line;
full_text_->SetMultiline(multi_line);
- OnPropertyChanged(&multi_line_, kPropertyEffectsLayout);
+ ClearDisplayText();
+ OnPropertyChanged(&multi_line_, kPropertyEffectsPreferredSizeChanged);
}
int Label::GetMaxLines() const {
@@ -262,7 +283,7 @@ void Label::SetMaxLines(int max_lines) {
if (max_lines_ == max_lines)
return;
max_lines_ = max_lines;
- OnPropertyChanged(&max_lines_, kPropertyEffectsLayout);
+ OnPropertyChanged(&max_lines_, kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetObscured() const {
@@ -273,9 +294,11 @@ void Label::SetObscured(bool obscured) {
if (this->GetObscured() == obscured)
return;
full_text_->SetObscured(obscured);
+ ClearDisplayText();
if (obscured)
SetSelectable(false);
- OnPropertyChanged(&full_text_ + kLabelObscured, kPropertyEffectsLayout);
+ OnPropertyChanged(&full_text_ + kLabelObscured,
+ kPropertyEffectsPreferredSizeChanged);
}
bool Label::IsDisplayTextTruncated() const {
@@ -290,8 +313,7 @@ bool Label::IsDisplayTextTruncated() const {
}
bool Label::GetAllowCharacterBreak() const {
- return full_text_->word_wrap_behavior() == gfx::WRAP_LONG_WORDS ? true
- : false;
+ return full_text_->word_wrap_behavior() == gfx::WRAP_LONG_WORDS;
}
void Label::SetAllowCharacterBreak(bool allow_character_break) {
@@ -300,8 +322,9 @@ void Label::SetAllowCharacterBreak(bool allow_character_break) {
if (full_text_->word_wrap_behavior() == behavior)
return;
full_text_->SetWordWrapBehavior(behavior);
+ ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelAllowCharacterBreak,
- kPropertyEffectsLayout);
+ kPropertyEffectsPreferredSizeChanged);
}
size_t Label::GetTextIndexOfLine(size_t line) const {
@@ -322,7 +345,8 @@ void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
if (elide_behavior_ == elide_behavior)
return;
elide_behavior_ = elide_behavior;
- OnPropertyChanged(&elide_behavior_, kPropertyEffectsLayout);
+ ClearDisplayText();
+ OnPropertyChanged(&elide_behavior_, kPropertyEffectsPreferredSizeChanged);
}
base::string16 Label::GetTooltipText() const {
@@ -365,7 +389,7 @@ void Label::SetMaximumWidth(int max_width) {
if (max_width_ == max_width)
return;
max_width_ = max_width;
- OnPropertyChanged(&max_width_, kPropertyEffectsLayout);
+ OnPropertyChanged(&max_width_, kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetCollapseWhenHidden() const {
@@ -385,7 +409,6 @@ size_t Label::GetRequiredLines() const {
}
base::string16 Label::GetDisplayTextForTesting() {
- ClearDisplayText();
MaybeBuildDisplayText();
return display_text_ ? display_text_->GetDisplayText() : base::string16();
}
@@ -487,7 +510,7 @@ gfx::Size Label::GetMinimumSize() const {
return gfx::Size();
// Always reserve vertical space for at least one line.
- gfx::Size size(0, std::max(font_list().GetHeight(), GetLineHeight()));
+ gfx::Size size(0, GetLineHeight());
if (elide_behavior_ == gfx::ELIDE_HEAD ||
elide_behavior_ == gfx::ELIDE_MIDDLE ||
elide_behavior_ == gfx::ELIDE_TAIL ||
@@ -517,7 +540,7 @@ int Label::GetHeightForWidth(int w) const {
w -= GetInsets().width();
int height = 0;
- int base_line_height = std::max(GetLineHeight(), font_list().GetHeight());
+ int base_line_height = GetLineHeight();
if (!GetMultiLine() || GetText().empty() || w <= 0) {
height = base_line_height;
} else {
@@ -576,15 +599,6 @@ base::string16 Label::GetTooltipText(const gfx::Point& p) const {
return base::string16();
}
-void Label::OnHandlePropertyChangeEffects(PropertyEffects property_effects) {
- if (property_effects & kPropertyEffectsPreferredSizeChanged)
- SizeToPreferredSize();
- if (property_effects & kPropertyEffectsLayout)
- ResetLayout();
- if (property_effects & kPropertyEffectsPaint)
- RecalculateColors();
-}
-
std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const {
// Multi-line labels only support NO_ELIDE and ELIDE_TAIL for now.
// TODO(warx): Investigate more elide text support.
@@ -717,8 +731,9 @@ bool Label::OnMousePressed(const ui::MouseEvent& event) {
return selection_controller_->OnMousePressed(
event, false,
- had_focus ? SelectionController::FOCUSED
- : SelectionController::UNFOCUSED);
+ had_focus
+ ? SelectionController::InitialFocusStateOnMousePress::kFocused
+ : SelectionController::InitialFocusStateOnMousePress::kUnFocused);
}
bool Label::OnMouseDragged(const ui::MouseEvent& event) {
@@ -804,7 +819,7 @@ void Label::OnDeviceScaleFactorChanged(float old_device_scale_factor,
// When the device scale factor is changed, some font rendering parameters is
// changed (especially, hinting). The bounding box of the text has to be
// re-computed based on the new parameters. See crbug.com/441439
- ResetLayout();
+ PreferredSizeChanged();
}
void Label::VisibilityChanged(View* starting_from, bool is_visible) {
@@ -963,13 +978,14 @@ void Label::Init(const base::string16& text,
gfx::DirectionalityMode directionality_mode) {
full_text_ = gfx::RenderText::CreateRenderText();
full_text_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
- full_text_->SetDirectionalityMode(directionality_mode);
- // 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);
full_text_->SetFontList(font_list);
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);
+ full_text_->SetDirectionalityMode(directionality_mode);
SetText(text);
@@ -983,12 +999,6 @@ void Label::Init(const base::string16& text,
AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
}
-void Label::ResetLayout() {
- PreferredSizeChanged();
- SchedulePaint();
- ClearDisplayText();
-}
-
void Label::MaybeBuildDisplayText() const {
if (display_text_)
return;
@@ -1007,7 +1017,7 @@ void Label::MaybeBuildDisplayText() const {
gfx::Size Label::GetTextSize() const {
gfx::Size size;
if (GetText().empty()) {
- size = gfx::Size(0, std::max(GetLineHeight(), font_list().GetHeight()));
+ size = gfx::Size(0, GetLineHeight());
} else {
// Cancel the display rect of |full_text_|. The display rect may be
// specified in GetHeightForWidth(), and specifying empty Rect cancels
@@ -1087,7 +1097,7 @@ bool Label::ShouldShowDefaultTooltip() const {
(GetMultiLine() && text_size.height() > size.height()));
}
-void Label::ClearDisplayText() const {
+void Label::ClearDisplayText() {
// The HasSelection() call below will build |display_text_| in case it is
// empty. Return early to avoid this.
if (!display_text_)
@@ -1099,6 +1109,8 @@ void Label::ClearDisplayText() const {
GetRenderTextForSelectionController()->selection();
}
display_text_ = nullptr;
+
+ SchedulePaint();
}
base::string16 Label::GetSelectedText() const {
diff --git a/chromium/ui/views/controls/label.h b/chromium/ui/views/controls/label.h
index 5a45ef3cbf0..6cb286abd74 100644
--- a/chromium/ui/views/controls/label.h
+++ b/chromium/ui/views/controls/label.h
@@ -10,6 +10,7 @@
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
+#include "base/optional.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/render_text.h"
@@ -150,10 +151,9 @@ class VIEWS_EXPORT Label : public View,
void SetVerticalAlignment(gfx::VerticalAlignment alignment);
// Get or set the distance in pixels between baselines of multi-line text.
- // Default is 0, indicating the distance between lines should be the standard
- // one for the label's text, font list, and platform.
+ // Default is the height of the default font.
int GetLineHeight() const;
- void SetLineHeight(int height);
+ void SetLineHeight(int line_height);
// Get or set if the label text can wrap on multiple lines; default is false.
bool GetMultiLine() const;
@@ -186,7 +186,10 @@ class VIEWS_EXPORT Label : public View,
// returns the text position of the first character of that line.
size_t GetTextIndexOfLine(size_t line) const;
- // Set the truncate length of the rendered text.
+ // Set the truncate length of the |full_text_|.
+ // NOTE: This does not affect the |display_text_|, since right now the only
+ // consumer does not need that; if you need this function, you may need to
+ // implement this.
void SetTruncateLength(size_t truncate_length);
// Gets/Sets the eliding or fading behavior, applied as necessary. The default
@@ -261,7 +264,7 @@ class VIEWS_EXPORT Label : public View,
void SelectRange(const gfx::Range& range);
views::PropertyChangedSubscription AddTextChangedCallback(
- views::PropertyChangedCallback callback);
+ views::PropertyChangedCallback callback) WARN_UNUSED_RESULT;
// View:
int GetBaseline() const override;
@@ -273,7 +276,6 @@ class VIEWS_EXPORT Label : public View,
WordLookupClient* GetWordLookupClient() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
base::string16 GetTooltipText(const gfx::Point& p) const override;
- void OnHandlePropertyChangeEffects(PropertyEffects property_effects) override;
protected:
// Create a single RenderText instance to actually be painted.
@@ -353,8 +355,6 @@ class VIEWS_EXPORT Label : public View,
const gfx::FontList& font_list,
gfx::DirectionalityMode directionality_mode);
- void ResetLayout();
-
// Set up |display_text_| to actually be painted.
void MaybeBuildDisplayText() const;
@@ -377,7 +377,10 @@ class VIEWS_EXPORT Label : public View,
bool ShouldShowDefaultTooltip() const;
// Clears |display_text_| and updates |stored_selection_range_|.
- void ClearDisplayText() const;
+ // TODO(crbug.com/1103804) Most uses of this function are inefficient; either
+ // replace with setting attributes on both RenderTexts or collapse them to one
+ // RenderText.
+ void ClearDisplayText();
// Returns the currently selected text.
base::string16 GetSelectedText() const;
@@ -390,6 +393,7 @@ class VIEWS_EXPORT Label : public View,
const int text_context_;
int text_style_;
+ base::Optional<int> line_height_;
// An un-elided and single-line RenderText object used for preferred sizing.
std::unique_ptr<gfx::RenderText> full_text_;
diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc
index 33d969862cc..7c361f7b9ed 100644
--- a/chromium/ui/views/controls/label_unittest.cc
+++ b/chromium/ui/views/controls/label_unittest.cc
@@ -10,6 +10,7 @@
#include <utility>
#include <vector>
+#include "base/bind.h"
#include "base/command_line.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
@@ -29,10 +30,13 @@
#include "ui/gfx/text_elider.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/border.h"
+#include "ui/views/controls/base_control_test_widget.h"
#include "ui/views/controls/link.h"
#include "ui/views/style/typography.h"
#include "ui/views/test/focus_manager_test.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
@@ -45,7 +49,7 @@ namespace views {
namespace {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
const int kControlCommandModifier = ui::EF_COMMAND_DOWN;
#else
const int kControlCommandModifier = ui::EF_CONTROL_DOWN;
@@ -88,17 +92,10 @@ void SetRTL(bool rtl) {
EXPECT_EQ(rtl, base::i18n::IsRTL());
}
-// Returns true if |current| is bigger than |last|. Sets |last| to |current|.
-bool Increased(int current, int* last) {
- bool increased = current > *last;
- *last = current;
- return increased;
-}
-
base::string16 GetClipboardText(ui::ClipboardBuffer clipboard_buffer) {
base::string16 clipboard_text;
- ui::Clipboard::GetForCurrentThread()->ReadText(clipboard_buffer,
- &clipboard_text);
+ ui::Clipboard::GetForCurrentThread()->ReadText(
+ clipboard_buffer, /* data_dst = */ nullptr, &clipboard_text);
return clipboard_text;
}
@@ -116,43 +113,22 @@ base::string16 ToRTL(const char* ascii) {
} // namespace
-class LabelTest : public ViewsTestBase {
+class LabelTest : public test::BaseControlTestWidget {
public:
LabelTest() = default;
+ LabelTest(const LabelTest&) = delete;
+ LabelTest& operator=(const LabelTest&) = delete;
+ ~LabelTest() override = default;
- // ViewsTestBase:
- void SetUp() override {
- ViewsTestBase::SetUp();
-
- Widget::InitParams params =
- CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
- params.bounds = gfx::Rect(200, 200);
- params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- widget_.Init(std::move(params));
- View* container = new View();
- widget_.SetContentsView(container);
-
- label_ = new Label();
- container->AddChildView(label_);
-
- widget_.Show();
- }
-
- void TearDown() override {
- widget_.Close();
- ViewsTestBase::TearDown();
+ protected:
+ void CreateWidgetContent(View* container) override {
+ label_ = container->AddChildView(std::make_unique<Label>());
}
- protected:
Label* label() { return label_; }
- Widget* widget() { return &widget_; }
-
private:
Label* label_ = nullptr;
- Widget widget_;
-
- DISALLOW_COPY_AND_ASSIGN(LabelTest);
};
// Test fixture for text selection related tests.
@@ -215,31 +191,31 @@ class LabelSelectionTest : public LabelTest {
SimulatePaint();
gfx::RenderText* render_text =
label()->GetRenderTextForSelectionController();
- const gfx::Range range(index, index + 1);
- const std::vector<gfx::Rect> bounds =
- render_text->GetSubstringBounds(range);
- DCHECK_EQ(1u, bounds.size());
- const int mid_y = bounds[0].y() + bounds[0].height() / 2;
// For single-line text, use the glyph bounds since it gives a better
// representation of the midpoint between glyphs when considering selection.
- // TODO(tapted): When GetCursorSpan() supports returning a vertical range
- // as well as a horizontal range, just use that here.
+ // TODO(crbug.com/248597): Add multiline support to GetCursorBounds(...).
if (!render_text->multiline()) {
- return gfx::Point(render_text->GetCursorSpan(range).Round().start(),
- mid_y);
+ return render_text
+ ->GetCursorBounds(gfx::SelectionModel(index, gfx::CURSOR_FORWARD),
+ true)
+ .left_center();
}
- // Otherwise, GetCursorSpan() will give incorrect results. Multiline
+ // Otherwise, GetCursorBounds() will give incorrect results. Multiline
// editing is not supported (http://crbug.com/248597) so there hasn't been
// a need to draw a cursor. Instead, derive a point from the selection
// bounds, which always rounds up to an integer after the end of a glyph.
// This rounding differs to the glyph bounds, which rounds to nearest
// integer. See http://crbug.com/735346.
+ auto bounds = render_text->GetSubstringBounds({index, index + 1});
+ DCHECK_EQ(1u, bounds.size());
+
const bool rtl =
render_text->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT;
// Return Point corresponding to the leading edge of the character.
- return gfx::Point(rtl ? bounds[0].right() - 1 : bounds[0].x() + 1, mid_y);
+ return rtl ? bounds[0].right_center() + gfx::Vector2d(-1, 0)
+ : bounds[0].left_center() + gfx::Vector2d(1, 0);
}
size_t GetLineCount() {
@@ -261,8 +237,14 @@ class LabelSelectionTest : public LabelTest {
DISALLOW_COPY_AND_ASSIGN(LabelSelectionTest);
};
+TEST_F(LabelTest, Metadata) {
+ // Calling SetMultiLine() will DCHECK unless the label is in multi-line mode.
+ label()->SetMultiLine(true);
+ test::TestViewMetadata(label());
+}
+
TEST_F(LabelTest, FontPropertySymbol) {
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
// On linux, the fonts are mocked with a custom FontConfig. The "Courier New"
// family name is mapped to Cousine-Regular.ttf (see: $build/test_fonts/*).
std::string font_name("Courier New");
@@ -738,8 +720,8 @@ TEST_F(LabelTest, MultiLineSizing) {
required_size.width() + border.width());
}
-#if !defined(OS_MACOSX)
-// TODO(warx): Remove !defined(OS_MACOSX) once SetMaxLines() is applied to MAC
+#if !defined(OS_APPLE)
+// TODO(warx): Remove !defined(OS_APPLE) once SetMaxLines() is applied to MAC
// (crbug.com/758720).
TEST_F(LabelTest, MultiLineSetMaxLines) {
// Ensure SetMaxLines clamps the line count of a string with returns.
@@ -920,11 +902,15 @@ TEST_F(LabelTest, MultilineSupportedRenderText) {
// Ensures SchedulePaint() calls are not made in OnPaint().
TEST_F(LabelTest, NoSchedulePaintInOnPaint) {
TestLabel label;
+ int count = 0;
+ const auto expect_paint_count_increased = [&]() {
+ EXPECT_GT(label.schedule_paint_count(), count);
+ count = label.schedule_paint_count();
+ };
// Initialization should schedule at least one paint, but the precise number
// doesn't really matter.
- int count = label.schedule_paint_count();
- EXPECT_LT(0, count);
+ expect_paint_count_increased();
// Painting should never schedule another paint.
label.SimulatePaint();
@@ -932,16 +918,16 @@ TEST_F(LabelTest, NoSchedulePaintInOnPaint) {
// Test a few things that should schedule paints. Multiple times is OK.
label.SetEnabled(false);
- EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+ expect_paint_count_increased();
label.SetText(label.GetText() + ASCIIToUTF16("Changed"));
- EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+ expect_paint_count_increased();
label.SizeToPreferredSize();
- EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+ expect_paint_count_increased();
label.SetEnabledColor(SK_ColorBLUE);
- EXPECT_TRUE(Increased(label.schedule_paint_count(), &count));
+ expect_paint_count_increased();
label.SimulatePaint();
EXPECT_EQ(count, label.schedule_paint_count()); // Unchanged.
@@ -954,7 +940,7 @@ TEST_F(LabelTest, EmptyLabel) {
EXPECT_TRUE(label()->size().IsEmpty());
// With no text, neither links nor labels have a size in any dimension.
- Link concrete_link((base::string16()));
+ Link concrete_link;
EXPECT_TRUE(concrete_link.GetPreferredSize().IsEmpty());
}
diff --git a/chromium/ui/views/controls/link.cc b/chromium/ui/views/controls/link.cc
index 29348fd8a9d..7ce8a18c0e1 100644
--- a/chromium/ui/views/controls/link.cc
+++ b/chromium/ui/views/controls/link.cc
@@ -210,7 +210,7 @@ void Link::ConfigureFocus() {
if (GetText().empty()) {
SetFocusBehavior(FocusBehavior::NEVER);
} else {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
#else
SetFocusBehavior(FocusBehavior::ALWAYS);
diff --git a/chromium/ui/views/controls/link.h b/chromium/ui/views/controls/link.h
index 51b5a2341a5..427498c7153 100644
--- a/chromium/ui/views/controls/link.h
+++ b/chromium/ui/views/controls/link.h
@@ -35,7 +35,7 @@ class VIEWS_EXPORT Link : public Label {
using ClickedCallback =
base::RepeatingCallback<void(Link* source, int event_flags)>;
- explicit Link(const base::string16& title,
+ explicit Link(const base::string16& title = base::string16(),
int text_context = style::CONTEXT_LABEL,
int text_style = style::STYLE_LINK);
~Link() override;
diff --git a/chromium/ui/views/controls/link_unittest.cc b/chromium/ui/views/controls/link_unittest.cc
new file mode 100644
index 00000000000..aaa0782049a
--- /dev/null
+++ b/chromium/ui/views/controls/link_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/link.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/events/test/event_generator.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/base_control_test_widget.h"
+#include "ui/views/test/view_metadata_test_utils.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+namespace {
+
+class LinkTest : public test::BaseControlTestWidget {
+ public:
+ LinkTest() = default;
+ LinkTest(const LinkTest&) = delete;
+ LinkTest& operator=(const LinkTest&) = delete;
+ ~LinkTest() override = default;
+
+ protected:
+ void CreateWidgetContent(View* container) override {
+ link_ = container->AddChildView(
+ std::make_unique<Link>(base::ASCIIToUTF16("TestLink")));
+ }
+
+ Link* link() { return link_; }
+
+ public:
+ Link* link_ = nullptr;
+};
+
+} // namespace
+
+TEST_F(LinkTest, Metadata) {
+ link()->SetMultiLine(true);
+ test::TestViewMetadata(link());
+}
+
+TEST_F(LinkTest, TestLinkClick) {
+ bool link_clicked = false;
+ link()->set_callback(
+ base::BindRepeating([](bool* link_clicked, Link* link,
+ int event_flags) { *link_clicked = true; },
+ &link_clicked));
+ link()->SizeToPreferredSize();
+ gfx::Point point = link()->bounds().CenterPoint();
+ ui::MouseEvent release(ui::ET_MOUSE_RELEASED, point, point,
+ ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_LEFT_MOUSE_BUTTON);
+ link()->OnMouseReleased(release);
+ EXPECT_TRUE(link_clicked);
+}
+
+TEST_F(LinkTest, TestLinkTap) {
+ bool link_clicked = false;
+ link()->set_callback(
+ base::BindRepeating([](bool* link_clicked, Link* link,
+ int event_flags) { *link_clicked = true; },
+ &link_clicked));
+ link()->SizeToPreferredSize();
+ gfx::Point point = link()->bounds().CenterPoint();
+ ui::GestureEvent tap_event(point.x(), point.y(), 0, ui::EventTimeForNow(),
+ ui::GestureEventDetails(ui::ET_GESTURE_TAP));
+ link()->OnGestureEvent(&tap_event);
+ EXPECT_TRUE(link_clicked);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/menu/menu_config.h b/chromium/ui/views/controls/menu/menu_config.h
index c84d1377c94..e64b39342ea 100644
--- a/chromium/ui/views/controls/menu/menu_config.h
+++ b/chromium/ui/views/controls/menu/menu_config.h
@@ -208,6 +208,30 @@ struct VIEWS_EXPORT MenuConfig {
// Margins for footnotes (HIGHLIGHTED item at the end of a menu).
int footnote_vertical_margin = 11;
+ // New Badge -----------------------------------------------------------------
+ // Note that there are a few differences between Views and Mac constants here
+ // that are due to the fact that the rendering is different and therefore
+ // tweaks to the spacing need to be made to achieve the same visual result.
+
+ // Difference in the font size (in pixels) between menu label font and "new"
+ // badge font size.
+ static constexpr int kNewBadgeFontSizeAdjustment = -1;
+
+ // Space between primary text and "new" badge.
+ static constexpr int kNewBadgeHorizontalMargin = 8;
+
+ // Highlight padding around "new" text.
+ static constexpr int kNewBadgeInternalPadding = 4;
+ static constexpr int kNewBadgeInternalPaddingTopMac = 1;
+
+ // The baseline offset of the "new" badge image to the menu text baseline.
+ static constexpr int kNewBadgeBaslineOffsetMac = -4;
+
+ // The corner radius of the rounded rect for the "new" badge.
+ static constexpr int kNewBadgeCornerRadius = 3;
+ static_assert(kNewBadgeCornerRadius <= kNewBadgeInternalPadding,
+ "New badge corner radius should not exceed padding.");
+
private:
// Configures a MenuConfig as appropriate for the current platform.
void Init();
diff --git a/chromium/ui/views/controls/menu/menu_config_mac.mm b/chromium/ui/views/controls/menu/menu_config_mac.mm
index 1de2eb9c7ec..757cafaf612 100644
--- a/chromium/ui/views/controls/menu/menu_config_mac.mm
+++ b/chromium/ui/views/controls/menu/menu_config_mac.mm
@@ -7,6 +7,7 @@
#import <AppKit/AppKit.h>
#include "base/mac/mac_util.h"
+#include "ui/gfx/platform_font_mac.h"
namespace {
@@ -42,7 +43,8 @@ void InitMaterialMenuConfig(views::MenuConfig* config) {
namespace views {
void MenuConfig::Init() {
- font_list = gfx::FontList(gfx::Font([NSFont menuFontOfSize:0.0]));
+ font_list = gfx::FontList(gfx::Font(
+ new gfx::PlatformFontMac(gfx::PlatformFontMac::SystemFontType::kMenu)));
check_selected_combobox_item = true;
arrow_key_selection_wraps = false;
use_mnemonics = false;
diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc
index 87f9291b67d..bad1730e19a 100644
--- a/chromium/ui/views/controls/menu/menu_controller.cc
+++ b/chromium/ui/views/controls/menu/menu_controller.cc
@@ -17,6 +17,7 @@
#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 "ui/base/dragdrop/os_exchange_data.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
@@ -62,6 +63,7 @@
#endif
#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif
@@ -72,7 +74,7 @@ namespace views {
namespace {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
bool AcceleratorShouldCancelMenu(const ui::Accelerator& accelerator) {
// Since AcceleratorShouldCancelMenu() is called quite early in key
// event handling, it is actually invoked for modifier keys themselves
@@ -108,12 +110,12 @@ bool ShouldIgnoreScreenBoundsForMenus() {
#if defined(USE_OZONE)
// Wayland requires placing menus is screen coordinates. See comment in
// ozone_platform_wayland.cc.
- return ui::OzonePlatform::GetInstance()
- ->GetPlatformProperties()
- .ignore_screen_bounds_for_menus;
-#else
- return false;
+ if (features::IsUsingOzonePlatform())
+ return ui::OzonePlatform::GetInstance()
+ ->GetPlatformProperties()
+ .ignore_screen_bounds_for_menus;
#endif
+ return false;
}
// The amount of time the mouse should be down before a mouse release is
@@ -514,7 +516,7 @@ void MenuController::Run(Widget* parent,
menu_pre_target_handler_ = MenuPreTargetHandler::Create(this, owner_);
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
menu_cocoa_watcher_ = std::make_unique<MenuCocoaWatcherMac>(base::BindOnce(
&MenuController::Cancel, this->AsWeakPtr(), ExitType::kAll));
#endif
@@ -546,7 +548,7 @@ void MenuController::Run(Widget* parent,
}
void MenuController::Cancel(ExitType type) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
menu_closure_animation_.reset();
#endif
@@ -871,12 +873,12 @@ bool MenuController::OnMouseWheel(SubmenuView* source,
void MenuController::OnGestureEvent(SubmenuView* source,
ui::GestureEvent* event) {
if (owner_ && send_gesture_events_to_owner()) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
NOTIMPLEMENTED();
-#else // !defined(OS_MACOSX)
+#else // !defined(OS_APPLE)
event->ConvertLocationToTarget(source->GetWidget()->GetNativeWindow(),
owner()->GetNativeWindow());
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
owner()->OnGestureEvent(event);
// Reset |send_gesture_events_to_owner_| when the first gesture ends.
if (event->type() == ui::ET_GESTURE_END)
@@ -1181,22 +1183,22 @@ ui::PostDispatchAction MenuController::OnWillDispatchKeyEvent(
base::WeakPtr<MenuController> this_ref = AsWeakPtr();
if (event->type() == ui::ET_KEY_PRESSED) {
bool key_handled = false;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Special handling for Option-Up and Option-Down, which should behave like
// Home and End respectively in menus.
if ((event->flags() & ui::EF_ALT_DOWN)) {
+ ui::KeyEvent rewritten_event(*event);
if (event->key_code() == ui::VKEY_UP) {
- key_handled = OnKeyPressed(ui::VKEY_HOME);
+ rewritten_event.set_key_code(ui::VKEY_HOME);
} else if (event->key_code() == ui::VKEY_DOWN) {
- key_handled = OnKeyPressed(ui::VKEY_END);
- } else {
- key_handled = OnKeyPressed(event->key_code());
+ rewritten_event.set_key_code(ui::VKEY_END);
}
+ key_handled = OnKeyPressed(rewritten_event);
} else {
- key_handled = OnKeyPressed(event->key_code());
+ key_handled = OnKeyPressed(*event);
}
#else
- key_handled = OnKeyPressed(event->key_code());
+ key_handled = OnKeyPressed(*event);
#endif
if (key_handled)
@@ -1228,7 +1230,7 @@ ui::PostDispatchAction MenuController::OnWillDispatchKeyEvent(
ui::Accelerator accelerator(*event);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (AcceleratorShouldCancelMenu(accelerator)) {
Cancel(ExitType::kAll);
return ui::POST_DISPATCH_PERFORM_DEFAULT;
@@ -1294,7 +1296,7 @@ void MenuController::TurnOffMenuSelectionHoldForTest() {
}
void MenuController::OnMenuItemDestroying(MenuItemView* menu_item) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (menu_closure_animation_ && menu_closure_animation_->item() == menu_item)
menu_closure_animation_.reset();
#endif
@@ -1379,7 +1381,7 @@ void MenuController::SetSelection(MenuItemView* menu_item,
StartShowTimer();
// Notify an accessibility focus event on all menu items except for the root.
- if (menu_item &&
+ if (menu_item && pending_item_changed &&
(MenuDepth(menu_item) != 1 ||
menu_item->GetType() != MenuItemView::Type::kSubMenu ||
(menu_item->GetType() == MenuItemView::Type::kActionableSubMenu &&
@@ -1475,23 +1477,25 @@ void MenuController::StartDrag(SubmenuView* source,
int drag_ops = item->GetDelegate()->GetDragOperations(item);
did_initiate_drag_ = true;
base::WeakPtr<MenuController> this_ref = AsWeakPtr();
- // TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below.
+ // TODO(varunjain): Properly determine and send DragEventSource below.
item->GetWidget()->RunShellDrag(nullptr, std::move(data), widget_loc,
- drag_ops,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
+ drag_ops, ui::mojom::DragEventSource::kMouse);
// MenuController may have been deleted so check before accessing member
// variables.
if (this_ref)
did_initiate_drag_ = false;
}
-bool MenuController::OnKeyPressed(ui::KeyboardCode key_code) {
- // Do not process while performing drag-and-drop
+bool MenuController::OnKeyPressed(const ui::KeyEvent& event) {
+ DCHECK_EQ(event.type(), ui::ET_KEY_PRESSED);
+
+ // Do not process while performing drag-and-drop.
if (for_drop_)
return false;
bool handled_key_code = false;
+ const ui::KeyboardCode key_code = event.key_code();
switch (key_code) {
case ui::VKEY_HOME:
if (IsEditableCombobox())
@@ -1506,10 +1510,12 @@ bool MenuController::OnKeyPressed(ui::KeyboardCode key_code) {
break;
case ui::VKEY_UP:
+ case ui::VKEY_PRIOR:
IncrementSelection(INCREMENT_SELECTION_UP);
break;
case ui::VKEY_DOWN:
+ case ui::VKEY_NEXT:
IncrementSelection(INCREMENT_SELECTION_DOWN);
break;
@@ -1534,7 +1540,7 @@ bool MenuController::OnKeyPressed(ui::KeyboardCode key_code) {
break;
// On Mac, treat space the same as return.
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
case ui::VKEY_SPACE:
SendAcceleratorToHotTrackedView();
break;
@@ -1546,7 +1552,7 @@ bool MenuController::OnKeyPressed(ui::KeyboardCode key_code) {
// Fallthrough to accept or dismiss combobox menus on F4, like windows.
FALLTHROUGH;
case ui::VKEY_RETURN:
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
case ui::VKEY_SPACE:
#endif
// An odd special case: if a prefix selection is in flight, space should
@@ -1570,7 +1576,7 @@ bool MenuController::OnKeyPressed(ui::KeyboardCode key_code) {
handled_key_code = true;
if (!SendAcceleratorToHotTrackedView() &&
pending_state_.item->GetEnabled()) {
- Accept(pending_state_.item, 0);
+ Accept(pending_state_.item, event.flags());
}
}
}
@@ -1590,7 +1596,7 @@ bool MenuController::OnKeyPressed(ui::KeyboardCode key_code) {
CloseSubmenu();
break;
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
case ui::VKEY_APPS: {
Button* hot_view = GetFirstHotTrackedView(pending_state_.item);
if (hot_view) {
@@ -1698,7 +1704,7 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds,
}
void MenuController::Accept(MenuItemView* item, int event_flags) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>(
item, item->GetParentMenuItem()->GetSubmenu(),
base::BindOnce(&MenuController::ReallyAccept, base::Unretained(this),
@@ -1712,7 +1718,7 @@ void MenuController::Accept(MenuItemView* item, int event_flags) {
void MenuController::ReallyAccept(MenuItemView* item, int event_flags) {
DCHECK(!for_drop_);
result_ = item;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Reset the closure animation since it's now finished - this also unblocks
// input events for the menu.
menu_closure_animation_.reset();
@@ -2827,7 +2833,7 @@ void MenuController::RepostEventAndCancel(SubmenuView* source,
if (last_part.type != MenuPart::NONE)
exit_type = ExitType::kOutermost;
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// When doing a menu closure animation, target the deepest submenu - that way
// MenuClosureAnimationMac will fade out all the menus in sync, rather than
// the shallowest menu only.
@@ -3114,16 +3120,21 @@ void MenuController::SetNextHotTrackedView(
SetInitialHotTrackedView(to_select, direction);
}
-void MenuController::SetHotTrackedButton(Button* hot_button) {
+void MenuController::SetHotTrackedButton(Button* new_hot_button) {
+ // Set hot tracked state and fire a11y events for the hot tracked button.
+ // This must be done whether or not it was the previous hot tracked button.
+ // For example, when a zoom button is pressed, the menu remains open and the
+ // same zoom button should have its hot tracked state set again.
+
// If we're providing a new hot-tracked button, first remove the existing one.
- if (hot_button_ && hot_button_ != hot_button) {
+ if (hot_button_ && hot_button_ != new_hot_button) {
hot_button_->SetHotTracked(false);
hot_button_->GetViewAccessibility().EndPopupFocusOverride();
}
// Then set the new one.
- hot_button_ = hot_button;
- if (hot_button_ && !hot_button_->IsHotTracked()) {
+ hot_button_ = new_hot_button;
+ if (hot_button_) {
hot_button_->GetViewAccessibility().SetPopupFocusOverride();
hot_button_->SetHotTracked(true);
hot_button_->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
@@ -3155,7 +3166,7 @@ void MenuController::UnregisterAlertedItem(MenuItemView* item) {
}
bool MenuController::CanProcessInputEvents() const {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
return !menu_closure_animation_;
#else
return true;
diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h
index 2b3625c5b5a..9da3b74e527 100644
--- a/chromium/ui/views/controls/menu/menu_controller.h
+++ b/chromium/ui/views/controls/menu/menu_controller.h
@@ -27,7 +27,7 @@
#include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/widget/widget_observer.h"
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
#include "ui/views/controls/menu/menu_closure_animation_mac.h"
#include "ui/views/controls/menu/menu_cocoa_watcher_mac.h"
#endif
@@ -353,9 +353,8 @@ class VIEWS_EXPORT MenuController
const ui::LocatedEvent* event);
void StartDrag(SubmenuView* source, const gfx::Point& location);
- // Handles |key_code| as a keypress. Returns true if OnKeyPressed handled the
- // key code.
- bool OnKeyPressed(ui::KeyboardCode key_code);
+ // Returns true if OnKeyPressed handled the key |event|.
+ bool OnKeyPressed(const ui::KeyEvent& event);
// Creates a MenuController. See |for_drop_| member for details on |for_drop|.
MenuController(bool for_drop, internal::MenuControllerDelegate* delegate);
@@ -612,7 +611,7 @@ class VIEWS_EXPORT MenuController
SelectionIncrementDirectionType direction);
// Updates the current |hot_button_| and its hot tracked state.
- void SetHotTrackedButton(Button* hot_button);
+ void SetHotTrackedButton(Button* new_hot_button);
// Returns whether typing a new character will continue the existing prefix
// selection. If this returns false, typing a new character will start a new
@@ -768,7 +767,7 @@ class VIEWS_EXPORT MenuController
// A mask of the EventFlags for the mouse buttons currently pressed.
int current_mouse_pressed_state_ = 0;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
std::unique_ptr<MenuClosureAnimationMac> menu_closure_animation_;
std::unique_ptr<MenuCocoaWatcherMac> menu_cocoa_watcher_;
#endif
diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
index 67168197f0a..a473b624f83 100644
--- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
@@ -4,13 +4,16 @@
#include "ui/views/controls/menu/menu_controller.h"
+#include <vector>
+
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
-#include "base/message_loop/message_loop_current.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/task/current_thread.h"
+#include "base/test/bind_test_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h"
@@ -45,6 +48,7 @@
#include "ui/aura/null_window_targeter.h"
#include "ui/aura/scoped_window_targeter.h"
#include "ui/aura/window.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/views/controls/menu/menu_pre_target_handler.h"
#endif
@@ -53,11 +57,8 @@
#include "ui/gfx/x/x11.h" // nogncheck
#endif
-#if defined(OS_CHROMEOS)
-#include "ui/base/ui_base_features.h"
-#endif
-
#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif
@@ -68,14 +69,15 @@ namespace {
bool ShouldIgnoreScreenBoundsForMenus() {
#if defined(USE_OZONE)
- // Wayland requires placing menus is screen coordinates. See comment in
- // ozone_platform_wayland.cc.
- return ui::OzonePlatform::GetInstance()
- ->GetPlatformProperties()
- .ignore_screen_bounds_for_menus;
-#else
- return false;
+ if (features::IsUsingOzonePlatform()) {
+ // Wayland requires placing menus is screen coordinates. See comment in
+ // ozone_platform_wayland.cc.
+ return ui::OzonePlatform::GetInstance()
+ ->GetPlatformProperties()
+ .ignore_screen_bounds_for_menus;
+ }
#endif
+ return false;
}
// Test implementation of MenuControllerDelegate that only reports the values
@@ -203,7 +205,7 @@ class TestDragDropClient : public aura::client::DragDropClient {
aura::Window* source_window,
const gfx::Point& screen_location,
int operation,
- ui::DragDropTypes::DragEventSource source) override;
+ ui::mojom::DragEventSource source) override;
void DragCancel() override;
bool IsDragDropInProgress() override;
@@ -224,7 +226,7 @@ int TestDragDropClient::StartDragAndDrop(
aura::Window* source_window,
const gfx::Point& screen_location,
int operation,
- ui::DragDropTypes::DragEventSource source) {
+ ui::mojom::DragEventSource source) {
drag_in_progress_ = true;
start_drag_and_drop_callback_.Run();
return 0;
@@ -346,7 +348,7 @@ class MenuControllerTest : public ViewsTestBase,
set_views_delegate(std::move(test_views_delegate));
ViewsTestBase::SetUp();
Init();
- ASSERT_TRUE(base::MessageLoopCurrentForUI::IsSet());
+ ASSERT_TRUE(base::CurrentUIThread::IsSet());
}
void TearDown() override {
@@ -875,7 +877,7 @@ class MenuControllerTest : public ViewsTestBase,
INSTANTIATE_TEST_SUITE_P(All, MenuControllerTest, testing::Bool());
-#if defined(USE_X11)
+#if defined(USE_AURA)
// Tests that an event targeter which blocks events will be honored by the menu
// event dispatcher.
TEST_F(MenuControllerTest, EventTargeter) {
@@ -892,13 +894,14 @@ TEST_F(MenuControllerTest, EventTargeter) {
TestAsyncEscapeKey();
EXPECT_EQ(MenuController::ExitType::kAll, menu_exit_type());
}
-
-#endif // defined(USE_X11)
+#endif // defined(USE_AURA)
#if defined(USE_X11)
// Tests that touch event ids are released correctly. See crbug.com/439051 for
// details. When the ids aren't managed correctly, we get stuck down touches.
TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) {
+ if (features::IsUsingOzonePlatform())
+ return;
TestEventHandler test_event_handler;
GetRootWindow(owner())->AddPreTargetHandler(&test_event_handler);
@@ -1062,67 +1065,115 @@ TEST_F(MenuControllerTest, LastSelectedItem) {
ResetSelection();
}
-// Tests that opening menu and pressing 'Down' and 'Up' iterates over enabled
-// items.
-TEST_F(MenuControllerTest, NextSelectedItem) {
- // Disabling the item "Three" gets it skipped when using keyboard to navigate.
- menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
+// MenuController tests which set expectations about how menu item selection
+// behaves should verify test cases work as intended for all supported selection
+// mechanisms.
+class MenuControllerSelectionTest : public MenuControllerTest {
+ public:
+ MenuControllerSelectionTest() = default;
+ ~MenuControllerSelectionTest() override = default;
- // Fake initial hot selection.
- SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
- EXPECT_EQ(1, pending_state_item()->GetCommand());
+ protected:
+ // Models a mechanism by which menu item selection can be incremented and/or
+ // decremented.
+ struct SelectionMechanism {
+ base::RepeatingClosure IncrementSelection;
+ base::RepeatingClosure DecrementSelection;
+ };
- // Move down in the menu.
- // Select next item.
- IncrementSelection();
- EXPECT_EQ(2, pending_state_item()->GetCommand());
+ // Returns all mechanisms by which menu item selection can be incremented
+ // and/or decremented.
+ const std::vector<SelectionMechanism>& selection_mechanisms() {
+ return selection_mechanisms_;
+ }
- // Skip disabled item.
- IncrementSelection();
- EXPECT_EQ(4, pending_state_item()->GetCommand());
+ private:
+ const std::vector<SelectionMechanism> selection_mechanisms_ = {
+ // Updates selection via IncrementSelection()/DecrementSelection().
+ SelectionMechanism{
+ base::BindLambdaForTesting([this]() { IncrementSelection(); }),
+ base::BindLambdaForTesting([this]() { DecrementSelection(); })},
+ // Updates selection via down/up arrow keys.
+ SelectionMechanism{
+ base::BindLambdaForTesting([this]() { DispatchKey(ui::VKEY_DOWN); }),
+ base::BindLambdaForTesting([this]() { DispatchKey(ui::VKEY_UP); })},
+ // Updates selection via next/prior keys.
+ SelectionMechanism{
+ base::BindLambdaForTesting([this]() { DispatchKey(ui::VKEY_NEXT); }),
+ base::BindLambdaForTesting(
+ [this]() { DispatchKey(ui::VKEY_PRIOR); })}};
+};
- if (SelectionWraps()) {
- // Wrap around.
- IncrementSelection();
+// Tests that opening menu and exercising various mechanisms to update selection
+// iterates over enabled items.
+TEST_F(MenuControllerSelectionTest, NextSelectedItem) {
+ for (const auto& selection_mechanism : selection_mechanisms()) {
+ // Disabling the item "Three" gets it skipped when using keyboard to
+ // navigate.
+ menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
+
+ // Fake initial hot selection.
+ SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
EXPECT_EQ(1, pending_state_item()->GetCommand());
- // Move up in the menu.
- // Wrap around.
- DecrementSelection();
- EXPECT_EQ(4, pending_state_item()->GetCommand());
- } else {
- // Don't wrap.
- IncrementSelection();
+ // Move down in the menu.
+ // Select next item.
+ selection_mechanism.IncrementSelection.Run();
+ EXPECT_EQ(2, pending_state_item()->GetCommand());
+
+ // Skip disabled item.
+ selection_mechanism.IncrementSelection.Run();
EXPECT_EQ(4, pending_state_item()->GetCommand());
- }
- // Skip disabled item.
- DecrementSelection();
- EXPECT_EQ(2, pending_state_item()->GetCommand());
+ if (SelectionWraps()) {
+ // Wrap around.
+ selection_mechanism.IncrementSelection.Run();
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+
+ // Move up in the menu.
+ // Wrap around.
+ selection_mechanism.DecrementSelection.Run();
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+ } else {
+ // Don't wrap.
+ selection_mechanism.IncrementSelection.Run();
+ EXPECT_EQ(4, pending_state_item()->GetCommand());
+ }
- // Select previous item.
- DecrementSelection();
- EXPECT_EQ(1, pending_state_item()->GetCommand());
+ // Skip disabled item.
+ selection_mechanism.DecrementSelection.Run();
+ EXPECT_EQ(2, pending_state_item()->GetCommand());
- // Clear references in menu controller to the menu item that is going away.
- ResetSelection();
+ // Select previous item.
+ selection_mechanism.DecrementSelection.Run();
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+
+ // Clear references in menu controller to the menu item that is going
+ // away.
+ ResetSelection();
+ }
}
-// Tests that opening menu and pressing 'Up' selects the last enabled menu item.
-TEST_F(MenuControllerTest, PreviousSelectedItem) {
- // Disabling the item "Four" gets it skipped when using keyboard to navigate.
- menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
+// Tests that opening menu and exercising various mechanisms to decrement
+// selection selects the last enabled menu item.
+TEST_F(MenuControllerSelectionTest, PreviousSelectedItem) {
+ for (const auto& selection_mechanism : selection_mechanisms()) {
+ // Disabling the item "Four" gets it skipped when using keyboard to
+ // navigate.
+ menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
- // Fake initial root item selection and submenu showing.
- SetPendingStateItem(menu_item());
- EXPECT_EQ(0, pending_state_item()->GetCommand());
+ // Fake initial root item selection and submenu showing.
+ SetPendingStateItem(menu_item());
+ EXPECT_EQ(0, pending_state_item()->GetCommand());
- // Move up and select a previous (in our case the last enabled) item.
- DecrementSelection();
- EXPECT_EQ(3, pending_state_item()->GetCommand());
+ // Move up and select a previous (in our case the last enabled) item.
+ selection_mechanism.DecrementSelection.Run();
+ EXPECT_EQ(3, pending_state_item()->GetCommand());
- // Clear references in menu controller to the menu item that is going away.
- ResetSelection();
+ // Clear references in menu controller to the menu item that is going
+ // away.
+ ResetSelection();
+ }
}
// Tests that the APIs related to the current selected item work correctly.
@@ -1318,6 +1369,13 @@ TEST_F(MenuControllerTest, ChildButtonHotTrackedWhenNested) {
EXPECT_TRUE(button1->IsHotTracked());
EXPECT_EQ(button1, GetHotButton());
+ // Setting the hot tracked state twice on the same button via the
+ // menu controller should still set the hot tracked state on the button again.
+ button1->SetHotTracked(false);
+ SetHotTrackedButton(button1);
+ EXPECT_TRUE(button1->IsHotTracked());
+ EXPECT_EQ(button1, GetHotButton());
+
ExitMenuRun();
EXPECT_FALSE(button1->IsHotTracked());
EXPECT_TRUE(button2->IsHotTracked());
@@ -1713,36 +1771,6 @@ TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) {
EXPECT_EQ(1, nested_delegate->on_menu_closed_called());
}
-TEST_F(MenuControllerTest, ArrowKeysAtEnds) {
- menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
-
- SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
- EXPECT_EQ(1, pending_state_item()->GetCommand());
-
- if (SelectionWraps()) {
- DispatchKey(ui::VKEY_UP);
- EXPECT_EQ(4, pending_state_item()->GetCommand());
-
- DispatchKey(ui::VKEY_DOWN);
- EXPECT_EQ(1, pending_state_item()->GetCommand());
- } else {
- DispatchKey(ui::VKEY_UP);
- EXPECT_EQ(1, pending_state_item()->GetCommand());
- }
-
- DispatchKey(ui::VKEY_DOWN);
- EXPECT_EQ(2, pending_state_item()->GetCommand());
-
- DispatchKey(ui::VKEY_DOWN);
- EXPECT_EQ(4, pending_state_item()->GetCommand());
-
- DispatchKey(ui::VKEY_DOWN);
- if (SelectionWraps())
- EXPECT_EQ(1, pending_state_item()->GetCommand());
- else
- EXPECT_EQ(4, pending_state_item()->GetCommand());
-}
-
// Test that the menu is properly placed where it best fits.
TEST_F(MenuControllerTest, CalculateMenuBoundsBestFitTest) {
MenuBoundsOptions options;
@@ -2476,14 +2504,14 @@ TEST_F(MenuControllerTest, SetSelectionIndices_NestedButtons) {
container_view->AddChildView(new Label());
// Add two focusable buttons (buttons in menus are always focusable).
- Button* const button1 = new LabelButton(nullptr, base::string16());
+ Button* const button1 =
+ container_view->AddChildView(std::make_unique<LabelButton>());
button1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
button1->GetViewAccessibility().OverrideRole(ax::mojom::Role::kMenuItem);
- container_view->AddChildView(button1);
- Button* const button2 = new LabelButton(nullptr, base::string16());
+ Button* const button2 =
+ container_view->AddChildView(std::make_unique<LabelButton>());
button2->GetViewAccessibility().OverrideRole(ax::mojom::Role::kMenuItem);
button2->SetFocusBehavior(View::FocusBehavior::ALWAYS);
- container_view->AddChildView(button2);
OpenMenu(menu_item());
@@ -2551,7 +2579,7 @@ TEST_F(MenuControllerTest, AccessibilityEmitsSelectChildrenChanged) {
EXPECT_EQ(observer.saw_selected_children_changed_, true);
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// This test exercises a Mac-specific behavior, by which hotkeys using modifiers
// cause menus to close and the hotkeys to be handled by the browser window.
// This specific test case tries using cmd-ctrl-f, which normally means
diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc
index fc94bfc7f17..9b3cc9c5f21 100644
--- a/chromium/ui/views/controls/menu/menu_host.cc
+++ b/chromium/ui/views/controls/menu/menu_host.cc
@@ -23,7 +23,7 @@
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/widget.h"
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
#include "ui/aura/window.h"
#endif
@@ -31,7 +31,7 @@ namespace views {
namespace internal {
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// This class adds itself as the pre target handler for the |window|
// passed in. It currently handles touch events and forwards them to the
// controller. Reason for this approach is views does not get raw touch
@@ -78,16 +78,16 @@ class PreMenuEventDispatchHandler : public ui::EventHandler,
DISALLOW_COPY_AND_ASSIGN(PreMenuEventDispatchHandler);
};
-#endif // OS_MACOSX
+#endif // OS_APPLE
void TransferGesture(Widget* source, Widget* target) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
NOTIMPLEMENTED();
-#else // !defined(OS_MACOSX)
+#else // !defined(OS_APPLE)
source->GetGestureRecognizer()->TransferEventsTo(
source->GetNativeView(), target->GetNativeView(),
ui::TransferTouchesBehavior::kDontCancel);
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
}
} // namespace internal
@@ -138,7 +138,7 @@ void MenuHost::InitMenuHost(Widget* parent,
#endif
Init(std::move(params));
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
pre_dispatch_handler_ =
std::make_unique<internal::PreMenuEventDispatchHandler>(
menu_controller, submenu_, GetNativeView());
@@ -173,11 +173,6 @@ void MenuHost::ShowMenuHost(bool do_capture) {
} else {
GetGestureRecognizer()->CancelActiveTouchesExcept(nullptr);
}
-#if defined(MACOSX)
- // Cancel existing touches, so we don't miss some touch release/cancel
- // events due to the menu taking capture.
- GetGestureRecognizer()->CancelActiveTouchesExcept(nullptr);
-#endif // defined (OS_MACOSX)
// If MenuHost has no parent widget, it needs to call Show to get focus,
// so that it will get keyboard events.
if (owner_ == nullptr)
@@ -203,7 +198,7 @@ void MenuHost::DestroyMenuHost() {
HideMenuHost();
destroying_ = true;
static_cast<MenuHostRootView*>(GetRootView())->ClearSubmenu();
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
pre_dispatch_handler_.reset();
#endif
Close();
diff --git a/chromium/ui/views/controls/menu/menu_host.h b/chromium/ui/views/controls/menu/menu_host.h
index 9735ff987bf..30f097e745c 100644
--- a/chromium/ui/views/controls/menu/menu_host.h
+++ b/chromium/ui/views/controls/menu/menu_host.h
@@ -95,7 +95,7 @@ class MenuHost : public Widget, public WidgetObserver {
// If true and capture is lost we don't notify the delegate.
bool ignore_capture_lost_;
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Handles raw touch events at the moment.
std::unique_ptr<internal::PreMenuEventDispatchHandler> pre_dispatch_handler_;
#endif
diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc
index 262ea70539a..f14eb806914 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view.cc
@@ -53,29 +53,26 @@ namespace views {
namespace {
-// Difference in the font size (in pixels) between menu label font and "new"
-// badge font size.
-constexpr int kNewBadgeFontSizeAdjustment = -1;
-
-// Space between primary text and "new" badge.
-constexpr int kNewBadgeHorizontalMargin = 8;
-
-// Highlight size around "new" badge.
-constexpr gfx::Insets kNewBadgeInternalPadding{4};
-
-// The corner radius of the rounded rect for the "new" badge.
-constexpr int kNewBadgeCornerRadius = 3;
-static_assert(kNewBadgeCornerRadius <= kNewBadgeInternalPadding.left(),
- "New badge corner radius should not exceed padding.");
+// Returns the appropriate font to use for the "new" badge based on the font
+// currently being used to render the title of the menu item.
+gfx::FontList DeriveNewBadgeFont(const gfx::FontList& primary_font) {
+ // Preferred font is slightly smaller and slightly more bold than the title
+ // 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.
+ return primary_font.Derive(MenuConfig::kNewBadgeFontSizeAdjustment,
+ gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM);
+}
// Returns the horizontal space required for the "new" badge.
int GetNewBadgeRequiredWidth(const gfx::FontList& primary_font) {
const base::string16 new_text =
l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
- gfx::FontList badge_font =
- primary_font.DeriveWithSizeDelta(kNewBadgeFontSizeAdjustment);
+ gfx::FontList badge_font = DeriveNewBadgeFont(primary_font);
return gfx::GetStringWidth(new_text, badge_font) +
- kNewBadgeInternalPadding.width() + 2 * kNewBadgeHorizontalMargin;
+ 2 * MenuConfig::kNewBadgeInternalPadding +
+ 2 * MenuConfig::kNewBadgeHorizontalMargin;
}
// Returns the highlight rect for the "new" badge given the font and text rect
@@ -83,8 +80,8 @@ int GetNewBadgeRequiredWidth(const gfx::FontList& primary_font) {
gfx::Rect GetNewBadgeRectOutsetAroundText(const gfx::FontList& badge_font,
const gfx::Rect& badge_text_rect) {
gfx::Rect badge_rect = badge_text_rect;
- badge_rect.Inset(
- -gfx::AdjustVisualBorderForFont(badge_font, kNewBadgeInternalPadding));
+ badge_rect.Inset(-gfx::AdjustVisualBorderForFont(
+ badge_font, gfx::Insets(MenuConfig::kNewBadgeInternalPadding)));
return badge_rect;
}
@@ -174,7 +171,9 @@ base::string16 MenuItemView::GetTooltipText(const gfx::Point& p) const {
}
const MenuDelegate* delegate = GetDelegate();
- CHECK(delegate);
+ if (!delegate)
+ return base::string16();
+
gfx::Point location(p);
ConvertPointToScreen(this, &location);
return delegate->GetTooltipText(command_, location);
@@ -206,7 +205,8 @@ void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
} else {
item_text = title_;
}
- node_data->SetName(GetAccessibleNameForMenuItem(item_text, GetMinorText()));
+ node_data->SetName(GetAccessibleNameForMenuItem(item_text, GetMinorText(),
+ ShouldShowNewBadge()));
switch (type_) {
case Type::kSubMenu:
@@ -215,7 +215,8 @@ void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
break;
case Type::kCheckbox:
case Type::kRadio: {
- const bool is_checked = GetDelegate()->IsItemChecked(GetCommand());
+ const bool is_checked =
+ GetDelegate() && GetDelegate()->IsItemChecked(GetCommand());
node_data->SetCheckedState(is_checked ? ax::mojom::CheckedState::kTrue
: ax::mojom::CheckedState::kFalse);
} break;
@@ -262,7 +263,8 @@ bool MenuItemView::IsBubble(MenuAnchorPosition anchor) {
// static
base::string16 MenuItemView::GetAccessibleNameForMenuItem(
const base::string16& item_text,
- const base::string16& minor_text) {
+ const base::string16& minor_text,
+ bool is_new_feature) {
base::string16 accessible_name = item_text;
// Filter out the "&" for accessibility clients.
@@ -284,6 +286,12 @@ base::string16 MenuItemView::GetAccessibleNameForMenuItem(
accessible_name.append(minor_text);
}
+ if (is_new_feature) {
+ accessible_name.push_back(' ');
+ accessible_name.append(l10n_util::GetStringUTF16(
+ IDS_MENU_ITEM_NEW_BADGE_SCREEN_READER_MESSAGE));
+ }
+
return accessible_name;
}
@@ -748,6 +756,12 @@ void MenuItemView::SetAlerted() {
SchedulePaint();
}
+bool MenuItemView::ShouldShowNewBadge() const {
+ static const bool feature_enabled =
+ base::FeatureList::IsEnabled(features::kEnableNewBadgeOnMenuItems);
+ return feature_enabled && is_new_;
+}
+
MenuItemView::MenuItemView(MenuItemView* parent,
int command,
MenuItemView::Type type) {
@@ -816,7 +830,7 @@ void MenuItemView::Init(MenuItemView* parent,
if (type_ == Type::kCheckbox || type_ == Type::kRadio) {
radio_check_image_view_ = AddChildView(std::make_unique<ImageView>());
bool show_check_radio_icon =
- type_ == Type::kRadio || (type_ == Type::kCheckbox &&
+ type_ == Type::kRadio || (type_ == Type::kCheckbox && GetDelegate() &&
GetDelegate()->IsItemChecked(GetCommand()));
radio_check_image_view_->SetVisible(show_check_radio_icon);
radio_check_image_view_->set_can_process_events_within_subtree(false);
@@ -943,7 +957,6 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
if (forced_visual_selection_.has_value())
render_selection = *forced_visual_selection_;
- MenuDelegate* delegate = GetDelegate();
// Render the background. As MenuScrollViewContainer draws the background, we
// only need the background when we want it to look different, as when we're
// selected.
@@ -966,10 +979,12 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
top_margin += (available_height - total_text_height) / 2;
// Render the check.
- if (type_ == Type::kCheckbox && delegate->IsItemChecked(GetCommand())) {
+ MenuDelegate* delegate = GetDelegate();
+ if (type_ == Type::kCheckbox && delegate &&
+ delegate->IsItemChecked(GetCommand())) {
radio_check_image_view_->SetImage(GetMenuCheckImage(icon_color));
} else if (type_ == Type::kRadio) {
- const bool toggled = delegate->IsItemChecked(GetCommand());
+ const bool toggled = delegate && delegate->IsItemChecked(GetCommand());
const gfx::VectorIcon& radio_icon =
toggled ? kMenuRadioSelectedIcon : kMenuRadioEmptyIcon;
const SkColor radio_icon_color = GetNativeTheme()->GetSystemColor(
@@ -1009,7 +1024,7 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
DrawNewBadge(
canvas,
gfx::Point(label_start + gfx::GetStringWidth(title(), style.font_list) +
- kNewBadgeHorizontalMargin,
+ MenuConfig::kNewBadgeHorizontalMargin,
top_margin),
style.font_list, flags);
}
@@ -1333,8 +1348,7 @@ void MenuItemView::DrawNewBadge(gfx::Canvas* canvas,
const gfx::Point& unmirrored_badge_start,
const gfx::FontList& primary_font,
int text_render_flags) {
- gfx::FontList badge_font =
- primary_font.DeriveWithSizeDelta(kNewBadgeFontSizeAdjustment);
+ gfx::FontList badge_font = DeriveNewBadgeFont(primary_font);
const base::string16 new_text =
l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
@@ -1342,7 +1356,7 @@ void MenuItemView::DrawNewBadge(gfx::Canvas* canvas,
gfx::Rect badge_text_bounds(unmirrored_badge_start,
gfx::GetStringSize(new_text, badge_font));
badge_text_bounds.Offset(
- kNewBadgeInternalPadding.left(),
+ MenuConfig::kNewBadgeInternalPadding,
gfx::GetFontCapHeightCenterOffset(primary_font, badge_font));
if (base::i18n::IsRTL())
badge_text_bounds.set_x(GetMirroredXForRect(badge_text_bounds));
@@ -1354,7 +1368,7 @@ void MenuItemView::DrawNewBadge(gfx::Canvas* canvas,
new_flags.setColor(background_color);
canvas->DrawRoundRect(
GetNewBadgeRectOutsetAroundText(badge_font, badge_text_bounds),
- kNewBadgeCornerRadius, new_flags);
+ MenuConfig::kNewBadgeCornerRadius, new_flags);
// Render the badge text.
const SkColor foreground_color = GetNativeTheme()->GetSystemColor(
@@ -1440,12 +1454,6 @@ bool MenuItemView::HasChecksOrRadioButtons() const {
[](const auto* item) { return item->HasChecksOrRadioButtons(); });
}
-bool MenuItemView::ShouldShowNewBadge() const {
- static const bool feature_enabled =
- base::FeatureList::IsEnabled(features::kEnableNewBadgeOnMenuItems);
- return feature_enabled && is_new_;
-}
-
BEGIN_METADATA(MenuItemView)
METADATA_PARENT_CLASS(View)
END_METADATA()
diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h
index 01b797549ac..3309df7eeee 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.h
+++ b/chromium/ui/views/controls/menu/menu_item_view.h
@@ -117,7 +117,7 @@ class VIEWS_EXPORT MenuItemView : public View {
// Constructor for use with the top level menu item. This menu is never
// shown to the user, rather its use as the parent for all menu items.
- explicit MenuItemView(MenuDelegate* delegate);
+ explicit MenuItemView(MenuDelegate* delegate = nullptr);
// Overridden from View:
base::string16 GetTooltipText(const gfx::Point& p) const override;
@@ -138,7 +138,8 @@ class VIEWS_EXPORT MenuItemView : public View {
// removed and the menu item accelerator text is appended.
static base::string16 GetAccessibleNameForMenuItem(
const base::string16& item_text,
- const base::string16& accelerator_text);
+ const base::string16& accelerator_text,
+ bool is_new_feature);
// Hides and cancels the menu. This does nothing if the menu is not open.
void Cancel();
@@ -353,6 +354,10 @@ class VIEWS_EXPORT MenuItemView : public View {
void SetAlerted();
bool is_alerted() const { return is_alerted_; }
+ // Returns whether or not a "new" badge should be shown on this menu item.
+ // Takes into account whether the badging feature is enabled.
+ bool ShouldShowNewBadge() const;
+
protected:
// Creates a MenuItemView. This is used by the various AddXXX methods.
MenuItemView(MenuItemView* parent, int command, Type type);
@@ -491,10 +496,6 @@ class VIEWS_EXPORT MenuItemView : public View {
// Returns true if the menu has items with a checkbox or a radio button.
bool HasChecksOrRadioButtons() const;
- // Returns whether or not a "new" badge should be shown on this menu item.
- // Takes into account whether the badging feature is enabled.
- bool ShouldShowNewBadge() const;
-
void invalidate_dimensions() { dimensions_.height = 0; }
bool is_dimensions_valid() const { return dimensions_.height > 0; }
diff --git a/chromium/ui/views/controls/menu/menu_runner_impl.cc b/chromium/ui/views/controls/menu/menu_runner_impl.cc
index d5db143769b..a350498702e 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl.cc
+++ b/chromium/ui/views/controls/menu/menu_runner_impl.cc
@@ -27,6 +27,12 @@
#include "ui/events/x/events_x_utils.h" // nogncheck
#endif
+#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
+#include "ui/events/event_constants.h"
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
namespace views {
namespace {
@@ -44,11 +50,27 @@ void FireFocusAfterMenuClose(base::WeakPtr<Widget> widget) {
}
}
+#if defined(USE_X11) || defined(USE_OZONE)
+bool IsAltPressed() {
+#if defined(USE_OZONE)
+ if (features::IsUsingOzonePlatform()) {
+ return (ui::OzonePlatform::GetInstance()->GetKeyModifiers() &
+ ui::EF_ALT_DOWN) != 0;
+ }
+#endif
+#if defined(USE_X11)
+ return ui::IsAltPressed();
+#else
+ return false;
+#endif
+}
+#endif // defined(USE_X11) || degined(USE_OZONE)
+
} // namespace
namespace internal {
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
MenuRunnerImplInterface* MenuRunnerImplInterface::Create(
ui::MenuModel* menu_model,
int32_t run_types,
@@ -241,9 +263,9 @@ bool MenuRunnerImpl::ShouldShowMnemonics(int32_t run_types) {
// Show mnemonics if the button has focus or alt is pressed.
#if defined(OS_WIN)
show_mnemonics |= ui::win::IsAltPressed();
-#elif defined(USE_X11)
- show_mnemonics |= ui::IsAltPressed();
-#elif defined(OS_MACOSX)
+#elif defined(USE_X11) || defined(USE_OZONE)
+ show_mnemonics |= IsAltPressed();
+#elif defined(OS_APPLE)
show_mnemonics = false;
#endif
return show_mnemonics;
diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h
index d24e6915480..ce54769f786 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h
+++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h
@@ -14,6 +14,7 @@
#include "ui/views/controls/menu/menu_runner_impl_interface.h"
@class MenuControllerCocoa;
+@class MenuControllerDelegate;
namespace views {
namespace test {
@@ -45,6 +46,9 @@ class VIEWS_EXPORT MenuRunnerImplCocoa : public MenuRunnerImplInterface {
// The Cocoa menu controller that this instance is bridging.
base::scoped_nsobject<MenuControllerCocoa> menu_controller_;
+ // The delegate for the |menu_controller_|.
+ base::scoped_nsobject<MenuControllerDelegate> menu_delegate_;
+
// Are we in run waiting for it to return?
bool running_;
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 29c6ad4d742..c51447a72ac 100644
--- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
+++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm
@@ -4,17 +4,178 @@
#import "ui/views/controls/menu/menu_runner_impl_cocoa.h"
+#include "base/mac/mac_util.h"
#import "base/message_loop/message_pump_mac.h"
+#import "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/cocoa_base_utils.h"
#import "ui/base/cocoa/menu_controller.h"
+#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/models/menu_model.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/mac/coordinate_conversion.h"
+#include "ui/gfx/platform_font_mac.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_runner_impl_adapter.h"
+#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
+namespace {
+
+NSImage* NewTagImage() {
+ static NSImage* new_tag = []() {
+ // 1. Make the attributed string.
+
+ NSString* badge_text = l10n_util::GetNSString(IDS_MENU_ITEM_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::MenuConfig::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)];
+ }
+
+ // 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::MenuConfig::kNewBadgeInternalPadding +
+ 2 * views::MenuConfig::kNewBadgeHorizontalMargin;
+ badge_size.height += views::MenuConfig::kNewBadgeInternalPaddingTopMac;
+
+ // 3. Craft the image.
+
+ return [[NSImage
+ imageWithSize:badge_size
+ flipped:NO
+ drawingHandler:^(NSRect dest_rect) {
+ NSRect badge_frame = NSInsetRect(
+ dest_rect, views::MenuConfig::kNewBadgeHorizontalMargin, 0);
+ NSBezierPath* rounded_badge_rect = [NSBezierPath
+ bezierPathWithRoundedRect:badge_frame
+ xRadius:views::MenuConfig::kNewBadgeCornerRadius
+ yRadius:views::MenuConfig::
+ 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::MenuConfig::kNewBadgeInternalPadding,
+ NSMinY(badge_frame) +
+ views::MenuConfig::kNewBadgeInternalPaddingTopMac);
+ [badge_attr_string drawAtPoint:badge_text_location];
+
+ return YES;
+ }] retain];
+ }();
+
+ return new_tag;
+}
+
+} // namespace
+
+@interface NewTagAttachmentCell : NSTextAttachmentCell
+@end
+
+@implementation NewTagAttachmentCell
+
+- (instancetype)init {
+ if (self = [super init]) {
+ self.image = NewTagImage();
+ }
+ return self;
+}
+
+- (NSPoint)cellBaselineOffset {
+ return NSMakePoint(0, views::MenuConfig::kNewBadgeBaslineOffsetMac);
+}
+
+- (NSSize)cellSize {
+ return [self.image size];
+}
+
+@end
+
+@interface MenuControllerDelegate : NSObject <MenuControllerCocoaDelegate>
+@end
+
+@implementation MenuControllerDelegate
+
+- (void)controllerWillAddItem:(NSMenuItem*)menuItem
+ fromModel:(ui::MenuModel*)model
+ atIndex:(NSInteger)index {
+ static const bool feature_enabled =
+ 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]];
+
+ menuItem.attributedTitle = attrTitle;
+}
+
+@end
+
namespace views {
namespace internal {
namespace {
@@ -124,7 +285,7 @@ MenuRunnerImplInterface* MenuRunnerImplInterface::Create(
int32_t run_types,
base::RepeatingClosure on_menu_closed_callback) {
if ((run_types & MenuRunner::CONTEXT_MENU) &&
- !(run_types & MenuRunner::IS_NESTED)) {
+ !(run_types & (MenuRunner::IS_NESTED))) {
return new MenuRunnerImplCocoa(menu_model,
std::move(on_menu_closed_callback));
}
@@ -139,8 +300,11 @@ MenuRunnerImplCocoa::MenuRunnerImplCocoa(
delete_after_run_(false),
closing_event_time_(base::TimeTicks()),
on_menu_closed_callback_(std::move(on_menu_closed_callback)) {
- menu_controller_.reset([[MenuControllerCocoa alloc] initWithModel:menu
- useWithPopUpButtonCell:NO]);
+ menu_delegate_.reset([[MenuControllerDelegate alloc] init]);
+ menu_controller_.reset([[MenuControllerCocoa alloc]
+ initWithModel:menu
+ delegate:menu_delegate_.get()
+ useWithPopUpButtonCell:NO]);
}
bool MenuRunnerImplCocoa::IsRunning() const {
diff --git a/chromium/ui/views/controls/menu/menu_runner_unittest.cc b/chromium/ui/views/controls/menu/menu_runner_unittest.cc
index 193bc0af4ce..9c8e6348fcc 100644
--- a/chromium/ui/views/controls/menu/menu_runner_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_runner_unittest.cc
@@ -257,7 +257,7 @@ TEST_F(MenuRunnerTest, PrefixSelect) {
// This test is Mac-specific: Mac is the only platform where VKEY_SPACE
// activates menu items.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
TEST_F(MenuRunnerTest, SpaceActivatesItem) {
if (!MenuConfig::instance().all_menus_use_prefix_selection)
return;
@@ -282,7 +282,7 @@ TEST_F(MenuRunnerTest, SpaceActivatesItem) {
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
-#endif // OS_MACOSX
+#endif // OS_APPLE
// Tests that attempting to nest a menu within a drag-and-drop menu does not
// cause a crash. Instead the drag and drop action should be canceled, and the
@@ -595,11 +595,12 @@ TEST_F(MenuRunnerImplTest, FocusOnMenuClose) {
new internal::MenuRunnerImpl(menu_item_view());
// Create test button that has focus.
+ auto button_managed = std::make_unique<LabelButton>();
+ button_managed->SetID(1);
+ button_managed->SetSize(gfx::Size(20, 20));
LabelButton* button =
- new LabelButton(nullptr, base::string16(), style::CONTEXT_BUTTON);
- button->SetID(1);
- button->SetSize(gfx::Size(20, 20));
- owner()->GetRootView()->AddChildView(button);
+ owner()->GetRootView()->AddChildView(std::move(button_managed));
+
button->SetFocusBehavior(View::FocusBehavior::ALWAYS);
button->GetWidget()->widget_delegate()->SetCanActivate(true);
button->GetWidget()->Activate();
diff --git a/chromium/ui/views/controls/menu/menu_separator.cc b/chromium/ui/views/controls/menu/menu_separator.cc
index b48d80919ce..712460c1593 100644
--- a/chromium/ui/views/controls/menu/menu_separator.cc
+++ b/chromium/ui/views/controls/menu/menu_separator.cc
@@ -88,8 +88,21 @@ gfx::Size MenuSeparator::CalculatePreferredSize() const {
height);
}
+ui::MenuSeparatorType MenuSeparator::GetType() const {
+ return type_;
+}
+
+void MenuSeparator::SetType(ui::MenuSeparatorType type) {
+ if (type_ == type)
+ return;
+
+ type_ = type;
+ OnPropertyChanged(&type_, kPropertyEffectsPreferredSizeChanged);
+}
+
BEGIN_METADATA(MenuSeparator)
METADATA_PARENT_CLASS(View)
+ADD_PROPERTY_METADATA(MenuSeparator, ui::MenuSeparatorType, Type)
END_METADATA()
} // namespace views
diff --git a/chromium/ui/views/controls/menu/menu_separator.h b/chromium/ui/views/controls/menu/menu_separator.h
index a331914d44a..18566efae79 100644
--- a/chromium/ui/views/controls/menu/menu_separator.h
+++ b/chromium/ui/views/controls/menu/menu_separator.h
@@ -8,6 +8,7 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "ui/base/models/menu_separator_types.h"
+#include "ui/views/metadata/metadata_header_macros.h"
#include "ui/views/view.h"
#include "ui/views/views_export.h"
@@ -17,15 +18,20 @@ class VIEWS_EXPORT MenuSeparator : public View {
public:
METADATA_HEADER(MenuSeparator);
- explicit MenuSeparator(ui::MenuSeparatorType type) : type_(type) {}
+ explicit MenuSeparator(
+ ui::MenuSeparatorType type = ui::MenuSeparatorType::NORMAL_SEPARATOR)
+ : type_(type) {}
// View overrides.
void OnPaint(gfx::Canvas* canvas) override;
gfx::Size CalculatePreferredSize() const override;
+ ui::MenuSeparatorType GetType() const;
+ void SetType(ui::MenuSeparatorType type);
+
private:
// The type of the separator.
- const ui::MenuSeparatorType type_;
+ ui::MenuSeparatorType type_;
DISALLOW_COPY_AND_ASSIGN(MenuSeparator);
};
diff --git a/chromium/ui/views/controls/menu/menu_separator_unittest.cc b/chromium/ui/views/controls/menu/menu_separator_unittest.cc
new file mode 100644
index 00000000000..a046f8f16ef
--- /dev/null
+++ b/chromium/ui/views/controls/menu/menu_separator_unittest.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/menu/menu_separator.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/models/menu_separator_types.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/test/view_metadata_test_utils.h"
+#include "ui/views/test/views_test_base.h"
+
+namespace views {
+
+using MenuSeparatorTest = ViewsTestBase;
+
+TEST_F(MenuSeparatorTest, Metadata) {
+ auto separator = std::make_unique<MenuSeparator>();
+ test::TestViewMetadata(separator.get());
+}
+
+TEST_F(MenuSeparatorTest, TypeChangeEffect) {
+ auto separator = std::make_unique<MenuSeparator>();
+ separator->SizeToPreferredSize();
+ const MenuConfig& config = MenuConfig::instance();
+ EXPECT_EQ(config.separator_height, separator->height());
+
+ separator->SetType(ui::MenuSeparatorType::DOUBLE_SEPARATOR);
+ separator->SizeToPreferredSize();
+ EXPECT_EQ(config.double_separator_height, separator->height());
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc
index 28a9c908988..554f1d2baec 100644
--- a/chromium/ui/views/controls/menu/submenu_view.cc
+++ b/chromium/ui/views/controls/menu/submenu_view.cc
@@ -9,13 +9,13 @@
#include <set>
#include "base/compiler_specific.h"
+#include "base/numerics/safe_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ime/input_method.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
@@ -373,8 +373,9 @@ void SubmenuView::SetSelectedRow(int row) {
}
base::string16 SubmenuView::GetTextForRow(int row) {
- return MenuItemView::GetAccessibleNameForMenuItem(GetMenuItemAt(row)->title(),
- base::string16());
+ return MenuItemView::GetAccessibleNameForMenuItem(
+ GetMenuItemAt(row)->title(), base::string16(),
+ GetMenuItemAt(row)->ShouldShowNewBadge());
}
bool SubmenuView::IsShowing() const {
@@ -540,7 +541,7 @@ bool SubmenuView::OnScroll(float dx, float dy) {
const gfx::Rect& full_bounds = bounds();
int x = vis_bounds.x();
float y_f = vis_bounds.y() - dy - roundoff_error_;
- int y = gfx::ToRoundedInt(y_f);
+ int y = base::ClampRound(y_f);
roundoff_error_ = y - y_f;
// clamp y to [0, full_height - vis_height)
y = std::min(y, full_bounds.height() - vis_bounds.height() - 1);
diff --git a/chromium/ui/views/controls/menu/submenu_view_unittest.cc b/chromium/ui/views/controls/menu/submenu_view_unittest.cc
index 2816426be56..f277cd1092d 100644
--- a/chromium/ui/views/controls/menu/submenu_view_unittest.cc
+++ b/chromium/ui/views/controls/menu/submenu_view_unittest.cc
@@ -11,7 +11,7 @@
namespace views {
TEST(SubmenuViewTest, GetLastItem) {
- MenuItemView* parent = new MenuItemView(nullptr);
+ MenuItemView* parent = new MenuItemView();
MenuRunner menu_runner(parent, 0);
SubmenuView* submenu = parent->CreateSubmenu();
@@ -20,14 +20,14 @@ TEST(SubmenuViewTest, GetLastItem) {
submenu->AddChildView(new View());
EXPECT_EQ(nullptr, submenu->GetLastItem());
- MenuItemView* first = new MenuItemView(nullptr);
+ MenuItemView* first = new MenuItemView();
submenu->AddChildView(first);
EXPECT_EQ(first, submenu->GetLastItem());
submenu->AddChildView(new View());
EXPECT_EQ(first, submenu->GetLastItem());
- MenuItemView* second = new MenuItemView(nullptr);
+ MenuItemView* second = new MenuItemView();
submenu->AddChildView(second);
EXPECT_EQ(second, submenu->GetLastItem());
}
diff --git a/chromium/ui/views/controls/menu/test_menu_item_view.cc b/chromium/ui/views/controls/menu/test_menu_item_view.cc
index 76cb42ca116..7329b162b87 100644
--- a/chromium/ui/views/controls/menu/test_menu_item_view.cc
+++ b/chromium/ui/views/controls/menu/test_menu_item_view.cc
@@ -6,7 +6,7 @@
namespace views {
-TestMenuItemView::TestMenuItemView() : MenuItemView(nullptr) {}
+TestMenuItemView::TestMenuItemView() = default;
TestMenuItemView::TestMenuItemView(MenuDelegate* delegate)
: MenuItemView(delegate) {}
diff --git a/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm b/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm
index 9126b956b94..0d125253db1 100644
--- a/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm
+++ b/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm
@@ -8,6 +8,7 @@
#include <memory>
+#include "base/mac/mac_util.h"
#import "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#import "testing/gtest_mac.h"
@@ -161,13 +162,17 @@ TEST_F(NativeViewHostMacTest, ContentViewPositionAndSize) {
CreateHost();
toplevel()->SetBounds(gfx::Rect(0, 0, 100, 100));
- // TODO(amp): Update expect rect after Mac native size is implemented.
- // For now the native size is ignored on mac.
+ // The new visual style on macOS 11 (and presumably later) has slightly taller
+ // titlebars, which means the window rect has to leave a bit of extra space
+ // for the titlebar.
+ int titlebar_extra = base::mac::IsAtLeastOS11() ? 6 : 0;
+
native_host()->ShowWidget(5, 10, 100, 100, 200, 200);
- EXPECT_NSEQ(NSMakeRect(5, -32, 100, 100), [native_view_ frame]);
+ EXPECT_NSEQ(NSMakeRect(5, -32 - titlebar_extra, 100, 100),
+ [native_view_ frame]);
native_host()->ShowWidget(10, 25, 50, 50, 50, 50);
- EXPECT_NSEQ(NSMakeRect(10, 3, 50, 50), [native_view_ frame]);
+ EXPECT_NSEQ(NSMakeRect(10, 3 - titlebar_extra, 50, 50), [native_view_ frame]);
DestroyHost();
}
diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc
index aa0d2fe8423..c0cc92ea180 100644
--- a/chromium/ui/views/controls/prefix_selector.cc
+++ b/chromium/ui/views/controls/prefix_selector.cc
@@ -42,7 +42,9 @@ bool PrefixSelector::ShouldContinueSelection() const {
void PrefixSelector::SetCompositionText(
const ui::CompositionText& composition) {}
-void PrefixSelector::ConfirmCompositionText(bool keep_selection) {}
+uint32_t PrefixSelector::ConfirmCompositionText(bool keep_selection) {
+ return UINT32_MAX;
+}
void PrefixSelector::ClearCompositionText() {}
@@ -172,12 +174,26 @@ bool PrefixSelector::SetCompositionFromExistingText(
#endif
#if defined(OS_CHROMEOS)
+gfx::Range PrefixSelector::GetAutocorrectRange() const {
+ NOTIMPLEMENTED_LOG_ONCE();
+ return gfx::Range();
+}
+
+gfx::Rect PrefixSelector::GetAutocorrectCharacterBounds() const {
+ NOTIMPLEMENTED_LOG_ONCE();
+ return gfx::Rect();
+}
+
bool PrefixSelector::SetAutocorrectRange(const base::string16& autocorrect_text,
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 12dffcc111b..9df6de750fb 100644
--- a/chromium/ui/views/controls/prefix_selector.h
+++ b/chromium/ui/views/controls/prefix_selector.h
@@ -44,7 +44,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
// ui::TextInputClient:
void SetCompositionText(const ui::CompositionText& composition) override;
- void ConfirmCompositionText(bool keep_selection) override;
+ uint32_t ConfirmCompositionText(bool keep_selection) override;
void ClearCompositionText() override;
void InsertText(const base::string16& text) override;
void InsertChar(const ui::KeyEvent& event) override;
@@ -83,8 +83,11 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
#endif
#if defined(OS_CHROMEOS)
+ 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;
#endif
#if defined(OS_WIN)
diff --git a/chromium/ui/views/controls/resize_area_unittest.cc b/chromium/ui/views/controls/resize_area_unittest.cc
index 25725fdaf73..aaf484683d4 100644
--- a/chromium/ui/views/controls/resize_area_unittest.cc
+++ b/chromium/ui/views/controls/resize_area_unittest.cc
@@ -17,7 +17,7 @@
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
#include "ui/aura/window.h"
#endif
@@ -148,7 +148,7 @@ void ResizeAreaTest::TearDown() {
}
// TODO(tdanderson): Enable these tests on OSX. See crbug.com/710475.
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Verifies the correct calls have been made to
// TestResizeAreaDelegate::OnResize() for a sequence of mouse events
// corresponding to a successful resize operation.
@@ -202,6 +202,6 @@ TEST_F(ResizeAreaTest, NoDragOnGestureTap) {
EXPECT_EQ(0, resize_amount());
}
-#endif // !defined(OS_MACOSX)
+#endif // !defined(OS_APPLE)
} // namespace views
diff --git a/chromium/ui/views/controls/scroll_view.cc b/chromium/ui/views/controls/scroll_view.cc
index 8f1244d79c2..c161189c9fb 100644
--- a/chromium/ui/views/controls/scroll_view.cc
+++ b/chromium/ui/views/controls/scroll_view.cc
@@ -12,6 +12,9 @@
#include "base/macros.h"
#include "base/numerics/ranges.h"
#include "build/build_config.h"
+#include "ui/accessibility/ax_action_data.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/overscroll/scroll_input_handler.h"
#include "ui/events/event.h"
@@ -377,7 +380,7 @@ void ScrollView::Layout() {
// When horizontal scrollbar is disabled, it should not matter
// if its OverlapsContent matches vertical bar's.
if (!hide_horizontal_scrollbar_) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// On Mac, scrollbars may update their style one at a time, so they may
// temporarily be of different types. Refuse to lay out at this point.
if (horiz_sb_->OverlapsContent() != vert_sb_->OverlapsContent())
@@ -641,6 +644,70 @@ void ScrollView::OnThemeChanged() {
UpdateBackground();
}
+void ScrollView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+ View::GetAccessibleNodeData(node_data);
+ if (!contents_)
+ return;
+
+ ScrollBar* horizontal = horizontal_scroll_bar();
+ if (horizontal) {
+ node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollX,
+ CurrentOffset().x());
+ node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin,
+ horizontal->GetMinPosition());
+ node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax,
+ horizontal->GetMaxPosition());
+ }
+ ScrollBar* vertical = vertical_scroll_bar();
+ if (vertical) {
+ node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollY,
+ CurrentOffset().y());
+ node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollYMin,
+ vertical->GetMinPosition());
+ node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollYMax,
+ vertical->GetMaxPosition());
+ }
+ if (horizontal || vertical)
+ node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, true);
+}
+
+bool ScrollView::HandleAccessibleAction(const ui::AXActionData& action_data) {
+ if (!contents_)
+ return View::HandleAccessibleAction(action_data);
+
+ ScrollBar* horizontal = horizontal_scroll_bar();
+ ScrollBar* vertical = vertical_scroll_bar();
+ switch (action_data.action) {
+ case ax::mojom::Action::kScrollLeft:
+ if (horizontal)
+ return horizontal->ScrollByAmount(ScrollBar::ScrollAmount::kPrevPage);
+ else
+ return false;
+ case ax::mojom::Action::kScrollRight:
+ if (horizontal)
+ return horizontal->ScrollByAmount(ScrollBar::ScrollAmount::kNextPage);
+ else
+ return false;
+ case ax::mojom::Action::kScrollUp:
+ if (vertical)
+ return vertical->ScrollByAmount(ScrollBar::ScrollAmount::kPrevPage);
+ else
+ return false;
+ case ax::mojom::Action::kScrollDown:
+ if (vertical)
+ return vertical->ScrollByAmount(ScrollBar::ScrollAmount::kNextPage);
+ else
+ return false;
+ case ax::mojom::Action::kSetScrollOffset:
+ ScrollToOffset(gfx::ScrollOffset(action_data.target_point.x(),
+ action_data.target_point.y()));
+ return true;
+ default:
+ return View::HandleAccessibleAction(action_data);
+ break;
+ }
+}
+
void ScrollView::ScrollToPosition(ScrollBar* source, int position) {
if (!contents_)
return;
diff --git a/chromium/ui/views/controls/scroll_view.h b/chromium/ui/views/controls/scroll_view.h
index 54b1e11e5ce..25664deff7d 100644
--- a/chromium/ui/views/controls/scroll_view.h
+++ b/chromium/ui/views/controls/scroll_view.h
@@ -143,6 +143,8 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
void OnScrollEvent(ui::ScrollEvent* event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void OnThemeChanged() override;
+ void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+ bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
// ScrollBarController overrides:
void ScrollToPosition(ScrollBar* source, int position) override;
diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc
index 0359bc89ed3..6213feb7355 100644
--- a/chromium/ui/views/controls/scroll_view_unittest.cc
+++ b/chromium/ui/views/controls/scroll_view_unittest.cc
@@ -31,7 +31,7 @@
#include "ui/views/test/widget_test.h"
#include "ui/views/view_test_api.h"
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
@@ -237,7 +237,7 @@ class ScrollViewTest : public ViewsTestBase {
}
protected:
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
void SetOverlayScrollersEnabled(bool enabled) {
// Ensure the old scroller override is destroyed before creating a new one.
// Otherwise, the swizzlers are interleaved and restore incorrect methods.
@@ -285,7 +285,7 @@ class WidgetScrollViewTest : public test::WidgetTest,
// Adds a ScrollView with the given |contents_view| and does layout.
ScrollView* AddScrollViewWithContents(std::unique_ptr<View> contents,
bool commit_layers = true) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
scroller_style_ = std::make_unique<ui::test::ScopedPreferredScrollerStyle>(
use_overlay_scrollers_);
#endif
@@ -363,7 +363,7 @@ class WidgetScrollViewTest : public test::WidgetTest,
base::RepeatingClosure quit_closure_;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
std::unique_ptr<ui::test::ScopedPreferredScrollerStyle> scroller_style_;
#endif
@@ -1027,7 +1027,7 @@ TEST_F(ScrollViewTest, DontCreateLayerOnViewportIfLayerOnScrollViewCreated) {
EXPECT_FALSE(test_api.contents_viewport()->layer());
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Tests the overlay scrollbars on Mac. Ensure that they show up properly and
// do not overlap each other.
TEST_F(ScrollViewTest, CocoaOverlayScrollBars) {
@@ -1193,7 +1193,7 @@ TEST_F(WidgetScrollViewTest, ScrollersOnRest) {
EXPECT_EQ(gfx::ScrollOffset(x_offset, y_offset), test_api.CurrentOffset());
}
-#endif // OS_MACOSX
+#endif // OS_APPLE
// Test that increasing the size of the viewport "below" scrolled content causes
// the content to scroll up so that it still fills the viewport.
diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.cc b/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.cc
index 122d1c13caa..e16c7b3f6d6 100644
--- a/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.cc
+++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.cc
@@ -11,10 +11,12 @@
namespace views {
-BaseScrollBarButton::BaseScrollBarButton(ButtonListener* listener)
+BaseScrollBarButton::BaseScrollBarButton(ButtonListener* listener,
+ const base::TickClock* tick_clock)
: Button(listener),
repeater_(base::BindRepeating(&BaseScrollBarButton::RepeaterNotifyClick,
- base::Unretained(this))) {}
+ base::Unretained(this)),
+ tick_clock) {}
BaseScrollBarButton::~BaseScrollBarButton() = default;
diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.h b/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.h
index 052f5f1f62f..b055ffd8130 100644
--- a/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.h
+++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar_button.h
@@ -11,6 +11,10 @@
#include "build/build_config.h"
#include "ui/views/repeat_controller.h"
+namespace base {
+class TickClock;
+}
+
namespace views {
///////////////////////////////////////////////////////////////////////////////
@@ -26,7 +30,8 @@ class VIEWS_EXPORT BaseScrollBarButton : public Button {
public:
METADATA_HEADER(BaseScrollBarButton);
- explicit BaseScrollBarButton(ButtonListener* listener);
+ explicit BaseScrollBarButton(ButtonListener* listener,
+ const base::TickClock* tick_clock = nullptr);
~BaseScrollBarButton() override;
protected:
diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar_button_unittest.cc b/chromium/ui/views/controls/scrollbar/base_scroll_bar_button_unittest.cc
new file mode 100644
index 00000000000..9c5f679c17a
--- /dev/null
+++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar_button_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/scrollbar/base_scroll_bar_button.h"
+
+#include <memory>
+
+#include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/display/test/scoped_screen_override.h"
+#include "ui/display/test/test_screen.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/events/event.h"
+#include "ui/views/repeat_controller.h"
+#include "ui/views/test/view_metadata_test_utils.h"
+
+namespace views {
+
+namespace {
+
+using testing::_;
+using testing::AtLeast;
+using testing::AtMost;
+
+class MockButtonListener : public ButtonListener {
+ public:
+ MockButtonListener() = default;
+ MockButtonListener(const MockButtonListener&) = delete;
+ MockButtonListener& operator=(const MockButtonListener&) = delete;
+ ~MockButtonListener() override = default;
+
+ // ButtonListener:
+ MOCK_METHOD(void,
+ ButtonPressed,
+ (Button * sender, const ui::Event& event),
+ (override));
+};
+
+class BaseScrollBarButtonTest : public testing::Test {
+ public:
+ BaseScrollBarButtonTest()
+ : button_(std::make_unique<BaseScrollBarButton>(
+ &listener_,
+ task_environment_.GetMockTickClock())) {}
+
+ ~BaseScrollBarButtonTest() override = default;
+
+ protected:
+ testing::StrictMock<MockButtonListener>& listener() { return listener_; }
+ Button* button() { return button_.get(); }
+
+ void AdvanceTime(base::TimeDelta time_delta) {
+ task_environment_.FastForwardBy(time_delta);
+ }
+
+ private:
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ display::test::TestScreen test_screen_;
+ display::test::ScopedScreenOverride screen_override{&test_screen_};
+
+ testing::StrictMock<MockButtonListener> listener_;
+ const std::unique_ptr<Button> button_;
+};
+
+} // namespace
+
+TEST_F(BaseScrollBarButtonTest, Metadata) {
+ test::TestViewMetadata(button());
+}
+
+TEST_F(BaseScrollBarButtonTest, CallbackFiresOnMouseDown) {
+ EXPECT_CALL(listener(), ButtonPressed(_, _));
+
+ // By default the button should notify its listener on mouse release.
+ button()->OnMousePressed(ui::MouseEvent(
+ ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+}
+
+TEST_F(BaseScrollBarButtonTest, CallbackFilesMultipleTimesMouseHeldDown) {
+ EXPECT_CALL(listener(), ButtonPressed(_, _)).Times(AtLeast(2));
+
+ // By default the button should notify its listener on mouse release.
+ button()->OnMousePressed(ui::MouseEvent(
+ ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+
+ AdvanceTime(RepeatController::GetInitialWaitForTesting() * 10);
+}
+
+TEST_F(BaseScrollBarButtonTest, CallbackStopsFiringAfterMouseReleased) {
+ EXPECT_CALL(listener(), ButtonPressed(_, _)).Times(AtLeast(2));
+
+ // By default the button should notify its listener on mouse release.
+ button()->OnMousePressed(ui::MouseEvent(
+ ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+
+ AdvanceTime(RepeatController::GetInitialWaitForTesting() * 10);
+
+ testing::Mock::VerifyAndClearExpectations(&listener());
+
+ button()->OnMouseReleased(ui::MouseEvent(
+ ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+
+ AdvanceTime(RepeatController::GetInitialWaitForTesting() * 10);
+
+ EXPECT_CALL(listener(), ButtonPressed(_, _)).Times(AtMost(0));
+}
+
+TEST_F(BaseScrollBarButtonTest, CallbackStopsFiringAfterMouseCaptureReleased) {
+ EXPECT_CALL(listener(), ButtonPressed(_, _)).Times(AtLeast(2));
+
+ // By default the button should notify its listener on mouse release.
+ button()->OnMousePressed(ui::MouseEvent(
+ ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
+ ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+
+ AdvanceTime(RepeatController::GetInitialWaitForTesting() * 10);
+
+ testing::Mock::VerifyAndClearExpectations(&listener());
+
+ button()->OnMouseCaptureLost();
+
+ AdvanceTime(RepeatController::GetInitialWaitForTesting() * 10);
+
+ EXPECT_CALL(listener(), ButtonPressed(_, _)).Times(AtMost(0));
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/scrollbar/scroll_bar.cc b/chromium/ui/views/controls/scrollbar/scroll_bar.cc
index 4283f6783ee..1655bf7252c 100644
--- a/chromium/ui/views/controls/scrollbar/scroll_bar.cc
+++ b/chromium/ui/views/controls/scrollbar/scroll_bar.cc
@@ -14,6 +14,7 @@
#include "base/containers/flat_map.h"
#include "base/no_destructor.h"
#include "base/numerics/ranges.h"
+#include "base/numerics/safe_conversions.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
@@ -23,7 +24,6 @@
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/canvas.h"
-#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h"
@@ -158,11 +158,11 @@ void ScrollBar::OnGestureEvent(ui::GestureEvent* event) {
int scroll_amount;
if (IsHorizontal()) {
scroll_amount_f = event->details().scroll_x() - roundoff_error_.x();
- scroll_amount = gfx::ToRoundedInt(scroll_amount_f);
+ scroll_amount = base::ClampRound(scroll_amount_f);
roundoff_error_.set_x(scroll_amount - scroll_amount_f);
} else {
scroll_amount_f = event->details().scroll_y() - roundoff_error_.y();
- scroll_amount = gfx::ToRoundedInt(scroll_amount_f);
+ scroll_amount = base::ClampRound(scroll_amount_f);
roundoff_error_.set_y(scroll_amount - scroll_amount_f);
}
if (ScrollByContentsOffset(scroll_amount))
@@ -310,7 +310,7 @@ void ScrollBar::Update(int viewport_size,
// content size multiplied by the height of the thumb track.
float ratio =
std::min<float>(1.0, static_cast<float>(viewport_size) / contents_size_);
- thumb_->SetLength(gfx::ToRoundedInt(ratio * GetTrackSize()));
+ thumb_->SetLength(base::ClampRound(ratio * GetTrackSize()));
int thumb_position = CalculateThumbPosition(contents_scroll_offset);
thumb_->SetPosition(thumb_position);
@@ -343,7 +343,7 @@ ScrollBar::ScrollBar(bool is_horiz)
///////////////////////////////////////////////////////////////////////////////
// ScrollBar, private:
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// static
base::RetainingOneShotTimer* ScrollBar::GetHideTimerForTesting(
ScrollBar* scroll_bar) {
@@ -409,7 +409,7 @@ int ScrollBar::CalculateContentsOffset(float thumb_position,
thumb_position = thumb_position - (thumb_size / 2);
float result = (thumb_position * (contents_size_ - viewport_size_)) /
(track_size - thumb_size);
- return gfx::ToRoundedInt(result);
+ return base::ClampRound(result);
}
void ScrollBar::SetContentsScrollOffset(int contents_scroll_offset) {
diff --git a/chromium/ui/views/controls/scrollbar/scroll_bar.h b/chromium/ui/views/controls/scrollbar/scroll_bar.h
index cc3ec682be6..264db19019e 100644
--- a/chromium/ui/views/controls/scrollbar/scroll_bar.h
+++ b/chromium/ui/views/controls/scrollbar/scroll_bar.h
@@ -186,6 +186,9 @@ class VIEWS_EXPORT ScrollBar : public View,
friend class test::ScrollViewTestApi;
FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, ScrollBarFitsToBottom);
FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, ThumbFullLengthOfTrack);
+ FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, DragThumbScrollsContent);
+ FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, RightClickOpensMenu);
+ FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, TestPageScrollingByPress);
static base::RetainingOneShotTimer* GetHideTimerForTesting(
ScrollBar* scroll_bar);
diff --git a/chromium/ui/views/controls/scrollbar/scroll_bar_views.cc b/chromium/ui/views/controls/scrollbar/scroll_bar_views.cc
index 5da0afbd601..7d85eb0ac04 100644
--- a/chromium/ui/views/controls/scrollbar/scroll_bar_views.cc
+++ b/chromium/ui/views/controls/scrollbar/scroll_bar_views.cc
@@ -95,7 +95,7 @@ void ScrollBarButton::PaintButtonContents(gfx::Canvas* canvas) {
ui::NativeTheme::ExtraParams ScrollBarButton::GetNativeThemeParams() const {
ui::NativeTheme::ExtraParams params;
- switch (state()) {
+ switch (GetState()) {
case Button::STATE_HOVERED:
params.scrollbar_arrow.is_hovering = true;
break;
@@ -124,7 +124,7 @@ ui::NativeTheme::Part ScrollBarButton::GetNativeThemePart() const {
}
ui::NativeTheme::State ScrollBarButton::GetNativeThemeState() const {
- switch (state()) {
+ switch (GetState()) {
case Button::STATE_HOVERED:
return ui::NativeTheme::kHovered;
case Button::STATE_PRESSED:
diff --git a/chromium/ui/views/controls/scrollbar/scrollbar_unittest.cc b/chromium/ui/views/controls/scrollbar/scrollbar_unittest.cc
index 32933a471ea..12e55b83968 100644
--- a/chromium/ui/views/controls/scrollbar/scrollbar_unittest.cc
+++ b/chromium/ui/views/controls/scrollbar/scrollbar_unittest.cc
@@ -4,11 +4,20 @@
#include <memory>
+#include "base/time/time.h"
#include "build/build_config.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/events/test/event_generator.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
#include "ui/views/controls/scrollbar/scroll_bar.h"
#include "ui/views/controls/scrollbar/scroll_bar_views.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_utils.h"
namespace {
@@ -43,6 +52,20 @@ class TestScrollBarController : public views::ScrollBarController {
int last_position;
};
+// This container is used to forward gesture events to the scrollbar for
+// testing fling and other gestures.
+class TestScrollbarContainer : public views::View {
+ public:
+ TestScrollbarContainer() = default;
+ ~TestScrollbarContainer() override = default;
+ TestScrollbarContainer(const TestScrollbarContainer&) = delete;
+ TestScrollbarContainer& operator=(const TestScrollbarContainer&) = delete;
+
+ void OnGestureEvent(ui::GestureEvent* event) override {
+ children()[0]->OnGestureEvent(event);
+ }
+};
+
} // namespace
namespace views {
@@ -55,29 +78,30 @@ class ScrollBarViewsTest : public ViewsTestBase {
ViewsTestBase::SetUp();
controller_ = std::make_unique<TestScrollBarController>();
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
- params.bounds = gfx::Rect(0, 0, 100, 100);
+ params.bounds = gfx::Rect(0, 0, 100, 300);
widget_->Init(std::move(params));
- View* container = new View();
- widget_->SetContentsView(container);
+ widget_->Show();
+ auto* container =
+ widget_->SetContentsView(std::make_unique<TestScrollbarContainer>());
- scrollbar_ = new ScrollBarViews(true);
+ scrollbar_ =
+ container->AddChildView(std::make_unique<ScrollBarViews>(true));
scrollbar_->SetBounds(0, 0, 100, 100);
scrollbar_->Update(100, 1000, 0);
scrollbar_->set_controller(controller_.get());
- container->AddChildView(scrollbar_);
track_size_ = scrollbar_->GetTrackBounds().width();
}
void TearDown() override {
- widget_->Close();
+ widget_.reset();
ViewsTestBase::TearDown();
}
protected:
- Widget* widget_ = nullptr;
+ UniqueWidgetPtr widget_;
// This is the Views scrollbar.
ScrollBar* scrollbar_ = nullptr;
@@ -89,6 +113,11 @@ class ScrollBarViewsTest : public ViewsTestBase {
std::unique_ptr<TestScrollBarController> controller_;
};
+// Verify properties are accessible via metadata.
+TEST_F(ScrollBarViewsTest, MetaDataTest) {
+ test::TestViewMetadata(scrollbar_);
+}
+
TEST_F(ScrollBarViewsTest, Scrolling) {
EXPECT_EQ(0, scrollbar_->GetPosition());
EXPECT_EQ(900, scrollbar_->GetMaxPosition());
@@ -182,4 +211,53 @@ TEST_F(ScrollBarViewsTest, ThumbFullLengthOfTrack) {
EXPECT_EQ(0, scrollbar_->GetPosition());
}
+TEST_F(ScrollBarViewsTest, RightClickOpensMenu) {
+ EXPECT_EQ(nullptr, scrollbar_->menu_model_);
+ EXPECT_EQ(nullptr, scrollbar_->menu_runner_);
+ scrollbar_->set_context_menu_controller(scrollbar_);
+ scrollbar_->ShowContextMenu(scrollbar_->GetBoundsInScreen().CenterPoint(),
+ ui::MENU_SOURCE_MOUSE);
+ EXPECT_NE(nullptr, scrollbar_->menu_model_);
+ EXPECT_NE(nullptr, scrollbar_->menu_runner_);
+}
+
+#if !defined(OS_APPLE)
+TEST_F(ScrollBarViewsTest, TestPageScrollingByPress) {
+ ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
+ EXPECT_EQ(0, scrollbar_->GetPosition());
+ generator.MoveMouseTo(
+ scrollbar_->GetThumb()->GetBoundsInScreen().right_center() +
+ gfx::Vector2d(4, 0));
+ generator.ClickLeftButton();
+ generator.ClickLeftButton();
+ EXPECT_GT(scrollbar_->GetPosition(), 0);
+}
+
+TEST_F(ScrollBarViewsTest, DragThumbScrollsContent) {
+ ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
+ EXPECT_EQ(0, scrollbar_->GetPosition());
+ generator.MoveMouseTo(
+ scrollbar_->GetThumb()->GetBoundsInScreen().CenterPoint());
+ generator.DragMouseBy(15, 0);
+ EXPECT_GE(scrollbar_->GetPosition(), 10);
+}
+
+TEST_F(ScrollBarViewsTest, FlingGestureScrollsView) {
+ constexpr int kNumScrollSteps = 100;
+ constexpr int kScrollVelocity = 10;
+ ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
+ EXPECT_EQ(0, scrollbar_->GetPosition());
+ const gfx::Point start_pos =
+ widget_->GetContentsView()->GetBoundsInScreen().CenterPoint();
+ const gfx::Point end_pos = start_pos + gfx::Vector2d(-100, 0);
+ generator.GestureScrollSequence(
+ start_pos, end_pos,
+ generator.CalculateScrollDurationForFlingVelocity(
+ start_pos, end_pos, kScrollVelocity, kNumScrollSteps),
+ kNumScrollSteps);
+ // Just make sure the view scrolled
+ EXPECT_GT(scrollbar_->GetPosition(), 0);
+}
+#endif
+
} // namespace views
diff --git a/chromium/ui/views/controls/slider.cc b/chromium/ui/views/controls/slider.cc
index 0f71ce43d4a..87f35789cc2 100644
--- a/chromium/ui/views/controls/slider.cc
+++ b/chromium/ui/views/controls/slider.cc
@@ -8,9 +8,9 @@
#include <memory>
#include "base/check_op.h"
-#include "base/message_loop/message_loop_current.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/task/current_thread.h"
#include "build/build_config.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkCanvas.h"
@@ -53,7 +53,7 @@ Slider::Slider(SliderListener* listener)
pending_accessibility_value_change_(false) {
highlight_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(150));
EnableCanvasFlippingForRTLUI(true);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
#else
SetFocusBehavior(FocusBehavior::ALWAYS);
@@ -134,7 +134,7 @@ void Slider::SetValueInternal(float value, SliderChangeReason reason) {
if (listener_)
listener_->SliderValueChanged(this, value_, old_value, reason);
- if (old_value_valid && base::MessageLoopCurrent::Get()) {
+ if (old_value_valid && base::CurrentThread::Get()) {
// Do not animate when setting the value of the slider for the first time.
// There is no message-loop when running tests. So we cannot animate then.
if (!move_animation_) {
diff --git a/chromium/ui/views/controls/slider.h b/chromium/ui/views/controls/slider.h
index e1a78cb18dd..239cca3b254 100644
--- a/chromium/ui/views/controls/slider.h
+++ b/chromium/ui/views/controls/slider.h
@@ -47,7 +47,7 @@ class VIEWS_EXPORT Slider : public View, public gfx::AnimationDelegate {
public:
METADATA_HEADER(Slider);
- explicit Slider(SliderListener* listener);
+ explicit Slider(SliderListener* listener = nullptr);
~Slider() override;
float GetValue() const;
diff --git a/chromium/ui/views/controls/slider_unittest.cc b/chromium/ui/views/controls/slider_unittest.cc
index 895907e21ed..03b3b4fd329 100644
--- a/chromium/ui/views/controls/slider_unittest.cc
+++ b/chromium/ui/views/controls/slider_unittest.cc
@@ -25,6 +25,7 @@
#include "ui/views/test/slider_test_api.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_utils.h"
@@ -169,7 +170,7 @@ class SliderTest : public views::ViewsTestBase {
// The maximum y value within the bounds of the slider.
int max_y_ = 0;
// The widget container for the slider being tested.
- views::Widget* widget_ = nullptr;
+ views::UniqueWidgetPtr widget_;
// An event generator.
std::unique_ptr<ui::test::EventGenerator> event_generator_;
@@ -179,10 +180,9 @@ class SliderTest : public views::ViewsTestBase {
void SliderTest::SetUp() {
views::ViewsTestBase::SetUp();
- slider_ = new Slider(nullptr);
- View* view = slider_;
- gfx::Size size = view->GetPreferredSize();
- view->SetSize(size);
+ auto slider = std::make_unique<Slider>();
+ gfx::Size size = slider->GetPreferredSize();
+ slider->SetSize(size);
max_x_ = size.width() - 1;
max_y_ = size.height() - 1;
default_locale_ = base::i18n::GetConfiguredLocale();
@@ -191,19 +191,17 @@ void SliderTest::SetUp() {
CreateParams(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS));
init_params.bounds = gfx::Rect(size);
- widget_ = new views::Widget();
+ widget_ = std::make_unique<Widget>();
widget_->Init(std::move(init_params));
- widget_->SetContentsView(slider_);
+ slider_ = widget_->SetContentsView(std::move(slider));
widget_->Show();
event_generator_ =
- std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_));
+ std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_.get()));
}
void SliderTest::TearDown() {
- if (widget_ && !widget_->IsClosed())
- widget_->Close();
-
+ widget_.reset();
base::i18n::SetICUDefaultLocale(default_locale_);
views::ViewsTestBase::TearDown();
@@ -234,7 +232,7 @@ TEST_F(SliderTest, UpdateFromClickRTLHorizontal) {
}
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
-#if !defined(OS_MACOSX) || defined(USE_AURA)
+#if !defined(OS_APPLE) || defined(USE_AURA)
// Test the slider location after a tap gesture.
TEST_F(SliderTest, SliderValueForTapGesture) {
@@ -394,6 +392,6 @@ TEST_F(SliderTest, SliderRaisesA11yEvents) {
EXPECT_TRUE(observer.value_changed());
}
-#endif // !defined(OS_MACOSX) || defined(USE_AURA)
+#endif // !defined(OS_APPLE) || defined(USE_AURA)
} // namespace views
diff --git a/chromium/ui/views/controls/styled_label.cc b/chromium/ui/views/controls/styled_label.cc
index 5c915f9d452..b9e036c5d7d 100644
--- a/chromium/ui/views/controls/styled_label.cc
+++ b/chromium/ui/views/controls/styled_label.cc
@@ -100,8 +100,9 @@ void StyledLabel::SetText(const base::string16& text) {
OnPropertyChanged(&text_, kPropertyEffectsPreferredSizeChanged);
}
-gfx::FontList StyledLabel::GetDefaultFontList() const {
- return style::GetFont(text_context_, default_text_style_);
+gfx::FontList StyledLabel::GetFontList(const RangeStyleInfo& style_info) const {
+ return style_info.custom_font.value_or(style::GetFont(
+ text_context_, style_info.text_style.value_or(default_text_style_)));
}
void StyledLabel::AddStyleRange(const gfx::Range& range,
@@ -149,16 +150,16 @@ void StyledLabel::SetDefaultTextStyle(int text_style) {
}
int StyledLabel::GetLineHeight() const {
- return specified_line_height_;
+ return line_height_.value_or(
+ style::GetLineHeight(text_context_, default_text_style_));
}
void StyledLabel::SetLineHeight(int line_height) {
- if (specified_line_height_ == line_height)
+ if (line_height_ == line_height)
return;
- specified_line_height_ = line_height;
- OnPropertyChanged(&specified_line_height_,
- kPropertyEffectsPreferredSizeChanged);
+ line_height_ = line_height;
+ OnPropertyChanged(&line_height_, kPropertyEffectsPreferredSizeChanged);
}
base::Optional<SkColor> StyledLabel::GetDisplayedOnBackgroundColor() const {
@@ -338,26 +339,6 @@ int StyledLabel::StartX(int excess_space) const {
: excess_space);
}
-int StyledLabel::GetDefaultLineHeight() const {
- return specified_line_height_ > 0
- ? specified_line_height_
- : std::max(
- style::GetLineHeight(text_context_, default_text_style_),
- GetDefaultFontList().GetHeight());
-}
-
-gfx::FontList StyledLabel::GetFontListForRange(
- const StyleRanges::const_iterator& range) const {
- if (range == style_ranges_.end())
- return GetDefaultFontList();
-
- return range->style_info.custom_font
- ? range->style_info.custom_font.value()
- : style::GetFont(
- text_context_,
- range->style_info.text_style.value_or(default_text_style_));
-}
-
void StyledLabel::CalculateLayout(int width) const {
const gfx::Insets insets = GetInsets();
width = std::max(width, insets.width());
@@ -369,7 +350,7 @@ void StyledLabel::CalculateLayout(int width) const {
layout_views_ = std::make_unique<LayoutViews>();
const int content_width = width - insets.width();
- const int default_line_height = GetDefaultLineHeight();
+ const int line_height = GetLineHeight();
RangeStyleInfo default_style;
default_style.text_style = default_text_style_;
int max_width = 0, total_height = 0;
@@ -379,7 +360,7 @@ void StyledLabel::CalculateLayout(int width) const {
StyleRanges::const_iterator current_range = style_ranges_.begin();
for (base::string16 remaining_string = text_;
content_width > 0 && !remaining_string.empty();) {
- layout_size_info_.line_sizes.emplace_back(0, default_line_height);
+ layout_size_info_.line_sizes.emplace_back(0, line_height);
auto& line_size = layout_size_info_.line_sizes.back();
layout_views_->views_per_line.emplace_back();
auto& views = layout_views_->views_per_line.back();
@@ -412,13 +393,13 @@ void StyledLabel::CalculateLayout(int width) const {
!current_range->style_info.custom_view)) {
const gfx::Rect chunk_bounds(line_size.width(), 0,
content_width - line_size.width(),
- default_line_height);
+ line_height);
// If the start of the remaining text is inside a styled range, the font
// style may differ from the base font. The font specified by the range
// should be used when eliding text.
- gfx::FontList text_font_list = position >= range.start()
- ? GetFontListForRange(current_range)
- : GetDefaultFontList();
+ gfx::FontList text_font_list =
+ GetFontList((position >= range.start()) ? current_range->style_info
+ : RangeStyleInfo());
int elide_result = gfx::ElideRectangleText(
remaining_string, text_font_list, chunk_bounds.width(),
chunk_bounds.height(), gfx::WRAP_LONG_WORDS, &substrings);
diff --git a/chromium/ui/views/controls/styled_label.h b/chromium/ui/views/controls/styled_label.h
index 31b24580ecb..b12461bad21 100644
--- a/chromium/ui/views/controls/styled_label.h
+++ b/chromium/ui/views/controls/styled_label.h
@@ -118,9 +118,10 @@ class VIEWS_EXPORT StyledLabel : public View {
const base::string16& GetText() const;
void SetText(const base::string16& text);
- // Returns the font list that results from the default text context and style
- // for ranges. This can be used as the basis for a range |custom_font|.
- gfx::FontList GetDefaultFontList() const;
+ // Returns the FontList that should be used. |style_info| is an optional
+ // argument that takes precedence over the default values.
+ gfx::FontList GetFontList(
+ const RangeStyleInfo& style_info = RangeStyleInfo()) const;
// Marks the given range within |text_| with style defined by |style_info|.
// |range| must be contained in |text_|.
@@ -206,13 +207,6 @@ class VIEWS_EXPORT StyledLabel : public View {
// space available on that line.
int StartX(int excess_space) const;
- // Returns the default line height, based on the default style.
- int GetDefaultLineHeight() const;
-
- // Returns the FontList that should be used for |range|.
- gfx::FontList GetFontListForRange(
- const StyleRanges::const_iterator& range) const;
-
// Sets |layout_size_info_| and |layout_views_| for the given |width|. No-op
// if current_width <= width <= max_width, where:
// current_width = layout_size_info_.total_size.width()
@@ -239,8 +233,7 @@ class VIEWS_EXPORT StyledLabel : public View {
int text_context_ = style::CONTEXT_LABEL;
int default_text_style_ = style::STYLE_PRIMARY;
- // Line height. If zero, style::GetLineHeight() is used.
- int specified_line_height_ = 0;
+ base::Optional<int> line_height_;
// The listener that will be informed of link clicks.
StyledLabelListener* listener_;
diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc
index 2a1a5db6ab1..c42c1452e19 100644
--- a/chromium/ui/views/controls/styled_label_unittest.cc
+++ b/chromium/ui/views/controls/styled_label_unittest.cc
@@ -285,7 +285,7 @@ TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) {
StyledLabel::RangeStyleInfo style_info;
style_info.tooltip = ASCIIToUTF16("tooltip");
style_info.custom_font =
- styled()->GetDefaultFontList().DeriveWithStyle(gfx::Font::UNDERLINE);
+ styled()->GetFontList().DeriveWithStyle(gfx::Font::UNDERLINE);
styled()->AddStyleRange(
gfx::Range(text.size(), text.size() + underlined_text.size()),
style_info);
@@ -307,7 +307,7 @@ TEST_F(StyledLabelTest, StyledRangeTextStyleBold) {
// Pretend disabled text becomes bold for testing.
bold_provider.SetFont(
style::CONTEXT_LABEL, style::STYLE_DISABLED,
- styled()->GetDefaultFontList().DeriveWithWeight(gfx::Font::Weight::BOLD));
+ styled()->GetFontList().DeriveWithWeight(gfx::Font::Weight::BOLD));
StyledLabel::RangeStyleInfo style_info;
style_info.text_style = style::STYLE_DISABLED;
@@ -373,11 +373,8 @@ TEST_F(StyledLabelTest, Color) {
styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
- Widget* widget = new Widget();
- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
- widget->Init(std::move(params));
- View* container = new View();
- widget->SetContentsView(container);
+ std::unique_ptr<Widget> widget = CreateTestWidget();
+ View* container = widget->SetContentsView(std::make_unique<View>());
// The code below is not prepared to deal with dark mode.
widget->GetNativeTheme()->set_use_dark_colors(false);
@@ -394,6 +391,7 @@ TEST_F(StyledLabelTest, Color) {
container->AddChildView(std::make_unique<Link>(ASCIIToUTF16(text_link)));
const SkColor kDefaultLinkColor = link->GetEnabledColor();
+ ASSERT_EQ(3u, styled()->children().size());
EXPECT_EQ(SK_ColorBLUE, LabelAt(0)->GetEnabledColor());
EXPECT_EQ(kDefaultLinkColor,
LabelAt(1, Link::kViewClassName)->GetEnabledColor());
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 50ecb7c042b..cd1093b7a5d 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -76,7 +76,7 @@ void Tab::SetSelected(bool selected) {
contents_->SetVisible(selected);
contents_->parent()->InvalidateLayout();
SetState(selected ? State::kActive : State::kInactive);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
SetFocusBehavior(selected ? FocusBehavior::ACCESSIBLE_ONLY
: FocusBehavior::NEVER);
#else
@@ -471,10 +471,8 @@ void TabStrip::OnPaintBorder(gfx::Canvas* canvas) {
max_main_axis - min_main_axis, kSelectedBorderThickness);
if (!is_horizontal)
rect.Transpose();
- canvas->FillRect(
- rect, SkColorSetA(GetNativeTheme()->GetSystemColor(
- ui::NativeTheme::kColorId_FocusedBorderColor),
- SK_AlphaOPAQUE));
+ canvas->FillRect(rect, GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_TabSelectedBorderColor));
}
DEFINE_ENUM_CONVERTERS(TabbedPane::Orientation,
diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc
index fa791c4700c..5e9f53653aa 100644
--- a/chromium/ui/views/controls/table/table_view.cc
+++ b/chromium/ui/views/controls/table/table_view.cc
@@ -85,7 +85,7 @@ ui::NativeTheme::ColorId selected_text_color_id(bool has_focus) {
// Whether the platform "command" key is down.
bool IsCmdOrCtrl(const ui::Event& event) {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
return event.IsCommandDown();
#else
return event.IsControlDown();
@@ -462,7 +462,7 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
return true;
case ui::VKEY_UP:
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (event.IsAltDown()) {
if (GetRowCount())
SelectByViewIndex(0);
@@ -475,7 +475,7 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
return true;
case ui::VKEY_DOWN:
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (event.IsAltDown()) {
if (GetRowCount())
SelectByViewIndex(GetRowCount() - 1);
@@ -1234,7 +1234,7 @@ void TableView::UpdateVirtualAccessibilityChildren() {
cell_data.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
1);
if (base::i18n::IsRTL())
- cell_data.SetTextDirection(ax::mojom::TextDirection::kRtl);
+ cell_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
auto sort_direction = ax::mojom::SortDirection::kUnsorted;
if (column.sortable && primary_sorted_column_id.has_value() &&
@@ -1323,7 +1323,7 @@ void TableView::UpdateVirtualAccessibilityChildren() {
cell_data.SetName(model_->GetText(model_index, column.id));
if (base::i18n::IsRTL())
- cell_data.SetTextDirection(ax::mojom::TextDirection::kRtl);
+ cell_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
auto sort_direction = ax::mojom::SortDirection::kUnsorted;
if (column.sortable && primary_sorted_column_id.has_value() &&
diff --git a/chromium/ui/views/controls/table/table_view_unittest.cc b/chromium/ui/views/controls/table/table_view_unittest.cc
index df538cc5d3c..acf951de599 100644
--- a/chromium/ui/views/controls/table/table_view_unittest.cc
+++ b/chromium/ui/views/controls/table/table_view_unittest.cc
@@ -122,7 +122,7 @@ class TableViewTestHelper {
namespace {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
constexpr int kCtrlOrCmdMask = ui::EF_COMMAND_DOWN;
#else
constexpr int kCtrlOrCmdMask = ui::EF_CONTROL_DOWN;
@@ -1179,7 +1179,7 @@ TEST_F(TableViewTest, SelectionNoSelectOnRemove) {
}
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Verifies selection works by way of a gesture.
TEST_F(TableViewTest, SelectOnTap) {
// Initially no selection.
diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc
index d0c5e12a9d1..926f65e3623 100644
--- a/chromium/ui/views/controls/textfield/textfield.cc
+++ b/chromium/ui/views/controls/textfield/textfield.cc
@@ -15,6 +15,7 @@
#endif
#include "base/command_line.h"
+#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
@@ -70,7 +71,7 @@
#endif
#if defined(USE_X11)
-#include "ui/base/x/x11_util_internal.h" // nogncheck
+#include "ui/base/x/x11_util.h" // nogncheck
#endif
#if defined(OS_CHROMEOS)
@@ -78,11 +79,17 @@
#include "ui/wm/core/ime_util_chromeos.h"
#endif
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
#include "ui/base/cocoa/defaults_utils.h"
#include "ui/base/cocoa/secure_password_input.h"
#endif
+#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
+#include "ui/ozone/public/ozone_platform.h"
+#include "ui/ozone/public/platform_gl_egl_utility.h"
+#endif
+
namespace views {
namespace {
@@ -100,7 +107,7 @@ enum TextfieldPropertyKey {
kTextfieldSelectedRange,
};
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
constexpr gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_EXTEND;
constexpr gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_CARET;
constexpr gfx::SelectionBehavior kMoveParagraphSelectionBehavior =
@@ -185,14 +192,14 @@ ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) {
#endif
return ui::TextEditCommand::DELETE_BACKWARD;
}
-#if defined(OS_LINUX)
+#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)
+#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;
@@ -260,7 +267,7 @@ bool IsControlKeyModifier(int flags) {
// Control-modified key combination, but we cannot extend it to other platforms
// as Control has different meanings and behaviors.
// https://crrev.com/2580483002/#msg46
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
return flags & ui::EF_CONTROL_DOWN;
#else
return false;
@@ -272,6 +279,24 @@ bool IsValidCharToInsert(const base::char16& ch) {
return (ch >= 0x20 && ch < 0x7F) || ch > 0x9F;
}
+bool CanUseTransparentBackgroundForDragImage() {
+#if defined(USE_OZONE)
+ if (::features::IsUsingOzonePlatform()) {
+ const auto* const egl_utility =
+ ui::OzonePlatform::GetInstance()->GetPlatformGLEGLUtility();
+ return egl_utility ? egl_utility->IsTransparentBackgroundSupported()
+ : false;
+ }
+#endif
+#if defined(USE_X11)
+ // Fallback on the background color if the system doesn't support compositing.
+ return ui::XVisualManager::GetInstance()->ArgbVisualAvailable();
+#else
+ // Other platforms allow this.
+ return true;
+#endif
+}
+
} // namespace
// static
@@ -283,7 +308,7 @@ base::TimeDelta Textfield::GetCaretBlinkInterval() {
? base::TimeDelta()
: base::TimeDelta::FromMilliseconds(system_value);
}
-#elif defined(OS_MACOSX)
+#elif defined(OS_APPLE)
base::TimeDelta system_value;
if (ui::TextInsertionCaretBlinkPeriod(&system_value))
return system_value;
@@ -315,7 +340,7 @@ Textfield::Textfield()
if (use_focus_ring_)
focus_ring_ = FocusRing::Install(this);
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Do not map accelerators on Mac. E.g. They might not reflect custom
// keybindings that a user has set. But also on Mac, these commands dispatch
// via the "responder chain" when the OS searches through menu items in the
@@ -593,6 +618,26 @@ size_t Textfield::GetCursorPosition() const {
return model_->GetCursorPosition();
}
+void Textfield::SetTextAndScrollAndSelectRange(
+ const base::string16& text,
+ const size_t cursor_position,
+ const std::vector<size_t>& positions,
+ const gfx::Range range) {
+ // Pass cursor_position to SetText to ensure edit history works as expected.
+ model_->SetText(text, cursor_position);
+ NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+ for (auto position : positions) {
+ model_->MoveCursorTo(position);
+ GetRenderText()->GetUpdatedCursorBounds();
+ }
+ model_->SelectRange(range);
+ OnPropertyChanged(&model_ + kTextfieldSelectedRange, kPropertyEffectsPaint);
+ // We don't set the |text_changed| param to true because that would notify
+ // the TextfieldController::ContentsChanged(), which should only occur when
+ // the user changes the text.
+ UpdateAfterChange(false, true);
+}
+
void Textfield::SetColor(SkColor value) {
GetRenderText()->SetColor(value);
cursor_view_->layer()->SetColor(value);
@@ -644,6 +689,7 @@ void Textfield::SetAccessibleName(const base::string16& name) {
accessible_name_ = name;
OnPropertyChanged(&accessible_name_, kPropertyEffectsNone);
+ NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
}
void Textfield::SetObscuredGlyphSpacing(int spacing) {
@@ -655,6 +701,24 @@ void Textfield::SetExtraInsets(const gfx::Insets& insets) {
UpdateBorder();
}
+void Textfield::FitToLocalBounds() {
+ // Textfield insets include a reasonable amount of whitespace on all sides of
+ // the default font list. Fallback fonts with larger heights may paint over
+ // the vertical whitespace as needed. Alternate solutions involve undesirable
+ // behavior like changing the default font size, shrinking some fallback fonts
+ // 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);
+ bounds.set_x(GetMirroredXForRect(bounds));
+ GetRenderText()->SetDisplayRect(bounds);
+ OnCaretBoundsChanged();
+ UpdateCursorViewPosition();
+ UpdateCursorVisibility();
+}
+
////////////////////////////////////////////////////////////////////////////////
// Textfield, View overrides:
@@ -724,8 +788,9 @@ bool Textfield::OnMousePressed(const ui::MouseEvent& event) {
return selection_controller_.OnMousePressed(
event, handled,
- had_focus ? SelectionController::FOCUSED
- : SelectionController::UNFOCUSED);
+ had_focus
+ ? SelectionController::InitialFocusStateOnMousePress::kFocused
+ : SelectionController::InitialFocusStateOnMousePress::kUnFocused);
}
bool Textfield::OnMouseDragged(const ui::MouseEvent& event) {
@@ -1090,21 +1155,7 @@ bool Textfield::HandleAccessibleAction(const ui::AXActionData& action_data) {
}
void Textfield::OnBoundsChanged(const gfx::Rect& previous_bounds) {
- // Textfield insets include a reasonable amount of whitespace on all sides of
- // the default font list. Fallback fonts with larger heights may paint over
- // the vertical whitespace as needed. Alternate solutions involve undesirable
- // behavior like changing the default font size, shrinking some fallback fonts
- // 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);
- bounds.set_x(GetMirroredXForRect(bounds));
- GetRenderText()->SetDisplayRect(bounds);
- OnCaretBoundsChanged();
- UpdateCursorViewPosition();
- UpdateCursorVisibility();
+ FitToLocalBounds();
}
bool Textfield::GetNeedsNotificationWhenVisibleBoundsChange() const {
@@ -1127,11 +1178,11 @@ void Textfield::OnFocus() {
if (focus_reason_ == ui::TextInputClient::FOCUS_REASON_NONE)
focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD)
password_input_enabler_ =
std::make_unique<ui::ScopedPasswordInputEnabler>();
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
GetRenderText()->set_focused(true);
if (ShouldShowCursor()) {
@@ -1172,9 +1223,9 @@ void Textfield::OnBlur() {
SchedulePaint();
View::OnBlur();
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
password_input_enabler_.reset();
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
}
gfx::Point Textfield::GetKeyboardContextMenuLocation() {
@@ -1237,12 +1288,9 @@ void Textfield::WriteDragDataForView(View* sender,
SkBitmap bitmap;
float raster_scale = ScaleFactorForDragFromWidget(GetWidget());
- SkColor color = SK_ColorTRANSPARENT;
-#if defined(USE_X11)
- // Fallback on the background color if the system doesn't support compositing.
- if (!ui::XVisualManager::GetInstance()->ArgbVisualAvailable())
- color = GetBackgroundColor();
-#endif
+ SkColor color = CanUseTransparentBackgroundForDragImage()
+ ? SK_ColorTRANSPARENT
+ : GetBackgroundColor();
label.Paint(PaintInfo::CreateRootPaintInfo(
ui::CanvasPainter(&bitmap, label.size(), raster_scale, color,
GetWidget()->GetCompositor()->is_pixel_canvas())
@@ -1459,21 +1507,21 @@ void Textfield::SetCompositionText(const ui::CompositionText& composition) {
OnAfterUserAction();
}
-void Textfield::ConfirmCompositionText(bool keep_selection) {
+uint32_t Textfield::ConfirmCompositionText(bool keep_selection) {
// TODO(b/134473433) Modify this function so that when keep_selection is
// true, the selection is not changed when text committed
if (keep_selection) {
NOTIMPLEMENTED_LOG_ONCE();
}
if (!model_->HasCompositionText())
- return;
-
+ return 0;
OnBeforeUserAction();
skip_input_method_cancel_composition_ = true;
- model_->ConfirmCompositionText();
+ const uint32_t confirmed_text_length = model_->ConfirmCompositionText();
skip_input_method_cancel_composition_ = false;
UpdateAfterChange(true, true);
OnAfterUserAction();
+ return confirmed_text_length;
}
void Textfield::ClearCompositionText() {
@@ -1749,7 +1797,7 @@ bool Textfield::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
return readable && HasSelection();
case ui::TextEditCommand::PASTE:
ui::Clipboard::GetForCurrentThread()->ReadText(
- ui::ClipboardBuffer::kCopyPaste, &result);
+ ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &result);
return editable && !result.empty();
case ui::TextEditCommand::SELECT_ALL:
return !GetText().empty() &&
@@ -1768,7 +1816,7 @@ bool Textfield::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
case ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION:
// On Mac, the textfield should respond to Up/Down arrows keys and
// PageUp/PageDown.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
return true;
#else
return false;
@@ -1819,10 +1867,33 @@ bool Textfield::SetCompositionFromExistingText(
#endif
#if defined(OS_CHROMEOS)
+gfx::Range Textfield::GetAutocorrectRange() const {
+ return model_->autocorrect_range();
+}
+
+gfx::Rect Textfield::GetAutocorrectCharacterBounds() const {
+ gfx::Range autocorrect_range = model_->autocorrect_range();
+ if (autocorrect_range.is_empty())
+ return gfx::Rect();
+
+ gfx::RenderText* render_text = GetRenderText();
+ const gfx::SelectionModel caret(autocorrect_range, gfx::CURSOR_BACKWARD);
+ gfx::Rect rect;
+ rect = render_text->GetCursorBounds(caret, false);
+
+ ConvertRectToScreen(this, &rect);
+ return rect;
+}
+
bool Textfield::SetAutocorrectRange(const base::string16& autocorrect_text,
const gfx::Range& range) {
- // TODO(crbug.com/1091088) Implement autocorrect range textfield handling.
- return false;
+ base::UmaHistogramEnumeration("InputMethod.Assistive.Autocorrect.Count",
+ TextInputClient::SubClass::kTextField);
+ return model_->SetAutocorrectRange(autocorrect_text, range);
+}
+
+void Textfield::ClearAutocorrectRange() {
+ model_->SetAutocorrectRange(base::string16(), gfx::Range());
}
#endif
@@ -1863,7 +1934,8 @@ gfx::Point Textfield::GetLastClickRootLocation() const {
base::string16 Textfield::GetSelectionClipboardText() const {
base::string16 selection_clipboard_text;
ui::Clipboard::GetForCurrentThread()->ReadText(
- ui::ClipboardBuffer::kSelection, &selection_clipboard_text);
+ ui::ClipboardBuffer::kSelection, /* data_dst = */ nullptr,
+ &selection_clipboard_text);
return selection_clipboard_text;
}
diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h
index 775ce4f9683..0763cc99053 100644
--- a/chromium/ui/views/controls/textfield/textfield.h
+++ b/chromium/ui/views/controls/textfield/textfield.h
@@ -51,11 +51,11 @@ namespace base {
class TimeDelta;
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
namespace ui {
class ScopedPasswordInputEnabler;
}
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
namespace views {
@@ -122,6 +122,23 @@ class VIEWS_EXPORT Textfield : public View,
void SetText(const base::string16& new_text);
void SetText(const base::string16& new_text, size_t cursor_position);
+ // Similar to calling SetText followed by SetSelectedRange calls, but will
+ // dedupe some calls. Notably, this prevents OnCaretBoundsChanged from being
+ // called multiple times for a single change which can cause issues for
+ // accessibility tools.
+ // - |text| and |cursor_position| see SetText() comment above.
+ // - |scroll_positions| a vector of positions to scroll into view. This can
+ // contain multiple positions which can be useful to e.g. ensure multiple
+ // positions are in view (if possible). Scrolls are applied in the order of
+ // |scroll_positions|; i.e., later positions will have priority over earlier
+ // positions.
+ // - |range| is used to invoke SetSelectedRange and will also be scrolled to.
+ void SetTextAndScrollAndSelectRange(
+ const base::string16& text,
+ const size_t cursor_position,
+ const std::vector<size_t>& scroll_positions,
+ const gfx::Range range);
+
// Appends the given string to the previously-existing text in the field.
void AppendText(const base::string16& new_text);
@@ -261,6 +278,10 @@ class VIEWS_EXPORT Textfield : public View,
void SetExtraInsets(const gfx::Insets& insets);
+ // Fits the textfield to the local bounds, applying internal padding and
+ // updating the cursor position and visibility.
+ void FitToLocalBounds();
+
// View overrides:
int GetBaseline() const override;
gfx::Size CalculatePreferredSize() const override;
@@ -346,7 +367,7 @@ class VIEWS_EXPORT Textfield : public View,
// ui::TextInputClient overrides:
void SetCompositionText(const ui::CompositionText& composition) override;
- void ConfirmCompositionText(bool keep_selection) override;
+ uint32_t ConfirmCompositionText(bool keep_selection) override;
void ClearCompositionText() override;
void InsertText(const base::string16& text) override;
void InsertChar(const ui::KeyEvent& event) override;
@@ -387,8 +408,11 @@ class VIEWS_EXPORT Textfield : public View,
#endif
#if defined(OS_CHROMEOS)
+ 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;
#endif
#if defined(OS_WIN)
@@ -402,7 +426,7 @@ class VIEWS_EXPORT Textfield : public View,
#endif
views::PropertyChangedSubscription AddTextChangedCallback(
- views::PropertyChangedCallback callback);
+ views::PropertyChangedCallback callback) WARN_UNUSED_RESULT;
protected:
// Inserts or appends a character in response to an IME operation.
@@ -670,10 +694,10 @@ class VIEWS_EXPORT Textfield : public View,
// View containing the text cursor.
View* cursor_view_ = nullptr;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Used to track active password input sessions.
std::unique_ptr<ui::ScopedPasswordInputEnabler> password_input_enabler_;
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
// How this textfield was focused.
ui::TextInputClient::FocusReason focus_reason_ =
diff --git a/chromium/ui/views/controls/textfield/textfield_model.cc b/chromium/ui/views/controls/textfield/textfield_model.cc
index d3df8334a98..24514cb8e02 100644
--- a/chromium/ui/views/controls/textfield/textfield_model.cc
+++ b/chromium/ui/views/controls/textfield/textfield_model.cc
@@ -9,10 +9,10 @@
#include "base/check_op.h"
#include "base/macros.h"
-#include "base/message_loop/message_loop_current.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/task/current_thread.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/range/range.h"
@@ -345,7 +345,7 @@ gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) {
// the default kill ring size of 1 (i.e. a single buffer) is assumed.
base::string16* GetKillBuffer() {
static base::NoDestructor<base::string16> kill_buffer;
- DCHECK(base::MessageLoopCurrentForUI::IsSet());
+ DCHECK(base::CurrentUIThread::IsSet());
return kill_buffer.get();
}
@@ -621,7 +621,7 @@ bool TextfieldModel::Copy() {
bool TextfieldModel::Paste() {
base::string16 text;
ui::Clipboard::GetForCurrentThread()->ReadText(
- ui::ClipboardBuffer::kCopyPaste, &text);
+ ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &text);
if (text.empty())
return false;
@@ -753,6 +753,45 @@ 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);
+ }
+ return true;
+}
+#endif
+
void TextfieldModel::SetCompositionFromExistingText(const gfx::Range& range) {
if (range.is_empty() || !gfx::Range(0, text().length()).Contains(range)) {
ClearComposition();
@@ -763,10 +802,11 @@ void TextfieldModel::SetCompositionFromExistingText(const gfx::Range& range) {
render_text_->SetCompositionRange(range);
}
-void TextfieldModel::ConfirmCompositionText() {
+uint32_t TextfieldModel::ConfirmCompositionText() {
DCHECK(HasCompositionText());
base::string16 composition =
text().substr(composition_range_.start(), composition_range_.length());
+ uint32_t composition_length = composition_range_.length();
// TODO(oshima): current behavior on ChromeOS is a bit weird and not
// sure exactly how this should work. Find out and fix if necessary.
AddOrMergeEditHistory(std::make_unique<internal::InsertEdit>(
@@ -775,6 +815,7 @@ void TextfieldModel::ConfirmCompositionText() {
ClearComposition();
if (delegate_)
delegate_->OnCompositionTextConfirmedOrCleared();
+ return composition_length;
}
void TextfieldModel::CancelCompositionText() {
diff --git a/chromium/ui/views/controls/textfield/textfield_model.h b/chromium/ui/views/controls/textfield/textfield_model.h
index 9873bc160e9..2d581a86104 100644
--- a/chromium/ui/views/controls/textfield/textfield_model.h
+++ b/chromium/ui/views/controls/textfield/textfield_model.h
@@ -233,14 +233,26 @@ class VIEWS_EXPORT TextfieldModel {
// composition text.
void SetCompositionText(const ui::CompositionText& composition);
+#if defined(OS_CHROMEOS)
+ // 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);
+#endif
+
// Puts the text in the specified range into composition mode.
// This method should not be called with composition text or an invalid range.
// The provided range is checked against the string's length, if |range| is
// out of bounds, the composition will be cleared.
void SetCompositionFromExistingText(const gfx::Range& range);
- // Converts current composition text into final content.
- void ConfirmCompositionText();
+ // Converts current composition text into final content and returns the
+ // length of the text committed.
+ uint32_t ConfirmCompositionText();
// Removes current composition text.
void CancelCompositionText();
@@ -321,9 +333,16 @@ class VIEWS_EXPORT TextfieldModel {
// The stylized text, cursor, selection, and the visual layout model.
std::unique_ptr<gfx::RenderText> render_text_;
- // The composition range.
gfx::Range composition_range_;
+#if defined(OS_CHROMEOS)
+ 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
// newest ones are at the back of the list.
using EditHistory = std::list<std::unique_ptr<internal::Edit>>;
diff --git a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
index d9b29c3f0fb..f3919eece4a 100644
--- a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc
@@ -7,6 +7,7 @@
#include <stddef.h>
#include <memory>
+#include <string>
#include <vector>
#include "base/auto_reset.h"
@@ -185,7 +186,7 @@ TEST_F(TextfieldModelTest, EditString_ComplexScript) {
// TODO(xji): temporarily disable in platform Win since the complex script
// characters turned into empty square due to font regression. So, not able
// to test 2 characters belong to the same grapheme.
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
EXPECT_EQ(
base::WideToUTF16(L"\x0915\x093f\x0061\x0062\x0915\x094d\x092e\x094d"),
model.text());
@@ -197,7 +198,7 @@ TEST_F(TextfieldModelTest, EditString_ComplexScript) {
// TODO(xji): temporarily disable in platform Win since the complex script
// characters turned into empty square due to font regression. So, not able
// to test 2 characters belong to the same grapheme.
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
EXPECT_TRUE(model.Delete());
EXPECT_EQ(base::WideToUTF16(L"\x0061\x0062\x0915\x094d\x092e\x094d"),
model.text());
@@ -249,7 +250,7 @@ TEST_F(TextfieldModelTest, EditString_ComplexScript) {
model.SetText(base::WideToUTF16(L"ABC\xFF80\xFF9E"), 0);
model.MoveCursorTo(model.text().length());
model.Backspace();
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// On Mac, the entire cluster should be deleted to match
// NSTextField behavior.
EXPECT_EQ(base::WideToUTF16(L"ABC"), model.text());
@@ -264,7 +265,7 @@ TEST_F(TextfieldModelTest, EditString_ComplexScript) {
model.SetText(base::WideToUTF16(L"\U0001F466\U0001F3FE"), 0);
model.MoveCursorTo(model.text().length());
model.Backspace();
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// On Mac, the entire emoji should be deleted to match NSTextField
// behavior.
EXPECT_EQ(base::WideToUTF16(L""), model.text());
@@ -370,7 +371,7 @@ TEST_F(TextfieldModelTest, Selection_BidiWithNonSpacingMarks) {
// TODO(xji): temporarily disable in platform Win since the complex script
// characters turned into empty square due to font regression. So, not able
// to test 2 characters belong to the same grapheme.
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
model.Append(
base::WideToUTF16(L"abc\x05E9\x05BC\x05C1\x05B8\x05E0\x05B8"
L"def"));
@@ -705,14 +706,16 @@ TEST_F(TextfieldModelTest, Clipboard) {
// Cut with an empty selection should do nothing.
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
EXPECT_FALSE(model.Cut());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
// Copy with an empty selection should do nothing.
EXPECT_FALSE(model.Copy());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_EQ(11U, model.GetCursorPosition());
@@ -721,7 +724,8 @@ TEST_F(TextfieldModelTest, Clipboard) {
model.render_text()->SetObscured(true);
model.SelectAll(false);
EXPECT_FALSE(model.Cut());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_STR_EQ("HELLO WORLD", model.GetSelectedText());
@@ -729,7 +733,8 @@ TEST_F(TextfieldModelTest, Clipboard) {
// Copy on obscured (password) text should do nothing.
model.SelectAll(false);
EXPECT_FALSE(model.Copy());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_EQ(initial_clipboard_text, clipboard_text);
EXPECT_STR_EQ("HELLO WORLD", model.text());
EXPECT_STR_EQ("HELLO WORLD", model.GetSelectedText());
@@ -739,7 +744,8 @@ TEST_F(TextfieldModelTest, Clipboard) {
model.MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, gfx::SELECTION_NONE);
model.MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_RETAIN);
EXPECT_TRUE(model.Cut());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("WORLD", clipboard_text);
EXPECT_STR_EQ("HELLO ", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
@@ -747,7 +753,8 @@ TEST_F(TextfieldModelTest, Clipboard) {
// Copy with non-empty selection.
model.SelectAll(false);
EXPECT_TRUE(model.Copy());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("HELLO ", clipboard_text);
EXPECT_STR_EQ("HELLO ", model.text());
EXPECT_EQ(6U, model.GetCursorPosition());
@@ -768,7 +775,8 @@ TEST_F(TextfieldModelTest, Clipboard) {
model.SetText(base::ASCIIToUTF16("It's time to say goodbye."), 0);
model.SelectRange({17, 24});
EXPECT_TRUE(model.Paste());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("HELLO ", clipboard_text);
EXPECT_STR_EQ("It's time to say HELLO.", model.text());
EXPECT_EQ(22U, model.GetCursorPosition());
@@ -779,7 +787,8 @@ TEST_F(TextfieldModelTest, Clipboard) {
ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste);
model.SelectRange({5, 8});
EXPECT_FALSE(model.Paste());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_TRUE(clipboard_text.empty());
EXPECT_STR_EQ("It's time to say HELLO.", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
@@ -802,7 +811,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
model.SelectRange({0, 5});
model.SelectRange({13, 17}, false);
EXPECT_TRUE(model.Cut());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("It's ", clipboard_text);
EXPECT_STR_EQ("time to HELLO.", model.text());
EXPECT_EQ(0U, model.GetCursorPosition());
@@ -814,7 +824,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
model.SelectRange({13, 8});
model.SelectRange({0, 4}, false);
EXPECT_TRUE(model.Copy());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("HELLO", clipboard_text);
EXPECT_STR_EQ("time to HELLO.", model.text());
EXPECT_EQ(8U, model.GetCursorPosition());
@@ -827,7 +838,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
model.SelectRange({5, 8}, false);
model.SelectRange({14, 14}, false);
EXPECT_TRUE(model.Paste());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("HELLO", clipboard_text);
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(5U, model.GetCursorPosition());
@@ -840,7 +852,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
model.SelectRange({1, 2});
model.SelectRange({4, 5}, false);
EXPECT_FALSE(model.Paste());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_TRUE(clipboard_text.empty());
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
@@ -853,7 +866,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
model.SelectRange({2, 2});
model.SelectRange({4, 5}, false);
EXPECT_FALSE(model.Cut());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("initial text", clipboard_text);
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
@@ -862,7 +876,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
// Copy with an empty primary selection and nonempty secondary selections
// should not replace the clipboard.
EXPECT_FALSE(model.Copy());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("initial text", clipboard_text);
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
@@ -872,7 +887,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
// empty clipboard should change neither the text nor the selections.
ui::Clipboard::GetForCurrentThread()->Clear(ui::ClipboardBuffer::kCopyPaste);
EXPECT_FALSE(model.Paste());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_TRUE(clipboard_text.empty());
EXPECT_STR_EQ("HELLOime HELLO.", model.text());
EXPECT_EQ(2U, model.GetCursorPosition());
@@ -883,7 +899,8 @@ TEST_F(TextfieldModelTest, Clipboard_WithSecondarySelections) {
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(initial_clipboard_text);
EXPECT_TRUE(model.Paste());
- clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &clipboard_text);
+ clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+ &clipboard_text);
EXPECT_STR_EQ("initial text", clipboard_text);
EXPECT_STR_EQ("HEinitial textLLime HELLO.", model.text());
EXPECT_EQ(14U, model.GetCursorPosition());
@@ -938,7 +955,7 @@ TEST_F(TextfieldModelTest, SelectWordTest) {
// TODO(xji): temporarily disable in platform Win since the complex script
// characters and Chinese characters are turned into empty square due to font
// regression.
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
TEST_F(TextfieldModelTest, SelectWordTest_MixScripts) {
TextfieldModel model(nullptr);
std::vector<WordAndCursor> word_and_cursor;
@@ -1944,7 +1961,8 @@ TEST_F(TextfieldModelTest, UndoRedo_CompositionText) {
EXPECT_STR_EQ("ABCDEabc", model.text());
// Confirm the composition.
- model.ConfirmCompositionText();
+ uint32_t composition_text_length = model.ConfirmCompositionText();
+ EXPECT_EQ(composition_text_length, static_cast<uint32_t>(3));
EXPECT_STR_EQ("ABCDEabc", model.text());
EXPECT_TRUE(model.Undo());
EXPECT_STR_EQ("ABCDE", model.text());
diff --git a/chromium/ui/views/controls/textfield/textfield_test_api.cc b/chromium/ui/views/controls/textfield/textfield_test_api.cc
index 40176fa3a73..e939a3121a1 100644
--- a/chromium/ui/views/controls/textfield/textfield_test_api.cc
+++ b/chromium/ui/views/controls/textfield/textfield_test_api.cc
@@ -37,4 +37,12 @@ bool TextfieldTestApi::ShouldShowCursor() const {
return textfield_->ShouldShowCursor();
}
+int TextfieldTestApi::GetDisplayOffsetX() const {
+ return GetRenderText()->GetUpdatedDisplayOffset().x();
+}
+
+void TextfieldTestApi::SetDisplayOffsetX(int x) const {
+ return GetRenderText()->SetDisplayOffset(x);
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/textfield/textfield_test_api.h b/chromium/ui/views/controls/textfield/textfield_test_api.h
index 78436bcb5a4..9afc9ba332e 100644
--- a/chromium/ui/views/controls/textfield/textfield_test_api.h
+++ b/chromium/ui/views/controls/textfield/textfield_test_api.h
@@ -56,6 +56,9 @@ class TextfieldTestApi {
bool ShouldShowCursor() const;
+ int GetDisplayOffsetX() const;
+ void SetDisplayOffsetX(int x) const;
+
private:
Textfield* textfield_;
};
diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc
index 5a25faebf58..c33b2ae9706 100644
--- a/chromium/ui/views/controls/textfield/textfield_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc
@@ -19,7 +19,6 @@
#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 "ui/accessibility/ax_enums.mojom.h"
@@ -45,12 +44,14 @@
#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/test_ax_event_observer.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
@@ -72,7 +73,7 @@
#include "ui/wm/core/ime_util_chromeos.h"
#endif
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
#include "ui/base/cocoa/secure_password_input.h"
#include "ui/base/cocoa/text_services_context_menu.h"
#endif
@@ -157,7 +158,7 @@ ui::EventDispatchDetails MockInputMethod::DispatchKeyEvent(ui::KeyEvent* key) {
// On Mac, emulate InputMethodMac behavior for character events. Composition
// still needs to be mocked, since it's not possible to generate test events
// which trigger the appropriate NSResponder action messages for composition.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (key->is_char())
return DispatchKeyEventPostIME(key);
#endif
@@ -271,7 +272,7 @@ class TestTextfield : public views::Textfield {
// ui::TextInputClient overrides:
void InsertChar(const ui::KeyEvent& e) override {
views::Textfield::InsertChar(e);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// On Mac, characters are inserted directly rather than attempting to get a
// unicode character from the ui::KeyEvent (which isn't always possible).
key_received_ = true;
@@ -386,7 +387,8 @@ class TextfieldFocuser : public views::View {
base::string16 GetClipboardText(ui::ClipboardBuffer clipboard_buffer) {
base::string16 text;
- ui::Clipboard::GetForCurrentThread()->ReadText(clipboard_buffer, &text);
+ ui::Clipboard::GetForCurrentThread()->ReadText(
+ clipboard_buffer, /* data_dst = */ nullptr, &text);
return text;
}
@@ -493,7 +495,7 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
// True if native Mac keystrokes should be used (to avoid ifdef litter).
bool TestingNativeMac() {
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
return true;
#else
return false;
@@ -568,7 +570,7 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
std::vector<uint8_t>(ui::kPropertyFromVKSize);
event.SetProperties(properties);
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
event_generator_->Dispatch(&event);
#else
input_method_->DispatchKeyEvent(&event);
@@ -713,7 +715,7 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
int menu_index = 0;
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
if (textfield_has_selection) {
EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Look Up "Selection" */));
EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */));
@@ -829,7 +831,6 @@ TEST_F(TextfieldTest, ModelChangesTest) {
// text programmatically.
last_contents_.clear();
textfield_->SetText(ASCIIToUTF16("this is"));
-
EXPECT_STR_EQ("this is", model_->text());
EXPECT_STR_EQ("this is", textfield_->GetText());
EXPECT_TRUE(last_contents_.empty());
@@ -843,6 +844,123 @@ TEST_F(TextfieldTest, ModelChangesTest) {
textfield_->SelectAll(false);
EXPECT_STR_EQ("this is a test", textfield_->GetSelectedText());
EXPECT_TRUE(last_contents_.empty());
+
+ textfield_->SetTextAndScrollAndSelectRange(ASCIIToUTF16("another test"), 3,
+ {}, {4, 5});
+ EXPECT_STR_EQ("another test", model_->text());
+ EXPECT_STR_EQ("another test", textfield_->GetText());
+ EXPECT_STR_EQ("h", textfield_->GetSelectedText());
+ EXPECT_TRUE(last_contents_.empty());
+}
+
+TEST_F(TextfieldTest, SetTextAndScrollAndSelectRange_Scrolling) {
+ InitTextfield();
+
+ // Size the textfield wide enough to hold 10 characters.
+ gfx::test::RenderTextTestApi render_text_test_api(test_api_->GetRenderText());
+ render_text_test_api.SetGlyphWidth(10);
+ // 10px/char * 10chars + 1px for cursor width
+ test_api_->GetRenderText()->SetDisplayRect(gfx::Rect(0, 0, 101, 20));
+
+ // Should scroll cursor into view.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {}, {0, 20});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -100);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(0, 20));
+
+ // Cursor position should not affect scroll.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 30, {}, {20, 20});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -100);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20));
+
+ // Scroll positions should affect scroll.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {30}, {20, 20});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -200);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20));
+
+ // Should scroll no more than necessary; e.g., scrolling right should put the
+ // cursor at the right edge.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {}, {15, 15});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -50);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(15));
+
+ // Should scroll no more than necessary; e.g., scrolling left should put the
+ // cursor at the left edge.
+ test_api_->SetDisplayOffsetX(-200); // Scroll all the way right.
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {}, {15, 15});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -150);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(15));
+
+ // Should scroll no more than necessary; e.g., scrolling to a position already
+ // in view should not change the offset.
+ test_api_->SetDisplayOffsetX(-100); // Scroll the middle 10 chars into view.
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {}, {15, 15});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -100);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(15));
+
+ // With multiple scroll positions, the Last scroll position should be scrolled
+ // to after previous scroll positions.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {30, 0}, {20, 20});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -100);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20));
+
+ // With multiple scroll positions, the previous scroll positions should be
+ // scrolled to anyways.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {30, 20}, {20, 20});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), -200);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20));
+
+ // With a non empty selection, only the selection end should affect scrolling.
+ test_api_->SetDisplayOffsetX(0);
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 0, {0}, {30, 0});
+ EXPECT_EQ(test_api_->GetDisplayOffsetX(), 0);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(30, 0));
+}
+
+TEST_F(TextfieldTest, SetTextAndScrollAndSelectRange_ModelEditHistory) {
+ InitTextfield();
+
+ // The cursor and selected range should reflect the |range| parameter.
+ textfield_->SetTextAndScrollAndSelectRange(
+ ASCIIToUTF16("0123456789_123456789_123456789"), 20, {}, {10, 15});
+ EXPECT_EQ(textfield_->GetCursorPosition(), 15u);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(10, 15));
+
+ // After undo, the cursor and selected range should reflect the state prior to
+ // the edit.
+ textfield_->InsertOrReplaceText(ASCIIToUTF16("xyz")); // 2nd edit
+ SendKeyEvent(ui::VKEY_Z, false, true); // Undo 2nd edit
+ EXPECT_EQ(textfield_->GetCursorPosition(), 15u);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(10, 15));
+
+ // After redo, the cursor and selected range should reflect the
+ // |cursor_position| parameter.
+ SendKeyEvent(ui::VKEY_Z, false, true); // Undo 2nd edit
+ SendKeyEvent(ui::VKEY_Z, false, true); // Undo 1st edit
+ SendKeyEvent(ui::VKEY_Z, true, true); // Redo 1st edit
+ EXPECT_EQ(textfield_->GetCursorPosition(), 20u);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20, 20));
+
+ // After undo, the cursor and selected range should reflect the state prior to
+ // the edit, even if that differs than the state after the current (1st) edit.
+ textfield_->InsertOrReplaceText(ASCIIToUTF16("xyz")); // (2')nd edit
+ SendKeyEvent(ui::VKEY_Z, false, true); // Undo (2')nd edit
+ EXPECT_EQ(textfield_->GetCursorPosition(), 20u);
+ EXPECT_EQ(textfield_->GetSelectedRange(), gfx::Range(20, 20));
}
TEST_F(TextfieldTest, KeyTest) {
@@ -864,7 +982,7 @@ TEST_F(TextfieldTest, KeyTest) {
EXPECT_STR_EQ("TexT!1!1", textfield_->GetText());
}
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
// Control key shouldn't generate a printable character on Linux.
TEST_F(TextfieldTest, KeyTestControlModifier) {
InitTextfield();
@@ -885,7 +1003,7 @@ TEST_F(TextfieldTest, KeyTestControlModifier) {
}
#endif
-#if defined(OS_WIN) || defined(OS_MACOSX)
+#if defined(OS_WIN) || defined(OS_APPLE)
#define MAYBE_KeysWithModifiersTest KeysWithModifiersTest
#else
// TODO(crbug.com/645104): Implement keyboard layout changing for other
@@ -969,7 +1087,7 @@ TEST_F(TextfieldTest, ControlAndSelectTest) {
SendHomeEvent(true);
// On Mac, the existing selection should be extended.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_STR_EQ("ZERO two three", textfield_->GetSelectedText());
#else
EXPECT_STR_EQ("ZERO ", textfield_->GetSelectedText());
@@ -1000,7 +1118,7 @@ TEST_F(TextfieldTest, WordSelection) {
// On Mac, the selection should reduce to a caret when the selection direction
// changes for a word selection.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange());
#else
EXPECT_STR_EQ("345", textfield_->GetSelectedText());
@@ -1008,7 +1126,7 @@ TEST_F(TextfieldTest, WordSelection) {
#endif
SendWordEvent(ui::VKEY_LEFT, true);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_STR_EQ("345", textfield_->GetSelectedText());
#else
EXPECT_STR_EQ("12 345", textfield_->GetSelectedText());
@@ -1033,7 +1151,7 @@ TEST_F(TextfieldTest, LineSelection) {
// Select line towards left. On Mac, the existing selection should be extended
// to cover the whole line.
SendHomeEvent(true);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_EQ(textfield_->GetText(), textfield_->GetSelectedText());
#else
EXPECT_STR_EQ("12 345", textfield_->GetSelectedText());
@@ -1042,7 +1160,7 @@ TEST_F(TextfieldTest, LineSelection) {
// Select line towards right.
SendEndEvent(true);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_EQ(textfield_->GetText(), textfield_->GetSelectedText());
#else
EXPECT_STR_EQ("67 89", textfield_->GetSelectedText());
@@ -1059,7 +1177,7 @@ TEST_F(TextfieldTest, MoveUpDownAndModifySelection) {
// commands.
SendKeyEvent(ui::VKEY_UP);
EXPECT_TRUE(textfield_->key_received());
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_TRUE(textfield_->key_handled());
EXPECT_EQ(gfx::Range(0), textfield_->GetSelectedRange());
#else
@@ -1069,7 +1187,7 @@ TEST_F(TextfieldTest, MoveUpDownAndModifySelection) {
SendKeyEvent(ui::VKEY_DOWN);
EXPECT_TRUE(textfield_->key_received());
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_TRUE(textfield_->key_handled());
EXPECT_EQ(gfx::Range(11), textfield_->GetSelectedRange());
#else
@@ -1099,7 +1217,7 @@ TEST_F(TextfieldTest, MovePageUpDownAndModifySelection) {
// MOVE_PAGE_[UP/DOWN] and the associated selection commands should only be
// enabled on Mac.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
textfield_->SetText(ASCIIToUTF16("12 34567 89"));
textfield_->SetEditableSelectionRange(gfx::Range(6));
@@ -1151,7 +1269,7 @@ TEST_F(TextfieldTest, MoveParagraphForwardBackwardAndModifySelection) {
ui::TextEditCommand::MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION);
// On Mac, the selection should reduce to a caret when the selection direction
// is reversed for MOVE_PARAGRAPH_[FORWARD/BACKWARD]_AND_MODIFY_SELECTION.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange());
#else
EXPECT_EQ(gfx::Range(6, 0), textfield_->GetSelectedRange());
@@ -1163,7 +1281,7 @@ TEST_F(TextfieldTest, MoveParagraphForwardBackwardAndModifySelection) {
test_api_->ExecuteTextEditCommand(
ui::TextEditCommand::MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
EXPECT_EQ(gfx::Range(6), textfield_->GetSelectedRange());
#else
EXPECT_EQ(gfx::Range(6, 11), textfield_->GetSelectedRange());
@@ -1216,7 +1334,7 @@ TEST_F(TextfieldTest, InsertionDeletionTest) {
SendWordEvent(ui::VKEY_LEFT, shift);
shift = true;
SendWordEvent(ui::VKEY_BACK, shift);
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
EXPECT_STR_EQ("three ", textfield_->GetText());
#else
EXPECT_STR_EQ("one three ", textfield_->GetText());
@@ -1237,7 +1355,7 @@ TEST_F(TextfieldTest, InsertionDeletionTest) {
SendWordEvent(ui::VKEY_RIGHT, shift);
shift = true;
SendWordEvent(ui::VKEY_DELETE, shift);
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
EXPECT_STR_EQ(" two", textfield_->GetText());
#elif defined(OS_WIN)
EXPECT_STR_EQ("two four", textfield_->GetText());
@@ -1373,7 +1491,7 @@ TEST_F(TextfieldTest, TextInputType_InsertionTest) {
SendKeyEvent(ui::VKEY_A);
EXPECT_EQ(-1, textfield_->GetPasswordCharRevealIndex());
SendKeyEvent(kHebrewLetterSamekh, ui::EF_NONE, true /* from_vk */);
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Don't verifies the password character reveal on MacOS, because on MacOS,
// the text insertion is not done through TextInputClient::InsertChar().
EXPECT_EQ(1, textfield_->GetPasswordCharRevealIndex());
@@ -2318,7 +2436,7 @@ TEST_F(TextfieldTest, UndoRedoTest) {
// Ctrl+Y is bound to "Yank" and Cmd+Y is bound to "Show full history". So, on
// Mac, Cmd+Shift+Z is sent for the tests above and the Ctrl+Y test below is
// skipped.
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// Test that Ctrl+Y works for Redo, as well as Ctrl+Shift+Z.
TEST_F(TextfieldTest, RedoWithCtrlY) {
@@ -2335,11 +2453,11 @@ TEST_F(TextfieldTest, RedoWithCtrlY) {
EXPECT_STR_EQ("a", textfield_->GetText());
}
-#endif // !defined(OS_MACOSX)
+#endif // !defined(OS_APPLE)
// Non-Mac platforms don't have a key binding for Yank. Since this test is only
// run on Mac, it uses some Mac specific key bindings.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
TEST_F(TextfieldTest, Yank) {
InitTextfields(2);
@@ -2398,7 +2516,7 @@ TEST_F(TextfieldTest, Yank) {
EXPECT_STR_EQ("efabefeef", textfield_->GetText());
}
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
TEST_F(TextfieldTest, CutCopyPaste) {
InitTextfield();
@@ -2863,36 +2981,59 @@ TEST_F(TextfieldTest, OverflowInRTLTest) {
base::i18n::SetICUDefaultLocale(locale);
}
+TEST_F(TextfieldTest, CommitComposingTextTest) {
+ InitTextfield();
+ ui::CompositionText composition;
+ composition.text = UTF8ToUTF16("abc123");
+ textfield_->SetCompositionText(composition);
+ uint32_t composed_text_length =
+ textfield_->ConfirmCompositionText(/* keep_selection */ false);
+
+ EXPECT_EQ(composed_text_length, static_cast<uint32_t>(6));
+}
+
+TEST_F(TextfieldTest, CommitEmptyComposingTextTest) {
+ InitTextfield();
+ ui::CompositionText composition;
+ composition.text = UTF8ToUTF16("");
+ textfield_->SetCompositionText(composition);
+ uint32_t composed_text_length =
+ textfield_->ConfirmCompositionText(/* keep_selection */ false);
+
+ EXPECT_EQ(composed_text_length, static_cast<uint32_t>(0));
+}
+
TEST_F(TextfieldTest, GetCompositionCharacterBoundsTest) {
InitTextfield();
ui::CompositionText composition;
composition.text = UTF8ToUTF16("abc123");
const uint32_t char_count = static_cast<uint32_t>(composition.text.length());
- ui::TextInputClient* client = textfield_;
// Compare the composition character bounds with surrounding cursor bounds.
for (uint32_t i = 0; i < char_count; ++i) {
composition.selection = gfx::Range(i);
- client->SetCompositionText(composition);
+ textfield_->SetCompositionText(composition);
gfx::Point cursor_origin = GetCursorBounds().origin();
views::View::ConvertPointToScreen(textfield_, &cursor_origin);
composition.selection = gfx::Range(i + 1);
- client->SetCompositionText(composition);
+ textfield_->SetCompositionText(composition);
gfx::Point next_cursor_bottom_left = GetCursorBounds().bottom_left();
views::View::ConvertPointToScreen(textfield_, &next_cursor_bottom_left);
gfx::Rect character;
- EXPECT_TRUE(client->GetCompositionCharacterBounds(i, &character));
+ EXPECT_TRUE(textfield_->GetCompositionCharacterBounds(i, &character));
EXPECT_EQ(character.origin(), cursor_origin) << " i=" << i;
EXPECT_EQ(character.bottom_right(), next_cursor_bottom_left) << " i=" << i;
}
// Return false if the index is out of range.
gfx::Rect rect;
- EXPECT_FALSE(client->GetCompositionCharacterBounds(char_count, &rect));
- EXPECT_FALSE(client->GetCompositionCharacterBounds(char_count + 1, &rect));
- EXPECT_FALSE(client->GetCompositionCharacterBounds(char_count + 100, &rect));
+ EXPECT_FALSE(textfield_->GetCompositionCharacterBounds(char_count, &rect));
+ EXPECT_FALSE(
+ textfield_->GetCompositionCharacterBounds(char_count + 1, &rect));
+ EXPECT_FALSE(
+ textfield_->GetCompositionCharacterBounds(char_count + 100, &rect));
}
TEST_F(TextfieldTest, GetCompositionCharacterBounds_ComplexText) {
@@ -2918,13 +3059,12 @@ TEST_F(TextfieldTest, GetCompositionCharacterBounds_ComplexText) {
ui::CompositionText composition;
composition.text.assign(kUtf16Chars, kUtf16Chars + kUtf16CharsCount);
- ui::TextInputClient* client = textfield_;
- client->SetCompositionText(composition);
+ textfield_->SetCompositionText(composition);
// Make sure GetCompositionCharacterBounds never fails for index.
gfx::Rect rects[kUtf16CharsCount];
for (uint32_t i = 0; i < kUtf16CharsCount; ++i)
- EXPECT_TRUE(client->GetCompositionCharacterBounds(i, &rects[i]));
+ EXPECT_TRUE(textfield_->GetCompositionCharacterBounds(i, &rects[i]));
// Here we might expect the following results but it actually depends on how
// Uniscribe or HarfBuzz treats them with given font.
@@ -2933,6 +3073,141 @@ TEST_F(TextfieldTest, GetCompositionCharacterBounds_ComplexText) {
// - rects[6] == rects[7]
}
+#if defined(OS_CHROMEOS)
+TEST_F(TextfieldTest, SetAutocorrectRangeText) {
+ InitTextfield();
+
+ ui::CompositionText composition;
+ composition.text = UTF8ToUTF16("Initial txt");
+ textfield_->SetCompositionText(composition);
+ textfield_->SetAutocorrectRange(ASCIIToUTF16("text replacement"),
+ gfx::Range(8, 11));
+
+ 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"));
+}
+
+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);
+
+ 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);
+}
+
+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();
+
+ EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range());
+}
+
+TEST_F(TextfieldTest, GetAutocorrectCharacterBoundsTest) {
+ InitTextfield();
+
+ textfield_->InsertText(UTF8ToUTF16("hello placeholder text"));
+ textfield_->SetAutocorrectRange(ASCIIToUTF16("longlonglongtext"),
+ gfx::Range(3, 10));
+
+ EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range(3, 19));
+
+ 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));
+
+ EXPECT_EQ(textfield_->GetAutocorrectRange(), gfx::Range(3, 8));
+
+ gfx::Rect rect_for_short_text = textfield_->GetAutocorrectCharacterBounds();
+
+ EXPECT_LT(rect_for_short_text.x(), rect_for_long_text.x());
+ EXPECT_EQ(rect_for_short_text.y(), rect_for_long_text.y());
+ EXPECT_EQ(rect_for_short_text.height(), rect_for_long_text.height());
+ // TODO(crbug.com/1108170): Investigate why the rectangle width is wrong.
+ // The value seems to be wrong due to the incorrect value being returned from
+ // RenderText::GetCursorBounds(). Unfortuantly, that is tricky to fix, since
+ // RenderText is used in other parts of the codebase.
+ // When fixed, the following EXPECT statement should pass.
+ // EXPECT_LT(rect_for_short_text.width(), rect_for_long_text.width());
+}
+
+// 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
+
// The word we select by double clicking should remain selected regardless of
// where we drag the mouse afterwards without releasing the left button.
TEST_F(TextfieldTest, KeepInitiallySelectedWord) {
@@ -3202,6 +3477,22 @@ TEST_F(TextfieldTest, CursorBlinkRestartsOnInsertOrReplace) {
EXPECT_TRUE(test_api_->IsCursorBlinkTimerRunning());
}
+// Verifies setting the accessible name will call NotifyAccessibilityEvent.
+TEST_F(TextfieldTest, SetAccessibleNameNotifiesAccessibilityEvent) {
+ InitTextfield();
+ base::string16 test_tooltip_text = ASCIIToUTF16("Test Accessible Name");
+ test::TestAXEventObserver observer;
+ EXPECT_EQ(0, observer.text_changed_event_count());
+ textfield_->SetAccessibleName(test_tooltip_text);
+ EXPECT_EQ(1, observer.text_changed_event_count());
+ EXPECT_EQ(test_tooltip_text, textfield_->GetAccessibleName());
+ ui::AXNodeData data;
+ textfield_->GetAccessibleNodeData(&data);
+ const std::string& name =
+ data.GetStringAttribute(ax::mojom::StringAttribute::kName);
+ EXPECT_EQ(test_tooltip_text, ASCIIToUTF16(name));
+}
+
#if defined(OS_CHROMEOS)
// Check that when accessibility virtual keyboard is enabled, windows are
// shifted up when focused and restored when focus is lost.
@@ -3341,7 +3632,7 @@ TEST_F(TextfieldTouchSelectionTest, TouchSelectionInUnfocusableTextfield) {
}
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
#define MAYBE_TapOnSelection DISABLED_TapOnSelection
#else
#define MAYBE_TapOnSelection TapOnSelection
@@ -3410,6 +3701,22 @@ TEST_F(TextfieldTest, CursorVisibility) {
EXPECT_TRUE(test_api_->IsCursorVisible());
}
+// Tests that Textfield::FitToLocalBounds() sets the RenderText's display rect
+// to the view's bounds, taking the border into account.
+TEST_F(TextfieldTest, FitToLocalBounds) {
+ const int kDisplayRectWidth = 100;
+ const int kBorderWidth = 5;
+ InitTextfield();
+ textfield_->SetBounds(0, 0, kDisplayRectWidth, 100);
+ textfield_->SetBorder(views::CreateEmptyBorder(
+ gfx::Insets(kBorderWidth, kBorderWidth, kBorderWidth, kBorderWidth)));
+ test_api_->GetRenderText()->SetDisplayRect(gfx::Rect(0, 0, 20, 20));
+ ASSERT_EQ(20, test_api_->GetRenderText()->display_rect().width());
+ textfield_->FitToLocalBounds();
+ EXPECT_EQ(kDisplayRectWidth - 2 * kBorderWidth,
+ test_api_->GetRenderText()->display_rect().width());
+}
+
// Verify that cursor view height does not exceed the textfield height.
TEST_F(TextfieldTest, CursorViewHeight) {
InitTextfield();
@@ -3601,7 +3908,7 @@ TEST_F(TextfieldTest, EmojiItem_FieldWithText) {
InitTextfield();
EXPECT_TRUE(textfield_->context_menu_controller());
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// On Mac, when there is text, the "Look up" item (+ separator) takes the top
// position, and emoji comes after.
constexpr int kExpectedEmojiIndex = 2;
@@ -3621,7 +3928,7 @@ TEST_F(TextfieldTest, EmojiItem_FieldWithText) {
l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI));
}
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
// Tests to see if the BiDi submenu items are updated correctly when the
// textfield's text direction is changed.
TEST_F(TextfieldTest, TextServicesContextMenuTextDirectionTest) {
@@ -3688,7 +3995,7 @@ TEST_F(TextfieldTest, SecurePasswordInput) {
textfield_->OnBlur();
EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
}
-#endif // defined(OS_MACOSX)
+#endif // defined(OS_APPLE)
TEST_F(TextfieldTest, AccessibilitySelectionEvents) {
const std::string& kText = "abcdef";
diff --git a/chromium/ui/views/controls/tree/tree_view.cc b/chromium/ui/views/controls/tree/tree_view.cc
index 69333ebe84c..b42ba498d95 100644
--- a/chromium/ui/views/controls/tree/tree_view.cc
+++ b/chromium/ui/views/controls/tree/tree_view.cc
@@ -84,7 +84,7 @@ TreeView::TreeView()
drawing_provider_(std::make_unique<TreeViewDrawingProvider>()) {
// Always focusable, even on Mac (consistent with NSOutlineView).
SetFocusBehavior(FocusBehavior::ALWAYS);
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
constexpr bool kUseMdIcons = true;
#else
constexpr bool kUseMdIcons = false;
@@ -890,7 +890,7 @@ std::unique_ptr<AXVirtualView> TreeView::CreateAndSetAccessibilityView(
ui::AXNodeData& node_data = ax_view->GetCustomData();
node_data.role = ax::mojom::Role::kTreeItem;
if (base::i18n::IsRTL())
- node_data.SetTextDirection(ax::mojom::TextDirection::kRtl);
+ node_data.SetTextDirection(ax::mojom::WritingDirection::kRtl);
base::RepeatingCallback<void(ui::AXNodeData*)> selected_callback =
base::BindRepeating(&TreeView::PopulateAccessibilityData,
diff --git a/chromium/ui/views/controls/tree/tree_view_unittest.cc b/chromium/ui/views/controls/tree/tree_view_unittest.cc
index 6081d640219..0173210e0d1 100644
--- a/chromium/ui/views/controls/tree/tree_view_unittest.cc
+++ b/chromium/ui/views/controls/tree/tree_view_unittest.cc
@@ -19,6 +19,7 @@
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/models/tree_node_model.h"
+#include "ui/compositor/canvas_painter.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
@@ -162,11 +163,11 @@ class TreeViewTest : public ViewsTestBase {
void TreeViewTest::SetUp() {
ViewsTestBase::SetUp();
widget_ = std::make_unique<Widget>();
- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
+ Widget::InitParams params =
+ CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(0, 0, 200, 200);
widget_->Init(std::move(params));
- tree_ =
- widget_->GetContentsView()->AddChildView(std::make_unique<TreeView>());
+ tree_ = widget_->SetContentsView(std::make_unique<TreeView>());
tree_->RequestFocus();
ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback =
@@ -376,6 +377,16 @@ TEST_F(TreeViewTest, MetadataTest) {
test::TestViewMetadata(tree_);
}
+TEST_F(TreeViewTest, TreeViewPaintCoverage) {
+ tree_->SetModel(&model_);
+ SkBitmap bitmap;
+ gfx::Size size = tree_->size();
+ ui::CanvasPainter canvas_painter(&bitmap, size, 1.f, SK_ColorTRANSPARENT,
+ false);
+ widget_->GetRootView()->Paint(
+ PaintInfo::CreateRootPaintInfo(canvas_painter.context(), size));
+}
+
// Verifies setting model correctly updates internal state.
TEST_F(TreeViewTest, SetModel) {
tree_->SetModel(&model_);
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 a9b53aeb9c2..a2c0df01350 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
@@ -50,7 +50,7 @@ bool ViewsTextServicesContextMenuBase::GetAcceleratorForCommandId(
#if defined(OS_WIN)
*accelerator = ui::Accelerator(ui::VKEY_OEM_PERIOD, ui::EF_COMMAND_DOWN);
return true;
-#elif defined(OS_MACOSX)
+#elif defined(OS_APPLE)
*accelerator = ui::Accelerator(ui::VKEY_SPACE,
ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN);
return true;
@@ -85,7 +85,7 @@ bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
return command_id == IDS_CONTENT_CONTEXT_EMOJI;
}
-#if !defined(OS_MACOSX)
+#if !defined(OS_APPLE)
// 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 3c8484778f8..2318cf80d6b 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
@@ -31,7 +31,7 @@ class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu {
bool SupportsCommand(int command_id) const override;
protected:
-#if defined(OS_MACOSX)
+#if defined(OS_APPLE)
Textfield* client() { return client_; }
const Textfield* client() const { return client_; }
#endif
diff --git a/chromium/ui/views/controls/webview/BUILD.gn b/chromium/ui/views/controls/webview/BUILD.gn
index 66ba75adfcc..ba38ba89adc 100644
--- a/chromium/ui/views/controls/webview/BUILD.gn
+++ b/chromium/ui/views/controls/webview/BUILD.gn
@@ -2,15 +2,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/jumbo.gni")
-
# Reset sources_assignment_filter for the BUILD.gn file to prevent
# regression during the migration of Chromium away from the feature.
# See docs/no_sources_assignment_filter.md for more information.
# TODO(crbug.com/1018739): remove this when migration is done.
set_sources_assignment_filter([])
-jumbo_component("webview") {
+component("webview") {
sources = [
"unhandled_keyboard_event_handler.cc",
"unhandled_keyboard_event_handler.h",
@@ -34,7 +32,7 @@ jumbo_component("webview") {
defines = [ "WEBVIEW_IMPLEMENTATION" ]
if (is_mac) {
- libs = [ "CoreFoundation.framework" ]
+ frameworks = [ "CoreFoundation.framework" ]
}
deps = [
@@ -59,7 +57,7 @@ jumbo_component("webview") {
"//ui/views",
]
- if (is_linux || is_android || is_fuchsia) {
+ if (is_linux || is_chromeos || is_android || is_fuchsia) {
sources += [ "unhandled_keyboard_event_handler_default.cc" ]
}
}
diff --git a/chromium/ui/views/controls/webview/web_dialog_view.cc b/chromium/ui/views/controls/webview/web_dialog_view.cc
index f8c7d7670ef..759b9c6c418 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view.cc
+++ b/chromium/ui/views/controls/webview/web_dialog_view.cc
@@ -74,13 +74,11 @@ void ObservableWebView::ResetDelegate() {
WebDialogView::WebDialogView(content::BrowserContext* context,
WebDialogDelegate* delegate,
- std::unique_ptr<WebContentsHandler> handler,
- bool use_dialog_frame)
+ std::unique_ptr<WebContentsHandler> handler)
: ClientView(nullptr, nullptr),
WebDialogWebContentsDelegate(context, std::move(handler)),
delegate_(delegate),
- web_view_(new ObservableWebView(context, delegate)),
- use_dialog_frame_(use_dialog_frame) {
+ web_view_(new ObservableWebView(context, delegate)) {
web_view_->set_allow_accelerators(true);
AddChildView(web_view_);
set_contents_view(web_view_);
@@ -140,12 +138,18 @@ void WebDialogView::ViewHierarchyChanged(
InitDialog();
}
-bool WebDialogView::CanClose() {
+views::CloseRequestResult WebDialogView::OnWindowCloseRequested() {
// Don't close UI if |delegate_| does not allow users to close it by
// clicking on "x" button or pressing Escape shortcut key on hosting
// dialog.
- if (!delegate_->CanCloseDialog() && !close_contents_called_)
- return false;
+ if (!is_attempting_close_dialog_ && !delegate_->OnDialogCloseRequested()) {
+ if (!close_contents_called_)
+ return views::CloseRequestResult::kCannotClose;
+ // This is a web dialog, if the WebContents has been closed, there is no
+ // reason to keep the dialog alive.
+ LOG(ERROR) << "delegate tries to stop closing when CloseContents() has "
+ "been called";
+ }
// If CloseContents() is called before CanClose(), which is called by
// RenderViewHostImpl::ClosePageIgnoringUnloadEvents, it indicates
@@ -154,7 +158,7 @@ bool WebDialogView::CanClose() {
close_contents_called_) {
is_attempting_close_dialog_ = false;
before_unload_fired_ = false;
- return true;
+ return views::CloseRequestResult::kCanClose;
}
if (!is_attempting_close_dialog_) {
@@ -162,14 +166,14 @@ bool WebDialogView::CanClose() {
is_attempting_close_dialog_ = true;
web_view_->web_contents()->DispatchBeforeUnload(false /* auto_cancel */);
}
- return false;
+ return views::CloseRequestResult::kCannotClose;
}
////////////////////////////////////////////////////////////////////////////////
// WebDialogView, views::WidgetDelegate implementation:
bool WebDialogView::OnCloseRequested(Widget::ClosedReason close_reason) {
- return !delegate_ || delegate_->OnDialogCloseRequested();
+ return !delegate_ || delegate_->DeprecatedOnDialogCloseRequested();
}
bool WebDialogView::CanResize() const {
@@ -216,10 +220,20 @@ views::ClientView* WebDialogView::CreateClientView(views::Widget* widget) {
return this;
}
-NonClientFrameView* WebDialogView::CreateNonClientFrameView(Widget* widget) {
- if (use_dialog_frame_)
- return DialogDelegate::CreateDialogFrameView(widget);
- return WidgetDelegate::CreateNonClientFrameView(widget);
+std::unique_ptr<NonClientFrameView> WebDialogView::CreateNonClientFrameView(
+ Widget* widget) {
+ if (!delegate_)
+ return WidgetDelegate::CreateNonClientFrameView(widget);
+
+ switch (delegate_->GetWebDialogFrameKind()) {
+ case WebDialogDelegate::FrameKind::kNonClient:
+ return WidgetDelegate::CreateNonClientFrameView(widget);
+ case WebDialogDelegate::FrameKind::kDialog:
+ return DialogDelegate::CreateDialogFrameView(widget);
+ default:
+ NOTREACHED() << "Unknown frame kind type enum specified.";
+ return std::unique_ptr<NonClientFrameView>{};
+ }
}
views::View* WebDialogView::GetInitiallyFocusedView() {
diff --git a/chromium/ui/views/controls/webview/web_dialog_view.h b/chromium/ui/views/controls/webview/web_dialog_view.h
index 5b04647c4a3..31164a4c8e8 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view.h
+++ b/chromium/ui/views/controls/webview/web_dialog_view.h
@@ -78,8 +78,7 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
// client frame view.
WebDialogView(content::BrowserContext* context,
ui::WebDialogDelegate* delegate,
- std::unique_ptr<WebContentsHandler> handler,
- bool use_dialog_frame = false);
+ std::unique_ptr<WebContentsHandler> handler);
~WebDialogView() override;
content::WebContents* web_contents();
@@ -90,7 +89,7 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) override;
- bool CanClose() override;
+ views::CloseRequestResult OnWindowCloseRequested() override;
// WidgetDelegate:
bool OnCloseRequested(Widget::ClosedReason close_reason) override;
@@ -102,7 +101,8 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
void WindowClosing() override;
View* GetContentsView() override;
ClientView* CreateClientView(Widget* widget) override;
- NonClientFrameView* CreateNonClientFrameView(Widget* widget) override;
+ std::unique_ptr<NonClientFrameView> CreateNonClientFrameView(
+ Widget* widget) override;
View* GetInitiallyFocusedView() override;
bool ShouldShowWindowTitle() const override;
bool ShouldShowCloseButton() const override;
@@ -193,9 +193,6 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView,
// Handler for unhandled key events from renderer.
UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
- // Whether to use dialog frame view for non client frame view.
- bool use_dialog_frame_ = false;
-
bool disable_url_load_for_test_ = false;
DISALLOW_COPY_AND_ASSIGN(WebDialogView);
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 cb149be77fa..004f97daf2a 100644
--- a/chromium/ui/views/controls/webview/web_dialog_view_unittest.cc
+++ b/chromium/ui/views/controls/webview/web_dialog_view_unittest.cc
@@ -39,7 +39,7 @@ class TestWebDialogViewWebDialogDelegate
}
// ui::WebDialogDelegate
- bool CanCloseDialog() const override { return true; }
+ bool OnDialogCloseRequested() override { return true; }
bool ShouldCloseDialogOnEscape() const override { return close_on_escape_; }
ui::ModalType GetDialogModalType() const override {
return ui::MODAL_TYPE_WINDOW;
diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc
index 0c523877565..639ba0b04be 100644
--- a/chromium/ui/views/controls/webview/webview.cc
+++ b/chromium/ui/views/controls/webview/webview.cc
@@ -60,9 +60,9 @@ WebView::ScopedWebContentsCreatorForTesting::
////////////////////////////////////////////////////////////////////////////////
// WebView, public:
-WebView::WebView(content::BrowserContext* browser_context)
- : browser_context_(browser_context) {
+WebView::WebView(content::BrowserContext* browser_context) {
ui::AXPlatformNode::AddAXModeObserver(this);
+ SetBrowserContext(browser_context);
}
WebView::~WebView() {
@@ -72,6 +72,8 @@ WebView::~WebView() {
content::WebContents* WebView::GetWebContents() {
if (!web_contents()) {
+ if (!browser_context_)
+ return nullptr;
wc_owner_ = CreateWebContents(browser_context_);
wc_owner_->SetDelegate(this);
SetWebContents(wc_owner_.get());
@@ -109,7 +111,17 @@ void WebView::SetEmbedFullscreenWidgetMode(bool enable) {
embed_fullscreen_widget_mode_enabled_ = enable;
}
+content::BrowserContext* WebView::GetBrowserContext() {
+ return browser_context_;
+}
+
+void WebView::SetBrowserContext(content::BrowserContext* browser_context) {
+ browser_context_ = browser_context;
+}
+
void WebView::LoadInitialURL(const GURL& url) {
+ // Loading requires a valid WebContents.
+ DCHECK(GetWebContents());
GetWebContents()->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
std::string());
@@ -260,8 +272,17 @@ gfx::NativeViewAccessible WebView::GetNativeViewAccessible() {
if (web_contents() && !web_contents()->IsCrashed()) {
content::RenderWidgetHostView* host_view =
web_contents()->GetRenderWidgetHostView();
- if (host_view)
- return host_view->GetNativeViewAccessible();
+ if (host_view) {
+ gfx::NativeViewAccessible accessible =
+ host_view->GetNativeViewAccessible();
+ // |accessible| needs to know whether this is the primary WebContents.
+ if (auto* ax_platform_node =
+ ui::AXPlatformNode::FromNativeViewAccessible(accessible)) {
+ ax_platform_node->SetIsPrimaryWebContentsForWindow(
+ is_primary_web_contents_for_window_);
+ }
+ return accessible;
+ }
}
return View::GetNativeViewAccessible();
}
@@ -341,6 +362,10 @@ void WebView::RenderProcessGone(base::TerminationStatus status) {
NotifyAccessibilityWebContentsChanged();
}
+void WebView::AXTreeIDForMainFrameHasChanged() {
+ NotifyAccessibilityWebContentsChanged();
+}
+
void WebView::ResizeDueToAutoResize(content::WebContents* source,
const gfx::Size& new_size) {
if (source != web_contents())
diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h
index 624dcb1a4d8..f18eea14290 100644
--- a/chromium/ui/views/controls/webview/webview.h
+++ b/chromium/ui/views/controls/webview/webview.h
@@ -42,11 +42,12 @@ class WEBVIEW_EXPORT WebView : public View,
public:
METADATA_HEADER(WebView);
- explicit WebView(content::BrowserContext* browser_context);
+ explicit WebView(content::BrowserContext* browser_context = nullptr);
~WebView() override;
- // This creates a WebContents if none is yet associated with this WebView. The
- // WebView owns this implicitly created WebContents.
+ // This creates a WebContents if |kBrowserContext| has been set and there is
+ // not yet a WebContents associated with this WebView, otherwise it will
+ // return a nullptr.
content::WebContents* GetWebContents();
// WebView does not assume ownership of WebContents set via this method, only
@@ -59,7 +60,8 @@ class WEBVIEW_EXPORT WebView : public View,
// widget or restore the normal WebContentsView.
void SetEmbedFullscreenWidgetMode(bool mode);
- content::BrowserContext* browser_context() { return browser_context_; }
+ content::BrowserContext* GetBrowserContext();
+ void SetBrowserContext(content::BrowserContext* browser_context);
// Loads the initial URL to display in the attached WebContents. Creates the
// WebContents if none is attached yet. Note that this is intended as a
@@ -87,6 +89,11 @@ class WEBVIEW_EXPORT WebView : public View,
// if the web contents is changed.
void SetCrashedOverlayView(View* crashed_overlay_view);
+ // Sets whether this is the primary web contents for the window.
+ void set_is_primary_web_contents_for_window(bool is_primary) {
+ is_primary_web_contents_for_window_ = is_primary;
+ }
+
// When used to host UI, we need to explicitly allow accelerators to be
// processed. Default is false.
void set_allow_accelerators(bool allow_accelerators) {
@@ -157,6 +164,7 @@ class WEBVIEW_EXPORT WebView : public View,
void OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) override;
void RenderProcessGone(base::TerminationStatus status) override;
+ void AXTreeIDForMainFrameHasChanged() override;
// Override from ui::AXModeObserver
void OnAXModeAdded(ui::AXMode mode) override;
@@ -196,6 +204,7 @@ class WEBVIEW_EXPORT WebView : public View,
content::BrowserContext* browser_context_;
bool allow_accelerators_ = false;
View* crashed_overlay_view_ = nullptr;
+ bool is_primary_web_contents_for_window_ = false;
// Minimum and maximum sizes to determine WebView bounds for auto-resizing.
// Empty if auto resize is not enabled.
diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc
index 5c28d3091f8..1f2322d2fb9 100644
--- a/chromium/ui/views/controls/webview/webview_unittest.cc
+++ b/chromium/ui/views/controls/webview/webview_unittest.cc
@@ -566,6 +566,25 @@ TEST_F(WebViewUnitTest, CrashedOverlayViewOwnedbyClient) {
delete crashed_overlay_view;
}
+// Tests to make sure we can default construct the WebView class and set the
+// BrowserContext after construction.
+TEST_F(WebViewUnitTest, DefaultConstructability) {
+ auto browser_context = std::make_unique<content::TestBrowserContext>();
+ auto web_view = std::make_unique<WebView>();
+
+ // Test to make sure the WebView returns a nullptr in the absence of an
+ // explicitly supplied WebContents and BrowserContext.
+ EXPECT_EQ(nullptr, web_view->GetWebContents());
+
+ web_view->SetBrowserContext(browser_context.get());
+
+ // WebView should be able to create a WebContents object from the previously
+ // set |browser_context|.
+ auto* web_contents = web_view->GetWebContents();
+ EXPECT_NE(nullptr, web_contents);
+ EXPECT_EQ(browser_context.get(), web_contents->GetBrowserContext());
+}
+
#if defined(USE_AURA)
namespace {