summaryrefslogtreecommitdiff
path: root/chromium/ui/views
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/ui/views
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/views')
-rw-r--r--chromium/ui/views/BUILD.gn31
-rw-r--r--chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc2
-rw-r--r--chromium/ui/views/accessibility/ax_virtual_view.cc14
-rw-r--r--chromium/ui/views/accessibility/ax_virtual_view.h5
-rw-r--r--chromium/ui/views/accessibility/ax_virtual_view_unittest.cc13
-rw-r--r--chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc9
-rw-r--r--chromium/ui/views/accessibility/ax_widget_obj_wrapper.h1
-rw-r--r--chromium/ui/views/accessibility/ax_window_obj_wrapper.cc28
-rw-r--r--chromium/ui/views/accessibility/ax_window_obj_wrapper.h10
-rw-r--r--chromium/ui/views/accessibility/view_accessibility.cc25
-rw-r--r--chromium/ui/views/accessibility/view_accessibility.h15
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc96
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate.h13
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc42
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc16
-rw-r--r--chromium/ui/views/animation/animation_delegate_views.cc22
-rw-r--r--chromium/ui/views/animation/animation_delegate_views.h2
-rw-r--r--chromium/ui/views/animation/bounds_animator.cc18
-rw-r--r--chromium/ui/views/animation/bounds_animator_unittest.cc148
-rw-r--r--chromium/ui/views/animation/compositor_animation_runner.cc1
-rw-r--r--chromium/ui/views/animation/compositor_animation_runner.h8
-rw-r--r--chromium/ui/views/animation/installable_ink_drop_painter_unittest.cc87
-rw-r--r--chromium/ui/views/animation/installable_ink_drop_unittest.cc69
-rw-r--r--chromium/ui/views/bubble/bubble_dialog_delegate_view.cc313
-rw-r--r--chromium/ui/views/bubble/bubble_dialog_delegate_view.h436
-rw-r--r--chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc75
-rw-r--r--chromium/ui/views/bubble/bubble_frame_view.cc79
-rw-r--r--chromium/ui/views/bubble/bubble_frame_view.h17
-rw-r--r--chromium/ui/views/bubble/bubble_frame_view_unittest.cc28
-rw-r--r--chromium/ui/views/controls/button/button.cc9
-rw-r--r--chromium/ui/views/controls/button/button.h4
-rw-r--r--chromium/ui/views/controls/button/button_unittest.cc34
-rw-r--r--chromium/ui/views/controls/button/checkbox_unittest.cc4
-rw-r--r--chromium/ui/views/controls/button/image_button_factory.cc14
-rw-r--r--chromium/ui/views/controls/button/image_button_factory.h6
-rw-r--r--chromium/ui/views/controls/button/image_button_factory_unittest.cc10
-rw-r--r--chromium/ui/views/controls/button/label_button.cc39
-rw-r--r--chromium/ui/views/controls/button/label_button_label_unittest.cc26
-rw-r--r--chromium/ui/views/controls/button/label_button_unittest.cc23
-rw-r--r--chromium/ui/views/controls/button/md_text_button.cc14
-rw-r--r--chromium/ui/views/controls/button/md_text_button.h7
-rw-r--r--chromium/ui/views/controls/button/md_text_button_unittest.cc25
-rw-r--r--chromium/ui/views/controls/button/radio_button_unittest.cc3
-rw-r--r--chromium/ui/views/controls/combobox/combobox.cc97
-rw-r--r--chromium/ui/views/controls/combobox/combobox.h4
-rw-r--r--chromium/ui/views/controls/combobox/combobox_unittest.cc82
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.cc78
-rw-r--r--chromium/ui/views/controls/editable_combobox/editable_combobox.h18
-rw-r--r--chromium/ui/views/controls/focus_ring.cc33
-rw-r--r--chromium/ui/views/controls/focus_ring.h32
-rw-r--r--chromium/ui/views/controls/label.cc25
-rw-r--r--chromium/ui/views/controls/label.h14
-rw-r--r--chromium/ui/views/controls/label_unittest.cc5
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.cc33
-rw-r--r--chromium/ui/views/controls/menu/menu_controller.h10
-rw-r--r--chromium/ui/views/controls/menu/menu_controller_unittest.cc21
-rw-r--r--chromium/ui/views/controls/menu/menu_delegate.h1
-rw-r--r--chromium/ui/views/controls/menu/menu_host.cc1
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.cc161
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view.h32
-rw-r--r--chromium/ui/views/controls/menu/menu_item_view_unittest.cc33
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter.cc17
-rw-r--r--chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc22
-rw-r--r--chromium/ui/views/controls/menu/submenu_view.cc33
-rw-r--r--chromium/ui/views/controls/prefix_selector.cc9
-rw-r--r--chromium/ui/views/controls/prefix_selector.h5
-rw-r--r--chromium/ui/views/controls/scroll_view.h2
-rw-r--r--chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h3
-rw-r--r--chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm48
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc15
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane.h2
-rw-r--r--chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc2
-rw-r--r--chromium/ui/views/controls/table/table_view.cc2
-rw-r--r--chromium/ui/views/controls/table/table_view.h3
-rw-r--r--chromium/ui/views/controls/textfield/textfield.cc34
-rw-r--r--chromium/ui/views/controls/textfield/textfield.h13
-rw-r--r--chromium/ui/views/controls/textfield/textfield_test_api.cc7
-rw-r--r--chromium/ui/views/controls/textfield/textfield_test_api.h10
-rw-r--r--chromium/ui/views/controls/textfield/textfield_unittest.cc77
-rw-r--r--chromium/ui/views/controls/tree/tree_view.cc341
-rw-r--r--chromium/ui/views/controls/tree/tree_view.h41
-rw-r--r--chromium/ui/views/controls/tree/tree_view_unittest.cc273
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu.cc28
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu.h21
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.cc29
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_base.h27
-rw-r--r--chromium/ui/views/controls/views_text_services_context_menu_mac.mm187
-rw-r--r--chromium/ui/views/controls/webview/webview.cc8
-rw-r--r--chromium/ui/views/controls/webview/webview.h2
-rw-r--r--chromium/ui/views/controls/webview/webview_unittest.cc4
-rw-r--r--chromium/ui/views/corewm/tooltip_aura.cc107
-rw-r--r--chromium/ui/views/corewm/tooltip_aura.h12
-rw-r--r--chromium/ui/views/corewm/tooltip_controller.cc25
-rw-r--r--chromium/ui/views/corewm/tooltip_controller.h4
-rw-r--r--chromium/ui/views/corewm/tooltip_controller_test_helper.h1
-rw-r--r--chromium/ui/views/corewm/tooltip_controller_unittest.cc7
-rw-r--r--chromium/ui/views/examples/combobox_example.cc2
-rw-r--r--chromium/ui/views/examples/example_combobox_model.cc2
-rw-r--r--chromium/ui/views/examples/example_combobox_model.h2
-rw-r--r--chromium/ui/views/examples/examples_main_proc.cc1
-rw-r--r--chromium/ui/views/examples/examples_skia_gold_pixel_diff.cc4
-rw-r--r--chromium/ui/views/examples/examples_skia_gold_pixel_diff.h2
-rw-r--r--chromium/ui/views/examples/examples_window.cc7
-rw-r--r--chromium/ui/views/focus/focus_manager.cc38
-rw-r--r--chromium/ui/views/focus/focus_manager.h3
-rw-r--r--chromium/ui/views/focus/focus_manager_unittest.cc53
-rw-r--r--chromium/ui/views/focus/focus_search.cc7
-rw-r--r--chromium/ui/views/focus/focus_traversal_unittest.cc2
-rw-r--r--chromium/ui/views/layout/grid_layout.h2
-rw-r--r--chromium/ui/views/linux_ui/linux_ui.cc1
-rw-r--r--chromium/ui/views/linux_ui/linux_ui.h4
-rw-r--r--chromium/ui/views/native_cursor_mac.mm2
-rw-r--r--chromium/ui/views/touchui/touch_selection_controller_impl.cc134
-rw-r--r--chromium/ui/views/touchui/touch_selection_controller_impl.h20
-rw-r--r--chromium/ui/views/touchui/touch_selection_controller_impl_unittest.cc2
-rw-r--r--chromium/ui/views/touchui/touch_selection_menu_views.cc19
-rw-r--r--chromium/ui/views/touchui/touch_selection_menu_views.h4
-rw-r--r--chromium/ui/views/vector_icons/vector_icons.cc.template1
-rw-r--r--chromium/ui/views/view.cc55
-rw-r--r--chromium/ui/views/view.h6
-rw-r--r--chromium/ui/views/view_class_properties.cc4
-rw-r--r--chromium/ui/views/view_class_properties.h6
-rw-r--r--chromium/ui/views/view_model.h2
-rw-r--r--chromium/ui/views/view_targeter_unittest.cc25
-rw-r--r--chromium/ui/views/view_unittest.cc10
-rw-r--r--chromium/ui/views/views_features.cc5
-rw-r--r--chromium/ui/views/views_features.h1
-rw-r--r--chromium/ui/views/widget/ax_native_widget_mac_unittest.mm2
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc61
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h10
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc468
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc313
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h86
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc77
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc51
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h10
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc11
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen.cc2
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen_linux.cc35
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc6
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen_win.cc4
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen_win.h2
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc34
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_screen_x11.h9
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc17
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h8
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_unittest.cc228
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc64
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc26
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h8
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc16
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h5
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_interactive_uitest.cc52
-rw-r--r--chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc675
-rw-r--r--chromium/ui/views/widget/desktop_aura/x11_drag_drop_client_unittest.cc827
-rw-r--r--chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc217
-rw-r--r--chromium/ui/views/widget/native_widget_aura_unittest.cc21
-rw-r--r--chromium/ui/views/widget/root_view_unittest.cc28
-rw-r--r--chromium/ui/views/widget/unique_widget_ptr.cc97
-rw-r--r--chromium/ui/views/widget/unique_widget_ptr.h43
-rw-r--r--chromium/ui/views/widget/unique_widget_ptr_unittest.cc133
-rw-r--r--chromium/ui/views/widget/widget.cc24
-rw-r--r--chromium/ui/views/widget/widget_delegate.cc47
-rw-r--r--chromium/ui/views/widget/widget_delegate.h68
-rw-r--r--chromium/ui/views/widget/widget_deletion_observer.cc1
-rw-r--r--chromium/ui/views/widget/widget_hwnd_utils.cc2
-rw-r--r--chromium/ui/views/widget/widget_interactive_uitest.cc30
-rw-r--r--chromium/ui/views/widget/window_reorderer_unittest.cc12
-rw-r--r--chromium/ui/views/win/hwnd_message_handler.cc83
-rw-r--r--chromium/ui/views/win/hwnd_message_handler.h26
-rw-r--r--chromium/ui/views/window/custom_frame_view_unittest.cc53
-rw-r--r--chromium/ui/views/window/dialog_delegate.cc7
-rw-r--r--chromium/ui/views/window/dialog_delegate.h20
-rw-r--r--chromium/ui/views/window/dialog_delegate_unittest.cc19
-rw-r--r--chromium/ui/views/window/vector_icons/vector_icons.cc.template1
175 files changed, 5398 insertions, 3009 deletions
diff --git a/chromium/ui/views/BUILD.gn b/chromium/ui/views/BUILD.gn
index bc19dfd88f9..3df474182de 100644
--- a/chromium/ui/views/BUILD.gn
+++ b/chromium/ui/views/BUILD.gn
@@ -255,6 +255,7 @@ jumbo_component("views") {
"widget/root_view.h",
"widget/root_view_targeter.h",
"widget/tooltip_manager.h",
+ "widget/unique_widget_ptr.h",
"widget/widget.h",
"widget/widget_delegate.h",
"widget/widget_deletion_observer.h",
@@ -380,7 +381,6 @@ jumbo_component("views") {
"controls/tree/tree_view.cc",
"controls/tree/tree_view_controller.cc",
"controls/tree/tree_view_drawing_provider.cc",
- "controls/views_text_services_context_menu.cc",
"controls/views_text_services_context_menu_base.cc",
"controls/views_text_services_context_menu_base.h",
"debug_utils.cc",
@@ -437,6 +437,7 @@ jumbo_component("views") {
"widget/root_view.cc",
"widget/root_view_targeter.cc",
"widget/tooltip_manager.cc",
+ "widget/unique_widget_ptr.cc",
"widget/widget.cc",
"widget/widget_aura_utils.cc",
"widget/widget_delegate.cc",
@@ -502,7 +503,7 @@ jumbo_component("views") {
"//ui/accessibility:ax_enums_mojo",
"//ui/base",
"//ui/base/clipboard",
- "//ui/base/cursor",
+ "//ui/base/cursor:cursor_base",
"//ui/base/ime/init",
"//ui/compositor",
"//ui/display",
@@ -513,6 +514,7 @@ jumbo_component("views") {
"//ui/gfx/animation",
"//ui/gfx/geometry",
"//ui/views/resources",
+ "//ui/views/window/vector_icons",
]
if (use_x11) {
@@ -526,6 +528,7 @@ jumbo_component("views") {
if (is_linux && !is_chromeos) {
sources -= [ "window/window_button_order_provider.cc" ]
+ public_deps += [ "//ui/base/cursor:theme_manager" ]
deps += [
"//ui/base/ime/linux",
"//ui/shell_dialogs",
@@ -587,7 +590,6 @@ jumbo_component("views") {
"widget/native_widget_mac.mm",
"widget/widget_utils_mac.mm",
]
- sources -= [ "controls/views_text_services_context_menu.cc" ]
public_deps += [ "//components/remote_cocoa/common:mojo" ]
deps += [
"//components/crash/core/common",
@@ -750,7 +752,10 @@ jumbo_component("views") {
"widget/desktop_aura/desktop_screen_position_client.cc",
"widget/desktop_aura/desktop_window_tree_host.cc",
]
- public_deps += [ "//ui/base/cursor/mojom:cursor_type" ]
+ public_deps += [
+ "//ui/base/cursor",
+ "//ui/base/cursor/mojom:cursor_type",
+ ]
if (use_x11) {
public_deps += [
"//ui/base/x",
@@ -777,7 +782,8 @@ jumbo_component("views") {
"widget/desktop_aura/desktop_window_tree_host_win.cc",
]
deps += [ "//ui/events:dom_keyboard_layout" ]
- } else if (use_ozone) {
+ }
+ if (use_ozone) {
public += [ "widget/desktop_aura/desktop_screen_ozone.h" ]
sources += [
"widget/desktop_aura/desktop_drag_drop_client_ozone.cc",
@@ -789,6 +795,7 @@ jumbo_component("views") {
public += [ "widget/desktop_aura/desktop_window_tree_host_linux.h" ]
sources += [
"style/platform_style_linux.cc",
+ "widget/desktop_aura/desktop_screen_linux.cc",
"widget/desktop_aura/desktop_window_tree_host_linux.cc",
"widget/desktop_aura/window_event_filter_linux.cc",
"widget/desktop_aura/window_event_filter_linux.h",
@@ -917,6 +924,8 @@ jumbo_source_set("test_support") {
"test/test_views_delegate.h",
"test/test_widget_observer.cc",
"test/test_widget_observer.h",
+ "test/view_metadata_test_utils.cc",
+ "test/view_metadata_test_utils.h",
"test/views_test_base.cc",
"test/views_test_base.h",
"test/views_test_helper.cc",
@@ -1000,8 +1009,6 @@ jumbo_source_set("test_support") {
]
if (use_x11) {
sources += [
- "test/desktop_screen_x11_test_api.cc",
- "test/desktop_screen_x11_test_api.h",
"test/test_desktop_screen_x11.cc",
"test/test_desktop_screen_x11.h",
"test/ui_controls_factory_desktop_aurax11.cc",
@@ -1033,6 +1040,7 @@ test("views_unittests") {
"animation/ink_drop_ripple_unittest.cc",
"animation/ink_drop_unittest.cc",
"animation/installable_ink_drop_animator_unittest.cc",
+ "animation/installable_ink_drop_painter_unittest.cc",
"animation/installable_ink_drop_unittest.cc",
"animation/slide_out_controller_unittest.cc",
"animation/square_ink_drop_ripple_unittest.cc",
@@ -1046,6 +1054,7 @@ test("views_unittests") {
"controls/button/image_button_unittest.cc",
"controls/button/label_button_label_unittest.cc",
"controls/button/label_button_unittest.cc",
+ "controls/button/md_text_button_unittest.cc",
"controls/button/menu_button_unittest.cc",
"controls/button/radio_button_unittest.cc",
"controls/button/toggle_button_unittest.cc",
@@ -1105,6 +1114,7 @@ test("views_unittests") {
"widget/any_widget_observer_unittest.cc",
"widget/native_widget_unittest.cc",
"widget/root_view_unittest.cc",
+ "widget/unique_widget_ptr_unittest.cc",
"widget/widget_unittest.cc",
"window/custom_frame_view_unittest.cc",
"window/dialog_client_view_unittest.cc",
@@ -1268,7 +1278,7 @@ test("views_unittests") {
sources += [
"widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc",
"widget/desktop_aura/desktop_screen_x11_unittest.cc",
- "widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc",
+ "widget/desktop_aura/x11_drag_drop_client_unittest.cc",
]
deps += [ "//ui/base/x:test_support" ]
}
@@ -1276,6 +1286,11 @@ test("views_unittests") {
sources += [
"widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc",
]
+ if (is_linux) {
+ sources += [
+ "widget/desktop_aura/desktop_window_tree_host_linux_unittest.cc",
+ ]
+ }
}
if (use_ozone) {
sources +=
diff --git a/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc b/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc
index bb90519552e..4f0f799ec48 100644
--- a/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc
+++ b/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc
@@ -50,7 +50,7 @@ class AXTreeSourceViewsTest : public ViewsTestBase {
params.bounds = gfx::Rect(11, 22, 333, 444);
params.context = GetContext();
widget_->Init(std::move(params));
- widget_->SetContentsView(new View());
+ widget_->SetContentsView(std::make_unique<View>());
label1_ = new Label(base::ASCIIToUTF16("Label 1"));
label1_->SetBounds(1, 1, 111, 111);
diff --git a/chromium/ui/views/accessibility/ax_virtual_view.cc b/chromium/ui/views/accessibility/ax_virtual_view.cc
index a222443d37e..060d4a77c65 100644
--- a/chromium/ui/views/accessibility/ax_virtual_view.cc
+++ b/chromium/ui/views/accessibility/ax_virtual_view.cc
@@ -395,7 +395,7 @@ bool AXVirtualView::AccessibilityPerformAction(const ui::AXActionData& data) {
if (custom_data_.HasAction(data.action))
result = HandleAccessibleAction(data);
if (!result && GetOwnerView())
- return GetOwnerView()->HandleAccessibleAction(data);
+ return HandleAccessibleActionInOwnerView(data);
return result;
}
@@ -449,7 +449,17 @@ bool AXVirtualView::HandleAccessibleAction(
break;
}
- return GetOwnerView()->HandleAccessibleAction(action_data);
+ return HandleAccessibleActionInOwnerView(action_data);
+}
+
+bool AXVirtualView::HandleAccessibleActionInOwnerView(
+ const ui::AXActionData& action_data) {
+ DCHECK(GetOwnerView());
+ // Save the node id so that the owner view can determine which virtual view
+ // is being targeted for action.
+ ui::AXActionData forwarded_action_data = action_data;
+ forwarded_action_data.target_node_id = GetData().id;
+ return GetOwnerView()->HandleAccessibleAction(forwarded_action_data);
}
View* AXVirtualView::GetOwnerView() const {
diff --git a/chromium/ui/views/accessibility/ax_virtual_view.h b/chromium/ui/views/accessibility/ax_virtual_view.h
index 105c961b102..6b1f9eeda4a 100644
--- a/chromium/ui/views/accessibility/ax_virtual_view.h
+++ b/chromium/ui/views/accessibility/ax_virtual_view.h
@@ -171,6 +171,11 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
// via NotifyAccessibilityEvent().
virtual bool HandleAccessibleAction(const ui::AXActionData& action_data);
+ protected:
+ // Forwards a request from assistive technology to perform an action on this
+ // virtual view to the owner view's accessible action handler.
+ bool HandleAccessibleActionInOwnerView(const ui::AXActionData& action_data);
+
private:
// Internal class name.
static const char kViewClassName[];
diff --git a/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc b/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc
index b48ada53153..23e1ba55ab2 100644
--- a/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc
+++ b/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc
@@ -487,6 +487,13 @@ TEST_F(AXVirtualViewTest, OverrideFocus) {
ASSERT_NE(nullptr, virtual_label_->GetNativeObject());
ExpectReceivedAccessibilityEvents({});
+ button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
+ button_->RequestFocus();
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(GetButtonAccessibility(), ax::mojom::Event::kFocus),
+ std::make_pair(GetButtonAccessibility(),
+ ax::mojom::Event::kChildrenChanged)});
+
EXPECT_EQ(button_accessibility.GetNativeObject(),
button_accessibility.GetFocusedDescendant());
button_accessibility.OverrideFocus(virtual_label_);
@@ -536,6 +543,10 @@ TEST_F(AXVirtualViewTest, OverrideFocus) {
// Test that calling GetFocus() while the owner view is not focused will
// return nullptr.
+ button_->SetFocusBehavior(View::FocusBehavior::NEVER);
+ button_->RequestFocus();
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
EXPECT_EQ(nullptr, virtual_label_->GetFocus());
EXPECT_EQ(nullptr, virtual_child_1->GetFocus());
EXPECT_EQ(nullptr, virtual_child_2->GetFocus());
@@ -544,7 +555,7 @@ TEST_F(AXVirtualViewTest, OverrideFocus) {
button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
button_->RequestFocus();
ExpectReceivedAccessibilityEvents(
- {std::make_pair(GetButtonAccessibility(), ax::mojom::Event::kFocus),
+ {std::make_pair(virtual_child_3, ax::mojom::Event::kFocus),
std::make_pair(GetButtonAccessibility(),
ax::mojom::Event::kChildrenChanged)});
diff --git a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc
index 9aeeb45a0bc..c58811c8b67 100644
--- a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc
+++ b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc
@@ -70,6 +70,15 @@ void AXWidgetObjWrapper::OnWidgetDestroying(Widget* widget) {
aura_obj_cache_->Remove(widget);
}
+void AXWidgetObjWrapper::OnWidgetDestroyed(Widget* widget) {
+ // Normally this does not run because of OnWidgetDestroying should have
+ // removed |this| from cache. However, some code could trigger a destroying
+ // widget to be created after OnWidgetDestroying. This guards against such
+ // situation and ensures the destroyed widget is removed from cache.
+ // See https://crbug.com/1091545
+ aura_obj_cache_->Remove(widget);
+}
+
void AXWidgetObjWrapper::OnWidgetClosing(Widget* widget) {
aura_obj_cache_->Remove(widget);
}
diff --git a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h
index 2f6cc93f1ef..9d768b80b85 100644
--- a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h
+++ b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h
@@ -40,6 +40,7 @@ class AXWidgetObjWrapper : public AXAuraObjWrapper,
// WidgetObserver overrides.
void OnWidgetDestroying(Widget* widget) override;
+ void OnWidgetDestroyed(Widget* widget) override;
void OnWidgetClosing(Widget* widget) override;
void OnWidgetVisibilityChanged(Widget*, bool) override;
diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc
index 5e79b94b22c..bc23c390e2f 100644
--- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc
+++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc
@@ -11,6 +11,7 @@
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/aura/aura_window_properties.h"
+#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_id.h"
@@ -88,6 +89,15 @@ AXWindowObjWrapper::AXWindowObjWrapper(AXAuraObjCache* aura_obj_cache,
AXWindowObjWrapper::~AXWindowObjWrapper() = default;
+bool AXWindowObjWrapper::HandleAccessibleAction(
+ const ui::AXActionData& action) {
+ if (action.action == ax::mojom::Action::kFocus) {
+ window_->Focus();
+ return true;
+ }
+ return false;
+}
+
bool AXWindowObjWrapper::IsIgnored() {
return false;
}
@@ -160,6 +170,9 @@ void AXWindowObjWrapper::OnWindowDestroyed(aura::Window* window) {
}
void AXWindowObjWrapper::OnWindowDestroying(aura::Window* window) {
+ if (window == window_)
+ window_destroying_ = true;
+
Widget* widget = GetWidgetForWindow(window);
if (widget)
aura_obj_cache_->Remove(widget);
@@ -179,6 +192,9 @@ void AXWindowObjWrapper::OnWindowBoundsChanged(
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
+ if (window_destroying_)
+ return;
+
if (window == window_)
FireLocationChangesRecursively(window_, aura_obj_cache_);
}
@@ -186,22 +202,34 @@ void AXWindowObjWrapper::OnWindowBoundsChanged(
void AXWindowObjWrapper::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
+ if (window_destroying_)
+ return;
+
if (window == window_ && key == ui::kChildAXTreeID)
FireEvent(ax::mojom::Event::kChildrenChanged);
}
void AXWindowObjWrapper::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
+ if (window_destroying_)
+ return;
+
FireEvent(ax::mojom::Event::kStateChanged);
}
void AXWindowObjWrapper::OnWindowTransformed(aura::Window* window,
ui::PropertyChangeReason reason) {
+ if (window_destroying_)
+ return;
+
if (window == window_)
FireLocationChangesRecursively(window_, aura_obj_cache_);
}
void AXWindowObjWrapper::OnWindowTitleChanged(aura::Window* window) {
+ if (window_destroying_)
+ return;
+
FireEventOnWindowChildWidgetAndRootView(
window_, ax::mojom::Event::kTreeChanged, aura_obj_cache_);
}
diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h
index 841a3d03710..7c5514b3b5e 100644
--- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h
+++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h
@@ -30,6 +30,7 @@ class AXWindowObjWrapper : public AXAuraObjWrapper,
~AXWindowObjWrapper() override;
// AXAuraObjWrapper overrides.
+ bool HandleAccessibleAction(const ui::AXActionData& action) override;
bool IsIgnored() override;
AXAuraObjWrapper* GetParent() override;
void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) override;
@@ -57,12 +58,17 @@ class AXWindowObjWrapper : public AXAuraObjWrapper,
// Fires an accessibility event.
void FireEvent(ax::mojom::Event event_type);
- aura::Window* window_;
+ aura::Window* const window_;
- bool is_root_window_;
+ const bool is_root_window_;
const ui::AXUniqueId unique_id_;
+ // Whether OnWindowDestroying has happened for |window_|. Used to suppress
+ // further events from |window| after OnWindowDestroying. Otherwise, dangling
+ // pointer could be left in |aura_obj_cache_|. See https://crbug.com/1091545
+ bool window_destroying_ = false;
+
ScopedObserver<aura::Window, aura::WindowObserver> observer_{this};
};
diff --git a/chromium/ui/views/accessibility/view_accessibility.cc b/chromium/ui/views/accessibility/view_accessibility.cc
index 4ff677b664e..5dfb136d2d1 100644
--- a/chromium/ui/views/accessibility/view_accessibility.cc
+++ b/chromium/ui/views/accessibility/view_accessibility.cc
@@ -121,6 +121,10 @@ const ui::AXUniqueId& ViewAccessibility::GetUniqueId() const {
return unique_id_;
}
+bool ViewAccessibility::IsLeaf() const {
+ return is_leaf_;
+}
+
void ViewAccessibility::GetAccessibleNodeData(ui::AXNodeData* data) const {
data->id = GetUniqueId().Get();
@@ -206,7 +210,7 @@ void ViewAccessibility::GetAccessibleNodeData(ui::AXNodeData* data) const {
return;
}
- if (view_->IsAccessibilityFocusable())
+ if (view_->IsAccessibilityFocusable() && !focused_virtual_child_)
data->AddState(ax::mojom::State::kFocusable);
if (!view_->GetEnabled())
@@ -224,13 +228,24 @@ void ViewAccessibility::OverrideFocus(AXVirtualView* virtual_view) {
<< "|virtual_view| must be nullptr or a descendant of this view.";
focused_virtual_child_ = virtual_view;
- if (focused_virtual_child_) {
- focused_virtual_child_->NotifyAccessibilityEvent(ax::mojom::Event::kFocus);
- } else {
- view_->NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
+ if (view_->HasFocus()) {
+ if (focused_virtual_child_) {
+ focused_virtual_child_->NotifyAccessibilityEvent(
+ ax::mojom::Event::kFocus);
+ } else {
+ view_->NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
+ }
}
}
+void ViewAccessibility::SetPopupFocusOverride() {}
+
+void ViewAccessibility::EndPopupFocusOverride() {}
+
+bool ViewAccessibility::IsFocusedForTesting() {
+ return view_->HasFocus() && !focused_virtual_child_;
+}
+
void ViewAccessibility::OverrideRole(const ax::mojom::Role role) {
DCHECK(IsValidRoleForViews(role)) << "Invalid role for Views.";
custom_data_.role = role;
diff --git a/chromium/ui/views/accessibility/view_accessibility.h b/chromium/ui/views/accessibility/view_accessibility.h
index 50fdcfef62b..69cb2423d88 100644
--- a/chromium/ui/views/accessibility/view_accessibility.h
+++ b/chromium/ui/views/accessibility/view_accessibility.h
@@ -108,7 +108,7 @@ class VIEWS_EXPORT ViewAccessibility {
View* view() const { return view_; }
AXVirtualView* FocusedVirtualChild() const { return focused_virtual_child_; }
- bool IsLeaf() const { return is_leaf_; }
+ virtual bool IsLeaf() const;
bool IsIgnored() const { return is_ignored_; }
//
@@ -144,6 +144,19 @@ class VIEWS_EXPORT ViewAccessibility {
// native accessibility object associated with this view.
gfx::NativeViewAccessible GetFocusedDescendant();
+ // Call when this is the active descendant of a popup view that temporarily
+ // takes over focus. It is only necessary to use this for menus like autofill,
+ // where the actual focus is in content.
+ // When the popup closes, call EndPopupFocusOverride().
+ virtual void SetPopupFocusOverride();
+
+ // Call when popup closes, if it used SetPopupFocusOverride().
+ virtual void EndPopupFocusOverride();
+
+ // Return true if this view is considered focused.
+ virtual bool IsFocusedForTesting();
+
+ // Call when a menu closes, to restore focus to where it was previously.
virtual void FireFocusAfterMenuClose();
// Used for testing. Allows a test to watch accessibility events.
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc
index 748c8569207..35dce74a77b 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc
@@ -11,6 +11,7 @@
#include <vector>
#include "base/bind.h"
+#include "base/containers/adapters.h"
#include "base/lazy_instance.h"
#include "base/threading/thread_task_runner_handle.h"
#include "ui/accessibility/ax_action_data.h"
@@ -121,9 +122,6 @@ struct ViewAXPlatformNodeDelegate::ChildWidgetsResult {
bool is_tab_modal_showing;
};
-// static
-int ViewAXPlatformNodeDelegate::menu_depth_ = 0;
-
ViewAXPlatformNodeDelegate::ViewAXPlatformNodeDelegate(View* view)
: ViewAccessibility(view) {
ax_platform_node_ = ui::AXPlatformNode::Create(this);
@@ -139,7 +137,7 @@ ViewAXPlatformNodeDelegate::ViewAXPlatformNodeDelegate(View* view)
ViewAXPlatformNodeDelegate::~ViewAXPlatformNodeDelegate() {
if (ui::AXPlatformNode::GetPopupFocusOverride() == GetNativeObject())
- ui::AXPlatformNode::SetPopupFocusOverride(nullptr);
+ EndPopupFocusOverride();
ax_platform_node_->Destroy();
}
@@ -148,6 +146,21 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() const {
return ax_platform_node_->GetNativeViewAccessible();
}
+void ViewAXPlatformNodeDelegate::SetPopupFocusOverride() {
+ ui::AXPlatformNode::SetPopupFocusOverride(GetNativeObject());
+}
+
+void ViewAXPlatformNodeDelegate::EndPopupFocusOverride() {
+ ui::AXPlatformNode::SetPopupFocusOverride(nullptr);
+}
+
+bool ViewAXPlatformNodeDelegate::IsFocusedForTesting() {
+ if (ui::AXPlatformNode::GetPopupFocusOverride())
+ return ui::AXPlatformNode::GetPopupFocusOverride() == GetNativeObject();
+
+ return ViewAccessibility::IsFocusedForTesting();
+}
+
void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
ax::mojom::Event event_type) {
DCHECK(ax_platform_node_);
@@ -160,16 +173,21 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
// Some events have special handling.
switch (event_type) {
- case ax::mojom::Event::kMenuStart:
- OnMenuStart();
+ case ax::mojom::Event::kFocusAfterMenuClose: {
+ DCHECK(!ui::AXPlatformNode::GetPopupFocusOverride())
+ << "Must call ViewAccessibility::EndPopupFocusOverride() as menu "
+ "closes.";
break;
- case ax::mojom::Event::kMenuEnd:
- OnMenuEnd();
- break;
- case ax::mojom::Event::kSelection: {
- ax::mojom::Role role = GetData().role;
- if (menu_depth_ && (ui::IsMenuItem(role) || ui::IsListItem(role)))
- OnMenuItemActive();
+ }
+ case ax::mojom::Event::kFocus: {
+ if (ui::AXPlatformNode::GetPopupFocusOverride()) {
+ DCHECK_EQ(ui::AXPlatformNode::GetPopupFocusOverride(),
+ GetNativeObject())
+ << "If the popup focus override is on, then the kFocus event must "
+ "match it. Most likely the popup has closed, but did not call "
+ "ViewAccessibility::EndPopupFocusOverride(), and focus has "
+ "now moved on.";
+ }
break;
}
case ax::mojom::Event::kFocusContext: {
@@ -201,27 +219,6 @@ void ViewAXPlatformNodeDelegate::AnnounceText(const base::string16& text) {
}
#endif
-void ViewAXPlatformNodeDelegate::OnMenuItemActive() {
- // When a native menu is shown and has an item selected, treat it and the
- // currently selected item as focused, even though the actual focus is in the
- // browser's currently focused textfield.
- ui::AXPlatformNode::SetPopupFocusOverride(
- ax_platform_node_->GetNativeViewAccessible());
-}
-
-void ViewAXPlatformNodeDelegate::OnMenuStart() {
- ++menu_depth_;
-}
-
-void ViewAXPlatformNodeDelegate::OnMenuEnd() {
- // When a native menu is hidden, restore accessibility focus to the current
- // focus in the document.
- if (menu_depth_ >= 1)
- --menu_depth_;
- if (menu_depth_ == 0)
- ui::AXPlatformNode::SetPopupFocusOverride(nullptr);
-}
-
void ViewAXPlatformNodeDelegate::FireFocusAfterMenuClose() {
ui::AXPlatformNodeBase* focused_node =
static_cast<ui::AXPlatformNodeBase*>(ax_platform_node_);
@@ -276,7 +273,7 @@ const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const {
}
int ViewAXPlatformNodeDelegate::GetChildCount() const {
- if (IsLeaf())
+ if (ViewAccessibility::IsLeaf())
return 0;
if (!virtual_children().empty()) {
@@ -371,6 +368,15 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetParent() {
return nullptr;
}
+bool ViewAXPlatformNodeDelegate::IsChildOfLeaf() const {
+ // Needed to prevent endless loops, see: http://crbug.com/1100047
+ return false;
+}
+
+bool ViewAXPlatformNodeDelegate::IsLeaf() const {
+ return ViewAccessibility::IsLeaf() || AXPlatformNodeDelegateBase::IsLeaf();
+}
+
gfx::Rect ViewAXPlatformNodeDelegate::GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
@@ -419,6 +425,22 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
if (!view()->HitTestPoint(point))
return nullptr;
+ // Check if the point is within any of the virtual children of this view.
+ // AXVirtualView's HitTestSync is a recursive function that will return the
+ // deepest child, since it does not support relative bounds.
+ if (!virtual_children().empty()) {
+ // Search the greater indices first, since they're on top in the z-order.
+ for (const std::unique_ptr<AXVirtualView>& child :
+ base::Reversed(virtual_children())) {
+ gfx::NativeViewAccessible result =
+ child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y);
+ if (result)
+ return result;
+ }
+ // If it's not inside any of our virtual children, it's inside this view.
+ return GetNativeObject();
+ }
+
// Check if the point is within any of the immediate children of this
// view. We don't have to search further because AXPlatformNode will
// do a recursive hit test if we return anything other than |this| or NULL.
@@ -426,6 +448,10 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
const auto is_point_in_child = [point, v](View* child) {
if (!child->GetVisible())
return false;
+ ui::AXNodeData child_data;
+ child->GetViewAccessibility().GetAccessibleNodeData(&child_data);
+ if (child_data.HasState(ax::mojom::State::kInvisible))
+ return false;
gfx::Point point_in_child_coords = point;
v->ConvertPointToTarget(v, child, &point_in_child_coords);
return child->HitTestPoint(point_in_child_coords);
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h
index 433ef8986f7..8d3ddca5571 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h
@@ -61,6 +61,8 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
gfx::NativeViewAccessible GetNSWindow() override;
gfx::NativeViewAccessible GetNativeViewAccessible() override;
gfx::NativeViewAccessible GetParent() override;
+ bool IsChildOfLeaf() const override;
+ bool IsLeaf() const override;
gfx::Rect GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
@@ -86,6 +88,10 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
base::Optional<int> GetPosInSet() const override;
base::Optional<int> GetSetSize() const override;
+ void SetPopupFocusOverride() override;
+ void EndPopupFocusOverride() override;
+ bool IsFocusedForTesting() override;
+
protected:
explicit ViewAXPlatformNodeDelegate(View* view);
@@ -100,18 +106,11 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
ChildWidgetsResult GetChildWidgets() const;
- void OnMenuItemActive();
- void OnMenuStart();
- void OnMenuEnd();
-
// We own this, but it is reference-counted on some platforms so we can't use
// a unique_ptr. It is destroyed in the destructor.
ui::AXPlatformNode* ax_platform_node_;
mutable ui::AXNodeData data_;
-
- // Levels of menu are currently open, e.g. 0: none, 1: top, 2: submenu ...
- static int32_t menu_depth_;
};
} // namespace views
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
index f9c0de73d1b..3f8100365a7 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
@@ -5,6 +5,7 @@
#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
#include <atk/atk.h>
+#include <memory>
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/views/controls/textfield/textfield.h"
@@ -29,12 +30,10 @@ TEST_F(ViewAXPlatformNodeDelegateAuraLinuxTest, TextfieldAccessibility) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(std::move(init_params));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
Textfield* textfield = new Textfield;
textfield->SetAccessibleName(base::UTF8ToUTF16("Name"));
- textfield->SetText(base::UTF8ToUTF16("Value"));
content->AddChildView(textfield);
AtkText* atk_text = ATK_TEXT(textfield->GetNativeViewAccessible());
@@ -56,49 +55,44 @@ TEST_F(ViewAXPlatformNodeDelegateAuraLinuxTest, TextfieldAccessibility) {
g_signal_connect(atk_text, "text-insert", callback, &text_insert_events);
g_signal_connect(atk_text, "text-remove", callback, &text_remove_events);
- textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+ textfield->SetText(base::UTF8ToUTF16("Value"));
ASSERT_EQ(text_remove_events.size(), 0ul);
ASSERT_EQ(text_insert_events.size(), 1ul);
- ASSERT_EQ(text_insert_events[0].position, 0);
- ASSERT_EQ(text_insert_events[0].length, 5);
- ASSERT_EQ(text_insert_events[0].text, "Value");
+ EXPECT_EQ(text_insert_events[0].position, 0);
+ EXPECT_EQ(text_insert_events[0].length, 5);
+ EXPECT_EQ(text_insert_events[0].text, "Value");
text_insert_events.clear();
textfield->SetText(base::UTF8ToUTF16("Value A"));
- textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
-
ASSERT_EQ(text_remove_events.size(), 0ul);
ASSERT_EQ(text_insert_events.size(), 1ul);
- ASSERT_EQ(text_insert_events[0].position, 5);
- ASSERT_EQ(text_insert_events[0].length, 2);
- ASSERT_EQ(text_insert_events[0].text, " A");
+ EXPECT_EQ(text_insert_events[0].position, 5);
+ EXPECT_EQ(text_insert_events[0].length, 2);
+ EXPECT_EQ(text_insert_events[0].text, " A");
text_insert_events.clear();
textfield->SetText(base::UTF8ToUTF16("Value"));
- textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
ASSERT_EQ(text_remove_events.size(), 1ul);
ASSERT_EQ(text_insert_events.size(), 0ul);
- ASSERT_EQ(text_remove_events[0].position, 5);
- ASSERT_EQ(text_remove_events[0].length, 2);
- ASSERT_EQ(text_remove_events[0].text, " A");
+ EXPECT_EQ(text_remove_events[0].position, 5);
+ EXPECT_EQ(text_remove_events[0].length, 2);
+ EXPECT_EQ(text_remove_events[0].text, " A");
text_remove_events.clear();
textfield->SetText(base::UTF8ToUTF16("Prefix Value"));
- textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
ASSERT_EQ(text_remove_events.size(), 0ul);
ASSERT_EQ(text_insert_events.size(), 1ul);
- ASSERT_EQ(text_insert_events[0].position, 0);
- ASSERT_EQ(text_insert_events[0].length, 7);
- ASSERT_EQ(text_insert_events[0].text, "Prefix ");
+ EXPECT_EQ(text_insert_events[0].position, 0);
+ EXPECT_EQ(text_insert_events[0].length, 7);
+ EXPECT_EQ(text_insert_events[0].text, "Prefix ");
text_insert_events.clear();
textfield->SetText(base::UTF8ToUTF16("Value"));
- textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
ASSERT_EQ(text_remove_events.size(), 1ul);
ASSERT_EQ(text_insert_events.size(), 0ul);
- ASSERT_EQ(text_remove_events[0].position, 0);
- ASSERT_EQ(text_remove_events[0].length, 7);
- ASSERT_EQ(text_remove_events[0].text, "Prefix ");
+ EXPECT_EQ(text_remove_events[0].position, 0);
+ EXPECT_EQ(text_remove_events[0].length, 7);
+ EXPECT_EQ(text_remove_events[0].text, "Prefix ");
text_insert_events.clear();
}
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc
index 797c0eb5482..00ffe077cd7 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc
@@ -7,6 +7,7 @@
#include <oleacc.h>
#include <wrl/client.h>
+#include <memory>
#include <utility>
#include "base/win/scoped_bstr.h"
@@ -69,8 +70,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAccessibility) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(std::move(init_params));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
Textfield* textfield = new Textfield;
textfield->SetAccessibleName(L"Name");
@@ -112,8 +112,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAssociatedLabel) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(std::move(init_params));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
Label* label = new Label(L"Label");
content->AddChildView(label);
@@ -259,8 +258,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(std::move(init_params));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
View* infobar = new View;
content->AddChildView(infobar);
@@ -358,8 +356,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, Overrides) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(std::move(init_params));
- View* contents_view = new View;
- widget.SetContentsView(contents_view);
+ View* contents_view = widget.SetContentsView(std::make_unique<View>());
View* alert_view = new ScrollView;
alert_view->GetViewAccessibility().OverrideRole(ax::mojom::Role::kAlert);
@@ -417,8 +414,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, GridRowColumnCount) {
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(std::move(init_params));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
TestListGridView* grid = new TestListGridView();
content->AddChildView(grid);
diff --git a/chromium/ui/views/animation/animation_delegate_views.cc b/chromium/ui/views/animation/animation_delegate_views.cc
index 815dc341dad..67399e294eb 100644
--- a/chromium/ui/views/animation/animation_delegate_views.cc
+++ b/chromium/ui/views/animation/animation_delegate_views.cc
@@ -41,7 +41,7 @@ void AnimationDelegateViews::OnViewAddedToWidget(View* observed_view) {
}
void AnimationDelegateViews::OnViewRemovedFromWidget(View* observed_view) {
- UpdateAnimationRunner();
+ ClearAnimationRunner();
}
void AnimationDelegateViews::OnViewIsDeleting(View* observed_view) {
@@ -53,7 +53,7 @@ void AnimationDelegateViews::OnViewIsDeleting(View* observed_view) {
void AnimationDelegateViews::AnimationContainerShuttingDown(
gfx::AnimationContainer* container) {
container_ = nullptr;
- compositor_animation_runner_ = nullptr;
+ ClearAnimationRunner();
}
base::TimeDelta AnimationDelegateViews::GetAnimationDurationForReporting()
@@ -76,18 +76,12 @@ void AnimationDelegateViews::SetAnimationMetricsReporter(
}
void AnimationDelegateViews::UpdateAnimationRunner() {
- if (!container_)
- return;
-
if (!view_ || !view_->GetWidget() || !view_->GetWidget()->GetCompositor()) {
- // TODO(https://crbug.com/960621): make sure the container has a correct
- // compositor-assisted runner.
- container_->SetAnimationRunner(nullptr);
- compositor_animation_runner_ = nullptr;
+ ClearAnimationRunner();
return;
}
- if (container_->has_custom_animation_runner())
+ if (!container_ || container_->has_custom_animation_runner())
return;
auto compositor_animation_runner =
@@ -98,4 +92,12 @@ void AnimationDelegateViews::UpdateAnimationRunner() {
container_->SetAnimationRunner(std::move(compositor_animation_runner));
}
+void AnimationDelegateViews::ClearAnimationRunner() {
+ // TODO(https://crbug.com/960621): make sure the container has a correct
+ // compositor-assisted runner.
+ if (container_)
+ container_->SetAnimationRunner(nullptr);
+ compositor_animation_runner_ = nullptr;
+}
+
} // namespace views
diff --git a/chromium/ui/views/animation/animation_delegate_views.h b/chromium/ui/views/animation/animation_delegate_views.h
index 8cc9fb504fa..df98f3fb536 100644
--- a/chromium/ui/views/animation/animation_delegate_views.h
+++ b/chromium/ui/views/animation/animation_delegate_views.h
@@ -60,10 +60,10 @@ class VIEWS_EXPORT AnimationDelegateViews
// Sets CompositorAnimationRunner to |container_| if possible. Otherwise,
// clears AnimationRunner of |container_|.
void UpdateAnimationRunner();
+ void ClearAnimationRunner();
View* view_;
gfx::AnimationContainer* container_ = nullptr;
-
ui::AnimationMetricsReporter* animation_metrics_reporter_ = nullptr;
// The animation runner that |container_| uses.
diff --git a/chromium/ui/views/animation/bounds_animator.cc b/chromium/ui/views/animation/bounds_animator.cc
index cf2598680d4..2f360194f0e 100644
--- a/chromium/ui/views/animation/bounds_animator.cc
+++ b/chromium/ui/views/animation/bounds_animator.cc
@@ -41,9 +41,15 @@ void BoundsAnimator::AnimateViewTo(
DCHECK(view);
DCHECK_EQ(view->parent(), parent_);
- Data existing_data;
+ const bool is_animating = IsAnimating(view);
+
+ // Return early if the existing animation on |view| has the same target
+ // bounds.
+ if (is_animating && target == data_[view].target_bounds)
+ return;
- if (IsAnimating(view)) {
+ Data existing_data;
+ if (is_animating) {
DCHECK(base::Contains(data_, view));
const bool used_transforms = data_[view].target_transform.has_value();
if (used_transforms) {
@@ -75,8 +81,14 @@ void BoundsAnimator::AnimateViewTo(
// If the start bounds are empty we cannot derive a transform from start to
// target. Views with existing transforms are not supported. Default back to
// using the bounds update animation in these cases.
+ // Note that transform is not used if bounds animation requires scaling.
+ // Because for some views, their children cannot be scaled with the same scale
+ // factor. For example, ShelfAppButton's size in normal state and dense state
+ // is 56 and 48 respectively while the size of icon image, the child of
+ // ShelfAppButton, is 44 and 36 respectively.
if (use_transforms_ && !data.start_bounds.IsEmpty() &&
- view->GetTransform().IsIdentity()) {
+ view->GetTransform().IsIdentity() &&
+ data.start_bounds.size() == data.target_bounds.size()) {
// Calculate the target transform. Note that we don't reset the transform if
// there already was one, otherwise users will end up with visual bounds
// different than what they set.
diff --git a/chromium/ui/views/animation/bounds_animator_unittest.cc b/chromium/ui/views/animation/bounds_animator_unittest.cc
index c09736c4572..2042d98b677 100644
--- a/chromium/ui/views/animation/bounds_animator_unittest.cc
+++ b/chromium/ui/views/animation/bounds_animator_unittest.cc
@@ -152,6 +152,42 @@ class BoundsAnimatorTest : public testing::Test {
animator_->SetAnimationDuration(base::TimeDelta::FromMilliseconds(10));
}
+ // Animates |child_| to |target_bounds|. Returns the repaint time.
+ // |use_long_duration| indicates whether long or short bounds animation is
+ // created.
+ int GetRepaintTimeFromBoundsAnimation(const gfx::Rect& target_bounds,
+ bool use_long_duration) {
+ child()->set_repaint_count(0);
+
+ const base::TimeDelta animation_duration =
+ base::TimeDelta::FromMilliseconds(use_long_duration ? 2000 : 10);
+ animator()->SetAnimationDuration(animation_duration);
+
+ animator()->AnimateViewTo(child(), target_bounds);
+ animator()->SetAnimationDelegate(child(),
+ std::make_unique<TestAnimationDelegate>());
+
+ // The animator should be animating now.
+ EXPECT_TRUE(animator()->IsAnimating());
+ EXPECT_TRUE(animator()->IsAnimating(child()));
+
+ // Run the message loop; the delegate exits the loop when the animation is
+ // done.
+ if (use_long_duration)
+ task_environment_.FastForwardBy(animation_duration);
+ base::RunLoop().Run();
+
+ // Make sure the bounds match of the view that was animated match and the
+ // layer is destroyed.
+ EXPECT_EQ(target_bounds, child()->bounds());
+ EXPECT_FALSE(child()->layer());
+
+ // |child| shouldn't be animating anymore.
+ EXPECT_FALSE(animator()->IsAnimating(child()));
+
+ return child()->repaint_count();
+ }
+
base::test::SingleThreadTaskEnvironment task_environment_;
private:
@@ -221,19 +257,38 @@ TEST_F(BoundsAnimatorTest, DeleteDelegateOnCancel) {
EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
}
-// Make sure an AnimationDelegate is deleted when another animation is
-// scheduled.
+// Make sure that the AnimationDelegate of the running animation is deleted when
+// a new animation is scheduled.
TEST_F(BoundsAnimatorTest, DeleteDelegateOnNewAnimate) {
- animator()->AnimateViewTo(child(), gfx::Rect(0, 0, 10, 10));
+ const gfx::Rect target_bounds_first(0, 0, 10, 10);
+ animator()->AnimateViewTo(child(), target_bounds_first);
animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
- animator()->AnimateViewTo(child(), gfx::Rect(0, 0, 10, 10));
+ // Start an animation on the same view with different target bounds.
+ const gfx::Rect target_bounds_second(0, 5, 10, 10);
+ animator()->AnimateViewTo(child(), target_bounds_second);
// Starting a new animation should both cancel the delegate and delete it.
EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
EXPECT_TRUE(OwnedDelegate::GetAndClearCanceled());
}
+// Make sure that the duplicate animation request does not interrupt the running
+// animation.
+TEST_F(BoundsAnimatorTest, HandleDuplicateAnimation) {
+ const gfx::Rect target_bounds(0, 0, 10, 10);
+
+ animator()->AnimateViewTo(child(), target_bounds);
+ animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
+
+ // Request the animation with the same view/target bounds.
+ animator()->AnimateViewTo(child(), target_bounds);
+
+ // Verify that the existing animation is not interrupted.
+ EXPECT_FALSE(OwnedDelegate::GetAndClearDeleted());
+ EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
+}
+
// Makes sure StopAnimating works.
TEST_F(BoundsAnimatorTest, StopAnimating) {
std::unique_ptr<OwnedDelegate> delegate(std::make_unique<OwnedDelegate>());
@@ -252,47 +307,57 @@ TEST_F(BoundsAnimatorTest, StopAnimating) {
EXPECT_TRUE(OwnedDelegate::GetAndClearCanceled());
}
-// Tests using the transforms option.
+// Verify that transform is used when the animation target bounds have the
+// same size with the current bounds' meanwhile having the transform option
+// enabled.
TEST_F(BoundsAnimatorTest, UseTransformsAnimateViewTo) {
RecreateAnimator(/*use_transforms=*/true);
- gfx::Rect initial_bounds(0, 0, 10, 10);
+ const gfx::Rect initial_bounds(0, 0, 10, 10);
child()->SetBoundsRect(initial_bounds);
- gfx::Rect target_bounds(10, 10, 20, 20);
- child()->set_repaint_count(0);
- animator()->AnimateViewTo(child(), target_bounds);
- animator()->SetAnimationDelegate(child(),
- std::make_unique<TestAnimationDelegate>());
-
- // The animator should be animating now.
- EXPECT_TRUE(animator()->IsAnimating());
- EXPECT_TRUE(animator()->IsAnimating(child()));
-
- // Run the message loop; the delegate exits the loop when the animation is
- // done.
- base::RunLoop().Run();
+ // Ensure that the target bounds have the same size with the initial bounds'
+ // to apply transform to bounds animation.
+ const gfx::Rect target_bounds_without_resize(gfx::Point(10, 10),
+ initial_bounds.size());
+
+ const int repaint_time_from_short_animation =
+ GetRepaintTimeFromBoundsAnimation(target_bounds_without_resize,
+ /*use_long_duration=*/false);
+ const int repaint_time_from_long_animation =
+ GetRepaintTimeFromBoundsAnimation(initial_bounds,
+ /*use_long_duration=*/true);
+
+ // The number of repaints in long animation should be the same as with the
+ // short animation.
+ EXPECT_EQ(repaint_time_from_short_animation,
+ repaint_time_from_long_animation);
+}
- // Make sure the bounds match of the view that was animated match and the
- // layer is destroyed.
- EXPECT_EQ(target_bounds, child()->bounds());
- EXPECT_FALSE(child()->layer());
+// Verify that transform is not used when the animation target bounds have the
+// different size from the current bounds' even if transform is preferred.
+TEST_F(BoundsAnimatorTest, NoTransformForScalingAnimation) {
+ RecreateAnimator(/*use_transforms=*/true);
- // |child| shouldn't be animating anymore.
- EXPECT_FALSE(animator()->IsAnimating(child()));
+ const gfx::Rect initial_bounds(0, 0, 10, 10);
+ child()->SetBoundsRect(initial_bounds);
- // Schedule a longer animation. The number of repaints should be the same as
- // with the short animation.
- const base::TimeDelta long_duration = base::TimeDelta::FromMilliseconds(2000);
- const int repaint_count = child()->repaint_count();
- animator()->SetAnimationDuration(long_duration);
- child()->set_repaint_count(0);
- animator()->AnimateViewTo(child(), initial_bounds);
- animator()->SetAnimationDelegate(child(),
- std::make_unique<TestAnimationDelegate>());
- task_environment_.FastForwardBy(long_duration);
- base::RunLoop().Run();
- EXPECT_EQ(repaint_count, child()->repaint_count());
+ // Ensure that the target bounds have the different size with the initial
+ // bounds' to repaint bounds in each animation tick.
+ const gfx::Rect target_bounds_with_reize(gfx::Point(10, 10),
+ gfx::Size(20, 20));
+
+ const int repaint_time_from_short_animation =
+ GetRepaintTimeFromBoundsAnimation(target_bounds_with_reize,
+ /*use_long_duration=*/false);
+ const int repaint_time_from_long_animation =
+ GetRepaintTimeFromBoundsAnimation(initial_bounds,
+ /*use_long_duration=*/true);
+
+ // When creating bounds animation with repaint, the longer bounds animation
+ // should have more repaint counts.
+ EXPECT_GT(repaint_time_from_long_animation,
+ repaint_time_from_short_animation);
}
// Tests that the transforms option does not crash when a view's bounds start
@@ -324,9 +389,12 @@ TEST_F(BoundsAnimatorTest, UseTransformsAnimateViewToEmptySrc) {
TEST_F(BoundsAnimatorTest, UseTransformsCancelAnimation) {
RecreateAnimator(/*use_transforms=*/true);
- gfx::Rect initial_bounds(0, 0, 10, 10);
+ // Ensure that |initial_bounds| has the same size with |target_bounds| to
+ // create bounds animation via the transform.
+ const gfx::Rect initial_bounds(0, 0, 10, 10);
+ const gfx::Rect target_bounds(10, 10, 10, 10);
+
child()->SetBoundsRect(initial_bounds);
- gfx::Rect target_bounds(10, 10, 20, 20);
const base::TimeDelta duration = base::TimeDelta::FromMilliseconds(200);
animator()->SetAnimationDuration(duration);
@@ -340,7 +408,7 @@ TEST_F(BoundsAnimatorTest, UseTransformsCancelAnimation) {
// Stop halfway and cancel. The child should have its bounds updated to
// exactly halfway between |initial_bounds| and |target_bounds|.
- const gfx::Rect expected_bounds(5, 5, 15, 15);
+ const gfx::Rect expected_bounds(5, 5, 10, 10);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(100));
EXPECT_EQ(initial_bounds, child()->bounds());
animator()->Cancel();
diff --git a/chromium/ui/views/animation/compositor_animation_runner.cc b/chromium/ui/views/animation/compositor_animation_runner.cc
index 2282a84fb4a..f945808e419 100644
--- a/chromium/ui/views/animation/compositor_animation_runner.cc
+++ b/chromium/ui/views/animation/compositor_animation_runner.cc
@@ -23,6 +23,7 @@ CompositorAnimationRunner::~CompositorAnimationRunner() {
if (widget_)
OnWidgetDestroying(widget_);
DCHECK(!compositor_ || !compositor_->HasAnimationObserver(this));
+ CHECK(!IsInObserverList());
}
void CompositorAnimationRunner::SetAnimationMetricsReporter(
diff --git a/chromium/ui/views/animation/compositor_animation_runner.h b/chromium/ui/views/animation/compositor_animation_runner.h
index e2293996be6..462404ae38d 100644
--- a/chromium/ui/views/animation/compositor_animation_runner.h
+++ b/chromium/ui/views/animation/compositor_animation_runner.h
@@ -13,6 +13,7 @@
#include "ui/compositor/compositor_animation_observer.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/gfx/animation/animation_container.h"
+#include "ui/views/views_export.h"
#include "ui/views/widget/widget_observer.h"
namespace ui {
@@ -24,9 +25,10 @@ namespace views {
class Widget;
// An animation runner based on ui::Compositor.
-class CompositorAnimationRunner : public gfx::AnimationRunner,
- public ui::CompositorAnimationObserver,
- public WidgetObserver {
+class VIEWS_EXPORT CompositorAnimationRunner
+ : public gfx::AnimationRunner,
+ public ui::CompositorAnimationObserver,
+ public WidgetObserver {
public:
explicit CompositorAnimationRunner(Widget* widget);
CompositorAnimationRunner(CompositorAnimationRunner&) = delete;
diff --git a/chromium/ui/views/animation/installable_ink_drop_painter_unittest.cc b/chromium/ui/views/animation/installable_ink_drop_painter_unittest.cc
new file mode 100644
index 00000000000..ec14f77aa31
--- /dev/null
+++ b/chromium/ui/views/animation/installable_ink_drop_painter_unittest.cc
@@ -0,0 +1,87 @@
+// 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/animation/installable_ink_drop_painter.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/gfx/canvas.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/views/animation/installable_ink_drop_config.h"
+
+namespace views {
+
+namespace {
+
+class InstallableInkDropPainterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ config_.base_color = SK_ColorCYAN;
+ config_.ripple_opacity = 1.0f;
+ config_.highlight_opacity = 1.0f;
+
+ state_.flood_fill_center = gfx::PointF(5.0f, 5.0f);
+ state_.flood_fill_progress = 1.0f;
+ state_.highlighted_ratio = 0.0f;
+ }
+
+ InstallableInkDropConfig config_;
+ InstallableInkDropPainter::State state_;
+};
+
+} // namespace
+
+TEST_F(InstallableInkDropPainterTest, MinSize) {
+ InstallableInkDropPainter painter(&config_, &state_);
+ EXPECT_EQ(gfx::Size(), painter.GetMinimumSize());
+}
+
+TEST_F(InstallableInkDropPainterTest, Paint) {
+ InstallableInkDropPainter painter(&config_, &state_);
+
+ {
+ // No highlight, half filled.
+ state_.flood_fill_progress = 0.5f;
+ gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
+ SkBitmap bitmap = canvas.GetBitmap();
+ painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
+ EXPECT_EQ(SkColorSetA(SK_ColorBLACK, 0), bitmap.getColor(0, 0));
+ EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
+ }
+
+ {
+ // No highlight, fully filled.
+ state_.flood_fill_progress = 1.0f;
+ gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
+ SkBitmap bitmap = canvas.GetBitmap();
+ painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
+ EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(0, 0));
+ EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
+ }
+
+ {
+ // Use highlight, half filled.
+ state_.flood_fill_progress = 0.5f;
+ state_.highlighted_ratio = 0.5f;
+ gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
+ SkBitmap bitmap = canvas.GetBitmap();
+ painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
+ EXPECT_EQ(0x7F007F7Fu, bitmap.getColor(1, 1));
+ EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
+ }
+
+ {
+ // Change highlight opacity.
+ state_.flood_fill_progress = 0.5f;
+ state_.highlighted_ratio = 0.1f;
+ gfx::Canvas canvas(gfx::Size(10, 10), 1.0f, true);
+ SkBitmap bitmap = canvas.GetBitmap();
+ painter.Paint(&canvas, gfx::Size(5.0f, 5.0f));
+ EXPECT_EQ(0x19001919u, bitmap.getColor(1, 1));
+ EXPECT_EQ(SK_ColorCYAN, bitmap.getColor(4, 4));
+ }
+}
+
+} // namespace views
diff --git a/chromium/ui/views/animation/installable_ink_drop_unittest.cc b/chromium/ui/views/animation/installable_ink_drop_unittest.cc
index a451970729c..e1f0216e7d0 100644
--- a/chromium/ui/views/animation/installable_ink_drop_unittest.cc
+++ b/chromium/ui/views/animation/installable_ink_drop_unittest.cc
@@ -5,31 +5,36 @@
#include "ui/views/animation/installable_ink_drop.h"
#include <memory>
+#include <utility>
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/geometry/rect.h"
+#include "ui/views/animation/ink_drop_host_view.h"
#include "ui/views/animation/ink_drop_state.h"
+#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
namespace views {
-class InstallableInkDropTest : public ::testing::Test {
+class InstallableInkDropTest : public ViewsTestBase {
protected:
void SetUp() override {
+ ViewsTestBase::SetUp();
+
+ root_view_ = std::make_unique<View>();
// Ink drop layers get installed as siblings to their host view's
// layer. Hence, there needs to be a root view with a layer above them.
- root_view_.SetPaintToLayer();
+ root_view_->SetPaintToLayer();
}
- View* root_view() { return &root_view_; }
+ View* root_view() { return root_view_.get(); }
+ std::unique_ptr<View> own_root_view() { return std::move(root_view_); }
private:
- base::test::TaskEnvironment task_environment_;
-
- View root_view_;
+ std::unique_ptr<View> root_view_;
};
TEST_F(InstallableInkDropTest, LayerIsAddedAndRemoved) {
@@ -68,6 +73,58 @@ TEST_F(InstallableInkDropTest, UpdatesState) {
ink_drop.AnimateToState(InkDropState::ACTIVATED);
EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::ACTIVATED);
+
+ ink_drop.SnapToHidden();
+ EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::HIDDEN);
+
+ ink_drop.SnapToActivated();
+ EXPECT_EQ(ink_drop.GetTargetInkDropState(), InkDropState::ACTIVATED);
+}
+
+TEST_F(InstallableInkDropTest, HighlightStates) {
+ View* view = root_view()->AddChildView(std::make_unique<View>());
+ InstallableInkDrop ink_drop(view);
+
+ // Initial state should be false.
+ EXPECT_FALSE(ink_drop.IsHighlightFadingInOrVisible());
+
+ ink_drop.SetFocused(true);
+ EXPECT_TRUE(ink_drop.IsHighlightFadingInOrVisible());
+
+ ink_drop.SetFocused(false);
+ EXPECT_FALSE(ink_drop.IsHighlightFadingInOrVisible());
+
+ ink_drop.SetHovered(true);
+ EXPECT_TRUE(ink_drop.IsHighlightFadingInOrVisible());
+
+ ink_drop.SetHovered(false);
+ EXPECT_FALSE(ink_drop.IsHighlightFadingInOrVisible());
+}
+
+TEST_F(InstallableInkDropTest, InstallOnHostView) {
+ InkDropHostView host_view;
+ InstallableInkDrop ink_drop(&host_view);
+
+ EXPECT_TRUE(ink_drop.SupportsGestureEvents());
+}
+
+TEST_F(InstallableInkDropTest, Paint) {
+ std::unique_ptr<Widget> widget = CreateTestWidget();
+ View* root_view = widget->SetContentsView(own_root_view());
+ View* view = root_view->AddChildView(std::make_unique<View>());
+ InstallableInkDrop ink_drop(view);
+
+ views::InstallableInkDropConfig config;
+ config.base_color = SK_ColorCYAN;
+ config.ripple_opacity = 0.05;
+ config.highlight_opacity = 0.07;
+ ink_drop.SetConfig(config);
+ ink_drop.AnimateToState(InkDropState::ACTIVATED);
+
+ auto list = base::MakeRefCounted<cc::DisplayItemList>();
+ ink_drop.OnPaintLayer(
+ ui::PaintContext(list.get(), 1.f, view->bounds(), false));
+ EXPECT_GT(2u, list->num_paint_ops());
}
} // namespace views
diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc
index be71a1d4243..e082f15fcbc 100644
--- a/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -37,10 +37,20 @@
namespace views {
// static
-bool BubbleDialogDelegateView::devtools_dismiss_override_ = false;
+bool BubbleDialogDelegate::devtools_dismiss_override_ = false;
namespace {
+// A BubbleFrameView will apply a masking path to its ClientView to ensure
+// contents are appropriately clipped to the frame's rounded corners. If the
+// bubble uses layers in its views hierarchy, these will not be clipped to
+// the client mask unless the ClientView is backed by a textured ui::Layer.
+// This flag tracks whether or not to to create a layer backed ClientView.
+//
+// TODO(tluk): Fix all cases where bubble transparency is used and have bubble
+// ClientViews always paint to a layer.
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kPaintClientToLayer, true)
+
// Override base functionality of Widget to give bubble dialogs access to the
// theme provider of the window they're anchored to.
class BubbleWidget : public Widget {
@@ -85,7 +95,7 @@ bool CustomShadowsSupported() {
}
// Create a widget to host the bubble.
-Widget* CreateBubbleWidget(BubbleDialogDelegateView* bubble) {
+Widget* CreateBubbleWidget(BubbleDialogDelegate* bubble) {
Widget* bubble_widget = new BubbleWidget();
Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE);
bubble_params.delegate = bubble;
@@ -124,9 +134,9 @@ Widget* CreateBubbleWidget(BubbleDialogDelegateView* bubble) {
} // namespace
-class BubbleDialogDelegateView::AnchorViewObserver : public ViewObserver {
+class BubbleDialogDelegate::AnchorViewObserver : public ViewObserver {
public:
- AnchorViewObserver(BubbleDialogDelegateView* parent, View* anchor_view)
+ AnchorViewObserver(BubbleDialogDelegate* parent, View* anchor_view)
: parent_(parent), anchor_view_(anchor_view) {
anchor_view_->AddObserver(this);
}
@@ -158,13 +168,116 @@ class BubbleDialogDelegateView::AnchorViewObserver : public ViewObserver {
// view bounds when the anchor is visible.
private:
- BubbleDialogDelegateView* const parent_;
+ BubbleDialogDelegate* const parent_;
View* const anchor_view_;
};
+// This class is responsible for observing events on a BubbleDialogDelegate's
+// anchor widget and notifying the BubbleDialogDelegate of them.
+class BubbleDialogDelegate::AnchorWidgetObserver : public WidgetObserver {
+ public:
+ AnchorWidgetObserver(BubbleDialogDelegate* owner, Widget* widget)
+ : owner_(owner) {
+ observer_.Add(widget);
+ }
+ ~AnchorWidgetObserver() override = default;
+
+ void OnWidgetDestroying(Widget* widget) override {
+ observer_.Remove(widget);
+ owner_->OnAnchorWidgetDestroying();
+ // |this| may be destroyed here!
+ }
+
+ void OnWidgetActivationChanged(Widget* widget, bool active) override {
+ owner_->OnWidgetActivationChanged(widget, active);
+ }
+
+ void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect&) override {
+ owner_->OnAnchorBoundsChanged();
+ }
+
+ private:
+ BubbleDialogDelegate* owner_;
+ ScopedObserver<views::Widget, views::WidgetObserver> observer_{this};
+};
+
+// This class is responsible for observing events on a BubbleDialogDelegate's
+// widget and notifying the BubbleDialogDelegate of them.
+class BubbleDialogDelegate::BubbleWidgetObserver : public WidgetObserver {
+ public:
+ BubbleWidgetObserver(BubbleDialogDelegate* owner, Widget* widget)
+ : owner_(owner) {
+ observer_.Add(widget);
+ }
+ ~BubbleWidgetObserver() override = default;
+
+ void OnWidgetClosing(Widget* widget) override {
+ owner_->OnBubbleWidgetClosing();
+ owner_->OnWidgetClosing(widget);
+ }
+
+ void OnWidgetDestroying(Widget* widget) override {
+ observer_.Remove(widget);
+ owner_->OnWidgetDestroying(widget);
+ }
+
+ void OnWidgetDestroyed(Widget* widget) override {
+ owner_->OnWidgetDestroyed(widget);
+ }
+
+ void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect& bounds) override {
+ owner_->OnWidgetBoundsChanged(widget, bounds);
+ }
+
+ void OnWidgetVisibilityChanging(Widget* widget, bool visible) override {
+#if defined(OS_WIN)
+ // On Windows we need to handle this before the bubble is visible or hidden.
+ // Please see the comment on the OnWidgetVisibilityChanging function. On
+ // other platforms it is fine to handle it after the bubble is shown/hidden.
+ owner_->OnBubbleWidgetVisibilityChanged(visible);
+#endif
+ }
+
+ void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
+#if !defined(OS_WIN)
+ owner_->OnBubbleWidgetVisibilityChanged(visible);
+#endif
+ owner_->OnWidgetVisibilityChanged(widget, visible);
+ }
+
+ void OnWidgetActivationChanged(Widget* widget, bool active) override {
+ owner_->OnBubbleWidgetActivationChanged(active);
+ owner_->OnWidgetActivationChanged(widget, active);
+ }
+
+ void OnWidgetPaintAsActiveChanged(Widget* widget, bool as_active) override {
+ owner_->OnBubbleWidgetPaintAsActiveChanged(as_active);
+ }
+
+ private:
+ BubbleDialogDelegate* owner_;
+ ScopedObserver<views::Widget, views::WidgetObserver> observer_{this};
+};
+
+BubbleDialogDelegate::BubbleDialogDelegate() = default;
+BubbleDialogDelegate::BubbleDialogDelegate(View* anchor_view,
+ BubbleBorder::Arrow arrow,
+ BubbleBorder::Shadow shadow)
+ : arrow_(arrow), shadow_(shadow) {}
+BubbleDialogDelegate::~BubbleDialogDelegate() = default;
+
// static
-Widget* BubbleDialogDelegateView::CreateBubble(
- BubbleDialogDelegateView* bubble_delegate) {
+Widget* BubbleDialogDelegate::CreateBubble(
+ BubbleDialogDelegate* bubble_delegate) {
+ // On Mac, MODAL_TYPE_WINDOW is implemented using sheets, which can't be
+ // anchored at a specific point - they are always placed near the top center
+ // of the window. To avoid unpleasant surprises, disallow setting an anchor
+ // view or rectangle on these types of bubbles.
+ if (bubble_delegate->GetModalType() == ui::MODAL_TYPE_WINDOW) {
+ DCHECK(!bubble_delegate->GetAnchorView());
+ DCHECK_EQ(bubble_delegate->GetAnchorRect(), gfx::Rect());
+ }
+
bubble_delegate->Init();
// Get the latest anchor widget from the anchor view at bubble creation time.
bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView());
@@ -177,17 +290,23 @@ Widget* BubbleDialogDelegateView::CreateBubble(
#endif
bubble_delegate->SizeToContents();
- bubble_delegate->widget_observer_.Add(bubble_widget);
+ bubble_delegate->bubble_widget_observer_ =
+ std::make_unique<BubbleWidgetObserver>(bubble_delegate, bubble_widget);
return bubble_widget;
}
+Widget* BubbleDialogDelegateView::CreateBubble(BubbleDialogDelegateView* view) {
+ return BubbleDialogDelegate::CreateBubble(view);
+}
+
BubbleDialogDelegateView::BubbleDialogDelegateView()
: BubbleDialogDelegateView(nullptr, BubbleBorder::TOP_LEFT) {}
BubbleDialogDelegateView::BubbleDialogDelegateView(View* anchor_view,
BubbleBorder::Arrow arrow,
BubbleBorder::Shadow shadow)
- : shadow_(shadow) {
+ : BubbleDialogDelegate(anchor_view, arrow, shadow) {
+ set_owned_by_client();
WidgetDelegate::SetShowCloseButton(false);
SetArrow(arrow);
@@ -195,7 +314,7 @@ BubbleDialogDelegateView::BubbleDialogDelegateView(View* anchor_view,
// An individual bubble should override these margins if its layout differs
// from the typical title/text/buttons.
set_margins(provider->GetDialogInsetsForContentType(TEXT, TEXT));
- title_margins_ = provider->GetInsetsMetric(INSETS_DIALOG_TITLE);
+ set_title_margins(provider->GetInsetsMetric(INSETS_DIALOG_TITLE));
if (anchor_view)
SetAnchorView(anchor_view);
UpdateColorsFromTheme();
@@ -207,11 +326,11 @@ BubbleDialogDelegateView::~BubbleDialogDelegateView() {
SetAnchorView(nullptr);
}
-BubbleDialogDelegateView* BubbleDialogDelegateView::AsBubbleDialogDelegate() {
+BubbleDialogDelegate* BubbleDialogDelegate::AsBubbleDialogDelegate() {
return this;
}
-NonClientFrameView* BubbleDialogDelegateView::CreateNonClientFrameView(
+NonClientFrameView* BubbleDialogDelegate::CreateNonClientFrameView(
Widget* widget) {
BubbleFrameView* frame = new BubbleDialogFrameView(title_margins_);
LayoutProvider* provider = LayoutProvider::Get();
@@ -233,6 +352,20 @@ NonClientFrameView* BubbleDialogDelegateView::CreateNonClientFrameView(
return frame;
}
+ClientView* BubbleDialogDelegate::CreateClientView(Widget* widget) {
+ client_view_ = DialogDelegate::CreateClientView(widget);
+ // In order for the |client_view|'s content view hierarchy to respect its clip
+ // mask we must paint to a layer. This is necessary because layers do not
+ // respect the clip of a non-layer backed parent.
+ if (base::FeatureList::IsEnabled(
+ features::kEnableMDRoundedCornersOnDialogs) &&
+ GetProperty(kPaintClientToLayer)) {
+ client_view_->SetPaintToLayer();
+ }
+
+ return client_view_;
+}
+
bool BubbleDialogDelegateView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
if (accelerator.key_code() == ui::VKEY_DOWN ||
@@ -241,79 +374,71 @@ bool BubbleDialogDelegateView::AcceleratorPressed(
GetFocusManager()->AdvanceFocus(accelerator.key_code() != ui::VKEY_DOWN);
return true;
}
- return DialogDelegateView::AcceleratorPressed(accelerator);
+ return View::AcceleratorPressed(accelerator);
+}
+
+Widget* BubbleDialogDelegateView::GetWidget() {
+ return View::GetWidget();
+}
+
+const Widget* BubbleDialogDelegateView::GetWidget() const {
+ return View::GetWidget();
+}
+
+void BubbleDialogDelegateView::AddedToWidget() {
+ if (ui::IsAlert(GetAccessibleWindowRole())) {
+ GetWidget()->GetRootView()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kAlert, true);
+ }
+}
+
+View* BubbleDialogDelegateView::GetContentsView() {
+ return this;
+}
+
+void BubbleDialogDelegateView::DeleteDelegate() {
+ delete this;
}
-void BubbleDialogDelegateView::OnWidgetClosing(Widget* widget) {
+void BubbleDialogDelegate::OnBubbleWidgetClosing() {
// To prevent keyboard focus traversal issues, the anchor view's
// kAnchoredDialogKey property is cleared immediately upon Close(). This
// avoids a bug that occured when a focused anchor view is made unfocusable
// right after the bubble is closed. Previously, focus would advance into the
// bubble then would be lost when the bubble was destroyed.
- if (widget == GetWidget() && GetAnchorView())
+ if (GetAnchorView())
GetAnchorView()->ClearProperty(kAnchoredDialogKey);
}
-void BubbleDialogDelegateView::OnWidgetDestroying(Widget* widget) {
- if (anchor_widget() == widget)
- SetAnchorView(nullptr);
-
- if (widget_observer_.IsObserving(widget))
- widget_observer_.Remove(widget);
-}
-
-void BubbleDialogDelegateView::OnWidgetVisibilityChanging(Widget* widget,
- bool visible) {
-#if defined(OS_WIN)
- // On Windows we need to handle this before the bubble is visible or hidden.
- // Please see the comment on the OnWidgetVisibilityChanging function. On
- // other platforms it is fine to handle it after the bubble is shown/hidden.
- HandleVisibilityChanged(widget, visible);
-#endif
-}
-
-void BubbleDialogDelegateView::OnWidgetVisibilityChanged(Widget* widget,
- bool visible) {
-#if !defined(OS_WIN)
- HandleVisibilityChanged(widget, visible);
-#endif
+void BubbleDialogDelegate::OnAnchorWidgetDestroying() {
+ SetAnchorView(nullptr);
}
-void BubbleDialogDelegateView::OnWidgetActivationChanged(Widget* widget,
- bool active) {
+void BubbleDialogDelegate::OnBubbleWidgetActivationChanged(bool active) {
if (devtools_dismiss_override_)
return;
#if defined(OS_MACOSX)
// Install |mac_bubble_closer_| the first time the widget becomes active.
- if (widget == GetWidget() && active && !mac_bubble_closer_) {
+ if (active && !mac_bubble_closer_) {
mac_bubble_closer_ = std::make_unique<ui::BubbleCloser>(
GetWidget()->GetNativeWindow().GetNativeNSWindow(),
- base::BindRepeating(&BubbleDialogDelegateView::OnDeactivate,
+ base::BindRepeating(&BubbleDialogDelegate::OnDeactivate,
base::Unretained(this)));
}
#endif
- if (widget == GetWidget() && !active)
+
+ if (!active)
OnDeactivate();
}
-void BubbleDialogDelegateView::OnWidgetBoundsChanged(
- Widget* widget,
- const gfx::Rect& new_bounds) {
- if (GetBubbleFrameView() && anchor_widget() == widget)
+void BubbleDialogDelegate::OnAnchorWidgetBoundsChanged() {
+ if (GetBubbleFrameView())
SizeToContents();
}
-void BubbleDialogDelegateView::OnWidgetPaintAsActiveChanged(
- Widget* widget,
- bool paint_as_active) {
- // We only care about the current widget having its state changed; if the
- // anchor widget receives active status directly then there's no need to apply
- // paint as active lock.
- if (widget != GetWidget())
- return;
-
- if (!paint_as_active) {
+void BubbleDialogDelegate::OnBubbleWidgetPaintAsActiveChanged(bool as_active) {
+ if (!as_active) {
paint_as_active_lock_.reset();
return;
}
@@ -327,20 +452,19 @@ void BubbleDialogDelegateView::OnWidgetPaintAsActiveChanged(
anchor_widget()->GetTopLevelWidget()->LockPaintAsActive();
}
-BubbleBorder::Shadow BubbleDialogDelegateView::GetShadow() const {
+BubbleBorder::Shadow BubbleDialogDelegate::GetShadow() const {
if (CustomShadowsSupported() || shadow_ == BubbleBorder::NO_ASSETS)
return shadow_;
return BubbleBorder::NO_SHADOW;
}
-View* BubbleDialogDelegateView::GetAnchorView() const {
+View* BubbleDialogDelegate::GetAnchorView() const {
if (!anchor_view_observer_)
return nullptr;
return anchor_view_observer_->anchor_view();
}
-void BubbleDialogDelegateView::SetHighlightedButton(
- Button* highlighted_button) {
+void BubbleDialogDelegate::SetHighlightedButton(Button* highlighted_button) {
bool visible = GetWidget() && GetWidget()->IsVisible();
// If the Widget is visible, ensure the old highlight (if any) is removed
// when the highlighted view changes.
@@ -351,7 +475,7 @@ void BubbleDialogDelegateView::SetHighlightedButton(
UpdateHighlightedButton(true);
}
-void BubbleDialogDelegateView::SetArrow(BubbleBorder::Arrow arrow) {
+void BubbleDialogDelegate::SetArrow(BubbleBorder::Arrow arrow) {
SetArrowWithoutResizing(arrow);
// If SetArrow() is called before CreateWidget(), there's no need to update
// the BubbleFrameView.
@@ -359,8 +483,7 @@ void BubbleDialogDelegateView::SetArrow(BubbleBorder::Arrow arrow) {
SizeToContents();
}
-void BubbleDialogDelegateView::SetArrowWithoutResizing(
- BubbleBorder::Arrow arrow) {
+void BubbleDialogDelegate::SetArrowWithoutResizing(BubbleBorder::Arrow arrow) {
if (base::i18n::IsRTL())
arrow = BubbleBorder::horizontal_mirror(arrow);
if (arrow_ == arrow)
@@ -373,7 +496,7 @@ void BubbleDialogDelegateView::SetArrowWithoutResizing(
GetBubbleFrameView()->SetArrow(arrow);
}
-gfx::Rect BubbleDialogDelegateView::GetAnchorRect() const {
+gfx::Rect BubbleDialogDelegate::GetAnchorRect() const {
// TODO(tluk) eliminate the need for GetAnchorRect() to return an empty rect
// if neither an |anchor_rect_| or an anchor view have been set.
if (!GetAnchorView())
@@ -384,19 +507,20 @@ gfx::Rect BubbleDialogDelegateView::GetAnchorRect() const {
return anchor_rect_.value();
}
-void BubbleDialogDelegateView::OnBeforeBubbleWidgetInit(
- Widget::InitParams* params,
- Widget* widget) const {}
-
-ui::LayerType BubbleDialogDelegateView::GetLayerType() const {
+ui::LayerType BubbleDialogDelegate::GetLayerType() const {
return ui::LAYER_TEXTURED;
}
-void BubbleDialogDelegateView::UseCompactMargins() {
+void BubbleDialogDelegate::SetPaintClientToLayer(bool paint_client_to_layer) {
+ DCHECK(!client_view_);
+ SetProperty(kPaintClientToLayer, paint_client_to_layer);
+}
+
+void BubbleDialogDelegate::UseCompactMargins() {
set_margins(gfx::Insets(6));
}
-void BubbleDialogDelegateView::OnAnchorBoundsChanged() {
+void BubbleDialogDelegate::OnAnchorBoundsChanged() {
if (!GetWidget())
return;
// TODO(pbos): Reconsider whether to update the anchor when the view isn't
@@ -404,7 +528,7 @@ void BubbleDialogDelegateView::OnAnchorBoundsChanged() {
SizeToContents();
}
-gfx::Rect BubbleDialogDelegateView::GetBubbleBounds() {
+gfx::Rect BubbleDialogDelegate::GetBubbleBounds() {
// The argument rect has its origin at the bubble's arrow anchor point;
// its size is the preferred size of the bubble's client view (this view).
bool anchor_minimized = anchor_widget() && anchor_widget()->IsMinimized();
@@ -417,7 +541,7 @@ gfx::Rect BubbleDialogDelegateView::GetBubbleBounds() {
adjust_if_offscreen_ && !anchor_minimized && has_anchor);
}
-ax::mojom::Role BubbleDialogDelegateView::GetAccessibleWindowRole() {
+ax::mojom::Role BubbleDialogDelegate::GetAccessibleWindowRole() {
// If something in the dialog has initial focus, use the dialog role.
// Screen readers understand what to announce when focus moves within one.
if (GetInitiallyFocusedView())
@@ -441,13 +565,19 @@ gfx::Size BubbleDialogDelegateView::GetMaximumSize() const {
}
void BubbleDialogDelegateView::OnThemeChanged() {
- DialogDelegateView::OnThemeChanged();
+ View::OnThemeChanged();
UpdateColorsFromTheme();
}
void BubbleDialogDelegateView::Init() {}
-void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) {
+void BubbleDialogDelegate::SetAnchorView(View* anchor_view) {
+ if (anchor_view && anchor_view->GetWidget()) {
+ anchor_widget_observer_ =
+ std::make_unique<AnchorWidgetObserver>(this, anchor_view->GetWidget());
+ } else {
+ anchor_widget_observer_.reset();
+ }
if (GetAnchorView()) {
GetAnchorView()->ClearProperty(kAnchoredDialogKey);
anchor_view_observer_.reset();
@@ -460,13 +590,11 @@ void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) {
if (GetWidget() && GetWidget()->IsVisible())
UpdateHighlightedButton(false);
paint_as_active_lock_.reset();
- anchor_widget_->RemoveObserver(this);
anchor_widget_ = nullptr;
}
if (anchor_view) {
anchor_widget_ = anchor_view->GetWidget();
if (anchor_widget_) {
- anchor_widget_->AddObserver(this);
const bool visible = GetWidget() && GetWidget()->IsVisible();
UpdateHighlightedButton(visible);
// Have the anchor widget's paint-as-active state track this view's
@@ -498,13 +626,13 @@ void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) {
}
}
-void BubbleDialogDelegateView::SetAnchorRect(const gfx::Rect& rect) {
+void BubbleDialogDelegate::SetAnchorRect(const gfx::Rect& rect) {
anchor_rect_ = rect;
if (GetWidget())
OnAnchorBoundsChanged();
}
-void BubbleDialogDelegateView::SizeToContents() {
+void BubbleDialogDelegate::SizeToContents() {
gfx::Rect bubble_bounds = GetBubbleBounds();
#if defined(OS_MACOSX)
// GetBubbleBounds() doesn't take the Mac NativeWindow's style mask into
@@ -518,9 +646,10 @@ void BubbleDialogDelegateView::SizeToContents() {
}
void BubbleDialogDelegateView::UpdateColorsFromTheme() {
- if (!color_explicitly_set_)
- color_ = GetNativeTheme()->GetSystemColor(
- ui::NativeTheme::kColorId_BubbleBackground);
+ if (!color_explicitly_set()) {
+ set_color_internal(GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_BubbleBackground));
+ }
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view)
frame_view->SetBackgroundColor(color());
@@ -538,29 +667,27 @@ void BubbleDialogDelegateView::EnableUpDownKeyboardAccelerators() {
AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE));
}
-void BubbleDialogDelegateView::HandleVisibilityChanged(Widget* widget,
- bool visible) {
- if (widget == GetWidget())
- UpdateHighlightedButton(visible);
+void BubbleDialogDelegate::OnBubbleWidgetVisibilityChanged(bool visible) {
+ UpdateHighlightedButton(visible);
// Fire ax::mojom::Event::kAlert for bubbles marked as
// ax::mojom::Role::kAlertDialog; this instructs accessibility tools to read
// the bubble in its entirety rather than just its title and initially focused
// view. See http://crbug.com/474622 for details.
- if (widget == GetWidget() && visible) {
+ if (visible) {
if (ui::IsAlert(GetAccessibleWindowRole())) {
- widget->GetRootView()->NotifyAccessibilityEvent(ax::mojom::Event::kAlert,
- true);
+ GetWidget()->GetRootView()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kAlert, true);
}
}
}
-void BubbleDialogDelegateView::OnDeactivate() {
- if (close_on_deactivate() && GetWidget())
+void BubbleDialogDelegate::OnDeactivate() {
+ if (close_on_deactivate_ && GetWidget())
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
}
-void BubbleDialogDelegateView::UpdateHighlightedButton(bool highlighted) {
+void BubbleDialogDelegate::UpdateHighlightedButton(bool highlighted) {
Button* button = Button::AsButton(highlighted_button_tracker_.view());
button = button ? button : Button::AsButton(GetAnchorView());
if (button && highlight_button_when_shown_)
diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_view.h b/chromium/ui/views/bubble/bubble_dialog_delegate_view.h
index c56ab1fa993..4653b6a184e 100644
--- a/chromium/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -13,6 +13,7 @@
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/class_property.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/view_tracker.h"
#include "ui/views/widget/widget.h"
@@ -39,239 +40,315 @@ namespace views {
class Button;
-// BubbleDialogDelegateView is a special DialogDelegateView for bubbles.
-class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView,
- public WidgetObserver {
+class VIEWS_EXPORT BubbleDialogDelegate : public DialogDelegate,
+ public ui::PropertyHandler {
public:
- METADATA_HEADER(BubbleDialogDelegateView);
-
enum class CloseReason {
DEACTIVATION,
CLOSE_BUTTON,
UNKNOWN,
};
- // Create and initialize the bubble Widget(s) with proper bounds.
- static Widget* CreateBubble(BubbleDialogDelegateView* bubble_delegate);
-
- BubbleDialogDelegateView();
- // |shadow| usually doesn't need to be explicitly set, just uses the default
- // argument. Unless on Mac when the bubble needs to use Views base shadow,
- // override it with suitable bubble border type.
- BubbleDialogDelegateView(
- View* anchor_view,
- BubbleBorder::Arrow arrow,
- BubbleBorder::Shadow shadow = BubbleBorder::DIALOG_SHADOW);
-
- ~BubbleDialogDelegateView() override;
+ BubbleDialogDelegate();
+ BubbleDialogDelegate(View* anchor_view,
+ BubbleBorder::Arrow arrow,
+ BubbleBorder::Shadow shadow);
+ BubbleDialogDelegate(const BubbleDialogDelegate& other) = delete;
+ BubbleDialogDelegate& operator=(const BubbleDialogDelegate& other) = delete;
+ ~BubbleDialogDelegate() override;
- // DialogDelegateView:
- BubbleDialogDelegateView* AsBubbleDialogDelegate() override;
+ // DialogDelegate:
+ BubbleDialogDelegate* AsBubbleDialogDelegate() override;
NonClientFrameView* CreateNonClientFrameView(Widget* widget) override;
- bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
-
- // WidgetObserver:
- void OnWidgetClosing(Widget* widget) override;
- void OnWidgetDestroying(Widget* widget) override;
- void OnWidgetVisibilityChanging(Widget* widget, bool visible) override;
- void OnWidgetVisibilityChanged(Widget* widget, bool visible) override;
- void OnWidgetActivationChanged(Widget* widget, bool active) override;
- void OnWidgetBoundsChanged(Widget* widget,
- const gfx::Rect& new_bounds) override;
- void OnWidgetPaintAsActiveChanged(Widget* widget,
- bool paint_as_active) override;
+ ClientView* CreateClientView(Widget* widget) override;
+ ax::mojom::Role GetAccessibleWindowRole() override;
- bool close_on_deactivate() const { return close_on_deactivate_; }
- void set_close_on_deactivate(bool close) { close_on_deactivate_ = close; }
+ //////////////////////////////////////////////////////////////////////////////
+ // The anchor view and rectangle:
+ //
+ // The anchor view takes priority over the anchor rectangle.
+ // If the anchor moves, BubbleDialogDelegate will move its Widget to maintain
+ // the same position relative to its anchor. If an anchor view is used this
+ // happens automatically; if an anchor rect is used, the new anchor rect needs
+ // to be supplied via SetAnchorRect().
- void SetAnchorView(View* anchor_view);
+ void SetAnchorView(View* view);
View* GetAnchorView() const;
- Widget* anchor_widget() const { return anchor_widget_; }
-
- void SetHighlightedButton(Button* highlighted_button);
- // The anchor rect is used in the absence of an assigned anchor view.
+ // GetAnchorRect() takes into account the presence of an anchor view, while
+ // anchor_rect() always returns the configured anchor rect, regardless of
+ // whether there is also an anchor view. While it is possible to override
+ // GetAnchorRect(), you should not need to do so; if you do, you must remember
+ // to call OnAnchorBoundsChanged() when the return value of GetAnchorRect()
+ // changes.
+ //
+ // TODO(ellyjones): Remove overrides of GetAnchorRect() and make this not
+ // virtual.
+ virtual gfx::Rect GetAnchorRect() const;
const base::Optional<gfx::Rect>& anchor_rect() const { return anchor_rect_; }
+ void SetAnchorRect(const gfx::Rect& rect);
+
+ // The anchor view insets are applied to the anchor view's bounds. This is
+ // used to align the bubble properly with the visual center of the anchor View
+ // when the anchor View's visual center is not the same as the center of its
+ // bounding box.
+ // TODO(https://crbug.com/869928): Remove this concept in favor of
+ // View::GetAnchorBoundsInScreen().
+ const gfx::Insets& anchor_view_insets() const { return anchor_view_insets_; }
+ void set_anchor_view_insets(const gfx::Insets& i) { anchor_view_insets_ = i; }
- // Set the desired arrow for the bubble and updates the bubble's bounds
- // accordingly. The arrow will be mirrored for RTL.
+ //////////////////////////////////////////////////////////////////////////////
+ // The anchor widget:
+ //
+ // The bubble will close when the anchor widget closes. Also, when the anchor
+ // widget moves, the bubble will recompute its location from its anchor view.
+ // The bubble will also cause its anchor widget to paint as active when the
+ // bubble is active, and will optionally resize itself to fit within the
+ // anchor widget if the anchor widget's size changes.
+ //
+ // The anchor widget is implied by the anchor view - bubbles with no anchor
+ // view cannot be anchored to a widget.
+
+ Widget* anchor_widget() { return anchor_widget_; }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // The arrow:
+ //
+ // Each bubble has an "arrow", which describes the relationship between the
+ // bubble's position and the position of its anchor view. The arrow also
+ // supplies the - anchor offset eg, a top-left arrow puts the bubble below and
+ // to the right of the anchor view, and so on. The "arrow" name is a holdover
+ // from an earlier time when the arrow was an actual visual marker on the
+ // bubble's border as well, but these days the arrow has no visual presence.
+ //
+ // The arrow is automatically flipped in RTL locales, and by default is
+ // manually adjusted if necessary to fit the bubble on screen.
+
+ // Sets the desired arrow for the bubble and updates the bubble's bounds.
void SetArrow(BubbleBorder::Arrow arrow);
+ BubbleBorder::Arrow arrow() const { return arrow_; }
- // Sets the arrow without recaluclating or updating bounds. This could be used
- // proceeding another function call which also sets bounds, so that bounds are
+ // Sets the arrow without recalculating or updating bounds. This could be used
+ // before another function call which also sets bounds, so that bounds are
// not set multiple times in a row. When animating bounds changes, setting
// bounds twice in a row can make the widget position jump.
// TODO(crbug.com/982880) It would be good to be able to re-target the
- // animation rather than expet callers to use SetArrowWithoutResizing if they
+ // animation rather than expect callers to use SetArrowWithoutResizing if they
// are also changing the anchor rect, or similar.
void SetArrowWithoutResizing(BubbleBorder::Arrow arrow);
+ // Whether the arrow will be automatically adjusted if needed to fit the
+ // bubble on screen. Has no effect if the bubble has no arrow.
+ bool adjust_if_offscreen() const { return adjust_if_offscreen_; }
+ void set_adjust_if_offscreen(bool adjust) { adjust_if_offscreen_ = adjust; }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Shadows:
+ //
+ // Bubbles may optionally have a shadow. Only some platforms support drawing
+ // custom shadows on a bubble.
+
BubbleBorder::Shadow GetShadow() const;
void set_shadow(BubbleBorder::Shadow shadow) { shadow_ = shadow; }
- SkColor color() const { return color_; }
- void set_color(SkColor color) {
- color_ = color;
- color_explicitly_set_ = true;
- }
+ // Call this method to inform BubbleDialogDelegate that the return value of
+ // GetAnchorRect() has changed. You only need to do this if you have
+ // overridden GetAnchorRect() - if you are using an anchor view or anchor rect
+ // normally, do not call this.
+ void OnAnchorBoundsChanged();
- void set_title_margins(const gfx::Insets& title_margins) {
- title_margins_ = title_margins;
- }
+ //////////////////////////////////////////////////////////////////////////////
+ // Miscellaneous bubble behaviors:
+ //
- // TODO(pbos): Remove by overriding Views::GetAnchorBoundsInScreen() instead.
- // See https://crbug.com/869928.
- const gfx::Insets& anchor_view_insets() const { return anchor_view_insets_; }
- void set_anchor_view_insets(const gfx::Insets& i) { anchor_view_insets_ = i; }
+ // Whether the bubble closes when it ceases to be the active window.
+ bool close_on_deactivate() const { return close_on_deactivate_; }
+ void set_close_on_deactivate(bool close) { close_on_deactivate_ = close; }
+
+ // Explicitly set the button to automatically highlight when the bubble is
+ // shown. By default the anchor is highlighted, if it is a button.
+ //
+ // TODO(ellyjones): Is there ever a situation where this is the right thing to
+ // do UX-wise? It seems very odd to highlight something other than the anchor
+ // view.
+ void SetHighlightedButton(Button* highlighted_button);
+ // The bubble's parent window - this can only be usefully set before creating
+ // the bubble's widget. If there is one, the bubble will be stacked above it,
+ // and it will become the Views parent window for the bubble.
+ //
+ // TODO(ellyjones):
+ // - When does one actually need to call this?
+ // - Why is it separate from the anchor widget?
+ // - Why do most bubbles seem to work fine without this?
gfx::NativeView parent_window() const { return parent_window_; }
void set_parent_window(gfx::NativeView window) { parent_window_ = window; }
+ // Whether the bubble accepts mouse events or not.
bool accept_events() const { return accept_events_; }
void set_accept_events(bool accept_events) { accept_events_ = accept_events; }
- bool adjust_if_offscreen() const { return adjust_if_offscreen_; }
- void set_adjust_if_offscreen(bool adjust) { adjust_if_offscreen_ = adjust; }
-
+ // Whether focus can traverse from the anchor view into the bubble. Only
+ // meaningful if there is an anchor view.
void set_focus_traversable_from_anchor_view(bool focusable) {
focus_traversable_from_anchor_view_ = focusable;
}
+ // If this is true and either:
+ // - The anchor View is a Button, or
+ // - The highlighted Button is set,
+ // then BubbleDialogDelegate will ask the anchor View / highlighted button to
+ // highlight itself when the BubbleDialogDelegate's Widget is shown.
void set_highlight_button_when_shown(bool highlight) {
highlight_button_when_shown_ = highlight;
}
- // Get the arrow's anchor rect in screen space.
- virtual gfx::Rect GetAnchorRect() const;
+ //////////////////////////////////////////////////////////////////////////////
+ // Layout & colors:
+ //
+ // In general you shouldn't need to call any of these. If the default bubble
+ // look and feel does not work for your use case, BubbleDialogDelegate may not
+ // be a good fit for the UI you are building.
- // Allows delegates to provide custom parameters before widget initialization.
- virtual void OnBeforeBubbleWidgetInit(Widget::InitParams* params,
- Widget* widget) const;
+ // The bubble's background color:
+ SkColor color() const { return color_; }
+ void set_color(SkColor color) {
+ color_ = color;
+ color_explicitly_set_ = true;
+ }
- // The layer type of the bubble widget.
- virtual ui::LayerType GetLayerType() const;
+ void set_title_margins(const gfx::Insets& title_margins) {
+ title_margins_ = title_margins;
+ }
+
+ // Sets whether or not CreateClientView() returns a layer backed ClientView.
+ void SetPaintClientToLayer(bool paint_client_to_layer);
// Sets the content margins to a default picked for smaller bubbles.
void UseCompactMargins();
- // Call this method when the anchor bounds have changed to reposition the
- // bubble. The bubble is automatically repositioned when the anchor view
- // bounds change as a result of the widget's bounds changing.
- void OnAnchorBoundsChanged();
+ // Override to configure the layer type of the bubble widget.
+ virtual ui::LayerType GetLayerType() const;
+
+ // Override to provide custom parameters before widget initialization.
+ virtual void OnBeforeBubbleWidgetInit(Widget::InitParams* params,
+ Widget* widget) const {}
protected:
- // Returns the desired arrow post-RTL mirroring if needed.
- BubbleBorder::Arrow arrow() const { return arrow_; }
+ // Create and initialize the bubble Widget with proper bounds.
+ static Widget* CreateBubble(BubbleDialogDelegate* bubble_delegate);
- // Get bubble bounds from the anchor rect and client view's preferred size.
+ // Override this method if you want to position the bubble regardless of its
+ // anchor, while retaining the other anchor view logic.
virtual gfx::Rect GetBubbleBounds();
- // DialogDelegateView:
- ax::mojom::Role GetAccessibleWindowRole() override;
-
- // Disallow overrides of GetMinimumSize and GetMaximumSize(). These would only
- // be called by the FrameView, but the BubbleFrameView ignores these. Bubbles
- // are not user-sizable and always size to their preferred size (plus any
- // border / frame).
- gfx::Size GetMinimumSize() const final;
- gfx::Size GetMaximumSize() const final;
-
- void OnThemeChanged() override;
-
- // Perform view initialization on the contents for bubble sizing.
- virtual void Init();
+ // Update the button highlight, which may be the anchor view or an explicit
+ // view set in |highlighted_button_tracker_|. This can be overridden to
+ // provide different highlight effects.
+ //
+ // TODO(ellyjones): Remove this; it is only used in one place, to disable
+ // highlighting the button, but this is trivial to achieve using other
+ // methods.
+ virtual void UpdateHighlightedButton(bool highlight);
+
+ // Resize the bubble to fit its contents, and maybe move it if needed to keep
+ // it anchored properly.
+ void SizeToContents();
+
+ // Override this to perform initialization after the Widget is created but
+ // before it is shown.
+ virtual void Init() {}
+
+ // TODO(ellyjones): Replace uses of this with uses of set_color(), and/or
+ // otherwise get rid of this function.
+ void set_color_internal(SkColor color) { color_ = color; }
+
+ bool color_explicitly_set() const { return color_explicitly_set_; }
+
+ // Redeclarations of virtuals that BubbleDialogDelegate used to inherit from
+ // WidgetObserver. These should not exist; do not add new overrides of them.
+ // They exist to allow the WidgetObserver helper classes inside
+ // BubbleDialogDelegate (AnchorWidgetObserver and BubbleWidgetObserver) to
+ // forward specific events to BubbleDialogDelegate subclasses that were
+ // overriding WidgetObserver methods from BubbleDialogDelegate. Whether they
+ // are called for the anchor widget or the bubble widget and when is
+ // deliberately unspecified.
+ //
+ // TODO(ellyjones): Get rid of these.
+ virtual void OnWidgetClosing(Widget* widget) {}
+ virtual void OnWidgetDestroying(Widget* widget) {}
+ virtual void OnWidgetActivationChanged(Widget* widget, bool active) {}
+ virtual void OnWidgetDestroyed(Widget* widget) {}
+ virtual void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect& bounds) {}
+ virtual void OnWidgetVisibilityChanged(Widget* widget, bool visible) {}
- // Sets the anchor view or rect and repositions the bubble. Note that if a
- // valid view gets passed, the anchor rect will get ignored. If the view gets
- // deleted, but no new view gets set, the last known anchor postion will get
- // returned.
- void SetAnchorRect(const gfx::Rect& rect);
+ private:
+ class AnchorViewObserver;
+ class AnchorWidgetObserver;
+ class BubbleWidgetObserver;
- // Resize and potentially move the bubble to fit the content's preferred size.
- virtual void SizeToContents();
+ FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest,
+ VisibleWidgetShowsInkDropOnAttaching);
+ FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest,
+ AttachedWidgetShowsInkDropWhenVisible);
- // Allows the up and down arrow keys to tab between items.
- void EnableUpDownKeyboardAccelerators();
+ friend class AnchorViewObserver;
+ friend class AnchorWidgetObserver;
+ friend class BubbleWidgetObserver;
- private:
friend class BubbleBorderDelegate;
friend class BubbleWindowTargeter;
friend class ui_devtools::PageAgentViews;
- class AnchorViewObserver;
-
- FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CreateDelegate);
- FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, NonClientHitTest);
+ // Notify the BubbleDialogDelegate about changes in the anchor Widget. You do
+ // not need to call these yourself.
+ void OnAnchorWidgetDestroying();
+ void OnAnchorWidgetBoundsChanged();
- // Update the bubble color from the NativeTheme unless it was explicitly set.
- void UpdateColorsFromTheme();
+ // Notify the BubbleDialogDelegate about changes in the bubble Widget. You do
+ // not need to call these yourself.
+ void OnBubbleWidgetClosing();
+ void OnBubbleWidgetVisibilityChanged(bool visible);
+ void OnBubbleWidgetActivationChanged(bool active);
+ void OnBubbleWidgetPaintAsActiveChanged(bool as_active);
- // Handles widget visibility changes.
- void HandleVisibilityChanged(Widget* widget, bool visible);
-
- // Called when a deactivation is detected.
void OnDeactivate();
- // Update the button highlight, which may be the anchor view or an explicit
- // view set in |highlighted_button_tracker_|. This can be overridden to
- // provide different highlight effects.
- virtual void UpdateHighlightedButton(bool highlighted);
-
// Set from UI DevTools to prevent bubbles from closing in
// OnWidgetActivationChanged().
static bool devtools_dismiss_override_;
- // A flag controlling bubble closure on deactivation.
- bool close_on_deactivate_ = true;
-
- // The view and widget to which this bubble is anchored. AnchorViewObserver
- // is used to observe bounds changes and view deletion.
- std::unique_ptr<AnchorViewObserver> anchor_view_observer_;
+ gfx::Insets title_margins_;
+ BubbleBorder::Arrow arrow_ = BubbleBorder::NONE;
+ BubbleBorder::Shadow shadow_;
+ SkColor color_ = gfx::kPlaceholderColor;
+ bool color_explicitly_set_ = false;
Widget* anchor_widget_ = nullptr;
+ std::unique_ptr<AnchorViewObserver> anchor_view_observer_;
+ std::unique_ptr<AnchorWidgetObserver> anchor_widget_observer_;
+ std::unique_ptr<BubbleWidgetObserver> bubble_widget_observer_;
std::unique_ptr<Widget::PaintAsActiveLock> paint_as_active_lock_;
+ bool adjust_if_offscreen_ = true;
+ bool focus_traversable_from_anchor_view_ = true;
+ ViewTracker highlighted_button_tracker_;
+
+ // Insets applied to the |anchor_view_| bounds.
+ gfx::Insets anchor_view_insets_;
+
+ // A flag controlling bubble closure on deactivation.
+ bool close_on_deactivate_ = true;
// Whether the |anchor_widget_| (or the |highlighted_button_tracker_|, when
// provided) should be highlighted when this bubble is shown.
bool highlight_button_when_shown_ = true;
- // If provided, this button should be highlighted while the bubble is visible.
- // If not provided, the anchor_view will attempt to be highlighted. A
- // ViewTracker is used because the view can be deleted.
- ViewTracker highlighted_button_tracker_;
-
- // The anchor rect used in the absence of an anchor view.
mutable base::Optional<gfx::Rect> anchor_rect_;
- // The arrow's default location on the bubble post-RTL mirroring if needed.
- BubbleBorder::Arrow arrow_ = BubbleBorder::NONE;
-
- // Bubble border shadow to use.
- BubbleBorder::Shadow shadow_;
-
- // The background color of the bubble; and flag for when it's explicitly set.
- SkColor color_;
- bool color_explicitly_set_ = false;
-
- // The margins around the title.
- // TODO(tapted): Investigate deleting this when MD is default.
- gfx::Insets title_margins_;
-
- // Insets applied to the |anchor_view_| bounds.
- gfx::Insets anchor_view_insets_;
-
- // Specifies whether the bubble (or its border) handles mouse events, etc.
bool accept_events_ = true;
-
- // If true (defaults to true), the arrow may be mirrored and moved to fit the
- // bubble on screen better. It would be a no-op if the bubble has no arrow.
- bool adjust_if_offscreen_ = true;
-
- // Parent native window of the bubble.
gfx::NativeView parent_window_ = nullptr;
- // If true, focus can navigate to the bubble from the anchor view. This takes
- // effect only when SetAnchorView is called.
- bool focus_traversable_from_anchor_view_ = true;
+ // Pointer to this bubble's ClientView.
+ ClientView* client_view_ = nullptr;
#if defined(OS_MACOSX)
// Special handler for close_on_deactivate() on Mac. Window (de)activation is
@@ -279,8 +356,63 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView,
// monitor clicks as well for the desired behavior.
std::unique_ptr<ui::BubbleCloser> mac_bubble_closer_;
#endif
+};
+
+// BubbleDialogDelegateView is a BubbleDialogDelegate that is also a View.
+// If you can, it is better to subclass View and construct a
+// BubbleDialogDelegate instance as a member of your subclass.
+class VIEWS_EXPORT BubbleDialogDelegateView : public BubbleDialogDelegate,
+ public View {
+ public:
+ METADATA_HEADER(BubbleDialogDelegateView);
+
+ // Create and initialize the bubble Widget(s) with proper bounds.
+ static Widget* CreateBubble(BubbleDialogDelegateView* bubble_delegate);
+
+ BubbleDialogDelegateView();
+ // |shadow| usually doesn't need to be explicitly set, just uses the default
+ // argument. Unless on Mac when the bubble needs to use Views base shadow,
+ // override it with suitable bubble border type.
+ BubbleDialogDelegateView(
+ View* anchor_view,
+ BubbleBorder::Arrow arrow,
+ BubbleBorder::Shadow shadow = BubbleBorder::DIALOG_SHADOW);
+
+ ~BubbleDialogDelegateView() override;
+
+ // BubbleDialogDelegate:
+ View* GetContentsView() override;
+ void DeleteDelegate() override;
+
+ // View:
+ Widget* GetWidget() override;
+ const Widget* GetWidget() const override;
+ void AddedToWidget() override;
+ bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
+
+ protected:
+ // Disallow overrides of GetMinimumSize and GetMaximumSize(). These would only
+ // be called by the FrameView, but the BubbleFrameView ignores these. Bubbles
+ // are not user-sizable and always size to their preferred size (plus any
+ // border / frame).
+ // View:
+ gfx::Size GetMinimumSize() const final;
+ gfx::Size GetMaximumSize() const final;
+
+ void OnThemeChanged() override;
- ScopedObserver<views::Widget, views::WidgetObserver> widget_observer_{this};
+ // Perform view initialization on the contents for bubble sizing.
+ void Init() override;
+
+ // Allows the up and down arrow keys to tab between items.
+ void EnableUpDownKeyboardAccelerators();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CreateDelegate);
+ FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, NonClientHitTest);
+
+ // Update the bubble color from the NativeTheme unless it was explicitly set.
+ void UpdateColorsFromTheme();
DISALLOW_COPY_AND_ASSIGN(BubbleDialogDelegateView);
};
diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
index 812261ecd46..f2d4cc73ada 100644
--- a/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
+++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
@@ -6,6 +6,7 @@
#include <stddef.h>
+#include <memory>
#include <string>
#include <utility>
@@ -13,6 +14,7 @@
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "ui/base/hit_test.h"
#include "ui/events/event_utils.h"
@@ -26,6 +28,7 @@
#include "ui/views/test/test_widget_observer.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
+#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
@@ -185,10 +188,9 @@ TEST_F(BubbleDialogDelegateViewTest, CloseAnchorWidget) {
TEST_F(BubbleDialogDelegateViewTest, CloseAnchorViewTest) {
// Create an anchor widget and add a view to be used as an anchor view.
std::unique_ptr<Widget> anchor_widget = CreateTestWidget();
- std::unique_ptr<View> anchor_view(new View());
- anchor_widget->SetContentsView(anchor_view.get());
+ View* anchor_view = anchor_widget->SetContentsView(std::make_unique<View>());
TestBubbleDialogDelegateView* bubble_delegate =
- new TestBubbleDialogDelegateView(anchor_view.get());
+ new TestBubbleDialogDelegateView(anchor_view);
// Prevent flakes by avoiding closing on activation changes.
bubble_delegate->set_close_on_deactivate(false);
Widget* bubble_widget =
@@ -197,7 +199,7 @@ TEST_F(BubbleDialogDelegateViewTest, CloseAnchorViewTest) {
// Check that the anchor view is correct and set up an anchor view rect.
// Make sure that this rect will get ignored (as long as the anchor view is
// attached).
- EXPECT_EQ(anchor_view.get(), bubble_delegate->GetAnchorView());
+ EXPECT_EQ(anchor_view, bubble_delegate->GetAnchorView());
const gfx::Rect set_anchor_rect = gfx::Rect(10, 10, 100, 100);
bubble_delegate->SetAnchorRect(set_anchor_rect);
const gfx::Rect view_rect = bubble_delegate->GetAnchorRect();
@@ -209,8 +211,7 @@ TEST_F(BubbleDialogDelegateViewTest, CloseAnchorViewTest) {
// Remove now the anchor view and make sure that the original found rect
// is still kept, so that the bubble does not jump when the view gets deleted.
- anchor_widget->SetContentsView(anchor_view.get());
- anchor_view.reset();
+ anchor_view->parent()->RemoveChildViewT(anchor_view);
EXPECT_EQ(nullptr, bubble_delegate->GetAnchorView());
EXPECT_EQ(view_rect.ToString(), bubble_delegate->GetAnchorRect().ToString());
}
@@ -510,8 +511,8 @@ TEST_F(BubbleDialogDelegateViewTest, StyledLabelTitle) {
// widget is shown or hidden respectively.
TEST_F(BubbleDialogDelegateViewTest, AttachedWidgetShowsInkDropWhenVisible) {
std::unique_ptr<Widget> anchor_widget = CreateTestWidget();
- LabelButton* button = new LabelButton(nullptr, base::string16());
- anchor_widget->SetContentsView(button);
+ LabelButton* button = anchor_widget->SetContentsView(
+ std::make_unique<LabelButton>(nullptr, base::string16()));
TestInkDrop* ink_drop = new TestInkDrop();
test::InkDropHostViewTestApi(button).SetInkDrop(base::WrapUnique(ink_drop));
TestBubbleDialogDelegateView* bubble_delegate =
@@ -525,11 +526,11 @@ TEST_F(BubbleDialogDelegateViewTest, AttachedWidgetShowsInkDropWhenVisible) {
// Explicitly calling OnWidgetVisibilityChanging to test functionality for
// OS_WIN. Outside of the test environment this happens automatically by way
// of HWNDMessageHandler.
- bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, true);
+ bubble_delegate->OnBubbleWidgetVisibilityChanged(true);
EXPECT_EQ(InkDropState::ACTIVATED, ink_drop->GetTargetInkDropState());
bubble_widget->Close();
- bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, false);
+ bubble_delegate->OnBubbleWidgetVisibilityChanged(false);
EXPECT_EQ(InkDropState::DEACTIVATED, ink_drop->GetTargetInkDropState());
}
@@ -538,8 +539,8 @@ TEST_F(BubbleDialogDelegateViewTest, AttachedWidgetShowsInkDropWhenVisible) {
// widget is shown.
TEST_F(BubbleDialogDelegateViewTest, VisibleWidgetShowsInkDropOnAttaching) {
std::unique_ptr<Widget> anchor_widget = CreateTestWidget();
- LabelButton* button = new LabelButton(nullptr, base::string16());
- anchor_widget->SetContentsView(button);
+ LabelButton* button = anchor_widget->SetContentsView(
+ std::make_unique<LabelButton>(nullptr, base::string16()));
TestInkDrop* ink_drop = new TestInkDrop();
test::InkDropHostViewTestApi(button).SetInkDrop(base::WrapUnique(ink_drop));
TestBubbleDialogDelegateView* bubble_delegate =
@@ -549,16 +550,16 @@ TEST_F(BubbleDialogDelegateViewTest, VisibleWidgetShowsInkDropOnAttaching) {
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
- // Explicitly calling OnWidgetVisibilityChanging to test functionality for
+ // Explicitly calling OnWidgetVisibilityChanged to test functionality for
// OS_WIN. Outside of the test environment this happens automatically by way
// of HWNDMessageHandler.
- bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, true);
+ bubble_delegate->OnBubbleWidgetVisibilityChanged(true);
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
bubble_delegate->SetHighlightedButton(button);
EXPECT_EQ(InkDropState::ACTIVATED, ink_drop->GetTargetInkDropState());
bubble_widget->Close();
- bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, false);
+ bubble_delegate->OnBubbleWidgetVisibilityChanged(false);
EXPECT_EQ(InkDropState::DEACTIVATED, ink_drop->GetTargetInkDropState());
}
@@ -602,6 +603,50 @@ TEST_F(BubbleDialogDelegateViewTest, GetThemeProvider_FromAnchorWidget) {
anchor_widget->GetThemeProvider());
}
+// Tests whether the BubbleDialogDelegateView will create a layer backed client
+// view when prompted to do so.
+class BubbleDialogDelegateClientLayerTest : public test::WidgetTest {
+ public:
+ BubbleDialogDelegateClientLayerTest() = default;
+ ~BubbleDialogDelegateClientLayerTest() override = default;
+
+ void SetUp() override {
+ WidgetTest::SetUp();
+ scoped_feature_list_.InitWithFeatures(
+ {features::kEnableMDRoundedCornersOnDialogs}, {});
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(BubbleDialogDelegateClientLayerTest, WithClientLayerTest) {
+ std::unique_ptr<Widget> anchor_widget =
+ CreateTestWidget(Widget::InitParams::TYPE_WINDOW);
+ auto bubble_delegate = std::make_unique<BubbleDialogDelegateView>(
+ nullptr, BubbleBorder::TOP_LEFT);
+ bubble_delegate->set_parent_window(anchor_widget->GetNativeView());
+
+ WidgetAutoclosePtr bubble_widget(
+ BubbleDialogDelegateView::CreateBubble(bubble_delegate.release()));
+
+ EXPECT_NE(nullptr, bubble_widget->client_view()->layer());
+}
+
+TEST_F(BubbleDialogDelegateClientLayerTest, WithoutClientLayerTest) {
+ std::unique_ptr<Widget> anchor_widget =
+ CreateTestWidget(Widget::InitParams::TYPE_WINDOW);
+ auto bubble_delegate = std::make_unique<BubbleDialogDelegateView>(
+ nullptr, BubbleBorder::TOP_LEFT);
+ bubble_delegate->SetPaintClientToLayer(false);
+ bubble_delegate->set_parent_window(anchor_widget->GetNativeView());
+
+ WidgetAutoclosePtr bubble_widget(
+ BubbleDialogDelegateView::CreateBubble(bubble_delegate.release()));
+
+ EXPECT_EQ(nullptr, bubble_widget->client_view()->layer());
+}
+
// Anchoring Tests -------------------------------------------------------------
namespace {
diff --git a/chromium/ui/views/bubble/bubble_frame_view.cc b/chromium/ui/views/bubble/bubble_frame_view.cc
index ea653f1fe22..7f94147a5ce 100644
--- a/chromium/ui/views/bubble/bubble_frame_view.cc
+++ b/chromium/ui/views/bubble/bubble_frame_view.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include <utility>
+#include "base/feature_list.h"
#include "build/build_config.h"
#include "components/vector_icons/vector_icons.h"
#include "third_party/skia/include/core/SkPath.h"
@@ -34,10 +35,12 @@
#include "ui/views/paint_info.h"
#include "ui/views/resources/grit/views_resources.h"
#include "ui/views/view_class_properties.h"
+#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/client_view.h"
#include "ui/views/window/dialog_delegate.h"
+#include "ui/views/window/vector_icons/vector_icons.h"
namespace views {
@@ -98,6 +101,15 @@ BubbleFrameView::BubbleFrameView(const gfx::Insets& title_margins,
#endif
close_ = AddChildView(std::move(close));
+ auto minimize = CreateMinimizeButton(this);
+ minimize->SetVisible(false);
+#if defined(OS_WIN)
+ minimize->SetTooltipText(base::string16());
+ minimize->SetAccessibleName(
+ l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
+#endif
+ minimize_ = AddChildView(std::move(minimize));
+
auto progress_indicator = std::make_unique<ProgressBar>(
kProgressIndicatorHeight, /*allow_round_corner=*/false);
progress_indicator->SetBackgroundColor(SK_ColorTRANSPARENT);
@@ -131,6 +143,21 @@ std::unique_ptr<Button> BubbleFrameView::CreateCloseButton(
return close_button;
}
+// static
+std::unique_ptr<Button> BubbleFrameView::CreateMinimizeButton(
+ ButtonListener* listener) {
+ auto minimize_button = CreateVectorImageButtonWithNativeTheme(
+ listener, kWindowControlMinimizeIcon);
+ minimize_button->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
+ minimize_button->SizeToPreferredSize();
+ minimize_button->SetFocusForPlatform();
+
+ InstallCircleHighlightPathGenerator(minimize_button.get());
+
+ return minimize_button;
+}
+
gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
// When NonClientView asks for this, the size of the frame view has been set
// (i.e. |this|), but not the client view bounds.
@@ -190,6 +217,8 @@ int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
return HTTRANSPARENT;
if (close_->GetVisible() && close_->GetMirroredBounds().Contains(point))
return HTCLOSE;
+ if (minimize_->GetVisible() && minimize_->GetMirroredBounds().Contains(point))
+ return HTMINBUTTON;
// Convert to RRectF to accurately represent the rounded corners of the
// dialog and allow events to pass through the shadows.
@@ -249,6 +278,7 @@ void BubbleFrameView::GetWindowMask(const gfx::Size& size,
void BubbleFrameView::ResetWindowControls() {
close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
+ minimize_->SetVisible(GetWidget()->widget_delegate()->CanMinimize());
}
void BubbleFrameView::UpdateWindowIcon() {
@@ -354,18 +384,23 @@ void BubbleFrameView::Layout() {
if (bounds.IsEmpty())
return;
+ // The buttons are positioned somewhat closer to the edge of the bubble.
+ const int close_margin =
+ LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
+ const int button_y = contents_bounds.y() + close_margin;
+ int button_right = contents_bounds.right() - close_margin;
int title_label_right = bounds.right();
- if (close_->GetVisible()) {
- // The close button is positioned somewhat closer to the edge of the bubble.
- const int close_margin =
- LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
- close_->SetPosition(
- gfx::Point(contents_bounds.right() - close_margin - close_->width(),
- contents_bounds.y() + close_margin));
- // Only reserve space if the close button extends over the header.
- if (close_->bounds().bottom() > header_bottom) {
+ for (Button* button : {close_, minimize_}) {
+ if (!button->GetVisible())
+ continue;
+ button->SetPosition(gfx::Point(button_right - button->width(), button_y));
+ button_right -= button->width();
+ button_right -= LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_BUTTON_HORIZONTAL);
+ // Only reserve space if the button extends over the header.
+ if (button->bounds().bottom() > header_bottom) {
title_label_right =
- std::min(title_label_right, close_->x() - close_margin);
+ std::min(title_label_right, button->x() - close_margin);
}
}
@@ -417,6 +452,7 @@ void BubbleFrameView::OnThemeChanged() {
if (bubble_border_ && bubble_border_->use_theme_background_color()) {
bubble_border_->set_background_color(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground));
+ UpdateClientViewBackground();
SchedulePaint();
}
}
@@ -464,6 +500,8 @@ void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) {
if (sender == close_) {
GetWidget()->CloseWithReason(Widget::ClosedReason::kCloseButtonClicked);
+ } else if (sender == minimize_) {
+ GetWidget()->Minimize();
}
}
@@ -519,6 +557,7 @@ void BubbleFrameView::SetArrow(BubbleBorder::Arrow arrow) {
void BubbleFrameView::SetBackgroundColor(SkColor color) {
bubble_border_->set_background_color(color);
+ UpdateClientViewBackground();
SchedulePaint();
}
@@ -526,6 +565,24 @@ SkColor BubbleFrameView::GetBackgroundColor() const {
return bubble_border_->background_color();
}
+void BubbleFrameView::UpdateClientViewBackground() {
+ if (!base::FeatureList::IsEnabled(features::kEnableMDRoundedCornersOnDialogs))
+ return;
+ DCHECK(GetWidget());
+ DCHECK(GetWidget()->client_view());
+
+ // If dealing with a layer backed ClientView we need to update it's color to
+ // match that of the frame view.
+ View* client_view = GetWidget()->client_view();
+ if (client_view->layer()) {
+ // If the ClientView's background is transparent this could result in visual
+ // artifacts. Make sure this isn't the case.
+ DCHECK_EQ(SK_AlphaOPAQUE, SkColorGetA(GetBackgroundColor()));
+ client_view->SetBackground(CreateSolidBackground(GetBackgroundColor()));
+ client_view->SchedulePaint();
+ }
+}
+
gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(
const gfx::Rect& anchor_rect,
const BubbleBorder::Arrow delegate_arrow,
@@ -581,7 +638,7 @@ gfx::Rect BubbleFrameView::GetAvailableScreenBounds(
}
gfx::Rect BubbleFrameView::GetAvailableAnchorWindowBounds() const {
- views::BubbleDialogDelegateView* bubble_delegate_view =
+ views::BubbleDialogDelegate* bubble_delegate_view =
GetWidget()->widget_delegate()->AsBubbleDialogDelegate();
if (bubble_delegate_view) {
views::View* const anchor_view = bubble_delegate_view->GetAnchorView();
diff --git a/chromium/ui/views/bubble/bubble_frame_view.h b/chromium/ui/views/bubble/bubble_frame_view.h
index c841b131d53..8e71b915b65 100644
--- a/chromium/ui/views/bubble/bubble_frame_view.h
+++ b/chromium/ui/views/bubble/bubble_frame_view.h
@@ -44,6 +44,9 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
// Creates a close button used in the corner of the dialog.
static std::unique_ptr<Button> CreateCloseButton(ButtonListener* listener);
+ // Creates a minimize button used in the corner of the dialog.
+ static std::unique_ptr<Button> CreateMinimizeButton(ButtonListener* listener);
+
// NonClientFrameView:
gfx::Rect GetBoundsForClientView() const override;
gfx::Rect GetWindowBoundsForClientBounds(
@@ -137,6 +140,12 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
void SetBackgroundColor(SkColor color);
SkColor GetBackgroundColor() const;
+ // For masking reasons, the ClientView may be painted to a textured layer. To
+ // ensure bubbles that rely on the frame background color continue to work as
+ // expected, we must set the background of the ClientView to match that of the
+ // BubbleFrameView.
+ void UpdateClientViewBackground();
+
// Given the size of the contents and the rect to point at, returns the bounds
// of the bubble window. The bubble's arrow location may change if the bubble
// does not fit on the monitor or anchor window (if one exists) and
@@ -176,7 +185,10 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, RemoveFootnoteView);
FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, LayoutWithIcon);
FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, LayoutWithProgressIndicator);
- FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, IgnorePossiblyUnintendedClicks);
+ FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest,
+ IgnorePossiblyUnintendedClicksClose);
+ FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest,
+ IgnorePossiblyUnintendedClicksMinimize);
FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CloseReasons);
FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest, CloseMethods);
FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest, CreateDelegate);
@@ -243,6 +255,9 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView,
// The optional close button (the X).
Button* close_ = nullptr;
+ // The optional minimize button.
+ Button* minimize_ = nullptr;
+
// The optional progress bar. Used to indicate bubble pending state. By
// default it is invisible.
ProgressBar* progress_indicator_ = nullptr;
diff --git a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc
index 0f6ba1f261d..fc20bd38f0a 100644
--- a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc
+++ b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc
@@ -1237,10 +1237,11 @@ TEST_F(BubbleFrameViewTest, NoElideTitle) {
}
// Ensures that clicks are ignored for short time after view has been shown.
-TEST_F(BubbleFrameViewTest, IgnorePossiblyUnintendedClicks) {
+TEST_F(BubbleFrameViewTest, IgnorePossiblyUnintendedClicksClose) {
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
delegate.SetAnchorView(anchor.widget().GetContentsView());
+ delegate.SetShouldShowCloseButton(true);
Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate);
bubble->Show();
@@ -1260,6 +1261,31 @@ TEST_F(BubbleFrameViewTest, IgnorePossiblyUnintendedClicks) {
EXPECT_TRUE(bubble->IsClosed());
}
+// Ensures that clicks are ignored for short time after view has been shown.
+TEST_F(BubbleFrameViewTest, IgnorePossiblyUnintendedClicksMinimize) {
+ TestBubbleDialogDelegateView delegate;
+ TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
+ delegate.SetAnchorView(anchor.widget().GetContentsView());
+ delegate.SetCanMinimize(true);
+ Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate);
+ bubble->Show();
+
+ BubbleFrameView* frame = delegate.GetBubbleFrameView();
+ frame->ButtonPressed(
+ frame->minimize_,
+ ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+ ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE));
+ EXPECT_FALSE(bubble->IsClosed());
+
+ frame->ButtonPressed(
+ frame->minimize_,
+ ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+ ui::EventTimeForNow() + base::TimeDelta::FromMilliseconds(
+ GetDoubleClickInterval()),
+ ui::EF_NONE, ui::EF_NONE));
+ EXPECT_TRUE(bubble->IsMinimized());
+}
+
// Ensures that layout is correct when the progress indicator is visible.
TEST_F(BubbleFrameViewTest, LayoutWithProgressIndicator) {
TestBubbleDialogDelegateView delegate;
diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc
index d74e05dc34c..5bb670ce30a 100644
--- a/chromium/ui/views/controls/button/button.cc
+++ b/chromium/ui/views/controls/button/button.cc
@@ -56,6 +56,7 @@ Button::WidgetObserverButtonBridge::WidgetObserverButtonBridge(Button* button)
Button::WidgetObserverButtonBridge::~WidgetObserverButtonBridge() {
if (owner_)
owner_->GetWidget()->RemoveObserver(this);
+ CHECK(!IsInObserverList());
}
void Button::WidgetObserverButtonBridge::OnWidgetPaintAsActiveChanged(
@@ -240,10 +241,12 @@ void Button::SetAnimationDuration(base::TimeDelta duration) {
}
void Button::SetInstallFocusRingOnFocus(bool install) {
- if (install)
+ if (focus_ring_ && !install) {
+ RemoveChildViewT(focus_ring_);
+ focus_ring_ = nullptr;
+ } else if (!focus_ring_ && install) {
focus_ring_ = FocusRing::Install(this);
- else
- focus_ring_.reset();
+ }
}
void Button::SetHotTracked(bool is_hot_tracked) {
diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h
index 79bab1f6034..6411eb3514b 100644
--- a/chromium/ui/views/controls/button/button.h
+++ b/chromium/ui/views/controls/button/button.h
@@ -292,7 +292,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
return hover_animation_;
}
- FocusRing* focus_ring() { return focus_ring_.get(); }
+ FocusRing* focus_ring() { return focus_ring_; }
// The button's listener. Notified when clicked.
ButtonListener* listener_;
@@ -368,7 +368,7 @@ class VIEWS_EXPORT Button : public InkDropHostView,
SkColor ink_drop_base_color_;
// The focus ring for this Button.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
std::unique_ptr<Painter> focus_painter_;
diff --git a/chromium/ui/views/controls/button/button_unittest.cc b/chromium/ui/views/controls/button/button_unittest.cc
index 3dfeba259fd..5850b215f25 100644
--- a/chromium/ui/views/controls/button/button_unittest.cc
+++ b/chromium/ui/views/controls/button/button_unittest.cc
@@ -35,6 +35,7 @@
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/style/platform_style.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"
@@ -178,15 +179,6 @@ TestInkDrop* AddTestInkDrop(TestButton* button) {
return ink_drop;
}
-// TODO(tluk): remove when the appropriate ownership APIs have been added for
-// Widget's SetContentsView().
-template <typename T>
-T* AddContentsView(Widget* widget, std::unique_ptr<T> view) {
- T* view_ptr = view.get();
- widget->SetContentsView(view.release());
- return view_ptr;
-}
-
} // namespace
class ButtonTest : public ViewsTestBase {
@@ -207,7 +199,7 @@ class ButtonTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- button_ = AddContentsView(widget(), std::make_unique<TestButton>(false));
+ button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
event_generator_ =
std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget()));
@@ -225,24 +217,21 @@ class ButtonTest : public ViewsTestBase {
}
TestInkDrop* CreateButtonWithInkDrop(bool has_ink_drop_action_on_click) {
- button_ = AddContentsView(
- widget(), std::make_unique<TestButton>(has_ink_drop_action_on_click));
- widget_->SetContentsView(button_);
+ button_ = widget()->SetContentsView(
+ std::make_unique<TestButton>(has_ink_drop_action_on_click));
return AddTestInkDrop(button_);
}
void CreateButtonWithRealInkDrop() {
- button_ = AddContentsView(widget(), std::make_unique<TestButton>(false));
+ button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
InkDropHostViewTestApi(button_).SetInkDrop(
std::make_unique<InkDropImpl>(button_, button_->size()));
- widget_->SetContentsView(button_);
}
void CreateButtonWithObserver() {
- button_ = AddContentsView(widget(), std::make_unique<TestButton>(false));
+ button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
button_observer_ = std::make_unique<TestButtonObserver>();
button_->AddButtonObserver(button_observer_.get());
- widget_->SetContentsView(button_);
}
protected:
@@ -263,7 +252,12 @@ class ButtonTest : public ViewsTestBase {
DISALLOW_COPY_AND_ASSIGN(ButtonTest);
};
-// Tests that hover state changes correctly when visiblity/enableness changes.
+// Iterate through the metadata for Button to ensure it all works.
+TEST_F(ButtonTest, MetadataTest) {
+ test::TestViewMetadata(button());
+}
+
+// Tests that hover state changes correctly when visibility/enableness changes.
TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
@@ -599,7 +593,7 @@ TEST_F(ButtonTest, InkDropAfterTryingToShowContextMenu) {
}
TEST_F(ButtonTest, HideInkDropHighlightWhenRemoved) {
- View* contents_view = AddContentsView(widget(), std::make_unique<View>());
+ View* contents_view = widget()->SetContentsView(std::make_unique<View>());
TestButton* button =
contents_view->AddChildView(std::make_unique<TestButton>(false));
@@ -779,7 +773,7 @@ class VisibilityTestButton : public TestButton {
// changed visibility states.
TEST_F(ButtonTest, NoLayerAddedForWidgetVisibilityChanges) {
VisibilityTestButton* button =
- AddContentsView(widget(), std::make_unique<VisibilityTestButton>());
+ widget()->SetContentsView(std::make_unique<VisibilityTestButton>());
// Ensure no layers are created during construction.
EXPECT_TRUE(button->GetVisible());
diff --git a/chromium/ui/views/controls/button/checkbox_unittest.cc b/chromium/ui/views/controls/button/checkbox_unittest.cc
index 3b3e5d83d76..a468b3d7212 100644
--- a/chromium/ui/views/controls/button/checkbox_unittest.cc
+++ b/chromium/ui/views/controls/button/checkbox_unittest.cc
@@ -32,8 +32,8 @@ class CheckboxTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- checkbox_ = new Checkbox(base::string16());
- widget_->SetContentsView(checkbox_);
+ checkbox_ =
+ widget_->SetContentsView(std::make_unique<Checkbox>(base::string16()));
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/button/image_button_factory.cc b/chromium/ui/views/controls/button/image_button_factory.cc
index 5ac49262cfe..7d7d6a9a520 100644
--- a/chromium/ui/views/controls/button/image_button_factory.cc
+++ b/chromium/ui/views/controls/button/image_button_factory.cc
@@ -110,21 +110,11 @@ void SetImageFromVectorIconWithColor(ImageButton* button,
button->set_ink_drop_base_color(icon_color);
}
-void SetToggledImageFromVectorIcon(ToggleImageButton* button,
- const gfx::VectorIcon& icon,
- int dip_size,
- SkColor related_text_color) {
- const SkColor icon_color =
- color_utils::DeriveDefaultIconColor(related_text_color);
- SetToggledImageFromVectorIconWithColor(button, icon, dip_size, icon_color);
-}
-
void SetToggledImageFromVectorIconWithColor(ToggleImageButton* button,
const gfx::VectorIcon& icon,
int dip_size,
- SkColor icon_color) {
- const SkColor disabled_color =
- SkColorSetA(icon_color, gfx::kDisabledControlAlpha);
+ SkColor icon_color,
+ SkColor disabled_color) {
const gfx::ImageSkia normal_image =
gfx::CreateVectorIcon(icon, dip_size, icon_color);
const gfx::ImageSkia disabled_image =
diff --git a/chromium/ui/views/controls/button/image_button_factory.h b/chromium/ui/views/controls/button/image_button_factory.h
index 38d7c70ad52..de2d57501b2 100644
--- a/chromium/ui/views/controls/button/image_button_factory.h
+++ b/chromium/ui/views/controls/button/image_button_factory.h
@@ -32,8 +32,7 @@ VIEWS_EXPORT std::unique_ptr<ImageButton> CreateVectorImageButton(
ButtonListener* listener);
// Creates a ToggleImageButton with an ink drop and a centered image in
-// preperation for applying a vector icon from SetImageFromVectorIcon and
-// SetToggledImageFromVectorIcon below.
+// preparation for applying a vector icon from SetImageFromVectorIcon below.
VIEWS_EXPORT std::unique_ptr<ToggleImageButton> CreateVectorToggleImageButton(
ButtonListener* listener);
@@ -73,7 +72,8 @@ VIEWS_EXPORT void SetToggledImageFromVectorIconWithColor(
ToggleImageButton* button,
const gfx::VectorIcon& icon,
int dip_size,
- SkColor icon_color);
+ SkColor icon_color,
+ SkColor disabled_color);
} // namespace views
diff --git a/chromium/ui/views/controls/button/image_button_factory_unittest.cc b/chromium/ui/views/controls/button/image_button_factory_unittest.cc
index 5b8a5e4aee6..2197b35a527 100644
--- a/chromium/ui/views/controls/button/image_button_factory_unittest.cc
+++ b/chromium/ui/views/controls/button/image_button_factory_unittest.cc
@@ -56,24 +56,22 @@ class ImageButtonFactoryWidgetTest : public ViewsTestBase {
}
void TearDown() override {
- button_.reset();
widget_.reset();
ViewsTestBase::TearDown();
}
ImageButton* AddImageButton(std::unique_ptr<ImageButton> button) {
- button_ = std::move(button);
- widget_->SetContentsView(button_.get());
- return button_.get();
+ button_ = widget_->SetContentsView(std::move(button));
+ return button_;
}
protected:
Widget* widget() { return widget_.get(); }
- ImageButton* button() { return button_.get(); }
+ ImageButton* button() { return button_; }
private:
std::unique_ptr<Widget> widget_;
- std::unique_ptr<ImageButton> button_;
+ ImageButton* button_ = nullptr; // owned by |widget_|.
DISALLOW_COPY_AND_ASSIGN(ImageButtonFactoryWidgetTest);
};
diff --git a/chromium/ui/views/controls/button/label_button.cc b/chromium/ui/views/controls/button/label_button.cc
index bb9105f1a18..3aef54f32cd 100644
--- a/chromium/ui/views/controls/button/label_button.cc
+++ b/chromium/ui/views/controls/button/label_button.cc
@@ -281,20 +281,21 @@ int LabelButton::GetHeightForWidth(int width) const {
}
void LabelButton::Layout() {
- gfx::Rect child_area = GetLocalBounds();
+ gfx::Rect image_area = GetLocalBounds();
- ink_drop_container_->SetBoundsRect(child_area);
- // The space that the label can use. Its actual bounds may be smaller if the
- // label is short.
- gfx::Rect label_area = child_area;
+ ink_drop_container_->SetBoundsRect(image_area);
gfx::Insets insets = GetInsets();
- child_area.Inset(insets);
- // Labels can paint over the vertical component of the border insets.
- label_area.Inset(insets.left(), 0, insets.right(), 0);
+ // If the button have a limited space to fit in, the image and the label
+ // may overlap with the border, which often times contains a lot of empty
+ // padding.
+ image_area.Inset(insets.left(), 0, insets.right(), 0);
+ // The space that the label can use. Labels truncate horizontally, so there
+ // is no need to allow the label to take up the complete horizontal space.
+ gfx::Rect label_area = image_area;
gfx::Size image_size = image_->GetPreferredSize();
- image_size.SetToMin(child_area.size());
+ image_size.SetToMin(image_area.size());
const auto horizontal_alignment = GetHorizontalAlignment();
if (!image_size.IsEmpty()) {
@@ -309,22 +310,28 @@ void LabelButton::Layout() {
std::min(label_area.width(), label_->GetPreferredSize().width()),
label_area.height());
- gfx::Point image_origin = child_area.origin();
+ gfx::Point image_origin = image_area.origin();
if (label_->GetMultiLine() && !image_centered_) {
- image_origin.Offset(
- 0, std::max(
- 0, (label_->font_list().GetHeight() - image_size.height()) / 2));
+ // This code assumes the text is vertically centered.
+ DCHECK_EQ(gfx::ALIGN_MIDDLE, label_->GetVerticalAlignment());
+ int label_height = label_->GetHeightForWidth(label_size.width());
+ int first_line_y =
+ label_area.y() + (label_area.height() - label_height) / 2;
+ int image_origin_y =
+ first_line_y +
+ (label_->font_list().GetHeight() - image_size.height()) / 2;
+ image_origin.Offset(0, std::max(0, image_origin_y));
} else {
- image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
+ image_origin.Offset(0, (image_area.height() - image_size.height()) / 2);
}
if (horizontal_alignment == gfx::ALIGN_CENTER) {
const int spacing = (image_size.width() > 0 && label_size.width() > 0)
? GetImageLabelSpacing()
: 0;
const int total_width = image_size.width() + label_size.width() + spacing;
- image_origin.Offset((child_area.width() - total_width) / 2, 0);
+ image_origin.Offset((image_area.width() - total_width) / 2, 0);
} else if (horizontal_alignment == gfx::ALIGN_RIGHT) {
- image_origin.Offset(child_area.width() - image_size.width(), 0);
+ image_origin.Offset(image_area.width() - image_size.width(), 0);
}
image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
diff --git a/chromium/ui/views/controls/button/label_button_label_unittest.cc b/chromium/ui/views/controls/button/label_button_label_unittest.cc
index 505c2d5ed6c..8e880de4eaf 100644
--- a/chromium/ui/views/controls/button/label_button_label_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_label_unittest.cc
@@ -62,12 +62,22 @@ class LabelButtonLabelTest : public ViewsTestBase {
void SetUp() override {
ViewsTestBase::SetUp();
- label_ = std::make_unique<TestLabel>(&last_color_);
+
+ widget_ = CreateTestWidget();
+ label_ =
+ widget_->SetContentsView(std::make_unique<TestLabel>(&last_color_));
+ label_->SetAutoColorReadabilityEnabled(false);
+ }
+
+ void TearDown() override {
+ widget_.reset();
+ ViewsTestBase::TearDown();
}
protected:
- SkColor last_color_ = SK_ColorCYAN;
- std::unique_ptr<TestLabel> label_;
+ SkColor last_color_ = gfx::kPlaceholderColor;
+ std::unique_ptr<views::Widget> widget_;
+ TestLabel* label_;
TestNativeTheme theme1_;
TestNativeTheme theme2_;
@@ -77,16 +87,6 @@ class LabelButtonLabelTest : public ViewsTestBase {
// Test that LabelButtonLabel reacts properly to themed and overridden colors.
TEST_F(LabelButtonLabelTest, Colors) {
- // The OnDidSchedulePaint() override won't be called while the base
- // class is initialized. Not much we can do about that, so give it the first
- // for free.
- EXPECT_EQ(SK_ColorCYAN, last_color_); // Sanity check.
-
- // At the same time we can check that changing the auto color readability
- // schedules a paint. Currently it does. Although it technically doesn't need
- // to since the color isn't actually changing.
- label_->SetAutoColorReadabilityEnabled(false);
-
// First one comes from the default theme. This check ensures the SK_ColorRED
// placeholder initializers were replaced.
SkColor default_theme_enabled_color =
diff --git a/chromium/ui/views/controls/button/label_button_unittest.cc b/chromium/ui/views/controls/button/label_button_unittest.cc
index cd33dbe4c0a..38443891fff 100644
--- a/chromium/ui/views/controls/button/label_button_unittest.cc
+++ b/chromium/ui/views/controls/button/label_button_unittest.cc
@@ -627,8 +627,8 @@ TEST_F(LabelButtonTest, HighlightedButtonStyle) {
// Ensure the label resets the enabled color after LabelButton::OnThemeChanged()
// is invoked.
TEST_F(LabelButtonTest, OnThemeChanged) {
- ASSERT_NE(button_->GetNativeTheme()->GetHighContrastColorScheme(),
- ui::NativeTheme::HighContrastColorScheme::kDark);
+ ASSERT_NE(button_->GetNativeTheme()->GetPlatformHighContrastColorScheme(),
+ ui::NativeTheme::PlatformHighContrastColorScheme::kDark);
ASSERT_NE(button_->label()->GetBackgroundColor(), SK_ColorBLACK);
EXPECT_EQ(themed_normal_text_color_, button_->label()->GetEnabledColor());
@@ -667,6 +667,25 @@ TEST_F(LabelButtonTest, SetEnabledTextColorsResetsToThemeColors) {
EXPECT_EQ(TestNativeTheme::kSystemColor, button_->label()->GetEnabledColor());
}
+TEST_F(LabelButtonTest, ImageOrLabelGetClipped) {
+ const base::string16 text(ASCIIToUTF16("abc"));
+ button_->SetText(text);
+
+ const gfx::FontList font_list = button_->label()->font_list();
+ const int image_size = font_list.GetHeight();
+ button_->SetImage(Button::STATE_NORMAL,
+ CreateTestImage(image_size, image_size));
+
+ button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize()));
+ // The border size + the content height is more than button's preferred size.
+ button_->SetBorder(CreateEmptyBorder(image_size / 2, 0, image_size / 2, 0));
+ button_->Layout();
+
+ // Ensure that content (image and label) doesn't get clipped by the border.
+ EXPECT_GE(button_->image()->height(), image_size);
+ EXPECT_GE(button_->label()->height(), image_size);
+}
+
// Test fixture for a LabelButton that has an ink drop configured.
class InkDropLabelButtonTest : public ViewsTestBase {
public:
diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc
index b9905fc6359..79c42e7b9ac 100644
--- a/chromium/ui/views/controls/button/md_text_button.cc
+++ b/chromium/ui/views/controls/button/md_text_button.cc
@@ -142,6 +142,11 @@ void MdTextButton::SetEnabledTextColors(base::Optional<SkColor> color) {
UpdateColors();
}
+void MdTextButton::SetCustomPadding(const gfx::Insets& padding) {
+ custom_padding_ = padding;
+ UpdatePadding();
+}
+
void MdTextButton::SetText(const base::string16& text) {
LabelButton::SetText(text);
UpdatePadding();
@@ -183,6 +188,11 @@ void MdTextButton::UpdatePadding() {
return;
}
+ SetBorder(
+ CreateEmptyBorder(custom_padding_.value_or(CalculateDefaultPadding())));
+}
+
+gfx::Insets MdTextButton::CalculateDefaultPadding() const {
// Text buttons default to 28dp in height on all platforms when the base font
// is in use, but should grow or shrink if the font size is adjusted up or
// down. When the system font size has been adjusted, the base font will be
@@ -213,8 +223,8 @@ void MdTextButton::UpdatePadding() {
// we apply the MD treatment to all buttons, even GTK buttons?
const int horizontal_padding = LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_BUTTON_HORIZONTAL_PADDING);
- SetBorder(CreateEmptyBorder(top_padding, horizontal_padding, bottom_padding,
- horizontal_padding));
+ return gfx::Insets(top_padding, horizontal_padding, bottom_padding,
+ horizontal_padding);
}
void MdTextButton::UpdateColors() {
diff --git a/chromium/ui/views/controls/button/md_text_button.h b/chromium/ui/views/controls/button/md_text_button.h
index 760ab2a80ae..1d293a70166 100644
--- a/chromium/ui/views/controls/button/md_text_button.h
+++ b/chromium/ui/views/controls/button/md_text_button.h
@@ -39,6 +39,9 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
void SetCornerRadius(float radius);
float GetCornerRadius() const;
+ // See |custom_padding_|.
+ void SetCustomPadding(const gfx::Insets& padding);
+
// LabelButton:
void OnThemeChanged() override;
std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
@@ -59,6 +62,7 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
private:
void UpdatePadding();
void UpdateColors();
+ gfx::Insets CalculateDefaultPadding() const;
// True if this button uses prominent styling (blue fill, etc.).
bool is_prominent_ = false;
@@ -68,6 +72,9 @@ class VIEWS_EXPORT MdTextButton : public LabelButton {
float corner_radius_ = 0.0f;
+ // Used to override default padding.
+ base::Optional<gfx::Insets> custom_padding_;
+
DISALLOW_COPY_AND_ASSIGN(MdTextButton);
};
diff --git a/chromium/ui/views/controls/button/md_text_button_unittest.cc b/chromium/ui/views/controls/button/md_text_button_unittest.cc
new file mode 100644
index 00000000000..5fe5d9c1bf1
--- /dev/null
+++ b/chromium/ui/views/controls/button/md_text_button_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/button/md_text_button.h"
+
+#include "ui/views/test/views_test_base.h"
+
+namespace views {
+
+using MdTextButtonTest = ViewsTestBase;
+
+TEST_F(MdTextButtonTest, CustomPadding) {
+ const base::string16 text = base::ASCIIToUTF16("abc");
+ std::unique_ptr<MdTextButton> button =
+ MdTextButton::Create(nullptr, text, views::style::CONTEXT_BUTTON_MD);
+
+ const gfx::Insets custom_padding(10, 20);
+ ASSERT_NE(button->GetInsets(), custom_padding);
+
+ button->SetCustomPadding(custom_padding);
+ EXPECT_EQ(button->GetInsets(), custom_padding);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/controls/button/radio_button_unittest.cc b/chromium/ui/views/controls/button/radio_button_unittest.cc
index 3d25994df2a..8d22480097e 100644
--- a/chromium/ui/views/controls/button/radio_button_unittest.cc
+++ b/chromium/ui/views/controls/button/radio_button_unittest.cc
@@ -34,8 +34,7 @@ class RadioButtonTest : public ViewsTestBase {
widget_->Init(std::move(params));
widget_->Show();
- button_container_ = new View();
- widget_->SetContentsView(button_container_);
+ button_container_ = widget_->SetContentsView(std::make_unique<View>());
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc
index b0364bcdb3e..72903d758de 100644
--- a/chromium/ui/views/controls/combobox/combobox.cc
+++ b/chromium/ui/views/controls/combobox/combobox.cc
@@ -17,12 +17,14 @@
#include "ui/base/ime/input_method.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/menu_model.h"
+#include "ui/base/ui_base_types.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
+#include "ui/native_theme/themed_vector_icon.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/background.h"
@@ -54,6 +56,15 @@ SkColor GetTextColorForEnableState(const Combobox& combobox, bool enabled) {
return style::GetColor(combobox, style::CONTEXT_TEXTFIELD, style);
}
+gfx::ImageSkia GetImageSkiaFromImageModel(const ui::ImageModel* model,
+ const ui::NativeTheme* native_theme) {
+ DCHECK(model);
+ DCHECK(!model->IsEmpty());
+ return model->IsImage() ? model->GetImage().AsImageSkia()
+ : ui::ThemedVectorIcon(model->GetVectorIcon())
+ .GetImageSkia(native_theme);
+}
+
// The transparent button which holds a button state but is not rendered.
class TransparentButton : public Button {
public:
@@ -132,7 +143,13 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
}
// Overridden from MenuModel:
- bool HasIcons() const override { return false; }
+ bool HasIcons() const override {
+ for (int i = 0; i < GetItemCount(); ++i) {
+ if (!GetIconAt(i).IsEmpty())
+ return true;
+ }
+ return false;
+ }
int GetItemCount() const override { return model_->GetItemCount(); }
@@ -155,7 +172,13 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
base::string16 GetLabelAt(int index) const override {
// Inserting the Unicode formatting characters if necessary so that the
// text is displayed correctly in right-to-left UIs.
- base::string16 text = model_->GetItemAt(index);
+ base::string16 text = model_->GetDropDownTextAt(index);
+ base::i18n::AdjustStringForLocaleDirection(&text);
+ return text;
+ }
+
+ base::string16 GetSecondaryLabelAt(int index) const override {
+ base::string16 text = model_->GetDropDownSecondaryTextAt(index);
base::i18n::AdjustStringForLocaleDirection(&text);
return text;
}
@@ -178,7 +201,7 @@ class Combobox::ComboboxMenuModel : public ui::MenuModel {
int GetGroupIdAt(int index) const override { return -1; }
ui::ImageModel GetIconAt(int index) const override {
- return ui::ImageModel();
+ return model_->GetDropDownIconAt(index);
}
ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
@@ -443,7 +466,7 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
void Combobox::OnPaint(gfx::Canvas* canvas) {
OnPaintBackground(canvas);
- PaintText(canvas);
+ PaintIconAndText(canvas);
OnPaintBorder(canvas);
}
@@ -504,16 +527,8 @@ void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
// TODO(hajimehoshi): Fix the problem that the arrow button blinks when
// cliking this while the dropdown menu is opened.
- const base::TimeDelta delta = base::TimeTicks::Now() - closed_time_;
- if (delta <= kMinimumTimeBetweenButtonClicks)
- return;
-
- ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
- if (event.IsKeyEvent())
- source_type = ui::MENU_SOURCE_KEYBOARD;
- else if (event.IsGestureEvent() || event.IsTouchEvent())
- source_type = ui::MENU_SOURCE_TOUCH;
- ShowDropDownMenu(source_type);
+ if ((base::TimeTicks::Now() - closed_time_) > kMinimumTimeBetweenButtonClicks)
+ ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event));
}
void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
@@ -543,7 +558,7 @@ void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
rect->set_x(GetMirroredXForRect(*rect));
}
-void Combobox::PaintText(gfx::Canvas* canvas) {
+void Combobox::PaintIconAndText(gfx::Canvas* canvas) {
gfx::Insets insets = GetInsets();
insets += gfx::Insets(0, LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING));
@@ -553,7 +568,20 @@ void Combobox::PaintText(gfx::Canvas* canvas) {
int x = insets.left();
int y = insets.top();
- int text_height = height() - insets.height();
+ int contents_height = height() - insets.height();
+
+ // Draw the icon.
+ ui::ImageModel icon = model()->GetIconAt(selected_index_);
+ if (!icon.IsEmpty()) {
+ gfx::ImageSkia icon_skia =
+ GetImageSkiaFromImageModel(&icon, GetNativeTheme());
+ int icon_y = y + (contents_height - icon_skia.height()) / 2;
+ canvas->DrawImageInt(icon_skia, x, icon_y);
+ x += icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
+ }
+
+ // Draw the text.
SkColor text_color = GetTextColorForEnableState(*this, GetEnabled());
DCHECK_GE(selected_index_, 0);
DCHECK_LT(selected_index_, model()->GetItemCount());
@@ -565,10 +593,10 @@ void Combobox::PaintText(gfx::Canvas* canvas) {
const gfx::FontList& font_list = GetFontList();
int text_width = gfx::GetStringWidth(text, font_list);
- if ((text_width + insets.width()) > disclosure_arrow_offset)
- text_width = disclosure_arrow_offset - insets.width();
+ text_width =
+ std::min(text_width, disclosure_arrow_offset - insets.right() - x);
- gfx::Rect text_bounds(x, y, text_width, text_height);
+ gfx::Rect text_bounds(x, y, text_width, contents_height);
AdjustBoundsForRTLUI(&text_bounds);
canvas->DrawStringRect(text, font_list, text_color, text_bounds);
@@ -581,21 +609,15 @@ void Combobox::PaintText(gfx::Canvas* canvas) {
}
void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
- // Menu border widths.
- constexpr int kMenuBorderWidthLeft = 1;
constexpr int kMenuBorderWidthTop = 1;
- constexpr int kMenuBorderWidthRight = 1;
-
+ // Menu's requested position's width should be the same as local bounds so the
+ // border of the menu lines up with the border of the combobox. The y
+ // coordinate however should be shifted to the bottom with the border with not
+ // to overlap with the combobox border.
gfx::Rect lb = GetLocalBounds();
gfx::Point menu_position(lb.origin());
-
- // Inset the menu's requested position so the border of the menu lines up
- // with the border of the combobox.
- menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
- lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
-
View::ConvertPointToScreen(this, &menu_position);
gfx::Rect bounds(menu_position, lb.size());
@@ -633,18 +655,27 @@ void Combobox::OnPerformAction() {
gfx::Size Combobox::GetContentSize() const {
const gfx::FontList& font_list = GetFontList();
-
+ int height = font_list.GetHeight();
int width = 0;
for (int i = 0; i < model()->GetItemCount(); ++i) {
if (model_->IsItemSeparatorAt(i))
continue;
if (size_to_largest_label_ || i == selected_index_) {
- width = std::max(
- width, gfx::GetStringWidth(menu_model_->GetLabelAt(i), font_list));
+ int item_width = gfx::GetStringWidth(model()->GetItemAt(i), font_list);
+ ui::ImageModel icon = model()->GetIconAt(i);
+ if (!icon.IsEmpty()) {
+ gfx::ImageSkia icon_skia =
+ GetImageSkiaFromImageModel(&icon, GetNativeTheme());
+ item_width +=
+ icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
+ height = std::max(height, icon_skia.height());
+ }
+ width = std::max(width, item_width);
}
}
- return gfx::Size(width, font_list.GetHeight());
+ return gfx::Size(width, height);
}
PrefixSelector* Combobox::GetPrefixSelector() {
diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h
index d6da07f7122..91ad60c9a14 100644
--- a/chromium/ui/views/controls/combobox/combobox.h
+++ b/chromium/ui/views/controls/combobox/combobox.h
@@ -126,7 +126,7 @@ class VIEWS_EXPORT Combobox : public View,
void AdjustBoundsForRTLUI(gfx::Rect* rect) const;
// Draws the selected value of the drop down list
- void PaintText(gfx::Canvas* canvas);
+ void PaintIconAndText(gfx::Canvas* canvas);
// Show the drop down list
void ShowDropDownMenu(ui::MenuSourceType source_type);
@@ -207,7 +207,7 @@ class VIEWS_EXPORT Combobox : public View,
bool size_to_largest_label_;
// The focus ring for this Combobox.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
ScopedObserver<ui::ComboboxModel, ui::ComboboxModelObserver> observer_{this};
diff --git a/chromium/ui/views/controls/combobox/combobox_unittest.cc b/chromium/ui/views/controls/combobox/combobox_unittest.cc
index 9f0698b0d0e..b081a0b7408 100644
--- a/chromium/ui/views/controls/combobox/combobox_unittest.cc
+++ b/chromium/ui/views/controls/combobox/combobox_unittest.cc
@@ -30,7 +30,9 @@
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/combobox_test_api.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
@@ -42,36 +44,7 @@ using test::ComboboxTestApi;
namespace {
-// A wrapper of Combobox to intercept the result of OnKeyPressed() and
-// OnKeyReleased() methods.
-class TestCombobox : public Combobox {
- public:
- explicit TestCombobox(ui::ComboboxModel* model)
- : Combobox(model), key_handled_(false), key_received_(false) {}
-
- bool OnKeyPressed(const ui::KeyEvent& e) override {
- key_received_ = true;
- key_handled_ = Combobox::OnKeyPressed(e);
- return key_handled_;
- }
-
- bool OnKeyReleased(const ui::KeyEvent& e) override {
- key_received_ = true;
- key_handled_ = Combobox::OnKeyReleased(e);
- return key_handled_;
- }
-
- bool key_handled() const { return key_handled_; }
- bool key_received() const { return key_received_; }
-
- void clear() { key_received_ = key_handled_ = false; }
-
- private:
- bool key_handled_;
- bool key_received_;
-
- DISALLOW_COPY_AND_ASSIGN(TestCombobox);
-};
+using TestCombobox = Combobox;
// A concrete class is needed to test the combobox.
class TestComboboxModel : public ui::ComboboxModel {
@@ -83,14 +56,14 @@ class TestComboboxModel : public ui::ComboboxModel {
// ui::ComboboxModel:
int GetItemCount() const override { return item_count_; }
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
if (IsItemSeparatorAt(index)) {
NOTREACHED();
return ASCIIToUTF16("SEPARATOR");
}
return ASCIIToUTF16(index % 2 == 0 ? "PEANUT BUTTER" : "JELLY");
}
- bool IsItemSeparatorAt(int index) override {
+ bool IsItemSeparatorAt(int index) const override {
return separators_.find(index) != separators_.end();
}
@@ -147,10 +120,10 @@ class VectorComboboxModel : public ui::ComboboxModel {
int GetItemCount() const override {
return static_cast<int>(values_->size());
}
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
return ASCIIToUTF16(values_->at(index));
}
- bool IsItemSeparatorAt(int index) override { return false; }
+ bool IsItemSeparatorAt(int index) const override { return false; }
int GetDefaultIndex() const override { return default_index_; }
void AddObserver(ui::ComboboxModelObserver* observer) override {
observers_.AddObserver(observer);
@@ -222,8 +195,7 @@ class ComboboxTest : public ViewsTestBase {
ComboboxTest() = default;
void TearDown() override {
- if (widget_)
- widget_->Close();
+ widget_.reset();
ViewsTestBase::TearDown();
}
@@ -234,26 +206,25 @@ class ComboboxTest : public ViewsTestBase {
model_->SetSeparators(*separators);
ASSERT_FALSE(combobox_);
- combobox_ = new TestCombobox(model_.get());
- test_api_ = std::make_unique<ComboboxTestApi>(combobox_);
+ auto combobox = std::make_unique<TestCombobox>(model_.get());
+ test_api_ = std::make_unique<ComboboxTestApi>(combobox.get());
test_api_->InstallTestMenuRunner(&menu_show_count_);
- combobox_->SetID(1);
+ combobox->SetID(1);
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(200, 200, 200, 200);
widget_->Init(std::move(params));
- View* container = new View();
- widget_->SetContentsView(container);
- container->AddChildView(combobox_);
+ View* container = widget_->SetContentsView(std::make_unique<View>());
+ combobox_ = container->AddChildView(std::move(combobox));
widget_->Show();
combobox_->RequestFocus();
combobox_->SizeToPreferredSize();
- event_generator_ =
- std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_));
+ event_generator_ = std::make_unique<ui::test::EventGenerator>(
+ GetRootWindow(widget_.get()));
event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW);
}
@@ -291,7 +262,7 @@ class ComboboxTest : public ViewsTestBase {
}
// We need widget to populate wrapper class.
- Widget* widget_ = nullptr;
+ UniqueWidgetPtr widget_;
// |combobox_| will be allocated InitCombobox() and then owned by |widget_|.
TestCombobox* combobox_ = nullptr;
@@ -356,23 +327,28 @@ TEST_F(ComboboxTest, KeyTestMac) {
}
#endif
+// Iterate through all the metadata and test each property.
+TEST_F(ComboboxTest, MetadataTest) {
+ InitCombobox(nullptr);
+ test::TestViewMetadata(combobox_);
+}
+
// Check that if a combobox is disabled before it has a native wrapper, then the
// native wrapper inherits the disabled state when it gets created.
TEST_F(ComboboxTest, DisabilityTest) {
model_ = std::make_unique<TestComboboxModel>();
ASSERT_FALSE(combobox_);
- combobox_ = new TestCombobox(model_.get());
- combobox_->SetEnabled(false);
+ auto combobox = std::make_unique<TestCombobox>(model_.get());
+ combobox->SetEnabled(false);
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(100, 100, 100, 100);
widget_->Init(std::move(params));
- View* container = new View();
- widget_->SetContentsView(container);
- container->AddChildView(combobox_);
+ View* container = widget_->SetContentsView(std::make_unique<View>());
+ combobox_ = container->AddChildView(std::move(combobox));
EXPECT_FALSE(combobox_->GetEnabled());
}
@@ -559,7 +535,7 @@ TEST_F(ComboboxTest, ListenerHandlesDelete) {
// |combobox| will be deleted on change.
TestCombobox* combobox = new TestCombobox(&model);
- std::unique_ptr<EvilListener> evil_listener(new EvilListener());
+ auto evil_listener = std::make_unique<EvilListener>();
combobox->set_listener(evil_listener.get());
ASSERT_NO_FATAL_FAILURE(ComboboxTestApi(combobox).PerformActionAt(2));
EXPECT_TRUE(evil_listener->deleted());
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
index 5f76ed0db20..b4c14a33505 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -37,7 +37,6 @@
#include "ui/gfx/range/range.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/scoped_canvas.h"
-#include "ui/native_theme/native_theme.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_host_view.h"
@@ -65,8 +64,7 @@ namespace {
class Arrow : public Button {
public:
- Arrow(const SkColor color, ButtonListener* listener)
- : Button(listener), color_(color) {
+ explicit Arrow(ButtonListener* listener) : Button(listener) {
// Similar to Combobox's TransparentButton.
SetFocusBehavior(FocusBehavior::NEVER);
button_controller()->set_notify_action(
@@ -93,8 +91,7 @@ class Arrow : public Button {
std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override {
return std::make_unique<views::FloodFillInkDropRipple>(
size(), GetInkDropCenterBasedOnLastEvent(),
- GetNativeTheme()->GetSystemColor(
- ui::NativeTheme::kColorId_LabelEnabledColor),
+ style::GetColor(*this, style::CONTEXT_TEXTFIELD, style::STYLE_PRIMARY),
ink_drop_visible_opacity());
}
@@ -104,7 +101,11 @@ class Arrow : public Button {
canvas->ClipRect(GetContentsBounds());
gfx::Rect arrow_bounds = GetLocalBounds();
arrow_bounds.ClampToCenteredSize(ComboboxArrowSize());
- PaintComboboxArrow(color_, arrow_bounds, canvas);
+ // Make sure the arrow use the same color as the text in the combobox.
+ PaintComboboxArrow(style::GetColor(*this, style::CONTEXT_TEXTFIELD,
+ GetEnabled() ? style::STYLE_PRIMARY
+ : style::STYLE_DISABLED),
+ arrow_bounds, canvas);
}
void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
@@ -115,8 +116,6 @@ class Arrow : public Button {
node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen);
}
- const SkColor color_;
-
DISALLOW_COPY_AND_ASSIGN(Arrow);
};
@@ -176,15 +175,10 @@ class EditableCombobox::EditableComboboxMenuModel
gfx::RenderText::kPasswordReplacementChar);
}
- //////////////////////////////////////////////////////////////////////////////
- // Overridden from ComboboxModelObserver:
void OnComboboxModelChanged(ui::ComboboxModel* model) override {
UpdateItemsShown();
}
- //////////////////////////////////////////////////////////////////////////////
- // Overridden from MenuModel:
-
int GetItemCount() const override { return items_shown_.size(); }
private:
@@ -309,8 +303,6 @@ class EditableCombobox::EditableComboboxPreTargetHandler
DISALLOW_COPY_AND_ASSIGN(EditableComboboxPreTargetHandler);
};
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, public, non-overridden methods:
EditableCombobox::EditableCombobox(
std::unique_ptr<ui::ComboboxModel> combobox_model,
const bool filter_on_edit,
@@ -341,8 +333,7 @@ EditableCombobox::EditableCombobox(
textfield_->SetExtraInsets(gfx::Insets(
/*top=*/0, /*left=*/0, /*bottom=*/0,
/*right=*/kComboboxArrowContainerWidth - kComboboxArrowPaddingWidth));
- arrow_ = new Arrow(textfield_->GetTextColor(), this);
- AddChildView(arrow_);
+ arrow_ = AddChildView(std::make_unique<Arrow>(this));
}
SetLayoutManager(std::make_unique<views::FillLayout>());
}
@@ -399,9 +390,6 @@ base::string16 EditableCombobox::GetItemForTest(int index) {
return menu_model_->GetItemTextAt(index, showing_password_text_);
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, View overrides:
-
void EditableCombobox::Layout() {
View::Layout();
if (arrow_) {
@@ -411,11 +399,6 @@ void EditableCombobox::Layout() {
}
}
-void EditableCombobox::OnThemeChanged() {
- View::OnThemeChanged();
- textfield_->OnThemeChanged();
-}
-
void EditableCombobox::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kComboBoxGrouping;
@@ -435,9 +418,6 @@ void EditableCombobox::OnVisibleBoundsChanged() {
CloseMenu();
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, TextfieldController overrides:
-
void EditableCombobox::ContentsChanged(Textfield* sender,
const base::string16& new_contents) {
HandleNewContent(new_contents);
@@ -455,32 +435,25 @@ bool EditableCombobox::HandleKeyEvent(Textfield* sender,
return false;
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, View overrides:
-
void EditableCombobox::OnViewBlurred(View* observed_view) {
CloseMenu();
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, ButtonListener overrides:
-
void EditableCombobox::ButtonPressed(Button* sender, const ui::Event& event) {
textfield_->RequestFocus();
- if (menu_runner_ && menu_runner_->IsRunning()) {
+ if (menu_runner_ && menu_runner_->IsRunning())
CloseMenu();
- return;
- }
- ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
- if (event.IsKeyEvent())
- source_type = ui::MENU_SOURCE_KEYBOARD;
- else if (event.IsGestureEvent() || event.IsTouchEvent())
- source_type = ui::MENU_SOURCE_TOUCH;
- ShowDropDownMenu(source_type);
+ else
+ ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event));
}
-////////////////////////////////////////////////////////////////////////////////
-// EditableCombobox, Private methods:
+void EditableCombobox::OnLayoutIsAnimatingChanged(
+ views::AnimatingLayoutManager* source,
+ bool is_animating) {
+ dropdown_blocked_for_animation_ = is_animating;
+ if (dropdown_blocked_for_animation_)
+ CloseMenu();
+}
void EditableCombobox::CloseMenu() {
menu_runner_.reset();
@@ -517,9 +490,10 @@ void EditableCombobox::HandleNewContent(const base::string16& new_content) {
}
void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
- constexpr int kMenuBorderWidthLeft = 1;
constexpr int kMenuBorderWidthTop = 1;
- constexpr int kMenuBorderWidthRight = 1;
+
+ if (dropdown_blocked_for_animation_)
+ return;
if (!menu_model_->GetItemCount()) {
CloseMenu();
@@ -540,13 +514,13 @@ void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
this, GetWidget()->GetRootView());
gfx::Rect local_bounds = textfield_->GetLocalBounds();
+
+ // Menu's requested position's width should be the same as local bounds so the
+ // border of the menu lines up with the border of the combobox. The y
+ // coordinate however should be shifted to the bottom with the border width
+ // not to overlap with the combobox border.
gfx::Point menu_position(local_bounds.origin());
- // Inset the menu's requested position so the border of the menu lines up
- // with the border of the textfield.
- menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
- local_bounds.set_width(local_bounds.width() -
- (kMenuBorderWidthLeft + kMenuBorderWidthRight));
View::ConvertPointToScreen(this, &menu_position);
gfx::Rect bounds(menu_position, local_bounds.size());
diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.h b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
index c0f6b26fad8..e4a0d3c3698 100644
--- a/chromium/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.h
@@ -14,6 +14,7 @@
#include "ui/base/ui_base_types.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/layout/animating_layout_manager.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
@@ -37,10 +38,12 @@ class MenuRunner;
class Textfield;
// Textfield that also shows a drop-down list with suggestions.
-class VIEWS_EXPORT EditableCombobox : public View,
- public TextfieldController,
- public ViewObserver,
- public ButtonListener {
+class VIEWS_EXPORT EditableCombobox
+ : public View,
+ public TextfieldController,
+ public ViewObserver,
+ public ButtonListener,
+ public views::AnimatingLayoutManager::Observer {
public:
METADATA_HEADER(EditableCombobox);
@@ -121,7 +124,6 @@ class VIEWS_EXPORT EditableCombobox : public View,
// Overridden from View:
void Layout() override;
- void OnThemeChanged() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
void RequestFocus() override;
bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
@@ -139,6 +141,10 @@ class VIEWS_EXPORT EditableCombobox : public View,
// Overridden from ButtonListener:
void ButtonPressed(Button* sender, const ui::Event& event) override;
+ // Overridden from views::AnimatingLayoutManager::Observer:
+ void OnLayoutIsAnimatingChanged(views::AnimatingLayoutManager* source,
+ bool is_animating) override;
+
Textfield* textfield_;
Button* arrow_ = nullptr;
std::unique_ptr<ui::ComboboxModel> combobox_model_;
@@ -171,6 +177,8 @@ class VIEWS_EXPORT EditableCombobox : public View,
// Type::kPassword.
bool showing_password_text_;
+ bool dropdown_blocked_for_animation_ = false;
+
ScopedObserver<View, ViewObserver> observer_{this};
DISALLOW_COPY_AND_ASSIGN(EditableCombobox);
diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc
index 443c199003c..0ee9512f5c3 100644
--- a/chromium/ui/views/controls/focus_ring.cc
+++ b/chromium/ui/views/controls/focus_ring.cc
@@ -8,6 +8,8 @@
#include <utility>
#include "base/memory/ptr_util.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/highlight_path_generator.h"
@@ -53,15 +55,15 @@ SkPath GetHighlightPathInternal(const View* view) {
} // namespace
// static
-std::unique_ptr<FocusRing> FocusRing::Install(View* parent) {
+FocusRing* FocusRing::Install(View* parent) {
auto ring = base::WrapUnique<FocusRing>(new FocusRing());
- ring->set_owned_by_client();
- parent->AddChildView(ring.get());
ring->InvalidateLayout();
ring->SchedulePaint();
- return ring;
+ return parent->AddChildView(std::move(ring));
}
+FocusRing::~FocusRing() = default;
+
void FocusRing::SetPathGenerator(
std::unique_ptr<HighlightPathGenerator> generator) {
path_generator_ = std::move(generator);
@@ -102,14 +104,14 @@ void FocusRing::ViewHierarchyChanged(
if (details.is_add) {
// Need to start observing the parent.
- details.parent->AddObserver(this);
- } else {
+ view_observer_.Add(details.parent);
+ RefreshLayer();
+ } else if (view_observer_.IsObserving(details.parent)) {
// This view is being removed from its parent. It needs to remove itself
- // from its parent's observer list. Otherwise, since its |parent_| will
- // become a nullptr, it won't be able to do so in its destructor.
- details.parent->RemoveObserver(this);
+ // from its parent's observer list in the case where the FocusView is
+ // removed from its parent but not deleted.
+ view_observer_.Remove(details.parent);
}
- RefreshLayer();
}
void FocusRing::OnPaint(gfx::Canvas* canvas) {
@@ -155,6 +157,12 @@ void FocusRing::OnPaint(gfx::Canvas* canvas) {
}
}
+void FocusRing::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+ // Mark the focus ring in the accessibility tree as invisible so that it will
+ // not be accessed by assistive technologies.
+ node_data->AddState(ax::mojom::State::kInvisible);
+}
+
void FocusRing::OnViewFocused(View* view) {
RefreshLayer();
}
@@ -168,11 +176,6 @@ FocusRing::FocusRing() {
set_can_process_events_within_subtree(false);
}
-FocusRing::~FocusRing() {
- if (parent())
- parent()->RemoveObserver(this);
-}
-
void FocusRing::RefreshLayer() {
// TODO(pbos): This always keeps the layer alive if |has_focus_predicate_| is
// set. This is done because we're not notified when the predicate might
diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h
index 9ac2f4ebeeb..c3e42b6a4da 100644
--- a/chromium/ui/views/controls/focus_ring.h
+++ b/chromium/ui/views/controls/focus_ring.h
@@ -19,38 +19,25 @@ namespace views {
class HighlightPathGenerator;
// FocusRing is a View that is designed to act as an indicator of focus for its
-// parent. It is a stand-alone view that paints to a layer which extends beyond
-// the bounds of its parent view.
-//
-// Using FocusRing looks something like this:
-//
-// class MyView : public View {
-// ...
-// private:
-// std::unique_ptr<FocusRing> focus_ring_;
-// };
-//
-// MyView::MyView() {
-// focus_ring_ = FocusRing::Install(this);
-// ...
-// }
-//
+// parent. It is a view that paints to a layer which extends beyond the bounds
+// of its parent view.
// If MyView should show a rounded rectangular focus ring when it has focus and
// hide the ring when it loses focus, no other configuration is necessary. In
// other cases, it might be necessary to use the Set*() functions on FocusRing;
// these take care of repainting it when the state changes.
+// TODO(tluk): FocusRing should not be a view but instead a new concept which
+// only participates in view painting ( https://crbug.com/840796 ).
class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
public:
METADATA_HEADER(FocusRing);
using ViewPredicate = std::function<bool(View* view)>;
- ~FocusRing() override;
-
// Create a FocusRing and adds it to |parent|. The returned focus ring is
- // owned by the client (the code calling FocusRing::Install), *not* by
- // |parent|.
- static std::unique_ptr<FocusRing> Install(View* parent);
+ // owned by the |parent|.
+ static FocusRing* Install(View* parent);
+
+ ~FocusRing() override;
// Sets the HighlightPathGenerator to draw this FocusRing around.
// Note: This method should only be used if the focus ring needs to differ
@@ -77,6 +64,7 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) override;
void OnPaint(gfx::Canvas* canvas) override;
+ void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
// ViewObserver:
void OnViewFocused(View* view) override;
@@ -108,6 +96,8 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver {
// The predicate used to determine whether the parent has focus.
base::Optional<ViewPredicate> has_focus_predicate_;
+ ScopedObserver<View, ViewObserver> view_observer_{this};
+
DISALLOW_COPY_AND_ASSIGN(FocusRing);
};
diff --git a/chromium/ui/views/controls/label.cc b/chromium/ui/views/controls/label.cc
index ad6af149e28..febce15ff86 100644
--- a/chromium/ui/views/controls/label.cc
+++ b/chromium/ui/views/controls/label.cc
@@ -108,6 +108,19 @@ int Label::GetTextContext() const {
return text_context_;
}
+int Label::GetTextStyle() const {
+ return text_style_;
+}
+
+void Label::SetTextStyle(int style) {
+ if (style == text_style_)
+ return;
+
+ text_style_ = style;
+ UpdateColorsFromTheme();
+ OnPropertyChanged(&text_style_, kPropertyEffectsPreferredSizeChanged);
+}
+
bool Label::GetAutoColorReadabilityEnabled() const {
return auto_color_readability_enabled_;
}
@@ -291,6 +304,14 @@ void Label::SetAllowCharacterBreak(bool allow_character_break) {
kPropertyEffectsLayout);
}
+size_t Label::GetTextIndexOfLine(size_t line) const {
+ return full_text_->GetTextIndexOfLine(line);
+}
+
+void Label::SetTruncateLength(size_t truncate_length) {
+ return full_text_->set_truncate_length(truncate_length);
+}
+
gfx::ElideBehavior Label::GetElideBehavior() const {
return elide_behavior_;
}
@@ -950,7 +971,6 @@ void Label::Init(const base::string16& text,
full_text_->SetCursorEnabled(false);
full_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS);
- UpdateColorsFromTheme();
SetText(text);
// Only selectable labels will get requests to show the context menu, due to
@@ -1102,8 +1122,9 @@ void Label::BuildContextMenuContents() {
BEGIN_METADATA(Label)
METADATA_PARENT_CLASS(View)
-ADD_PROPERTY_METADATA(Label, bool, AutoColorReadabilityEnabled)
ADD_PROPERTY_METADATA(Label, base::string16, Text)
+ADD_PROPERTY_METADATA(Label, int, TextStyle)
+ADD_PROPERTY_METADATA(Label, bool, AutoColorReadabilityEnabled)
ADD_PROPERTY_METADATA(Label, SkColor, EnabledColor)
ADD_PROPERTY_METADATA(Label, gfx::ElideBehavior, ElideBehavior)
ADD_PROPERTY_METADATA(Label, SkColor, BackgroundColor)
diff --git a/chromium/ui/views/controls/label.h b/chromium/ui/views/controls/label.h
index dc690b22dc2..5a45ef3cbf0 100644
--- a/chromium/ui/views/controls/label.h
+++ b/chromium/ui/views/controls/label.h
@@ -91,6 +91,11 @@ class VIEWS_EXPORT Label : public View,
// a value from views::style::TextContext or an enum that extends it.
int GetTextContext() const;
+ // The style of the label. This is a value from views::style::TextStyle or an
+ // enum that extends it.
+ int GetTextStyle() const;
+ void SetTextStyle(int style);
+
// Enables or disables auto-color-readability (enabled by default). If this
// is enabled, then calls to set any foreground or background color will
// trigger an automatic mapper that uses color_utils::BlendForMinContrast()
@@ -177,6 +182,13 @@ class VIEWS_EXPORT Label : public View,
bool GetAllowCharacterBreak() const;
void SetAllowCharacterBreak(bool allow_character_break);
+ // For the provided line index, gets the corresponding rendered line and
+ // returns the text position of the first character of that line.
+ size_t GetTextIndexOfLine(size_t line) const;
+
+ // Set the truncate length of the rendered text.
+ void SetTruncateLength(size_t truncate_length);
+
// Gets/Sets the eliding or fading behavior, applied as necessary. The default
// is to elide at the end. Eliding is not well-supported for multi-line
// labels.
@@ -377,7 +389,7 @@ class VIEWS_EXPORT Label : public View,
void BuildContextMenuContents();
const int text_context_;
- const int text_style_;
+ int text_style_;
// An un-elided and single-line RenderText object used for preferred sizing.
std::unique_ptr<gfx::RenderText> full_text_;
diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc
index 763a169b27e..33d969862cc 100644
--- a/chromium/ui/views/controls/label_unittest.cc
+++ b/chromium/ui/views/controls/label_unittest.cc
@@ -291,6 +291,11 @@ TEST_F(LabelTest, TextProperty) {
EXPECT_EQ(test_text, label()->GetText());
}
+TEST_F(LabelTest, TextStyleProperty) {
+ label()->SetTextStyle(views::style::STYLE_DISABLED);
+ EXPECT_EQ(views::style::STYLE_DISABLED, label()->GetTextStyle());
+}
+
TEST_F(LabelTest, ColorProperty) {
SkColor color = SkColorSetARGB(20, 40, 10, 5);
label()->SetAutoColorReadabilityEnabled(false);
diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc
index d9938916a53..87f9291b67d 100644
--- a/chromium/ui/views/controls/menu/menu_controller.cc
+++ b/chromium/ui/views/controls/menu/menu_controller.cc
@@ -621,6 +621,11 @@ bool MenuController::IsContextMenu() const {
return state_.context_menu;
}
+void MenuController::SelectItemAndOpenSubmenu(MenuItemView* item) {
+ DCHECK(item);
+ SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+}
+
bool MenuController::OnMousePressed(SubmenuView* source,
const ui::MouseEvent& event) {
// We should either have no current_mouse_event_target_, or should have a
@@ -1379,6 +1384,11 @@ void MenuController::SetSelection(MenuItemView* menu_item,
menu_item->GetType() != MenuItemView::Type::kSubMenu ||
(menu_item->GetType() == MenuItemView::Type::kActionableSubMenu &&
(selection_types & SELECTION_OPEN_SUBMENU) == 0))) {
+ // Before firing the selection event, ensure that focus appears to be
+ // within the popup. This is helpful for ATs on some platforms,
+ // specifically on Windows, where selection events in a list are mapped
+ // to focus events. Without this call, the focus appears to be elsewhere.
+ menu_item->GetViewAccessibility().SetPopupFocusOverride();
menu_item->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
// Notify an accessibility selected children changed event on the parent
// submenu.
@@ -1635,6 +1645,7 @@ MenuController::~MenuController() {
active_instance_ = nullptr;
StopShowTimer();
StopCancelAllTimer();
+ CHECK(!IsInObserverList());
}
bool MenuController::SendAcceleratorToHotTrackedView() {
@@ -3104,20 +3115,18 @@ void MenuController::SetNextHotTrackedView(
}
void MenuController::SetHotTrackedButton(Button* hot_button) {
- if (hot_button == hot_button_) {
- // Hot-tracked state may change outside of the MenuController. Correct it.
- if (hot_button && !hot_button->IsHotTracked()) {
- hot_button->SetHotTracked(true);
- hot_button->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
- }
- return;
- }
- if (hot_button_)
+ // If we're providing a new hot-tracked button, first remove the existing one.
+ if (hot_button_ && hot_button_ != hot_button) {
hot_button_->SetHotTracked(false);
+ hot_button_->GetViewAccessibility().EndPopupFocusOverride();
+ }
+
+ // Then set the new one.
hot_button_ = hot_button;
- if (hot_button) {
- hot_button->SetHotTracked(true);
- hot_button->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
+ if (hot_button_ && !hot_button_->IsHotTracked()) {
+ hot_button_->GetViewAccessibility().SetPopupFocusOverride();
+ hot_button_->SetHotTracked(true);
+ hot_button_->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
}
}
diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h
index 5fab7290f4b..2b3625c5b5a 100644
--- a/chromium/ui/views/controls/menu/menu_controller.h
+++ b/chromium/ui/views/controls/menu/menu_controller.h
@@ -125,7 +125,15 @@ class VIEWS_EXPORT MenuController
// WARNING: this may be NULL.
Widget* owner() { return owner_; }
- // Get the anchor position which is used to show this menu.
+ // Gets the most-current selected menu item, if any, including if the
+ // selection has not been committed yet.
+ views::MenuItemView* GetSelectedMenuItem() { return pending_state_.item; }
+
+ // Selects a menu-item and opens its sub-menu (if one exists) if not already
+ // so. Clears any selections within the submenu if it is already open.
+ void SelectItemAndOpenSubmenu(MenuItemView* item);
+
+ // Gets the anchor position which is used to show this menu.
MenuAnchorPosition GetAnchorPosition() { return state_.anchor; }
// Cancels the current Run. See ExitType for a description of what happens
diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
index 3176d01e445..67168197f0a 100644
--- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc
@@ -1125,6 +1125,27 @@ TEST_F(MenuControllerTest, PreviousSelectedItem) {
ResetSelection();
}
+// Tests that the APIs related to the current selected item work correctly.
+TEST_F(MenuControllerTest, CurrentSelectedItem) {
+ SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+
+ // Select the first menu-item.
+ DispatchKey(ui::VKEY_HOME);
+ EXPECT_EQ(pending_state_item(), menu_controller()->GetSelectedMenuItem());
+
+ // The API should let the submenu stay open if already so, but clear any
+ // selections within it.
+ EXPECT_TRUE(IsShowing());
+ EXPECT_EQ(1, pending_state_item()->GetCommand());
+ menu_controller()->SelectItemAndOpenSubmenu(menu_item());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_EQ(0, pending_state_item()->GetCommand());
+
+ // Clear references in menu controller to the menu item that is going away.
+ ResetSelection();
+}
+
// Tests that opening menu and calling SelectByChar works correctly.
TEST_F(MenuControllerTest, SelectByChar) {
SetComboboxType(MenuController::ComboboxType::kReadonly);
diff --git a/chromium/ui/views/controls/menu/menu_delegate.h b/chromium/ui/views/controls/menu/menu_delegate.h
index 2a03ff43f4a..158724b4752 100644
--- a/chromium/ui/views/controls/menu/menu_delegate.h
+++ b/chromium/ui/views/controls/menu/menu_delegate.h
@@ -8,7 +8,6 @@
#include <set>
#include <string>
-#include "base/logging.h"
#include "base/strings/string16.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/dragdrop/drag_drop_types.h"
diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc
index 0b9230bda55..fc94bfc7f17 100644
--- a/chromium/ui/views/controls/menu/menu_host.cc
+++ b/chromium/ui/views/controls/menu/menu_host.cc
@@ -103,6 +103,7 @@ MenuHost::MenuHost(SubmenuView* submenu)
MenuHost::~MenuHost() {
if (owner_)
owner_->RemoveObserver(this);
+ CHECK(!IsInObserverList());
}
void MenuHost::InitMenuHost(Widget* parent,
diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc
index d00618f37c3..262ea70539a 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view.cc
@@ -10,6 +10,7 @@
#include <algorithm>
#include <memory>
#include <numeric>
+#include <utility>
#include "base/containers/adapters.h"
#include "base/i18n/case_conversion.h"
@@ -45,12 +46,48 @@
#include "ui/views/style/typography.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view_class_properties.h"
+#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
+// Difference in the font size (in pixels) between menu label font and "new"
+// badge font size.
+constexpr int kNewBadgeFontSizeAdjustment = -1;
+
+// Space between primary text and "new" badge.
+constexpr int kNewBadgeHorizontalMargin = 8;
+
+// Highlight size around "new" badge.
+constexpr gfx::Insets kNewBadgeInternalPadding{4};
+
+// The corner radius of the rounded rect for the "new" badge.
+constexpr int kNewBadgeCornerRadius = 3;
+static_assert(kNewBadgeCornerRadius <= kNewBadgeInternalPadding.left(),
+ "New badge corner radius should not exceed padding.");
+
+// Returns the horizontal space required for the "new" badge.
+int GetNewBadgeRequiredWidth(const gfx::FontList& primary_font) {
+ const base::string16 new_text =
+ l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
+ gfx::FontList badge_font =
+ primary_font.DeriveWithSizeDelta(kNewBadgeFontSizeAdjustment);
+ return gfx::GetStringWidth(new_text, badge_font) +
+ kNewBadgeInternalPadding.width() + 2 * kNewBadgeHorizontalMargin;
+}
+
+// Returns the highlight rect for the "new" badge given the font and text rect
+// for the badge text.
+gfx::Rect GetNewBadgeRectOutsetAroundText(const gfx::FontList& badge_font,
+ const gfx::Rect& badge_text_rect) {
+ gfx::Rect badge_rect = badge_text_rect;
+ badge_rect.Inset(
+ -gfx::AdjustVisualBorderForFont(badge_font, kNewBadgeInternalPadding));
+ return badge_rect;
+}
+
// EmptyMenuMenuItem ---------------------------------------------------------
// EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem
@@ -261,6 +298,7 @@ MenuItemView* MenuItemView::AddMenuItemAt(
int index,
int item_id,
const base::string16& label,
+ const base::string16& secondary_label,
const base::string16& minor_text,
const ui::ThemedVectorIcon& minor_icon,
const gfx::ImageSkia& icon,
@@ -282,6 +320,7 @@ MenuItemView* MenuItemView::AddMenuItemAt(
item->SetTitle(GetDelegate()->GetLabel(item_id));
else
item->SetTitle(label);
+ item->SetSecondaryTitle(secondary_label);
item->SetMinorText(minor_text);
item->SetMinorIcon(minor_icon);
if (!vector_icon.empty()) {
@@ -337,6 +376,7 @@ void MenuItemView::AppendSeparator() {
void MenuItemView::AddSeparatorAt(int index) {
AddMenuItemAt(index, /*item_id=*/0, /*label=*/base::string16(),
+ /*secondary_label=*/base::string16(),
/*minor_text=*/base::string16(),
/*minor_icon=*/ui::ThemedVectorIcon(),
/*icon=*/gfx::ImageSkia(),
@@ -351,8 +391,8 @@ MenuItemView* MenuItemView::AppendMenuItemImpl(int item_id,
Type type) {
const int index = submenu_ ? int{submenu_->children().size()} : 0;
return AddMenuItemAt(index, item_id, label, base::string16(),
- ui::ThemedVectorIcon(), icon, ui::ThemedVectorIcon(),
- type, ui::NORMAL_SEPARATOR);
+ base::string16(), ui::ThemedVectorIcon(), icon,
+ ui::ThemedVectorIcon(), type, ui::NORMAL_SEPARATOR);
}
SubmenuView* MenuItemView::CreateSubmenu() {
@@ -383,6 +423,11 @@ void MenuItemView::SetTitle(const base::string16& title) {
invalidate_dimensions(); // Triggers preferred size recalculation.
}
+void MenuItemView::SetSecondaryTitle(const base::string16& secondary_title) {
+ secondary_title_ = secondary_title;
+ invalidate_dimensions(); // Triggers preferred size recalculation.
+}
+
void MenuItemView::SetMinorText(const base::string16& minor_text) {
minor_text_ = minor_text;
invalidate_dimensions(); // Triggers preferred size recalculation.
@@ -438,19 +483,16 @@ void MenuItemView::SetIcon(const gfx::ImageSkia& icon) {
void MenuItemView::SetIcon(const ui::ThemedVectorIcon& icon) {
vector_icon_ = icon;
+ UpdateIconViewFromVectorIconAndTheme();
}
void MenuItemView::UpdateIconViewFromVectorIconAndTheme() {
if (vector_icon_.empty())
return;
- if (!icon_view_)
- SetIconView(std::make_unique<ImageView>());
-
- const bool use_touchable_layout =
- GetMenuController() && GetMenuController()->use_touchable_layout();
- const int icon_size = use_touchable_layout ? 20 : 16;
- icon_view_->SetImage(vector_icon_.GetImageSkia(GetNativeTheme(), icon_size));
+ auto icon_view = std::make_unique<ImageView>();
+ icon_view->SetImage(vector_icon_.GetImageSkia(GetNativeTheme()));
+ SetIconView(std::move(icon_view));
}
void MenuItemView::SetIconView(std::unique_ptr<ImageView> icon_view) {
@@ -740,14 +782,17 @@ void MenuItemView::UpdateMenuPartSizes() {
icon_area_width_;
int padding = 0;
if (config.always_use_icon_to_label_padding) {
- padding = config.item_horizontal_padding;
+ padding = LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
} else if (!config.icons_in_label) {
padding = (has_icons_ || HasChecksOrRadioButtons())
- ? config.item_horizontal_padding
+ ? LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL)
: 0;
}
if (use_touchable_layout)
- padding = config.touchable_item_horizontal_padding;
+ padding = LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
label_start_ += padding;
@@ -904,17 +949,22 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
// selected.
PaintBackground(canvas, mode, render_selection);
- const int top_margin = GetTopMargin();
- const int bottom_margin = GetBottomMargin();
- const int available_height = height() - top_margin - bottom_margin;
-
// Calculate some colors.
MenuDelegate::LabelStyle style;
- style.foreground = GetTextColor(false, render_selection);
+ style.foreground = GetTextColor(/*minor=*/false, render_selection);
GetLabelStyle(&style);
SkColor icon_color = color_utils::DeriveDefaultIconColor(style.foreground);
+ // Calculate the margins.
+ int top_margin = GetTopMargin();
+ const int bottom_margin = GetBottomMargin();
+ const int available_height = height() - top_margin - bottom_margin;
+ const int text_height = style.font_list.GetHeight();
+ const int total_text_height =
+ secondary_title().empty() ? text_height : text_height * 2;
+ top_margin += (available_height - total_text_height) / 2;
+
// Render the check.
if (type_ == Type::kCheckbox && delegate->IsItemChecked(GetCommand())) {
radio_check_image_view_->SetImage(GetMenuCheckImage(icon_color));
@@ -937,15 +987,33 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
(!delegate || delegate->ShouldReserveSpaceForSubmenuIndicator()
? item_right_margin_
: config.arrow_to_edge_padding);
- gfx::Rect text_bounds(label_start, top_margin, width, available_height);
+ gfx::Rect text_bounds(label_start, top_margin, width, text_height);
text_bounds.set_x(GetMirroredXForRect(text_bounds));
int flags = GetDrawStringFlags();
if (mode == PaintButtonMode::kForDrag)
flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
canvas->DrawStringRectWithFlags(title(), style.font_list, style.foreground,
text_bounds, flags);
+
+ // The rest should be drawn with the minor foreground color.
+ style.foreground = GetTextColor(/*minor=*/true, render_selection);
+ if (!secondary_title().empty()) {
+ text_bounds.set_y(text_bounds.y() + text_height);
+ canvas->DrawStringRectWithFlags(secondary_title(), style.font_list,
+ style.foreground, text_bounds, flags);
+ }
+
PaintMinorIconAndText(canvas, style);
+ if (ShouldShowNewBadge()) {
+ DrawNewBadge(
+ canvas,
+ gfx::Point(label_start + gfx::GetStringWidth(title(), style.font_list) +
+ kNewBadgeHorizontalMargin,
+ top_margin),
+ style.font_list, flags);
+ }
+
// Set the submenu indicator (arrow) image and color.
if (HasSubmenu())
submenu_arrow_image_view_->SetImage(GetSubmenuArrowImage(icon_color));
@@ -1198,12 +1266,19 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const {
dimensions.standard_width = string_width + label_start + item_right_margin_;
// Determine the length of the right-side text.
dimensions.minor_text_width =
- minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, style.font_list);
+ (minor_text.empty() ? 0
+ : gfx::GetStringWidth(minor_text, style.font_list));
+
+ if (ShouldShowNewBadge())
+ dimensions.minor_text_width += GetNewBadgeRequiredWidth(style.font_list);
// Determine the height to use.
+ int label_text_height = secondary_title().empty()
+ ? style.font_list.GetHeight()
+ : style.font_list.GetHeight() * 2;
dimensions.height =
- std::max(dimensions.height, style.font_list.GetHeight() +
- GetBottomMargin() + GetTopMargin());
+ std::max(dimensions.height,
+ label_text_height + GetBottomMargin() + GetTopMargin());
dimensions.height =
std::max(dimensions.height, MenuConfig::instance().item_min_height);
@@ -1246,12 +1321,48 @@ int MenuItemView::GetLabelStartForThisItem() const {
if ((config.icons_in_label || type_ == Type::kCheckbox ||
type_ == Type::kRadio) &&
icon_view_) {
- label_start += icon_view_->size().width() + config.item_horizontal_padding;
+ label_start +=
+ icon_view_->size().width() + LayoutProvider::Get()->GetDistanceMetric(
+ DISTANCE_RELATED_LABEL_HORIZONTAL);
}
return label_start;
}
+void MenuItemView::DrawNewBadge(gfx::Canvas* canvas,
+ const gfx::Point& unmirrored_badge_start,
+ const gfx::FontList& primary_font,
+ int text_render_flags) {
+ gfx::FontList badge_font =
+ primary_font.DeriveWithSizeDelta(kNewBadgeFontSizeAdjustment);
+ const base::string16 new_text =
+ l10n_util::GetStringUTF16(IDS_MENU_ITEM_NEW_BADGE);
+
+ // Calculate bounding box for badge text.
+ gfx::Rect badge_text_bounds(unmirrored_badge_start,
+ gfx::GetStringSize(new_text, badge_font));
+ badge_text_bounds.Offset(
+ kNewBadgeInternalPadding.left(),
+ gfx::GetFontCapHeightCenterOffset(primary_font, badge_font));
+ if (base::i18n::IsRTL())
+ badge_text_bounds.set_x(GetMirroredXForRect(badge_text_bounds));
+
+ // Render the badge itself.
+ cc::PaintFlags new_flags;
+ const SkColor background_color = GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_ProminentButtonColor);
+ new_flags.setColor(background_color);
+ canvas->DrawRoundRect(
+ GetNewBadgeRectOutsetAroundText(badge_font, badge_text_bounds),
+ kNewBadgeCornerRadius, new_flags);
+
+ // Render the badge text.
+ const SkColor foreground_color = GetNativeTheme()->GetSystemColor(
+ ui::NativeTheme::kColorId_TextOnProminentButtonColor);
+ canvas->DrawStringRectWithFlags(new_text, badge_font, foreground_color,
+ badge_text_bounds, text_render_flags);
+}
+
base::string16 MenuItemView::GetMinorText() const {
if (GetID() == kEmptyMenuItemViewID) {
// Don't query the delegate for menus that represent no children.
@@ -1329,6 +1440,12 @@ bool MenuItemView::HasChecksOrRadioButtons() const {
[](const auto* item) { return item->HasChecksOrRadioButtons(); });
}
+bool MenuItemView::ShouldShowNewBadge() const {
+ static const bool feature_enabled =
+ base::FeatureList::IsEnabled(features::kEnableNewBadgeOnMenuItems);
+ return feature_enabled && is_new_;
+}
+
BEGIN_METADATA(MenuItemView)
METADATA_PARENT_CLASS(View)
END_METADATA()
diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h
index b38a048c22e..01b797549ac 100644
--- a/chromium/ui/views/controls/menu/menu_item_view.h
+++ b/chromium/ui/views/controls/menu/menu_item_view.h
@@ -5,11 +5,11 @@
#ifndef UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
#define UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
+#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
-#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
@@ -148,6 +148,7 @@ class VIEWS_EXPORT MenuItemView : public View {
MenuItemView* AddMenuItemAt(int index,
int item_id,
const base::string16& label,
+ const base::string16& secondary_label,
const base::string16& minor_text,
const ui::ThemedVectorIcon& minor_icon,
const gfx::ImageSkia& icon,
@@ -214,6 +215,11 @@ class VIEWS_EXPORT MenuItemView : public View {
void SetTitle(const base::string16& title);
const base::string16& title() const { return title_; }
+ // Sets/Gets the secondary title. When not empty, they are shown in the line
+ // below the title.
+ void SetSecondaryTitle(const base::string16& secondary_title);
+ const base::string16& secondary_title() const { return secondary_title_; }
+
// Sets the minor text.
void SetMinorText(const base::string16& minor_text);
@@ -264,6 +270,9 @@ class VIEWS_EXPORT MenuItemView : public View {
// Returns the command id of this item.
int GetCommand() const { return command_; }
+ void set_is_new(bool is_new) { is_new_ = is_new; }
+ bool is_new() const { return is_new_; }
+
// Paints the menu item.
void OnPaint(gfx::Canvas* canvas) override;
@@ -444,6 +453,13 @@ class VIEWS_EXPORT MenuItemView : public View {
// Get the horizontal position at which to draw the menu item's label.
int GetLabelStartForThisItem() const;
+ // Draws the "new" badge on |canvas|. |unmirrored_badge_start| is the
+ // upper-left corner of the badge, not mirrored for RTL.
+ void DrawNewBadge(gfx::Canvas* canvas,
+ const gfx::Point& unmirrored_badge_start,
+ const gfx::FontList& primary_font,
+ int text_render_flags);
+
// Used by MenuController to cache the menu position in use by the
// active menu.
MenuPosition actual_menu_position() const { return actual_menu_position_; }
@@ -475,6 +491,10 @@ class VIEWS_EXPORT MenuItemView : public View {
// Returns true if the menu has items with a checkbox or a radio button.
bool HasChecksOrRadioButtons() const;
+ // Returns whether or not a "new" badge should be shown on this menu item.
+ // Takes into account whether the badging feature is enabled.
+ bool ShouldShowNewBadge() const;
+
void invalidate_dimensions() { dimensions_.height = 0; }
bool is_dimensions_valid() const { return dimensions_.height > 0; }
@@ -505,16 +525,16 @@ class VIEWS_EXPORT MenuItemView : public View {
// Command id.
int command_ = 0;
+ // Whether the menu item should be badged as "New" (if badging is enabled) as
+ // a way to highlight a new feature for users.
+ bool is_new_ = false;
+
// Submenu, created via CreateSubmenu.
SubmenuView* submenu_ = nullptr;
- // Title.
base::string16 title_;
-
- // Minor text.
+ base::string16 secondary_title_;
base::string16 minor_text_;
-
- // Minor icon.
ui::ThemedVectorIcon minor_icon_;
// The icon used for |icon_view_| when a vector icon has been set instead of a
diff --git a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
index 119aa4a49c3..9c132d932b8 100644
--- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc
@@ -320,20 +320,37 @@ class MenuItemViewPaintUnitTest : public ViewsTestBase {
DISALLOW_COPY_AND_ASSIGN(MenuItemViewPaintUnitTest);
};
-// Provides assertion coverage for painting minor text and icons.
+// Provides assertion coverage for painting, secondary label, minor text and
+// icons.
TEST_F(MenuItemViewPaintUnitTest, MinorTextAndIconAssertionCoverage) {
- auto AddItem = [this](auto label, auto minor_label, auto minor_icon) {
+ auto AddItem = [this](auto label, auto secondary_label, auto minor_label,
+ auto minor_icon) {
menu_item_view()->AddMenuItemAt(
- 0, 1000, base::ASCIIToUTF16(label), minor_label, minor_icon,
- gfx::ImageSkia(), ui::ThemedVectorIcon(),
+ 0, 1000, base::ASCIIToUTF16(label), secondary_label, minor_label,
+ minor_icon, gfx::ImageSkia(), ui::ThemedVectorIcon(),
views::MenuItemView::Type::kNormal, ui::NORMAL_SEPARATOR);
};
- AddItem("No minor content", base::string16(), ui::ThemedVectorIcon());
- AddItem("Minor text only", base::ASCIIToUTF16("minor text"),
+ AddItem("No secondary label, no minor content", base::string16(),
+ base::string16(), ui::ThemedVectorIcon());
+ AddItem("No secondary label, minor text only", base::string16(),
+ base::ASCIIToUTF16("minor text"), ui::ThemedVectorIcon());
+ AddItem("No secondary label, minor icon only", base::string16(),
+ base::string16(), ui::ThemedVectorIcon(&views::kMenuCheckIcon));
+ AddItem("No secondary label, minor text and icon", base::string16(),
+ base::ASCIIToUTF16("minor text"),
+ ui::ThemedVectorIcon(&views::kMenuCheckIcon));
+ AddItem("Secondary label, no minor content",
+ base::ASCIIToUTF16("secondary label"), base::string16(),
ui::ThemedVectorIcon());
- AddItem("Minor icon only", base::string16(),
+ AddItem("Secondary label, minor text only",
+ base::ASCIIToUTF16("secondary label"),
+ base::ASCIIToUTF16("minor text"), ui::ThemedVectorIcon());
+ AddItem("Secondary label, minor icon only",
+ base::ASCIIToUTF16("secondary label"), base::string16(),
ui::ThemedVectorIcon(&views::kMenuCheckIcon));
- AddItem("Minor text and icon", base::ASCIIToUTF16("minor text"),
+ AddItem("Secondary label, minor text and icon",
+ base::ASCIIToUTF16("secondary label"),
+ base::ASCIIToUTF16("minor text"),
ui::ThemedVectorIcon(&views::kMenuCheckIcon));
menu_runner()->RunMenuAt(widget(), nullptr, gfx::Rect(),
diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc
index 79af9195b11..b0b1cf9a7a7 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc
@@ -99,16 +99,17 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
}
if (*type == MenuItemView::Type::kSeparator) {
- return menu->AddMenuItemAt(menu_index, item_id, base::string16(),
- base::string16(), ui::ThemedVectorIcon(),
- gfx::ImageSkia(), ui::ThemedVectorIcon(), *type,
- model->GetSeparatorTypeAt(model_index));
+ return menu->AddMenuItemAt(
+ menu_index, item_id, base::string16(), base::string16(),
+ base::string16(), ui::ThemedVectorIcon(), gfx::ImageSkia(),
+ ui::ThemedVectorIcon(), *type, model->GetSeparatorTypeAt(model_index));
}
ui::ImageModel icon = model->GetIconAt(model_index);
ui::ImageModel minor_icon = model->GetMinorIconAt(model_index);
- return menu->AddMenuItemAt(
+ auto* menu_item_view = menu->AddMenuItemAt(
menu_index, item_id, model->GetLabelAt(model_index),
+ model->GetSecondaryLabelAt(model_index),
model->GetMinorTextAt(model_index),
minor_icon.IsVectorIcon()
? ui::ThemedVectorIcon(minor_icon.GetVectorIcon())
@@ -117,6 +118,12 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model,
icon.IsVectorIcon() ? ui::ThemedVectorIcon(icon.GetVectorIcon())
: ui::ThemedVectorIcon(),
*type, ui::NORMAL_SEPARATOR);
+
+ if (model->IsAlertedAt(model_index))
+ menu_item_view->SetAlerted();
+ menu_item_view->set_is_new(model->IsNewFeatureAt(model_index));
+
+ return menu_item_view;
}
// Static.
diff --git a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
index 6241a9d7916..111b070d725 100644
--- a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
+++ b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc
@@ -78,6 +78,12 @@ class MenuModelBase : public ui::MenuModel {
bool IsVisibleAt(int index) const override { return items_[index].visible; }
+ bool IsAlertedAt(int index) const override { return items_[index].alerted; }
+
+ bool IsNewFeatureAt(int index) const override {
+ return items_[index].new_feature;
+ }
+
MenuModel* GetSubmenuModelAt(int index) const override {
return items_[index].submenu;
}
@@ -117,6 +123,8 @@ class MenuModelBase : public ui::MenuModel {
ui::MenuModel* submenu;
bool enabled;
bool visible;
+ bool alerted = false;
+ bool new_feature = false;
};
const Item& GetItemDefinition(size_t index) { return items_[index]; }
@@ -142,6 +150,7 @@ class SubmenuModel : public MenuModelBase {
SubmenuModel() : MenuModelBase(kSubmenuIdBase) {
items_.emplace_back(TYPE_COMMAND, "submenu item 0", nullptr, false, true);
items_.emplace_back(TYPE_COMMAND, "submenu item 1", nullptr);
+ items_[1].alerted = true;
}
~SubmenuModel() override = default;
@@ -155,6 +164,7 @@ class ActionableSubmenuModel : public MenuModelBase {
ActionableSubmenuModel() : MenuModelBase(kActionableSubmenuIdBase) {
items_.emplace_back(TYPE_COMMAND, "actionable submenu item 0", nullptr);
items_.emplace_back(TYPE_COMMAND, "actionable submenu item 1", nullptr);
+ items_[1].new_feature = true;
}
~ActionableSubmenuModel() override = default;
@@ -246,6 +256,12 @@ void CheckSubmenu(const RootModel& model,
// Check visibility.
EXPECT_EQ(model_item.visible, item->GetVisible());
+ // Check alert state.
+ EXPECT_EQ(model_item.alerted, item->is_alerted());
+
+ // Check new feature flag.
+ EXPECT_EQ(model_item.new_feature, item->is_new());
+
// Check activation.
static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id);
EXPECT_EQ(i, size_t{submodel->last_activation()});
@@ -324,6 +340,12 @@ TEST_F(MenuModelAdapterTest, BasicTest) {
// Check visibility.
EXPECT_EQ(model_item.visible, item->GetVisible());
+ // Check alert state.
+ EXPECT_EQ(model_item.alerted, item->is_alerted());
+
+ // Check new feature flag.
+ EXPECT_EQ(model_item.new_feature, item->is_new());
+
// Check activation.
static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
EXPECT_EQ(i, size_t{model.last_activation()});
diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc
index 413a1de18f9..28a9c908988 100644
--- a/chromium/ui/views/controls/menu/submenu_view.cc
+++ b/chromium/ui/views/controls/menu/submenu_view.cc
@@ -16,6 +16,7 @@
#include "ui/events/event.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
+#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_host.h"
@@ -396,8 +397,12 @@ void SubmenuView::ShowAt(Widget* parent,
host_->InitMenuHost(parent, bounds, scroll_view_container_, do_capture);
}
- GetScrollViewContainer()->NotifyAccessibilityEvent(
- ax::mojom::Event::kMenuStart, true);
+ // Only fire kMenuStart for the top level menu, not for each submenu.
+ if (!GetMenuItem()->GetParentMenuItem()) {
+ GetScrollViewContainer()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kMenuStart, true);
+ }
+ // Fire kMenuPopupStart for each menu/submenu that is shown.
NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupStart, true);
}
@@ -408,14 +413,6 @@ void SubmenuView::Reposition(const gfx::Rect& bounds) {
void SubmenuView::Close() {
if (host_) {
- // We send the event to the ScrollViewContainer first because the View
- // accessibility delegate sets up a focus override when receiving the
- // kMenuStart event that we want to be disabled when we send the
- // kMenuPopupEnd event in order to access the previously focused node.
- GetScrollViewContainer()->NotifyAccessibilityEvent(
- ax::mojom::Event::kMenuEnd, true);
- NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupEnd, true);
-
host_->DestroyMenuHost();
host_ = nullptr;
}
@@ -423,8 +420,22 @@ void SubmenuView::Close() {
void SubmenuView::Hide() {
if (host_) {
+ /// -- Fire accessibility events ----
+ // Both of these must be fired before HideMenuHost().
+ // Only fire kMenuStart for as top levels menu closes, not for each submenu.
+ // This is sent before kMenuPopupEnd to allow ViewAXPlatformNodeDelegate to
+ // remove its focus override before AXPlatformNodeAuraLinux needs to access
+ // the previously-focused node while handling kMenuPopupEnd.
+ if (!GetMenuItem()->GetParentMenuItem()) {
+ GetScrollViewContainer()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kMenuEnd, true);
+ GetViewAccessibility().EndPopupFocusOverride();
+ }
+ // Fire these kMenuPopupEnd for each menu/submenu that closes/hides.
+ if (host_->IsVisible())
+ NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupEnd, true);
+
host_->HideMenuHost();
- NotifyAccessibilityEvent(ax::mojom::Event::kMenuPopupHide, true);
}
if (scroll_animator_->is_scrolling())
diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc
index 704855a3897..aa0d2fe8423 100644
--- a/chromium/ui/views/controls/prefix_selector.cc
+++ b/chromium/ui/views/controls/prefix_selector.cc
@@ -171,6 +171,15 @@ bool PrefixSelector::SetCompositionFromExistingText(
}
#endif
+#if defined(OS_CHROMEOS)
+bool PrefixSelector::SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) {
+ // TODO(crbug.com/1091088) Implement setAutocorrectRange.
+ NOTIMPLEMENTED_LOG_ONCE();
+ return false;
+}
+#endif
+
#if defined(OS_WIN)
void PrefixSelector::SetActiveCompositionForAccessibility(
const gfx::Range& range,
diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h
index d61f6cf2ed8..12dffcc111b 100644
--- a/chromium/ui/views/controls/prefix_selector.h
+++ b/chromium/ui/views/controls/prefix_selector.h
@@ -82,6 +82,11 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient {
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
#endif
+#if defined(OS_CHROMEOS)
+ bool SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) override;
+#endif
+
#if defined(OS_WIN)
void GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
diff --git a/chromium/ui/views/controls/scroll_view.h b/chromium/ui/views/controls/scroll_view.h
index 6df7bca1027..54b1e11e5ce 100644
--- a/chromium/ui/views/controls/scroll_view.h
+++ b/chromium/ui/views/controls/scroll_view.h
@@ -278,7 +278,7 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
const bool scroll_with_layers_enabled_;
// The focus ring for this ScrollView.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(ScrollView);
};
diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h
index 68e804b5d06..4f102e97a09 100644
--- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h
+++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h
@@ -11,7 +11,6 @@
#import "components/remote_cocoa/app_shim/views_scrollbar_bridge.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/gfx/animation/slide_animation.h"
-#include "ui/gfx/mac/cocoa_scrollbar_painter.h"
#include "ui/views/controls/scrollbar/scroll_bar.h"
#include "ui/views/views_export.h"
@@ -63,7 +62,7 @@ class VIEWS_EXPORT CocoaScrollBar : public ScrollBar,
bool IsScrollbarFullyHidden() const;
// Get the parameters for painting.
- gfx::CocoaScrollbarPainter::Params GetPainterParams() const;
+ ui::NativeTheme::ExtraParams GetPainterParams() const;
protected:
// ScrollBar:
diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
index 1b4d5a5d129..05960d9ad3e 100644
--- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
+++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm
@@ -15,8 +15,6 @@
#include "ui/gfx/canvas.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
-using gfx::CocoaScrollbarPainter;
-
namespace views {
namespace {
@@ -100,9 +98,14 @@ gfx::Size CocoaScrollBarThumb::CalculatePreferredSize() const {
void CocoaScrollBarThumb::OnPaint(gfx::Canvas* canvas) {
auto params = cocoa_scroll_bar_->GetPainterParams();
// Set the hover state based only on the thumb.
- params.hovered = IsStateHovered() || IsStatePressed();
- CocoaScrollbarPainter::PaintThumb(
- canvas->sk_canvas(), gfx::RectToSkIRect(GetLocalBounds()), params);
+ params.scrollbar_extra.is_hovering = IsStateHovered() || IsStatePressed();
+ ui::NativeTheme::Part thumb_part =
+ params.scrollbar_extra.orientation ==
+ ui::NativeTheme::ScrollbarOrientation::kHorizontal
+ ? ui::NativeTheme::kScrollbarHorizontalThumb
+ : ui::NativeTheme::kScrollbarVerticalThumb;
+ GetNativeTheme()->Paint(canvas->sk_canvas(), thumb_part,
+ ui::NativeTheme::kNormal, GetLocalBounds(), params);
}
bool CocoaScrollBarThumb::OnMousePressed(const ui::MouseEvent& event) {
@@ -208,9 +211,14 @@ void CocoaScrollBar::OnPaint(gfx::Canvas* canvas) {
auto params = GetPainterParams();
// Transparency of the track is handled by the View opacity, so always draw
// using the non-overlay path.
- params.overlay = false;
- CocoaScrollbarPainter::PaintTrack(
- canvas->sk_canvas(), gfx::RectToSkIRect(GetLocalBounds()), params);
+ params.scrollbar_extra.is_overlay = false;
+ ui::NativeTheme::Part track_part =
+ params.scrollbar_extra.orientation ==
+ ui::NativeTheme::ScrollbarOrientation::kHorizontal
+ ? ui::NativeTheme::kScrollbarHorizontalTrack
+ : ui::NativeTheme::kScrollbarVerticalTrack;
+ GetNativeTheme()->Paint(canvas->sk_canvas(), track_part,
+ ui::NativeTheme::kNormal, GetLocalBounds(), params);
}
bool CocoaScrollBar::CanProcessEventsWithinSubtree() const {
@@ -400,16 +408,20 @@ bool CocoaScrollBar::IsScrollbarFullyHidden() const {
return layer()->opacity() == 0.0f;
}
-CocoaScrollbarPainter::Params CocoaScrollBar::GetPainterParams() const {
- CocoaScrollbarPainter::Params params;
- if (IsHorizontal())
- params.orientation = CocoaScrollbarPainter::Orientation::kHorizontal;
- else if (base::i18n::IsRTL())
- params.orientation = CocoaScrollbarPainter::Orientation::kVerticalOnLeft;
- else
- params.orientation = CocoaScrollbarPainter::Orientation::kVerticalOnRight;
- params.overlay = GetScrollerStyle() == NSScrollerStyleOverlay;
- params.dark_mode = GetNativeTheme()->ShouldUseDarkColors();
+ui::NativeTheme::ExtraParams CocoaScrollBar::GetPainterParams() const {
+ ui::NativeTheme::ExtraParams params;
+ if (IsHorizontal()) {
+ params.scrollbar_extra.orientation =
+ ui::NativeTheme::ScrollbarOrientation::kHorizontal;
+ } else if (base::i18n::IsRTL()) {
+ params.scrollbar_extra.orientation =
+ ui::NativeTheme::ScrollbarOrientation::kVerticalOnLeft;
+ } else {
+ params.scrollbar_extra.orientation =
+ ui::NativeTheme::ScrollbarOrientation::kVerticalOnRight;
+ }
+ params.scrollbar_extra.is_overlay =
+ GetScrollerStyle() == NSScrollerStyleOverlay;
return params;
}
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 4fdef37e512..50ecb7c042b 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -510,6 +510,11 @@ TabbedPane::TabbedPane(TabbedPane::Orientation orientation,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded));
contents_->SetLayoutManager(std::make_unique<views::FillLayout>());
+
+ // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
+ AddAccelerator(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
+ AddAccelerator(ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN));
}
TabbedPane::~TabbedPane() = default;
@@ -600,16 +605,6 @@ bool TabbedPane::MoveSelectionBy(int delta) {
return true;
}
-void TabbedPane::ViewHierarchyChanged(
- const ViewHierarchyChangedDetails& details) {
- if (details.is_add) {
- // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
- AddAccelerator(
- ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
- AddAccelerator(ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN));
- }
-}
-
bool TabbedPane::AcceleratorPressed(const ui::Accelerator& accelerator) {
// Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages.
DCHECK(accelerator.key_code() == ui::VKEY_TAB && accelerator.IsCtrlDown());
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h
index 4c42c238ecb..97553af580f 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h
@@ -123,8 +123,6 @@ class VIEWS_EXPORT TabbedPane : public View {
bool MoveSelectionBy(int delta);
// Overridden from View:
- void ViewHierarchyChanged(
- const ViewHierarchyChangedDetails& details) override;
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
index 808afa4d777..6bb23566546 100644
--- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
+++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc
@@ -121,7 +121,7 @@ class TabbedPaneWithWidgetTest : public ViewsTestBase {
params.bounds = gfx::Rect(0, 0, 650, 650);
widget_->Init(std::move(params));
tabbed_pane_ = tabbed_pane.get();
- widget_->SetContentsView(tabbed_pane.release());
+ widget_->SetContentsView(std::move(tabbed_pane));
}
void TearDown() override {
diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc
index 94eb7a0527c..fa791c4700c 100644
--- a/chromium/ui/views/controls/table/table_view.cc
+++ b/chromium/ui/views/controls/table/table_view.cc
@@ -183,6 +183,8 @@ TableView::TableView(ui::TableModel* model,
SetModel(model);
if (model_)
UpdateVirtualAccessibilityChildren();
+
+ focus_ring_ = FocusRing::Install(this);
}
TableView::~TableView() {
diff --git a/chromium/ui/views/controls/table/table_view.h b/chromium/ui/views/controls/table/table_view.h
index 1a640fdf53e..30f1b107602 100644
--- a/chromium/ui/views/controls/table/table_view.h
+++ b/chromium/ui/views/controls/table/table_view.h
@@ -13,7 +13,6 @@
#include "ui/base/models/table_model.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/gfx/font_list.h"
-#include "ui/views/controls/focus_ring.h"
#include "ui/views/view.h"
#include "ui/views/views_export.h"
@@ -396,7 +395,7 @@ class VIEWS_EXPORT TableView : public views::View,
int active_visible_column_index_ = -1;
// Used to draw a focus indicator around the active cell.
- std::unique_ptr<FocusRing> focus_ring_ = FocusRing::Install(this);
+ FocusRing* focus_ring_ = nullptr;
// The header. This is only created if more than one column is specified or
// the first column has a non-empty title.
diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc
index d8e8fd0ace0..d0c5e12a9d1 100644
--- a/chromium/ui/views/controls/textfield/textfield.cc
+++ b/chromium/ui/views/controls/textfield/textfield.cc
@@ -65,8 +65,8 @@
#endif
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
-#include "ui/base/ime/linux/text_edit_command_auralinux.h"
-#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
+#include "ui/base/ime/linux/text_edit_command_auralinux.h" // nogncheck
+#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h" // nogncheck
#endif
#if defined(USE_X11)
@@ -330,6 +330,12 @@ Textfield::Textfield()
AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
AddAccelerator(ui::Accelerator(ui::VKEY_V, ui::EF_CONTROL_DOWN));
#endif
+
+ // Sometimes there are additional ignored views, such as the View representing
+ // the cursor, inside the text field. These should always be ignored by
+ // accessibility since a plain text field should always be a leaf node in the
+ // accessibility trees of all the platforms we support.
+ GetViewAccessibility().OverrideIsLeaf(true);
}
Textfield::~Textfield() {
@@ -677,8 +683,10 @@ gfx::Size Textfield::GetMinimumSize() const {
void Textfield::SetBorder(std::unique_ptr<Border> b) {
use_focus_ring_ = false;
- if (focus_ring_)
- focus_ring_.reset();
+ if (focus_ring_) {
+ RemoveChildViewT(focus_ring_);
+ focus_ring_ = nullptr;
+ }
View::SetBorder(std::move(b));
}
@@ -1025,6 +1033,7 @@ void Textfield::OnDragDone() {
void Textfield::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kTextField;
+
if (label_ax_id_) {
node_data->AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
{label_ax_id_});
@@ -1427,7 +1436,7 @@ bool Textfield::GetAcceleratorForCommandId(int command_id,
void Textfield::ExecuteCommand(int command_id, int event_flags) {
if (text_services_context_menu_ &&
text_services_context_menu_->SupportsCommand(command_id)) {
- text_services_context_menu_->ExecuteCommand(command_id);
+ text_services_context_menu_->ExecuteCommand(command_id, event_flags);
return;
}
@@ -1786,8 +1795,11 @@ ukm::SourceId Textfield::GetClientSourceForMetrics() const {
}
bool Textfield::ShouldDoLearning() {
- // TODO(https://crbug.com/311180): Implement this method.
- NOTIMPLEMENTED_LOG_ONCE();
+ if (should_do_learning_.has_value())
+ return should_do_learning_.value();
+
+ NOTIMPLEMENTED_LOG_ONCE() << "A Textfield does not support ShouldDoLearning";
+ DVLOG(1) << "This Textfield instance does not support ShouldDoLearning";
return false;
}
@@ -1806,6 +1818,14 @@ bool Textfield::SetCompositionFromExistingText(
}
#endif
+#if defined(OS_CHROMEOS)
+bool Textfield::SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) {
+ // TODO(crbug.com/1091088) Implement autocorrect range textfield handling.
+ return false;
+}
+#endif
+
#if defined(OS_WIN)
void Textfield::GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h
index 825c45513cf..775ce4f9683 100644
--- a/chromium/ui/views/controls/textfield/textfield.h
+++ b/chromium/ui/views/controls/textfield/textfield.h
@@ -377,12 +377,20 @@ class VIEWS_EXPORT Textfield : public View,
ukm::SourceId GetClientSourceForMetrics() const override;
bool ShouldDoLearning() override;
+ // Set whether the text should be used to improve typing suggestions.
+ void SetShouldDoLearning(bool value) { should_do_learning_ = value; }
+
#if defined(OS_WIN) || defined(OS_CHROMEOS)
bool SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
#endif
+#if defined(OS_CHROMEOS)
+ bool SetAutocorrectRange(const base::string16& autocorrect_text,
+ const gfx::Range& range) override;
+#endif
+
#if defined(OS_WIN)
void GetActiveTextInputControlLayoutBounds(
base::Optional<gfx::Rect>* control_bounds,
@@ -651,6 +659,9 @@ class VIEWS_EXPORT Textfield : public View,
// True if this textfield should use a focus ring to indicate focus.
bool use_focus_ring_ = true;
+ // Whether the text should be used to improve typing suggestions.
+ base::Optional<bool> should_do_learning_;
+
// Context menu related members.
std::unique_ptr<ui::SimpleMenuModel> context_menu_contents_;
std::unique_ptr<ViewsTextServicesContextMenu> text_services_context_menu_;
@@ -669,7 +680,7 @@ class VIEWS_EXPORT Textfield : public View,
ui::TextInputClient::FOCUS_REASON_NONE;
// The focus ring for this TextField.
- std::unique_ptr<FocusRing> focus_ring_;
+ FocusRing* focus_ring_ = nullptr;
// The password char reveal index, for testing only.
int password_char_reveal_index_ = -1;
diff --git a/chromium/ui/views/controls/textfield/textfield_test_api.cc b/chromium/ui/views/controls/textfield/textfield_test_api.cc
index 099f22e9bcf..40176fa3a73 100644
--- a/chromium/ui/views/controls/textfield/textfield_test_api.cc
+++ b/chromium/ui/views/controls/textfield/textfield_test_api.cc
@@ -5,7 +5,6 @@
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/gfx/geometry/rect.h"
-#include "ui/views/controls/views_text_services_context_menu.h"
namespace views {
@@ -34,12 +33,6 @@ void TextfieldTestApi::SetCursorViewRect(gfx::Rect bounds) {
textfield_->cursor_view_->SetBoundsRect(bounds);
}
-bool TextfieldTestApi::IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection direction) const {
- return ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
- textfield_->text_services_context_menu_.get(), direction);
-}
-
bool TextfieldTestApi::ShouldShowCursor() const {
return textfield_->ShouldShowCursor();
}
diff --git a/chromium/ui/views/controls/textfield/textfield_test_api.h b/chromium/ui/views/controls/textfield/textfield_test_api.h
index 3f346da6059..78436bcb5a4 100644
--- a/chromium/ui/views/controls/textfield/textfield_test_api.h
+++ b/chromium/ui/views/controls/textfield/textfield_test_api.h
@@ -5,8 +5,6 @@
#ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_TEST_API_H_
#define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_TEST_API_H_
-#include "base/i18n/rtl.h"
-#include "base/macros.h"
#include "ui/views/controls/textfield/textfield.h"
namespace views {
@@ -15,6 +13,9 @@ namespace views {
class TextfieldTestApi {
public:
explicit TextfieldTestApi(Textfield* textfield);
+ TextfieldTestApi(const TextfieldTestApi&) = delete;
+ TextfieldTestApi& operator=(const TextfieldTestApi&) = delete;
+ ~TextfieldTestApi() = default;
void UpdateContextMenu();
@@ -53,15 +54,10 @@ class TextfieldTestApi {
return textfield_->cursor_view_->GetVisible();
}
- bool IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection direction) const;
-
bool ShouldShowCursor() const;
private:
Textfield* textfield_;
-
- DISALLOW_COPY_AND_ASSIGN(TextfieldTestApi);
};
} // namespace views
diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc
index 456cecaba14..5a25faebf58 100644
--- a/chromium/ui/views/controls/textfield/textfield_unittest.cc
+++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc
@@ -64,7 +64,7 @@
#endif
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
-#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
+#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h" // nogncheck
#endif
#if defined(OS_CHROMEOS)
@@ -74,6 +74,7 @@
#if defined(OS_MACOSX)
#include "ui/base/cocoa/secure_password_input.h"
+#include "ui/base/cocoa/text_services_context_menu.h"
#endif
using base::ASCIIToUTF16;
@@ -459,8 +460,7 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController {
widget_->Init(std::move(params));
input_method_->SetDelegate(
test::WidgetTest::GetInputMethodDelegateForWidget(widget_));
- View* container = new View();
- widget_->SetContentsView(container);
+ View* container = widget_->SetContentsView(std::make_unique<View>());
container->AddChildView(textfield_);
textfield_->SetBoundsRect(params.bounds);
textfield_->SetID(1);
@@ -1395,6 +1395,17 @@ TEST_F(TextfieldTest, TextInputType_InsertionTest) {
textfield_->GetText());
}
+TEST_F(TextfieldTest, ShouldDoLearning) {
+ InitTextfield();
+
+ // Defaults to false.
+ EXPECT_EQ(false, textfield_->ShouldDoLearning());
+
+ // The value can be set.
+ textfield_->SetShouldDoLearning(true);
+ EXPECT_EQ(true, textfield_->ShouldDoLearning());
+}
+
TEST_F(TextfieldTest, TextInputType) {
InitTextfield();
@@ -3478,13 +3489,12 @@ TEST_F(TextfieldTest, TextfieldBoundsChangeTest) {
TEST_F(TextfieldTest, TextfieldInitialization) {
TestTextfield* new_textfield = new TestTextfield();
new_textfield->set_controller(this);
- View* container = new View();
Widget* widget(new Widget());
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(100, 100, 100, 100);
widget->Init(std::move(params));
- widget->SetContentsView(container);
+ View* container = widget->SetContentsView(std::make_unique<View>());
container->AddChildView(new_textfield);
new_textfield->SetBoundsRect(params.bounds);
@@ -3624,56 +3634,23 @@ TEST_F(TextfieldTest, TextServicesContextMenuTextDirectionTest) {
base::i18n::TextDirection::LEFT_TO_RIGHT);
test_api_->UpdateContextMenu();
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::UNKNOWN_DIRECTION));
- EXPECT_TRUE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::LEFT_TO_RIGHT));
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::RIGHT_TO_LEFT));
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionDefault));
+ EXPECT_TRUE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionLtr));
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionRtl));
textfield_->ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection::RIGHT_TO_LEFT);
test_api_->UpdateContextMenu();
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::UNKNOWN_DIRECTION));
- EXPECT_FALSE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::LEFT_TO_RIGHT));
- EXPECT_TRUE(test_api_->IsTextDirectionCheckedInContextMenu(
- base::i18n::TextDirection::RIGHT_TO_LEFT));
-}
-
-// Tests to see if the look up item is updated when the textfield's selected
-// text has changed.
-TEST_F(TextfieldTest, LookUpItemUpdate) {
- InitTextfield();
- EXPECT_TRUE(textfield_->context_menu_controller());
-
- const base::string16 kTextOne = ASCIIToUTF16("crake");
- textfield_->SetText(kTextOne);
- textfield_->SelectAll(false);
-
- ui::MenuModel* context_menu = GetContextMenuModel();
- EXPECT_TRUE(context_menu);
- EXPECT_GT(context_menu->GetItemCount(), 0);
- EXPECT_EQ(context_menu->GetLabelAt(0),
- l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kTextOne));
-
-#if !defined(OS_MACOSX)
- // Mac context menus don't behave this way: it's not possible to update the
- // text while the menu is still "open", but also the selection can't change
- // while the menu is open (because the user can't interact with the rest of
- // the app).
- const base::string16 kTextTwo = ASCIIToUTF16("rail");
- textfield_->SetText(kTextTwo);
- textfield_->SelectAll(false);
-
- context_menu = GetContextMenuModel();
- EXPECT_TRUE(context_menu);
- EXPECT_GT(context_menu->GetItemCount(), 0);
- EXPECT_EQ(context_menu->GetLabelAt(0),
- l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kTextTwo));
-#endif
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionDefault));
+ EXPECT_FALSE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionLtr));
+ EXPECT_TRUE(textfield_->IsCommandIdChecked(
+ ui::TextServicesContextMenu::kWritingDirectionRtl));
}
// Tests to see if the look up item is hidden for password fields.
diff --git a/chromium/ui/views/controls/tree/tree_view.cc b/chromium/ui/views/controls/tree/tree_view.cc
index bd84a3f38d7..69333ebe84c 100644
--- a/chromium/ui/views/controls/tree/tree_view.cc
+++ b/chromium/ui/views/controls/tree/tree_view.cc
@@ -139,6 +139,7 @@ void TreeView::SetModel(TreeModel* model) {
model_ = model;
selected_node_ = nullptr;
+ active_node_ = nullptr;
icons_.clear();
if (model_) {
model_->AddObserver(this);
@@ -154,18 +155,9 @@ void TreeView::SetModel(TreeModel* model) {
root_.set_is_expanded(true);
if (root_shown_)
- selected_node_ = &root_;
+ SetSelectedNode(root_.model_node());
else if (!root_.children().empty())
- selected_node_ = root_.children().front().get();
-
- if (selected_node_) {
- AXVirtualView* ax_selected_view = selected_node_->accessibility_view();
- if (ax_selected_view) {
- GetViewAccessibility().OverrideFocus(ax_selected_view);
- ax_selected_view->NotifyAccessibilityEvent(
- ax::mojom::Event::kSelection);
- }
- }
+ SetSelectedNode(root_.children().front().get()->model_node());
}
DrawnNodesChanged();
@@ -252,57 +244,21 @@ TreeModelNode* TreeView::GetEditingNode() {
}
void TreeView::SetSelectedNode(TreeModelNode* model_node) {
- if (editing_ || model_node != selected_node_)
- CancelEdit();
- if (model_node && model_->GetParent(model_node))
- Expand(model_->GetParent(model_node));
- if (model_node && model_node == root_.model_node() && !root_shown_)
- return; // Ignore requests to select the root when not shown.
- InternalNode* node =
- model_node ? GetInternalNodeForModelNode(model_node, CREATE_IF_NOT_LOADED)
- : nullptr;
- bool was_empty_selection = (selected_node_ == nullptr);
- bool changed = (selected_node_ != node);
- if (changed) {
- SchedulePaintForNode(selected_node_);
- selected_node_ = node;
- if (selected_node_ == &root_ && !root_shown_)
- selected_node_ = nullptr;
- if (selected_node_ && selected_node_ != &root_)
- Expand(model_->GetParent(selected_node_->model_node()));
- SchedulePaintForNode(selected_node_);
- }
-
- if (selected_node_) {
- // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
- // Un-flip before passing to ScrollRectToVisible(), which uses layout
- // coordinates.
- ScrollRectToVisible(
- GetMirroredRect(GetForegroundBoundsForNode(selected_node_)));
- }
-
- // Notify controller if the old selection was empty to handle the case of
- // remove explicitly resetting selected_node_ before invoking this.
- if (controller_ && (changed || was_empty_selection))
- controller_->OnTreeViewSelectionChanged(this);
-
- if (changed) {
- AXVirtualView* ax_selected_view =
- selected_node_ ? selected_node_->accessibility_view() : nullptr;
- if (ax_selected_view) {
- GetViewAccessibility().OverrideFocus(ax_selected_view);
- ax_selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);
- } else {
- GetViewAccessibility().OverrideFocus(nullptr);
- NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
- }
- }
+ UpdateSelection(model_node, kActiveAndSelected);
}
const TreeModelNode* TreeView::GetSelectedNode() const {
return selected_node_ ? selected_node_->model_node() : nullptr;
}
+void TreeView::SetActiveNode(TreeModelNode* model_node) {
+ UpdateSelection(model_node, kActive);
+}
+
+const TreeModelNode* TreeView::GetActiveNode() const {
+ return active_node_ ? active_node_->model_node() : nullptr;
+}
+
void TreeView::Collapse(ui::TreeModelNode* model_node) {
// Don't collapse the root if the root isn't shown, otherwise nothing is
// displayed.
@@ -315,7 +271,9 @@ void TreeView::Collapse(ui::TreeModelNode* model_node) {
bool was_expanded = IsExpanded(model_node);
if (node->is_expanded()) {
if (selected_node_ && selected_node_->HasAncestor(node))
- SetSelectedNode(model_node);
+ UpdateSelection(model_node, kActiveAndSelected);
+ else if (active_node_ && active_node_->HasAncestor(node))
+ UpdateSelection(model_node, kActive);
node->set_is_expanded(false);
}
if (was_expanded) {
@@ -392,9 +350,13 @@ void TreeView::SetRootShown(bool root_shown) {
if (root_shown_ == root_shown)
return;
root_shown_ = root_shown;
- if (!root_shown_ && selected_node_ == &root_) {
+ if (!root_shown_ && (selected_node_ == &root_ || active_node_ == &root_)) {
const auto& children = model_->GetChildren(root_.model_node());
- SetSelectedNode(children.empty() ? nullptr : children.front());
+ TreeModelNode* first_child = children.empty() ? nullptr : children.front();
+ if (selected_node_ == &root_)
+ UpdateSelection(first_child, kActiveAndSelected);
+ else if (active_node_ == &root_)
+ UpdateSelection(first_child, kActive);
}
AXVirtualView* ax_view = root_.accessibility_view();
@@ -491,36 +453,53 @@ bool TreeView::HandleAccessibleAction(const ui::AXActionData& action_data) {
if (!model_)
return false;
- switch (action_data.action) {
- case ax::mojom::Action::kDoDefault: {
- CommitEdit();
- RequestFocus();
- TreeModelNode* selected_model_node = GetSelectedNode();
- if (!selected_model_node)
+ AXVirtualView* ax_view = AXVirtualView::GetFromId(action_data.target_node_id);
+ InternalNode* node =
+ ax_view ? GetInternalNodeForVirtualView(ax_view) : nullptr;
+ if (!node) {
+ switch (action_data.action) {
+ case ax::mojom::Action::kFocus:
+ if (active_node_)
+ return false;
+ if (!HasFocus())
+ RequestFocus();
return true;
+ case ax::mojom::Action::kBlur:
+ case ax::mojom::Action::kScrollToMakeVisible:
+ return View::HandleAccessibleAction(action_data);
+ default:
+ return false;
+ }
+ }
- if (IsExpanded(selected_model_node))
- Collapse(selected_model_node);
+ switch (action_data.action) {
+ case ax::mojom::Action::kDoDefault:
+ SetSelectedNode(node->model_node());
+ if (!HasFocus())
+ RequestFocus();
+ if (IsExpanded(node->model_node()))
+ Collapse(node->model_node());
else
- Expand(selected_model_node);
+ Expand(node->model_node());
break;
- }
case ax::mojom::Action::kFocus:
- RequestFocus();
+ SetSelectedNode(node->model_node());
+ if (!HasFocus())
+ RequestFocus();
break;
case ax::mojom::Action::kScrollToMakeVisible:
- if (selected_node_) {
- // GetForegroundBoundsForNode() returns RTL-flipped coordinates for
- // paint. Un-flip before passing to ScrollRectToVisible(), which uses
- // layout coordinates.
- ScrollRectToVisible(
- GetMirroredRect(GetForegroundBoundsForNode(selected_node_)));
- }
+ // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
+ // Un-flip before passing to ScrollRectToVisible(), which uses layout
+ // coordinates.
+ ScrollRectToVisible(GetMirroredRect(GetForegroundBoundsForNode(node)));
break;
case ax::mojom::Action::kShowContextMenu:
+ SetSelectedNode(node->model_node());
+ if (!HasFocus())
+ RequestFocus();
ShowContextMenu(GetBoundsInScreen().CenterPoint(),
ui::MENU_SOURCE_KEYBOARD);
break;
@@ -565,11 +544,14 @@ void TreeView::TreeNodesRemoved(TreeModel* model,
GetInternalNodeForModelNode(parent, DONT_CREATE_IF_NOT_LOADED);
if (!parent_node || !parent_node->loaded_children())
return;
- bool reset_selection = false;
+ bool reset_selected_node = false;
+ bool reset_active_node = false;
for (size_t i = 0; i < count; ++i) {
InternalNode* child_removing = parent_node->children()[start].get();
if (selected_node_ && selected_node_->HasAncestor(child_removing))
- reset_selection = true;
+ reset_selected_node = true;
+ if (active_node_ && active_node_->HasAncestor(child_removing))
+ reset_active_node = true;
DCHECK(parent_node->accessibility_view()->Contains(
child_removing->accessibility_view()));
@@ -578,21 +560,31 @@ void TreeView::TreeNodesRemoved(TreeModel* model,
child_removing->set_accessibility_view(nullptr);
parent_node->Remove(start);
}
- if (reset_selection) {
- // selected_node_ is no longer valid (at the time we enter this function
- // its model_node() is likely deleted). Explicitly NULL out the field
- // rather than invoking SetSelectedNode() otherwise, we'll try and use a
- // deleted value.
- selected_node_ = nullptr;
+
+ if (reset_selected_node || reset_active_node) {
+ // selected_node_ or active_node_ or both were no longer valid (i.e. the
+ // model_node() was likely deleted by the time we entered this function).
+ // Explicitly set to nullptr before continuing; otherwise, we might try to
+ // use a deleted value.
+ if (reset_selected_node)
+ selected_node_ = nullptr;
+ if (reset_active_node)
+ active_node_ = nullptr;
+
+ // Replace invalidated states with the nearest valid node.
const auto& children = model_->GetChildren(parent);
- TreeModelNode* to_select = nullptr;
+ TreeModelNode* nearest_node = nullptr;
if (!children.empty()) {
- to_select = children[std::min(start, children.size() - 1)];
+ nearest_node = children[std::min(start, children.size() - 1)];
} else if (parent != root_.model_node() || root_shown_) {
- to_select = parent;
+ nearest_node = parent;
}
- SetSelectedNode(to_select);
+ if (reset_selected_node)
+ UpdateSelection(nearest_node, kActiveAndSelected);
+ else if (reset_active_node)
+ UpdateSelection(nearest_node, kActive);
}
+
if (IsExpanded(parent)) {
NotifyAccessibilityEvent(ax::mojom::Event::kRowCountChanged, true);
DrawnNodesChanged();
@@ -652,11 +644,17 @@ int TreeView::GetRowCount() {
}
int TreeView::GetSelectedRow() {
- ui::TreeModelNode* model_node = GetSelectedNode();
+ // Type-ahead searches should be relative to the active node, so return the
+ // row of the active node for |PrefixSelector|.
+ ui::TreeModelNode* model_node = GetActiveNode();
return model_node ? GetRowForNode(model_node) : -1;
}
void TreeView::SetSelectedRow(int row) {
+ // Type-ahead manipulates selection because active node is synced to selected
+ // node, so call SetSelectedNode() instead of SetActiveNode().
+ // TODO(crbug.com/1080944): Decouple active node from selected node by adding
+ // new keyboard affordances.
SetSelectedNode(GetNodeForRow(row));
}
@@ -665,18 +663,20 @@ base::string16 TreeView::GetTextForRow(int row) {
}
gfx::Point TreeView::GetKeyboardContextMenuLocation() {
- int y = height() / 2;
- if (selected_node_) {
- gfx::Rect node_bounds(GetForegroundBoundsForNode(selected_node_));
- gfx::Rect vis_bounds(GetVisibleBounds());
- if (node_bounds.y() >= vis_bounds.y() &&
- node_bounds.y() < vis_bounds.bottom()) {
- y = node_bounds.y();
- }
+ gfx::Rect vis_bounds(GetVisibleBounds());
+ int x = 0;
+ int y = 0;
+ if (active_node_) {
+ gfx::Rect node_bounds(GetForegroundBoundsForNode(active_node_));
+ if (node_bounds.Intersects(vis_bounds))
+ node_bounds.Intersect(vis_bounds);
+ gfx::Point menu_point(node_bounds.CenterPoint());
+ x = base::ClampToRange(menu_point.x(), vis_bounds.x(), vis_bounds.right());
+ y = base::ClampToRange(menu_point.y(), vis_bounds.y(), vis_bounds.bottom());
}
- gfx::Point screen_loc(0, y);
+ gfx::Point screen_loc(x, y);
if (base::i18n::IsRTL())
- screen_loc.set_x(width());
+ screen_loc.set_x(vis_bounds.width() - screen_loc.x());
ConvertPointToScreen(this, &screen_loc);
return screen_loc;
}
@@ -688,10 +688,10 @@ bool TreeView::OnKeyPressed(const ui::KeyEvent& event) {
switch (event.key_code()) {
case ui::VKEY_F2:
if (!editing_) {
- TreeModelNode* selected_node = GetSelectedNode();
- if (selected_node &&
- (!controller_ || controller_->CanEdit(this, selected_node))) {
- StartEditing(selected_node);
+ TreeModelNode* active_node = GetActiveNode();
+ if (active_node &&
+ (!controller_ || controller_->CanEdit(this, active_node))) {
+ StartEditing(active_node);
}
}
return true;
@@ -762,13 +762,6 @@ void TreeView::OnFocus() {
GetInputMethod()->OnCaretBoundsChanged(GetPrefixSelector());
SetHasFocusIndicator(true);
- AXVirtualView* ax_selected_view =
- selected_node_ ? selected_node_->accessibility_view() : nullptr;
- if (ax_selected_view) {
- GetViewAccessibility().OverrideFocus(ax_selected_view);
- } else {
- GetViewAccessibility().OverrideFocus(nullptr);
- }
}
void TreeView::OnBlur() {
@@ -780,24 +773,82 @@ void TreeView::OnBlur() {
SetHasFocusIndicator(false);
}
+void TreeView::UpdateSelection(TreeModelNode* model_node,
+ SelectionType selection_type) {
+ CancelEdit();
+ if (model_node && model_->GetParent(model_node))
+ Expand(model_->GetParent(model_node));
+ if (model_node && model_node == root_.model_node() && !root_shown_)
+ return; // Ignore requests for the root when not shown.
+ InternalNode* node =
+ model_node ? GetInternalNodeForModelNode(model_node, CREATE_IF_NOT_LOADED)
+ : nullptr;
+
+ // Force update if old value was nullptr to handle case of TreeNodesRemoved
+ // explicitly resetting selected_node_ or active_node_ before invoking this.
+ bool active_changed = (!active_node_ || active_node_ != node);
+ bool selection_changed = (selection_type == kActiveAndSelected &&
+ (!selected_node_ || selected_node_ != node));
+
+ // Update tree view states to new values.
+ if (active_changed)
+ active_node_ = node;
+
+ if (selection_changed) {
+ SchedulePaintForNode(selected_node_);
+ selected_node_ = node;
+ SchedulePaintForNode(selected_node_);
+ }
+
+ if (active_changed && node) {
+ // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint.
+ // Un-flip before passing to ScrollRectToVisible(), which uses layout
+ // coordinates.
+ ScrollRectToVisible(GetMirroredRect(GetForegroundBoundsForNode(node)));
+ }
+
+ // Notify assistive technologies of state changes.
+ if (active_changed) {
+ // Update |ViewAccessibility| so that focus lands directly on this node when
+ // |FocusManager| gives focus to the tree view. This update also fires an
+ // accessible focus event.
+ GetViewAccessibility().OverrideFocus(node ? node->accessibility_view()
+ : nullptr);
+ }
+
+ if (selection_changed) {
+ AXVirtualView* ax_selected_view =
+ node ? node->accessibility_view() : nullptr;
+ if (ax_selected_view)
+ ax_selected_view->NotifyAccessibilityEvent(ax::mojom::Event::kSelection);
+ else
+ NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
+ }
+
+ // Notify controller of state changes.
+ if (selection_changed && controller_)
+ controller_->OnTreeViewSelectionChanged(this);
+}
+
bool TreeView::OnClickOrTap(const ui::LocatedEvent& event) {
CommitEdit();
- RequestFocus();
InternalNode* node = GetNodeAtPoint(event.location());
- if (!node)
- return true;
-
- bool hits_arrow = IsPointInExpandControl(node, event.location());
- if (!hits_arrow)
- SetSelectedNode(node->model_node());
+ if (node) {
+ bool hits_arrow = IsPointInExpandControl(node, event.location());
+ if (!hits_arrow)
+ SetSelectedNode(node->model_node());
- if (hits_arrow || EventIsDoubleTapOrClick(event)) {
- if (node->is_expanded())
- Collapse(node->model_node());
- else
- Expand(node->model_node());
+ if (hits_arrow || EventIsDoubleTapOrClick(event)) {
+ if (node->is_expanded())
+ Collapse(node->model_node());
+ else
+ Expand(node->model_node());
+ }
}
+
+ if (!HasFocus())
+ RequestFocus();
return true;
}
@@ -859,6 +910,7 @@ void TreeView::PopulateAccessibilityData(InternalNode* node,
: nullptr;
const bool selected = (node == selected_node);
data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected);
+ data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kSelect);
if (node->is_expanded())
data->AddState(ax::mojom::State::kExpanded);
@@ -916,7 +968,8 @@ void TreeView::PopulateAccessibilityData(InternalNode* node,
gfx::Rect node_bounds = GetBackgroundBoundsForNode(node);
data->relative_bounds.bounds = gfx::RectF(node_bounds);
} else {
- data->AddState(ax::mojom::State::kInvisible);
+ data->AddState(node != &root_ || root_shown_ ? ax::mojom::State::kInvisible
+ : ax::mojom::State::kIgnored);
}
}
@@ -1123,6 +1176,22 @@ TreeView::InternalNode* TreeView::GetInternalNodeForModelNode(
return parent_internal_node->children()[index].get();
}
+TreeView::InternalNode* TreeView::GetInternalNodeForVirtualView(
+ AXVirtualView* ax_view) {
+ if (ax_view == root_.accessibility_view())
+ return &root_;
+ DCHECK(ax_view);
+ InternalNode* parent_internal_node =
+ GetInternalNodeForVirtualView(ax_view->virtual_parent_view());
+ if (!parent_internal_node)
+ return nullptr;
+ DCHECK(parent_internal_node->loaded_children());
+ AXVirtualView* parent_ax_view = parent_internal_node->accessibility_view();
+ DCHECK(parent_ax_view);
+ size_t index = parent_ax_view->GetIndexOf(ax_view);
+ return parent_internal_node->children()[index].get();
+}
+
gfx::Rect TreeView::GetBoundsForNode(InternalNode* node) {
int row, ignored_depth;
row = GetRowForInternalNode(node, &ignored_depth);
@@ -1248,7 +1317,7 @@ void TreeView::IncrementSelection(IncrementType type) {
if (!model_)
return;
- if (!GetSelectedNode()) {
+ if (!active_node_) {
// If nothing is selected select the first or last node.
if (root_.children().empty())
return;
@@ -1268,7 +1337,7 @@ void TreeView::IncrementSelection(IncrementType type) {
int depth = 0;
int delta = type == INCREMENT_PREVIOUS ? -1 : 1;
- int row = GetRowForInternalNode(selected_node_, &depth);
+ int row = GetRowForInternalNode(active_node_, &depth);
int new_row = base::ClampToRange(row + delta, 0, GetRowCount() - 1);
if (new_row == row)
return; // At the end/beginning.
@@ -1276,20 +1345,20 @@ void TreeView::IncrementSelection(IncrementType type) {
}
void TreeView::CollapseOrSelectParent() {
- if (selected_node_) {
- if (selected_node_->is_expanded())
- Collapse(selected_node_->model_node());
- else if (selected_node_->parent())
- SetSelectedNode(selected_node_->parent()->model_node());
+ if (active_node_) {
+ if (active_node_->is_expanded())
+ Collapse(active_node_->model_node());
+ else if (active_node_->parent())
+ SetSelectedNode(active_node_->parent()->model_node());
}
}
void TreeView::ExpandOrSelectChild() {
- if (selected_node_) {
- if (!selected_node_->is_expanded())
- Expand(selected_node_->model_node());
- else if (!selected_node_->children().empty())
- SetSelectedNode(selected_node_->children().front()->model_node());
+ if (active_node_) {
+ if (!active_node_->is_expanded())
+ Expand(active_node_->model_node());
+ else if (!active_node_->children().empty())
+ SetSelectedNode(active_node_->children().front()->model_node());
}
}
diff --git a/chromium/ui/views/controls/tree/tree_view.h b/chromium/ui/views/controls/tree/tree_view.h
index b03b860f7b2..19504e30d01 100644
--- a/chromium/ui/views/controls/tree/tree_view.h
+++ b/chromium/ui/views/controls/tree/tree_view.h
@@ -44,6 +44,13 @@ class TreeViewController;
// can expand, collapse and edit the items. A Controller may be attached to
// receive notification of selection changes and restrict editing.
//
+// In addition to tracking selection, TreeView also tracks the active node,
+// which is the item that receives keyboard input when the tree has focus.
+// Active/focus is like a pointer for keyboard navigation, and operations such
+// as selection are performed at the point of focus. The active node is synced
+// to the selected node. When the active node is nullptr, the TreeView itself is
+// the target of keyboard input.
+//
// Note on implementation. This implementation doesn't scale well. In particular
// it does not store any row information, but instead calculates it as
// necessary. But it's more than adequate for current uses.
@@ -103,6 +110,20 @@ class VIEWS_EXPORT TreeView : public View,
}
const ui::TreeModelNode* GetSelectedNode() const;
+ // Marks the specified node as active, scrolls it into view, and reports a
+ // keyboard focus update to ATs. Active node should be synced to the selected
+ // node and should be nullptr when the tree is empty.
+ // TODO(crbug.com/1080944): Decouple active node from selected node by adding
+ // new keyboard affordances.
+ void SetActiveNode(ui::TreeModelNode* model_node);
+
+ // Returns the active node, or nullptr if nothing is active.
+ ui::TreeModelNode* GetActiveNode() {
+ return const_cast<ui::TreeModelNode*>(
+ const_cast<const TreeView*>(this)->GetActiveNode());
+ }
+ const ui::TreeModelNode* GetActiveNode() const;
+
// Marks |model_node| as collapsed. This only effects the UI if node and all
// its parents are expanded (IsExpanded(model_node) returns true).
void Collapse(ui::TreeModelNode* model_node);
@@ -193,6 +214,20 @@ class VIEWS_EXPORT TreeView : public View,
private:
friend class TreeViewTest;
+ // Enumeration of possible changes to tree view state when the UI is updated.
+ enum SelectionType {
+ // Active state is being set to a tree item.
+ kActive,
+
+ // Active and selected states are being set to a tree item.
+ kActiveAndSelected,
+ };
+
+ // Performs active node and selected node state transitions. Updates states
+ // and scrolling before notifying assistive technologies and the controller.
+ void UpdateSelection(ui::TreeModelNode* model_node,
+ SelectionType selection_type);
+
// Selects, expands or collapses nodes in the tree. Consistent behavior for
// tap gesture and click events.
bool OnClickOrTap(const ui::LocatedEvent& event);
@@ -354,6 +389,9 @@ class VIEWS_EXPORT TreeView : public View,
ui::TreeModelNode* model_node,
GetInternalNodeCreateType create_type);
+ // Returns the InternalNode for a virtual view.
+ InternalNode* GetInternalNodeForVirtualView(AXVirtualView* ax_view);
+
// Returns the bounds for a node. This rectangle contains the node's icon,
// text, arrow, and auxiliary text (if any). All of the other bounding
// rectangles computed by the functions below lie inside this rectangle.
@@ -435,6 +473,9 @@ class VIEWS_EXPORT TreeView : public View,
// The selected node, may be null.
InternalNode* selected_node_ = nullptr;
+ // The current active node, may be null.
+ InternalNode* active_node_ = nullptr;
+
bool editing_ = false;
// The editor; lazily created and never destroyed (well, until TreeView is
diff --git a/chromium/ui/views/controls/tree/tree_view_unittest.cc b/chromium/ui/views/controls/tree/tree_view_unittest.cc
index 7f56294c155..6081d640219 100644
--- a/chromium/ui/views/controls/tree/tree_view_unittest.cc
+++ b/chromium/ui/views/controls/tree/tree_view_unittest.cc
@@ -14,6 +14,7 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
@@ -24,7 +25,9 @@
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/tree/tree_view_controller.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
using ui::TreeModel;
@@ -102,10 +105,27 @@ class TreeViewTest : public ViewsTestBase {
std::string TreeViewAccessibilityContentsAsString() const;
+ // Gets the selected node from the tree view. The result can be compared with
+ // GetSelectedAccessibilityViewName() to check consistency between the tree
+ // view state and the accessibility data.
std::string GetSelectedNodeTitle();
+ // Finds the selected node via iterative depth first search over the internal
+ // accessibility tree, examining both ignored and unignored nodes. The result
+ // can be compared with GetSelectedNodeTitle() to check consistency between
+ // the tree view state and the accessibility data.
std::string GetSelectedAccessibilityViewName() const;
+ // Gets the active node from the tree view. The result can be compared with
+ // GetSelectedAccessibilityViewName() to check consistency between the tree
+ // view state and the accessibility data.
+ std::string GetActiveNodeTitle();
+
+ // Gets the active node from the tree view's |ViewAccessibility|. The result
+ // can be compared with GetSelectedNodeTitle() to check consistency between
+ // the tree view internal state and the accessibility data.
+ std::string GetActiveAccessibilityViewName() const;
+
std::string GetEditingNodeTitle();
AXVirtualView* GetRootAccessibilityView() const;
@@ -125,7 +145,7 @@ class TreeViewTest : public ViewsTestBase {
ui::TreeNodeModel<TestNode> model_;
TreeView* tree_;
- Widget* widget_;
+ UniqueWidgetPtr widget_;
private:
std::string InternalNodeAsString(TreeView::InternalNode* node);
@@ -141,12 +161,13 @@ class TreeViewTest : public ViewsTestBase {
void TreeViewTest::SetUp() {
ViewsTestBase::SetUp();
- widget_ = new Widget;
+ widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
widget_->Init(std::move(params));
- tree_ = new TreeView();
- widget_->GetContentsView()->AddChildView(tree_);
+ tree_ =
+ widget_->GetContentsView()->AddChildView(std::make_unique<TreeView>());
+ tree_->RequestFocus();
ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback =
base::BindRepeating(
@@ -164,8 +185,7 @@ void TreeViewTest::SetUp() {
}
void TreeViewTest::TearDown() {
- if (!widget_->IsClosed())
- widget_->Close();
+ widget_.reset();
ViewsTestBase::TearDown();
}
@@ -233,6 +253,20 @@ std::string TreeViewTest::GetSelectedAccessibilityViewName() const {
return {};
}
+std::string TreeViewTest::GetActiveNodeTitle() {
+ TreeModelNode* model_node = tree_->GetActiveNode();
+ return model_node ? base::UTF16ToASCII(model_node->GetTitle())
+ : std::string();
+}
+
+std::string TreeViewTest::GetActiveAccessibilityViewName() const {
+ const AXVirtualView* ax_view =
+ tree_->GetViewAccessibility().FocusedVirtualChild();
+ return ax_view ? ax_view->GetData().GetStringAttribute(
+ ax::mojom::StringAttribute::kName)
+ : std::string();
+}
+
std::string TreeViewTest::GetEditingNodeTitle() {
TreeModelNode* model_node = tree_->GetEditingNode();
return model_node ? base::UTF16ToASCII(model_node->GetTitle())
@@ -336,6 +370,12 @@ std::string TreeViewTest::InternalNodeAsString(TreeView::InternalNode* node) {
return result;
}
+// Verify properties are accessible via metadata.
+TEST_F(TreeViewTest, MetadataTest) {
+ tree_->SetModel(&model_);
+ test::TestViewMetadata(tree_);
+}
+
// Verifies setting model correctly updates internal state.
TEST_F(TreeViewTest, SetModel) {
tree_->SetModel(&model_);
@@ -930,4 +970,225 @@ TEST_F(TreeViewTest, CommitOnFocusLost) {
ax::mojom::StringAttribute::kName));
}
+// Verifies that virtual accessible actions go to virtual view targets.
+TEST_F(TreeViewTest, VirtualAccessibleAction) {
+ tree_->SetModel(&model_);
+ tree_->Expand(GetNodeByTitle("b1"));
+ EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
+ EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString());
+ EXPECT_EQ(5, GetRowCount());
+
+ // Set to nullptr should clear the selection.
+ tree_->SetSelectedNode(nullptr);
+ EXPECT_EQ(std::string(), GetActiveNodeTitle());
+ EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+ EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
+
+ // Test using each virtual view as target.
+ ui::AXActionData data;
+ const std::string test_cases[] = {"root", "a", "b", "b1", "c"};
+ for (const std::string& name : test_cases) {
+ data.target_node_id = GetAccessibilityViewByName(name)->GetData().id;
+ data.action = ax::mojom::Action::kDoDefault;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ(name, GetActiveNodeTitle());
+ EXPECT_EQ(name, GetActiveAccessibilityViewName());
+ EXPECT_EQ(name, GetSelectedNodeTitle());
+ EXPECT_EQ(name, GetSelectedAccessibilityViewName());
+ }
+
+ // Do nothing when a valid node id is not provided. This can happen if the
+ // actions target the owner view itself.
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kDoDefault;
+ EXPECT_FALSE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ("b", GetActiveNodeTitle());
+ EXPECT_EQ("b", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+
+ // Check that the active node is set if assistive technologies set focus.
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ data.target_node_id = GetAccessibilityViewByName("a")->GetData().id;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ("a", GetSelectedAccessibilityViewName());
+
+ // Do not handle accessible actions when no node is selected.
+ tree_->SetSelectedNode(nullptr);
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kDoDefault;
+ EXPECT_FALSE(tree_->HandleAccessibleAction(data));
+ EXPECT_EQ(std::string(), GetActiveNodeTitle());
+ EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+ EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
+}
+
+// Verifies that accessibility focus events get fired for the correct nodes when
+// the tree view is given focus.
+TEST_F(TreeViewTest, OnFocusAccessibilityEvents) {
+ // Without keyboard focus, model changes should not fire focus events.
+ tree_->GetFocusManager()->ClearFocus();
+ EXPECT_FALSE(tree_->HasFocus());
+ tree_->SetModel(&model_);
+ EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
+ EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString());
+ EXPECT_EQ("root", GetSelectedNodeTitle());
+ EXPECT_EQ("root", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(4, GetRowCount());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetRootAccessibilityView(),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+
+ // The initial focus should fire a focus event for the active node
+ // (in this case, the root node).
+ ClearAccessibilityEvents();
+ tree_->RequestFocus();
+ EXPECT_EQ((AccessibilityEventsVector{std::make_pair(
+ GetRootAccessibilityView(), ax::mojom::Event::kFocus)}),
+ accessibility_events());
+
+ // Focus clear and restore should fire a focus event for the active node.
+ ClearAccessibilityEvents();
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ tree_->SetActiveNode(GetNodeByTitle("a"));
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+ tree_->GetFocusManager()->ClearFocus();
+ EXPECT_FALSE(tree_->HasFocus());
+ tree_->GetFocusManager()->RestoreFocusedView();
+ EXPECT_TRUE(tree_->HasFocus());
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(
+ (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("b"),
+ ax::mojom::Event::kFocus),
+ std::make_pair(GetAccessibilityViewByName("b"),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kFocus),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kFocus)}),
+ accessibility_events());
+
+ // Without keyboard focus, selection should not fire focus events.
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ tree_->SetSelectedNode(GetNodeByTitle("a"));
+ EXPECT_FALSE(tree_->HasFocus());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ("a", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(
+ (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+
+ // A direct focus action on a tree item should give focus to the tree view but
+ // only fire a focus event for the target node.
+ ui::AXActionData data;
+ const std::string test_cases[] = {"root", "a", "b", "c"};
+ for (const std::string& name : test_cases) {
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ EXPECT_FALSE(tree_->HasFocus());
+ data.target_node_id = GetAccessibilityViewByName(name)->GetData().id;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_TRUE(tree_->HasFocus());
+ EXPECT_EQ(name, GetActiveNodeTitle());
+ EXPECT_EQ(name, GetActiveAccessibilityViewName());
+ EXPECT_EQ(name, GetSelectedNodeTitle());
+ EXPECT_EQ(name, GetSelectedAccessibilityViewName());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetAccessibilityViewByName(name),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetAccessibilityViewByName(name),
+ ax::mojom::Event::kFocus)}),
+ accessibility_events());
+ }
+
+ // A direct focus action on the tree view itself with an active node should
+ // have no effect.
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ tree_->SetSelectedNode(GetNodeByTitle("b"));
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_FALSE(tree_->HandleAccessibleAction(data));
+ EXPECT_FALSE(tree_->HasFocus());
+ EXPECT_EQ("b", GetActiveNodeTitle());
+ EXPECT_EQ("b", GetActiveAccessibilityViewName());
+ EXPECT_EQ("b", GetSelectedNodeTitle());
+ EXPECT_EQ("b", GetSelectedAccessibilityViewName());
+ EXPECT_EQ(
+ (AccessibilityEventsVector{std::make_pair(GetAccessibilityViewByName("b"),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+
+ // A direct focus action on a tree view without an active node (i.e. empty
+ // tree) should fire a focus event for the tree view.
+ ClearAccessibilityEvents();
+ tree_->GetFocusManager()->ClearFocus();
+ ui::TreeNodeModel<TestNode> empty_model(std::make_unique<TestNode>());
+ static_cast<TestNode*>(empty_model.GetRoot())->SetTitle(ASCIIToUTF16("root"));
+ tree_->SetModel(&empty_model);
+ tree_->SetRootShown(false);
+ data.target_node_id = -1;
+ data.action = ax::mojom::Action::kFocus;
+ EXPECT_TRUE(tree_->HandleAccessibleAction(data));
+ EXPECT_TRUE(tree_->HasFocus());
+ EXPECT_EQ(std::string(), GetActiveNodeTitle());
+ EXPECT_EQ(std::string(), GetActiveAccessibilityViewName());
+ EXPECT_EQ(std::string(), GetSelectedNodeTitle());
+ EXPECT_EQ(std::string(), GetSelectedAccessibilityViewName());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetRootAccessibilityView(),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kSelection),
+ std::make_pair(GetRootAccessibilityView(),
+ ax::mojom::Event::kStateChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kFocus)}),
+ accessibility_events());
+
+ // When a focused empty tree is populated with nodes, it should immediately
+ // hand off focus to one of them and select it.
+ ClearAccessibilityEvents();
+ tree_->SetModel(&model_);
+ EXPECT_EQ("a", GetActiveNodeTitle());
+ EXPECT_EQ("a", GetActiveAccessibilityViewName());
+ EXPECT_EQ("a", GetSelectedNodeTitle());
+ EXPECT_EQ("a", GetSelectedAccessibilityViewName());
+ EXPECT_EQ((AccessibilityEventsVector{
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetTreeAccessibilityView(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kFocus),
+ std::make_pair(GetAccessibilityViewByName("a"),
+ ax::mojom::Event::kSelection)}),
+ accessibility_events());
+}
+
} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu.cc b/chromium/ui/views/controls/views_text_services_context_menu.cc
deleted file mode 100644
index 0cebc532c69..00000000000
--- a/chromium/ui/views/controls/views_text_services_context_menu.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/views/controls/views_text_services_context_menu.h"
-
-#include <memory>
-
-#include "base/notreached.h"
-#include "ui/views/controls/views_text_services_context_menu_base.h"
-
-namespace views {
-
-// static
-std::unique_ptr<ViewsTextServicesContextMenu>
-ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
- Textfield* client) {
- return std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
-}
-
-bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
- ViewsTextServicesContextMenu* menu,
- base::i18n::TextDirection direction) {
- NOTREACHED();
- return false;
-}
-
-} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu.h b/chromium/ui/views/controls/views_text_services_context_menu.h
index 1b84aa43f20..ade35947552 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu.h
+++ b/chromium/ui/views/controls/views_text_services_context_menu.h
@@ -7,13 +7,7 @@
#include <memory>
-#include "base/i18n/rtl.h"
-#include "ui/base/accelerators/accelerator.h"
-#include "ui/views/views_export.h"
-
-namespace ui {
-class SimpleMenuModel;
-}
+#include "ui/base/models/simple_menu_model.h"
namespace views {
@@ -21,26 +15,15 @@ class Textfield;
// This class is used to add and handle text service items in the text context
// menu.
-class ViewsTextServicesContextMenu : public ui::AcceleratorProvider {
+class ViewsTextServicesContextMenu : public ui::SimpleMenuModel::Delegate {
public:
// Creates a platform-specific ViewsTextServicesContextMenu object.
static std::unique_ptr<ViewsTextServicesContextMenu> Create(
ui::SimpleMenuModel* menu,
Textfield* textfield);
- // Method for testing. Returns true if the text direction BiDi submenu item
- // in |menu| should be checked.
- VIEWS_EXPORT static bool IsTextDirectionCheckedForTesting(
- ViewsTextServicesContextMenu* menu,
- base::i18n::TextDirection direction);
-
// Returns true if the given |command_id| is handled by the menu.
virtual bool SupportsCommand(int command_id) const = 0;
-
- // Methods associated with SimpleMenuModel::Delegate.
- virtual bool IsCommandIdChecked(int command_id) const = 0;
- virtual bool IsCommandIdEnabled(int command_id) const = 0;
- virtual void ExecuteCommand(int command_id) = 0;
};
} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.cc b/chromium/ui/views/controls/views_text_services_context_menu_base.cc
index b1f7b1d9890..a9b53aeb9c2 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu_base.cc
+++ b/chromium/ui/views/controls/views_text_services_context_menu_base.cc
@@ -4,6 +4,8 @@
#include "ui/views/controls/views_text_services_context_menu_base.h"
+#include <memory>
+
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "ui/base/accelerators/accelerator.h"
@@ -41,10 +43,6 @@ ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase(
ViewsTextServicesContextMenuBase::~ViewsTextServicesContextMenuBase() = default;
-bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
- return command_id == IDS_CONTENT_CONTEXT_EMOJI;
-}
-
bool ViewsTextServicesContextMenuBase::GetAcceleratorForCommandId(
int command_id,
ui::Accelerator* accelerator) const {
@@ -72,17 +70,28 @@ bool ViewsTextServicesContextMenuBase::IsCommandIdChecked(
bool ViewsTextServicesContextMenuBase::IsCommandIdEnabled(
int command_id) const {
- if (command_id == IDS_CONTENT_CONTEXT_EMOJI)
- return true;
-
- return false;
+ return command_id == IDS_CONTENT_CONTEXT_EMOJI;
}
-void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id) {
+void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id,
+ int event_flags) {
if (command_id == IDS_CONTENT_CONTEXT_EMOJI) {
- client()->GetWidget()->ShowEmojiPanel();
+ client_->GetWidget()->ShowEmojiPanel();
UMA_HISTOGRAM_BOOLEAN(kViewsTextServicesContextMenuEmoji, true);
}
}
+bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const {
+ return command_id == IDS_CONTENT_CONTEXT_EMOJI;
+}
+
+#if !defined(OS_MACOSX)
+// static
+std::unique_ptr<ViewsTextServicesContextMenu>
+ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
+ Textfield* client) {
+ return std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
+}
+#endif
+
} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.h b/chromium/ui/views/controls/views_text_services_context_menu_base.h
index bbc3e19d206..3c8484778f8 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu_base.h
+++ b/chromium/ui/views/controls/views_text_services_context_menu_base.h
@@ -5,39 +5,40 @@
#ifndef UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_
#define UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_
-#include "base/macros.h"
+#include "build/build_config.h"
#include "ui/views/controls/views_text_services_context_menu.h"
namespace views {
-// This base class is used to add and handle text service items in the text
+// This base class is used to add and handle text service items in the textfield
// context menu. Specific platforms may subclass and add additional items.
class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu {
public:
ViewsTextServicesContextMenuBase(ui::SimpleMenuModel* menu,
Textfield* client);
+ ViewsTextServicesContextMenuBase(const ViewsTextServicesContextMenuBase&) =
+ delete;
+ ViewsTextServicesContextMenuBase& operator=(
+ const ViewsTextServicesContextMenuBase&) = delete;
~ViewsTextServicesContextMenuBase() override;
- // Returns true if the given |command_id| is handled by the menu.
- bool SupportsCommand(int command_id) const override;
-
- // ui::AcceleratorProvider:
+ // ViewsTextServicesContextMenu:
bool GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) const override;
-
- // Methods associated with SimpleMenuModel::Delegate.
bool IsCommandIdChecked(int command_id) const override;
bool IsCommandIdEnabled(int command_id) const override;
- void ExecuteCommand(int command_id) override;
+ void ExecuteCommand(int command_id, int event_flags) override;
+ bool SupportsCommand(int command_id) const override;
protected:
- Textfield* client() const { return client_; }
+#if defined(OS_MACOSX)
+ Textfield* client() { return client_; }
+ const Textfield* client() const { return client_; }
+#endif
private:
// The view associated with the menu. Weak. Owns |this|.
- Textfield* client_ = nullptr;
-
- DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuBase);
+ Textfield* const client_;
};
} // namespace views
diff --git a/chromium/ui/views/controls/views_text_services_context_menu_mac.mm b/chromium/ui/views/controls/views_text_services_context_menu_mac.mm
index a95d7058b80..4f7510d6a1e 100644
--- a/chromium/ui/views/controls/views_text_services_context_menu_mac.mm
+++ b/chromium/ui/views/controls/views_text_services_context_menu_mac.mm
@@ -29,108 +29,118 @@ class ViewsTextServicesContextMenuMac
: public ViewsTextServicesContextMenuBase,
public ui::TextServicesContextMenu::Delegate {
public:
- ViewsTextServicesContextMenuMac(ui::SimpleMenuModel* menu, Textfield* client)
- : ViewsTextServicesContextMenuBase(menu, client),
- text_services_menu_(this) {
- // Insert the "Look up" item in the first position.
- base::string16 text = GetSelectedText();
- if (!text.empty()) {
- menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR);
- menu->InsertItemAt(
- 0, IDS_CONTENT_CONTEXT_LOOK_UP,
- l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, text));
- }
-
- text_services_menu_.AppendToContextMenu(menu);
- text_services_menu_.AppendEditableItems(menu);
- }
-
+ ViewsTextServicesContextMenuMac(ui::SimpleMenuModel* menu, Textfield* client);
+ ViewsTextServicesContextMenuMac(const ViewsTextServicesContextMenuMac&) =
+ delete;
+ ViewsTextServicesContextMenuMac& operator=(
+ const ViewsTextServicesContextMenuMac&) = delete;
~ViewsTextServicesContextMenuMac() override = default;
// ViewsTextServicesContextMenu:
- bool SupportsCommand(int command_id) const override {
- return text_services_menu_.SupportsCommand(command_id) ||
- command_id == IDS_CONTENT_CONTEXT_LOOK_UP ||
- ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
- }
+ bool IsCommandIdChecked(int command_id) const override;
+ bool IsCommandIdEnabled(int command_id) const override;
+ void ExecuteCommand(int command_id, int event_flags) override;
+ bool SupportsCommand(int command_id) const override;
- bool IsCommandIdEnabled(int command_id) const override {
- if (text_services_menu_.SupportsCommand(command_id))
- return text_services_menu_.IsCommandIdEnabled(command_id);
-
- switch (command_id) {
- case IDS_CONTENT_CONTEXT_LOOK_UP:
- return true;
+ // TextServicesContextMenu::Delegate:
+ base::string16 GetSelectedText() const override;
+ bool IsTextDirectionEnabled(
+ base::i18n::TextDirection direction) const override;
+ bool IsTextDirectionChecked(
+ base::i18n::TextDirection direction) const override;
+ void UpdateTextDirection(base::i18n::TextDirection direction) override;
- default:
- return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
- }
- }
+ private:
+ // Handler for the "Look Up" menu item.
+ void LookUpInDictionary();
- void ExecuteCommand(int command_id) override {
- switch (command_id) {
- case IDS_CONTENT_CONTEXT_LOOK_UP:
- LookUpInDictionary();
- break;
+ ui::TextServicesContextMenu text_services_menu_{this};
+};
- default:
- ViewsTextServicesContextMenuBase::ExecuteCommand(command_id);
- break;
- }
+ViewsTextServicesContextMenuMac::ViewsTextServicesContextMenuMac(
+ ui::SimpleMenuModel* menu,
+ Textfield* client)
+ : ViewsTextServicesContextMenuBase(menu, client) {
+ // Insert the "Look up" item in the first position.
+ const base::string16 text = GetSelectedText();
+ if (!text.empty()) {
+ menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR);
+ menu->InsertItemAt(
+ 0, IDS_CONTENT_CONTEXT_LOOK_UP,
+ l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, text));
}
- // TextServicesContextMenu::Delegate:
- base::string16 GetSelectedText() const override {
- if (client()->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD)
- return base::string16();
+ text_services_menu_.AppendToContextMenu(menu);
+ text_services_menu_.AppendEditableItems(menu);
+}
- return client()->GetSelectedText();
- }
+bool ViewsTextServicesContextMenuMac::IsCommandIdChecked(int command_id) const {
+ return text_services_menu_.SupportsCommand(command_id)
+ ? text_services_menu_.IsCommandIdChecked(command_id)
+ : ViewsTextServicesContextMenuBase::IsCommandIdChecked(command_id);
+}
- bool IsTextDirectionEnabled(
- base::i18n::TextDirection direction) const override {
- return direction != base::i18n::UNKNOWN_DIRECTION;
- }
+bool ViewsTextServicesContextMenuMac::IsCommandIdEnabled(int command_id) const {
+ if (text_services_menu_.SupportsCommand(command_id))
+ return text_services_menu_.IsCommandIdEnabled(command_id);
+ return (command_id == IDS_CONTENT_CONTEXT_LOOK_UP) ||
+ ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
+}
- bool IsTextDirectionChecked(
- base::i18n::TextDirection direction) const override {
- return direction != base::i18n::UNKNOWN_DIRECTION &&
- client()->GetTextDirection() == direction;
- }
+void ViewsTextServicesContextMenuMac::ExecuteCommand(int command_id,
+ int event_flags) {
+ if (text_services_menu_.SupportsCommand(command_id))
+ text_services_menu_.ExecuteCommand(command_id, event_flags);
+ else if (command_id == IDS_CONTENT_CONTEXT_LOOK_UP)
+ LookUpInDictionary();
+ else
+ ViewsTextServicesContextMenuBase::ExecuteCommand(command_id, event_flags);
+}
- void UpdateTextDirection(base::i18n::TextDirection direction) override {
- DCHECK_NE(direction, base::i18n::UNKNOWN_DIRECTION);
+bool ViewsTextServicesContextMenuMac::SupportsCommand(int command_id) const {
+ return text_services_menu_.SupportsCommand(command_id) ||
+ command_id == IDS_CONTENT_CONTEXT_LOOK_UP ||
+ ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
+}
- base::i18n::TextDirection text_direction =
- direction == base::i18n::LEFT_TO_RIGHT ? base::i18n::LEFT_TO_RIGHT
- : base::i18n::RIGHT_TO_LEFT;
- client()->ChangeTextDirectionAndLayoutAlignment(text_direction);
- }
+base::string16 ViewsTextServicesContextMenuMac::GetSelectedText() const {
+ return (client()->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD)
+ ? base::string16()
+ : client()->GetSelectedText();
+}
- private:
- // Handler for the "Look Up" menu item.
- void LookUpInDictionary() {
- gfx::Point baseline_point;
- gfx::DecoratedText text;
- if (client()->GetWordLookupDataFromSelection(&text, &baseline_point)) {
- Widget* widget = client()->GetWidget();
- NSView* view = widget->GetNativeView().GetNativeNSView();
- views::View::ConvertPointToTarget(client(), widget->GetRootView(),
- &baseline_point);
-
- NSPoint lookup_point = NSMakePoint(
- baseline_point.x(), NSHeight([view frame]) - baseline_point.y());
- [view showDefinitionForAttributedString:
- gfx::GetAttributedStringFromDecoratedText(text)
- atPoint:lookup_point];
- }
- }
+bool ViewsTextServicesContextMenuMac::IsTextDirectionEnabled(
+ base::i18n::TextDirection direction) const {
+ return direction != base::i18n::UNKNOWN_DIRECTION;
+}
- // Appends and handles the text service menu.
- ui::TextServicesContextMenu text_services_menu_;
+bool ViewsTextServicesContextMenuMac::IsTextDirectionChecked(
+ base::i18n::TextDirection direction) const {
+ return IsTextDirectionEnabled(direction) &&
+ client()->GetTextDirection() == direction;
+}
- DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuMac);
-};
+void ViewsTextServicesContextMenuMac::UpdateTextDirection(
+ base::i18n::TextDirection direction) {
+ DCHECK(IsTextDirectionEnabled(direction));
+ client()->ChangeTextDirectionAndLayoutAlignment(direction);
+}
+
+void ViewsTextServicesContextMenuMac::LookUpInDictionary() {
+ gfx::DecoratedText text;
+ gfx::Point baseline_point;
+ if (client()->GetWordLookupDataFromSelection(&text, &baseline_point)) {
+ Widget* widget = client()->GetWidget();
+ views::View::ConvertPointToTarget(client(), widget->GetRootView(),
+ &baseline_point);
+ NSView* view = widget->GetNativeView().GetNativeNSView();
+ NSPoint lookup_point = NSMakePoint(
+ baseline_point.x(), NSHeight([view frame]) - baseline_point.y());
+ [view showDefinitionForAttributedString:
+ gfx::GetAttributedStringFromDecoratedText(text)
+ atPoint:lookup_point];
+ }
+}
} // namespace
@@ -141,11 +151,4 @@ ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
return std::make_unique<ViewsTextServicesContextMenuMac>(menu, client);
}
-// static
-bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting(
- ViewsTextServicesContextMenu* menu,
- base::i18n::TextDirection direction) {
- return static_cast<views::ViewsTextServicesContextMenuMac*>(menu)
- ->IsTextDirectionChecked(direction);
-}
} // namespace views
diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc
index 7bef84b8c9e..0c523877565 100644
--- a/chromium/ui/views/controls/webview/webview.cc
+++ b/chromium/ui/views/controls/webview/webview.cc
@@ -331,14 +331,6 @@ void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen,
ReattachForFullscreenChange(entered_fullscreen);
}
-void WebView::DidAttachInterstitialPage() {
- NotifyAccessibilityWebContentsChanged();
-}
-
-void WebView::DidDetachInterstitialPage() {
- NotifyAccessibilityWebContentsChanged();
-}
-
void WebView::OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) {
RequestFocus();
diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h
index 84d5cd520f7..624dcb1a4d8 100644
--- a/chromium/ui/views/controls/webview/webview.h
+++ b/chromium/ui/views/controls/webview/webview.h
@@ -149,8 +149,6 @@ class WEBVIEW_EXPORT WebView : public View,
void DidDestroyFullscreenWidget() override;
void DidToggleFullscreenModeForTab(bool entered_fullscreen,
bool will_cause_resize) override;
- void DidAttachInterstitialPage() override;
- void DidDetachInterstitialPage() override;
// Workaround for MSVC++ linker bug/feature that requires
// instantiation of the inline IPC::Listener methods in all translation units.
void OnChannelConnected(int32_t peer_id) override {}
diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc
index 5bfd431308d..5c28d3091f8 100644
--- a/chromium/ui/views/controls/webview/webview_unittest.cc
+++ b/chromium/ui/views/controls/webview/webview_unittest.cc
@@ -159,8 +159,8 @@ class WebViewUnitTest : public views::test::WidgetTest {
// child.
top_level_widget_ = CreateTopLevelFramelessPlatformWidget();
top_level_widget_->SetBounds(gfx::Rect(0, 10, 100, 100));
- View* const contents_view = new View();
- top_level_widget_->SetContentsView(contents_view);
+ View* const contents_view =
+ top_level_widget_->SetContentsView(std::make_unique<View>());
web_view_ = new WebView(browser_context_.get());
web_view_->SetBoundsRect(gfx::Rect(contents_view->size()));
contents_view->AddChildView(web_view_);
diff --git a/chromium/ui/views/corewm/tooltip_aura.cc b/chromium/ui/views/corewm/tooltip_aura.cc
index 2ae616b9896..9cee7148dc9 100644
--- a/chromium/ui/views/corewm/tooltip_aura.cc
+++ b/chromium/ui/views/corewm/tooltip_aura.cc
@@ -53,40 +53,14 @@ bool CanUseTranslucentTooltipWidget() {
#endif
}
-// Creates a widget of type TYPE_TOOLTIP
-views::Widget* CreateTooltipWidget(aura::Window* tooltip_window) {
- views::Widget* widget = new views::Widget;
- views::Widget::InitParams params;
- // For aura, since we set the type to TYPE_TOOLTIP, the widget will get
- // auto-parented to the right container.
- params.type = views::Widget::InitParams::TYPE_TOOLTIP;
- params.context = tooltip_window;
- DCHECK(params.context);
- params.z_order = ui::ZOrderLevel::kFloatingUIElement;
- params.accept_events = false;
- if (CanUseTranslucentTooltipWidget())
- params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
- params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
- // Use software compositing to avoid using unnecessary hardware resources
- // which just amount to overkill for this UI.
- params.force_software_compositing = true;
- widget->Init(std::move(params));
- return widget;
-}
-
-} // namespace
-
-namespace views {
-namespace corewm {
-
// TODO(oshima): Consider to use views::Label.
-class TooltipAura::TooltipView : public views::View {
+class TooltipView : public views::View {
public:
TooltipView() : render_text_(gfx::RenderText::CreateRenderText()) {
- SetBorder(CreateEmptyBorder(kVerticalPaddingTop, kHorizontalPadding,
- kVerticalPaddingBottom, kHorizontalPadding));
+ SetBorder(views::CreateEmptyBorder(kVerticalPaddingTop, kHorizontalPadding,
+ kVerticalPaddingBottom,
+ kHorizontalPadding));
- set_owned_by_client();
render_text_->SetWordWrapBehavior(gfx::WRAP_LONG_WORDS);
render_text_->SetMultiline(true);
@@ -177,18 +151,39 @@ class TooltipAura::TooltipView : public views::View {
DISALLOW_COPY_AND_ASSIGN(TooltipView);
};
-TooltipAura::TooltipAura() : tooltip_view_(new TooltipView) {}
+} // namespace
+
+namespace views {
+namespace corewm {
TooltipAura::~TooltipAura() {
DestroyWidget();
+ CHECK(!IsInObserverList());
}
+class TooltipAura::TooltipWidget : public Widget {
+ public:
+ TooltipWidget() = default;
+ ~TooltipWidget() override = default;
+
+ TooltipView* GetTooltipView() { return tooltip_view_; }
+
+ void SetTooltipView(std::unique_ptr<TooltipView> tooltip_view) {
+ tooltip_view_ = SetContentsView(std::move(tooltip_view));
+ }
+
+ private:
+ TooltipView* tooltip_view_ = nullptr;
+};
+
gfx::RenderText* TooltipAura::GetRenderTextForTest() {
- return tooltip_view_->render_text_for_test();
+ DCHECK(widget_);
+ return widget_->GetTooltipView()->render_text_for_test();
}
void TooltipAura::GetAccessibleNodeDataForTest(ui::AXNodeData* node_data) {
- tooltip_view_->GetAccessibleNodeData(node_data);
+ DCHECK(widget_);
+ widget_->GetTooltipView()->GetAccessibleNodeData(node_data);
}
gfx::Rect TooltipAura::GetTooltipBounds(const gfx::Point& mouse_pos,
@@ -214,6 +209,28 @@ gfx::Rect TooltipAura::GetTooltipBounds(const gfx::Point& mouse_pos,
return tooltip_rect;
}
+void TooltipAura::CreateTooltipWidget() {
+ DCHECK(!widget_);
+ DCHECK(tooltip_window_);
+ widget_ = new TooltipWidget;
+ views::Widget::InitParams params;
+ // For aura, since we set the type to TYPE_TOOLTIP, the widget will get
+ // auto-parented to the right container.
+ params.type = views::Widget::InitParams::TYPE_TOOLTIP;
+ params.context = tooltip_window_;
+ DCHECK(params.context);
+ params.z_order = ui::ZOrderLevel::kFloatingUIElement;
+ params.accept_events = false;
+ if (CanUseTranslucentTooltipWidget())
+ params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
+ params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
+ // Use software compositing to avoid using unnecessary hardware resources
+ // which just amount to overkill for this UI.
+ params.force_software_compositing = true;
+ widget_->Init(std::move(params));
+ widget_->SetTooltipView(std::make_unique<TooltipView>());
+}
+
void TooltipAura::DestroyWidget() {
if (widget_) {
widget_->RemoveObserver(this);
@@ -232,15 +249,17 @@ void TooltipAura::SetText(aura::Window* window,
const base::string16& tooltip_text,
const gfx::Point& location) {
tooltip_window_ = window;
- tooltip_view_->SetMaxWidth(GetMaxWidth(location));
- tooltip_view_->SetText(tooltip_text);
if (!widget_) {
- widget_ = CreateTooltipWidget(tooltip_window_);
- widget_->SetContentsView(tooltip_view_.get());
+ CreateTooltipWidget();
widget_->AddObserver(this);
}
+ TooltipView* tooltip_view = widget_->GetTooltipView();
+
+ tooltip_view->SetMaxWidth(GetMaxWidth(location));
+ tooltip_view->SetText(tooltip_text);
+
ui::NativeTheme* native_theme = widget_->GetNativeTheme();
auto background_color =
native_theme->GetSystemColor(ui::NativeTheme::kColorId_TooltipBackground);
@@ -254,8 +273,8 @@ void TooltipAura::SetText(aura::Window* window,
if (!CanUseTranslucentTooltipWidget())
foreground_color =
color_utils::GetResultingPaintColor(foreground_color, background_color);
- tooltip_view_->SetBackgroundColor(background_color, foreground_color);
- tooltip_view_->SetForegroundColor(foreground_color);
+ tooltip_view->SetBackgroundColor(background_color, foreground_color);
+ tooltip_view->SetForegroundColor(foreground_color);
// Calculate the tooltip preferred size after all tooltip attributes are
// updated - tooltip updates (for example setting text color) may invalidate
@@ -265,7 +284,7 @@ void TooltipAura::SetText(aura::Window* window,
// GetPreferredSize() will generate fresh render text layout, even if the
// actual tooltip text hasn't changed).
const gfx::Rect adjusted_bounds =
- GetTooltipBounds(location, tooltip_view_->GetPreferredSize());
+ GetTooltipBounds(location, tooltip_view->GetPreferredSize());
widget_->SetBounds(adjusted_bounds);
}
@@ -273,8 +292,8 @@ void TooltipAura::Show() {
if (widget_) {
widget_->Show();
widget_->StackAtTop();
- tooltip_view_->NotifyAccessibilityEvent(ax::mojom::Event::kTooltipOpened,
- true);
+ widget_->GetTooltipView()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTooltipOpened, true);
}
}
@@ -288,9 +307,9 @@ void TooltipAura::Hide() {
// guarantees we never show outdated information.
// TODO(http://crbug.com/998280): Figure out why the old content is
// displayed despite the size change.
+ widget_->GetTooltipView()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTooltipClosed, true);
DestroyWidget();
- tooltip_view_->NotifyAccessibilityEvent(ax::mojom::Event::kTooltipClosed,
- true);
}
}
diff --git a/chromium/ui/views/corewm/tooltip_aura.h b/chromium/ui/views/corewm/tooltip_aura.h
index 1c632da80ba..56e2c913762 100644
--- a/chromium/ui/views/corewm/tooltip_aura.h
+++ b/chromium/ui/views/corewm/tooltip_aura.h
@@ -32,11 +32,11 @@ class TooltipAuraTestApi;
// Implementation of Tooltip that shows the tooltip using a Widget and Label.
class VIEWS_EXPORT TooltipAura : public Tooltip, public WidgetObserver {
public:
- TooltipAura();
+ TooltipAura() = default;
~TooltipAura() override;
private:
- class TooltipView;
+ class TooltipWidget;
friend class test::TooltipAuraTestApi;
gfx::RenderText* GetRenderTextForTest();
@@ -47,6 +47,9 @@ class VIEWS_EXPORT TooltipAura : public Tooltip, public WidgetObserver {
gfx::Rect GetTooltipBounds(const gfx::Point& mouse_pos,
const gfx::Size& tooltip_size);
+ // Sets |widget_| to a new instance of TooltipWidget.
+ void CreateTooltipWidget();
+
// Destroys |widget_|.
void DestroyWidget();
@@ -62,11 +65,8 @@ class VIEWS_EXPORT TooltipAura : public Tooltip, public WidgetObserver {
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override;
- // The view showing the tooltip.
- std::unique_ptr<TooltipView> tooltip_view_;
-
// The widget containing the tooltip. May be NULL.
- Widget* widget_ = nullptr;
+ TooltipWidget* widget_ = nullptr;
// The window we're showing the tooltip for. Never NULL and valid while
// showing.
diff --git a/chromium/ui/views/corewm/tooltip_controller.cc b/chromium/ui/views/corewm/tooltip_controller.cc
index c19d2296ff9..0ac53224396 100644
--- a/chromium/ui/views/corewm/tooltip_controller.cc
+++ b/chromium/ui/views/corewm/tooltip_controller.cc
@@ -233,20 +233,24 @@ void TooltipController::OnMouseEvent(ui::MouseEvent* event) {
void TooltipController::OnTouchEvent(ui::TouchEvent* event) {
// Hide the tooltip for touch events.
- tooltip_->Hide();
- SetTooltipWindow(nullptr);
+ HideTooltipAndResetStates();
last_touch_loc_ = event->location();
}
void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
- tooltip_->Hide();
- SetTooltipWindow(nullptr);
+ HideTooltipAndResetStates();
}
void TooltipController::OnCursorVisibilityChanged(bool is_visible) {
UpdateIfRequired();
}
+void TooltipController::OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) {
+ if (!visible)
+ HideTooltipAndResetStates();
+}
+
void TooltipController::OnWindowDestroyed(aura::Window* window) {
if (tooltip_window_ == window) {
tooltip_->Hide();
@@ -350,6 +354,19 @@ void TooltipController::ShowTooltip() {
}
}
+void TooltipController::HideTooltipAndResetStates() {
+ // Hide any open tooltips.
+ if (tooltip_shown_timer_.IsRunning())
+ tooltip_shown_timer_.Stop();
+ tooltip_->Hide();
+
+ // Cancel pending tooltips and reset controller states.
+ if (tooltip_defer_timer_.IsRunning())
+ tooltip_defer_timer_.Stop();
+ SetTooltipWindow(nullptr);
+ tooltip_id_ = nullptr;
+}
+
bool TooltipController::IsTooltipVisible() {
return tooltip_->IsVisible();
}
diff --git a/chromium/ui/views/corewm/tooltip_controller.h b/chromium/ui/views/corewm/tooltip_controller.h
index 92595143a7c..8e2fd2f98ed 100644
--- a/chromium/ui/views/corewm/tooltip_controller.h
+++ b/chromium/ui/views/corewm/tooltip_controller.h
@@ -57,6 +57,7 @@ class VIEWS_EXPORT TooltipController
void OnCursorVisibilityChanged(bool is_visible) override;
// Overridden from aura::WindowObserver.
+ void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
void OnWindowDestroyed(aura::Window* window) override;
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
@@ -72,6 +73,9 @@ class VIEWS_EXPORT TooltipController
// Show the tooltip.
void ShowTooltip();
+ // Hide the tooltip, clear timers, and reset controller states.
+ void HideTooltipAndResetStates();
+
// Updates the tooltip if required (if there is any change in the tooltip
// text, tooltip id or the aura::Window).
void UpdateIfRequired();
diff --git a/chromium/ui/views/corewm/tooltip_controller_test_helper.h b/chromium/ui/views/corewm/tooltip_controller_test_helper.h
index 3c2909548f2..9148194618b 100644
--- a/chromium/ui/views/corewm/tooltip_controller_test_helper.h
+++ b/chromium/ui/views/corewm/tooltip_controller_test_helper.h
@@ -5,7 +5,6 @@
#ifndef UI_VIEWS_COREWM_TOOLTIP_CONTROLLER_TEST_HELPER_H_
#define UI_VIEWS_COREWM_TOOLTIP_CONTROLLER_TEST_HELPER_H_
-#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string16.h"
#include "ui/views/view.h"
diff --git a/chromium/ui/views/corewm/tooltip_controller_unittest.cc b/chromium/ui/views/corewm/tooltip_controller_unittest.cc
index 30ba48c792c..8ad58bb25e0 100644
--- a/chromium/ui/views/corewm/tooltip_controller_unittest.cc
+++ b/chromium/ui/views/corewm/tooltip_controller_unittest.cc
@@ -4,6 +4,7 @@
#include "ui/views/corewm/tooltip_controller.h"
+#include <memory>
#include <utility>
#include "base/at_exit.h"
@@ -96,7 +97,7 @@ class TooltipControllerTest : public ViewsTestBase {
}
#endif
widget_.reset(CreateWidget(root_window));
- widget_->SetContentsView(new View);
+ widget_->SetContentsView(std::make_unique<View>());
view_ = new TooltipTestView;
widget_->GetContentsView()->AddChildView(view_);
view_->SetBoundsRect(widget_->GetContentsView()->GetLocalBounds());
@@ -539,7 +540,7 @@ TEST_F(TooltipControllerTest, MAYBE_Capture) {
view_->set_tooltip_text(tooltip_text);
std::unique_ptr<views::Widget> widget2(CreateWidget(GetContext()));
- widget2->SetContentsView(new View);
+ widget2->SetContentsView(std::make_unique<View>());
TooltipTestView* view2 = new TooltipTestView;
widget2->GetContentsView()->AddChildView(view2);
view2->set_tooltip_text(tooltip_text2);
@@ -696,7 +697,7 @@ class TooltipControllerTest3 : public ViewsTestBase {
aura::Window* root_window = GetContext();
widget_.reset(CreateWidget(root_window));
- widget_->SetContentsView(new View);
+ widget_->SetContentsView(std::make_unique<View>());
view_ = new TooltipTestView;
widget_->GetContentsView()->AddChildView(view_);
view_->SetBoundsRect(widget_->GetContentsView()->GetLocalBounds());
diff --git a/chromium/ui/views/examples/combobox_example.cc b/chromium/ui/views/examples/combobox_example.cc
index dc1e8bba76f..79f9c2065ee 100644
--- a/chromium/ui/views/examples/combobox_example.cc
+++ b/chromium/ui/views/examples/combobox_example.cc
@@ -27,7 +27,7 @@ class ComboboxModelExample : public ui::ComboboxModel {
private:
// ui::ComboboxModel:
int GetItemCount() const override { return 10; }
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
return base::UTF8ToUTF16(base::StringPrintf("%c item", 'A' + index));
}
diff --git a/chromium/ui/views/examples/example_combobox_model.cc b/chromium/ui/views/examples/example_combobox_model.cc
index bd3fd9440c9..841d890e2d1 100644
--- a/chromium/ui/views/examples/example_combobox_model.cc
+++ b/chromium/ui/views/examples/example_combobox_model.cc
@@ -19,7 +19,7 @@ int ExampleComboboxModel::GetItemCount() const {
return count_;
}
-base::string16 ExampleComboboxModel::GetItemAt(int index) {
+base::string16 ExampleComboboxModel::GetItemAt(int index) const {
return base::ASCIIToUTF16(strings_[index]);
}
diff --git a/chromium/ui/views/examples/example_combobox_model.h b/chromium/ui/views/examples/example_combobox_model.h
index 8f6c389a4ae..79c0d64f9d6 100644
--- a/chromium/ui/views/examples/example_combobox_model.h
+++ b/chromium/ui/views/examples/example_combobox_model.h
@@ -18,7 +18,7 @@ class ExampleComboboxModel : public ui::ComboboxModel {
// ui::ComboboxModel:
int GetItemCount() const override;
- base::string16 GetItemAt(int index) override;
+ base::string16 GetItemAt(int index) const override;
private:
const char* const* const strings_;
diff --git a/chromium/ui/views/examples/examples_main_proc.cc b/chromium/ui/views/examples/examples_main_proc.cc
index fe417da38a3..ffdbc86fd5d 100644
--- a/chromium/ui/views/examples/examples_main_proc.cc
+++ b/chromium/ui/views/examples/examples_main_proc.cc
@@ -148,7 +148,6 @@ ExamplesExitCode ExamplesMainProc(bool under_test) {
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
std::unique_ptr<display::Screen> desktop_screen =
base::WrapUnique(views::CreateDesktopScreen());
- display::Screen::SetScreenInstance(desktop_screen.get());
#endif
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
diff --git a/chromium/ui/views/examples/examples_skia_gold_pixel_diff.cc b/chromium/ui/views/examples/examples_skia_gold_pixel_diff.cc
index ffabc89ae2b..36fb5bdeb44 100644
--- a/chromium/ui/views/examples/examples_skia_gold_pixel_diff.cc
+++ b/chromium/ui/views/examples/examples_skia_gold_pixel_diff.cc
@@ -39,8 +39,8 @@ ExamplesExitCode ExamplesSkiaGoldPixelDiff::CompareScreenshot(
run_loop.Run();
if (screenshot_.IsEmpty())
return ExamplesExitCode::kImageEmpty;
- return SkiaGoldPixelDiff::CompareScreenshot(screenshot_name,
- *screenshot_.ToSkBitmap())
+ return ui::test::SkiaGoldPixelDiff::CompareScreenshot(
+ screenshot_name, *screenshot_.ToSkBitmap())
? ExamplesExitCode::kSucceeded
: ExamplesExitCode::kFailed;
}
diff --git a/chromium/ui/views/examples/examples_skia_gold_pixel_diff.h b/chromium/ui/views/examples/examples_skia_gold_pixel_diff.h
index 4bb102d97eb..8f04778f736 100644
--- a/chromium/ui/views/examples/examples_skia_gold_pixel_diff.h
+++ b/chromium/ui/views/examples/examples_skia_gold_pixel_diff.h
@@ -15,7 +15,7 @@
namespace views {
namespace examples {
-class ExamplesSkiaGoldPixelDiff : public SkiaGoldPixelDiff {
+class ExamplesSkiaGoldPixelDiff : public ui::test::SkiaGoldPixelDiff {
public:
ExamplesSkiaGoldPixelDiff();
~ExamplesSkiaGoldPixelDiff() override;
diff --git a/chromium/ui/views/examples/examples_window.cc b/chromium/ui/views/examples/examples_window.cc
index d2cde9bb4e0..c8cb0f2ff9f 100644
--- a/chromium/ui/views/examples/examples_window.cc
+++ b/chromium/ui/views/examples/examples_window.cc
@@ -95,7 +95,7 @@ class ComboboxModelExampleList : public ui::ComboboxModel {
// ui::ComboboxModel:
int GetItemCount() const override { return example_list_.size(); }
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
return base::UTF8ToUTF16(example_list_[index]->example_title());
}
@@ -114,6 +114,8 @@ class ExamplesWindowContents : public WidgetDelegateView,
public:
ExamplesWindowContents(base::OnceClosure on_close, ExampleVector examples)
: on_close_(std::move(on_close)) {
+ SetHasWindowSizeControls(true);
+
auto combobox_model = std::make_unique<ComboboxModelExampleList>();
combobox_model_ = combobox_model.get();
combobox_model_->SetExamples(std::move(examples));
@@ -161,9 +163,6 @@ class ExamplesWindowContents : public WidgetDelegateView,
private:
// WidgetDelegateView:
- bool CanResize() const override { return true; }
- bool CanMaximize() const override { return true; }
- bool CanMinimize() const override { return true; }
base::string16 GetWindowTitle() const override {
return base::ASCIIToUTF16("Views Examples");
}
diff --git a/chromium/ui/views/focus/focus_manager.cc b/chromium/ui/views/focus/focus_manager.cc
index 0b634e3f151..536755394f4 100644
--- a/chromium/ui/views/focus/focus_manager.cc
+++ b/chromium/ui/views/focus/focus_manager.cc
@@ -206,7 +206,9 @@ bool FocusManager::RotatePaneFocus(Direction direction,
continue;
pane->RequestFocus();
- focused_view = GetFocusedView();
+ // |pane| may be in a different widget, so don't assume its focus manager
+ // is |this|.
+ focused_view = pane->GetWidget()->GetFocusManager()->GetFocusedView();
if (pane == focused_view || pane->Contains(focused_view))
return true;
}
@@ -609,7 +611,10 @@ void FocusManager::OnViewIsDeleting(View* view) {
bool FocusManager::RedirectAcceleratorToBubbleAnchorWidget(
const ui::Accelerator& accelerator) {
- Widget* anchor_widget = GetBubbleAnchorWidget();
+ views::BubbleDialogDelegate* widget_delegate =
+ widget_->widget_delegate()->AsBubbleDialogDelegate();
+ Widget* anchor_widget =
+ widget_delegate ? widget_delegate->anchor_widget() : nullptr;
if (!anchor_widget)
return false;
@@ -617,15 +622,32 @@ bool FocusManager::RedirectAcceleratorToBubbleAnchorWidget(
if (!focus_manager->IsAcceleratorRegistered(accelerator))
return false;
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ // Processing an accelerator can delete things. Because we
+ // need these objects afterwards on Linux, save widget_ as weak pointer and
+ // save the close_on_deactivate property value of widget_delegate in a
+ // variable.
+ base::WeakPtr<Widget> widget_weak_ptr = widget_->GetWeakPtr();
+ const bool close_widget_on_deactivate =
+ widget_delegate->close_on_deactivate();
+#endif
+
// The parent view must be focused for it to process events.
focus_manager->SetFocusedView(anchor_widget->GetRootView());
- return focus_manager->ProcessAccelerator(accelerator);
-}
+ const bool accelerator_processed =
+ focus_manager->ProcessAccelerator(accelerator);
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ // Need to manually close the bubble widget on Linux. On Linux when the
+ // bubble is shown, the main widget remains active. Because of that when
+ // focus is set to the main widget to process accelerator, the main widget
+ // isn't activated and the bubble widget isn't deactivated and closed.
+ if (accelerator_processed && close_widget_on_deactivate) {
+ widget_weak_ptr->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
+ }
+#endif
-Widget* FocusManager::GetBubbleAnchorWidget() {
- BubbleDialogDelegateView* widget_delegate =
- widget_->widget_delegate()->AsBubbleDialogDelegate();
- return widget_delegate ? widget_delegate->anchor_widget() : nullptr;
+ return accelerator_processed;
}
} // namespace views
diff --git a/chromium/ui/views/focus/focus_manager.h b/chromium/ui/views/focus/focus_manager.h
index 8c7888aeff3..bd1a2aef25e 100644
--- a/chromium/ui/views/focus/focus_manager.h
+++ b/chromium/ui/views/focus/focus_manager.h
@@ -336,9 +336,6 @@ class VIEWS_EXPORT FocusManager : public ViewObserver {
bool RedirectAcceleratorToBubbleAnchorWidget(
const ui::Accelerator& accelerator);
- // Returns bubble's anchor widget.
- Widget* GetBubbleAnchorWidget();
-
// Whether arrow key traversal is enabled globally.
static bool arrow_key_traversal_enabled_;
diff --git a/chromium/ui/views/focus/focus_manager_unittest.cc b/chromium/ui/views/focus/focus_manager_unittest.cc
index b4438b0aa46..ff91ea00b53 100644
--- a/chromium/ui/views/focus/focus_manager_unittest.cc
+++ b/chromium/ui/views/focus/focus_manager_unittest.cc
@@ -1244,9 +1244,8 @@ class RedirectToParentFocusManagerTest : public FocusManagerTest {
GetWidget()->GetRootView()->AddChildView(std::make_unique<View>());
anchor->SetFocusBehavior(View::FocusBehavior::ALWAYS);
- BubbleDialogDelegateView* bubble_delegate =
- TestBubbleDialogDelegateView::CreateAndShowBubble(anchor);
- Widget* bubble_widget = bubble_delegate->GetWidget();
+ bubble_ = TestBubbleDialogDelegateView::CreateAndShowBubble(anchor);
+ Widget* bubble_widget = bubble_->GetWidget();
parent_focus_manager_ = anchor->GetFocusManager();
bubble_focus_manager_ = bubble_widget->GetFocusManager();
@@ -1258,8 +1257,10 @@ class RedirectToParentFocusManagerTest : public FocusManagerTest {
}
protected:
- FocusManager* parent_focus_manager_;
- FocusManager* bubble_focus_manager_;
+ FocusManager* parent_focus_manager_ = nullptr;
+ FocusManager* bubble_focus_manager_ = nullptr;
+
+ BubbleDialogDelegateView* bubble_ = nullptr;
};
// Test that when an accelerator is sent to a bubble that isn't registered,
@@ -1267,6 +1268,7 @@ class RedirectToParentFocusManagerTest : public FocusManagerTest {
TEST_F(RedirectToParentFocusManagerTest, ParentHandlesAcceleratorFromBubble) {
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
ui::TestAcceleratorTarget parent_return_target(true);
+ Widget* bubble_widget = bubble_->GetWidget();
EXPECT_EQ(0, parent_return_target.accelerator_count());
parent_focus_manager_->RegisterAccelerator(
@@ -1275,9 +1277,23 @@ TEST_F(RedirectToParentFocusManagerTest, ParentHandlesAcceleratorFromBubble) {
EXPECT_TRUE(
!bubble_focus_manager_->IsAcceleratorRegistered(return_accelerator));
- // Accelerator was proccesed by the parent.
+
+ // The bubble should be closed after parent processed accelerator only if
+ // close_on_deactivate is true.
+ bubble_->set_close_on_deactivate(false);
+ // Accelerator was processed by the parent.
EXPECT_TRUE(bubble_focus_manager_->ProcessAccelerator(return_accelerator));
EXPECT_EQ(parent_return_target.accelerator_count(), 1);
+ EXPECT_FALSE(bubble_widget->IsClosed());
+
+ // Reset focus to the bubble widget. Focus was set to the the main widget
+ // to process accelerator.
+ bubble_focus_manager_->SetFocusedView(bubble_widget->GetRootView());
+
+ bubble_->set_close_on_deactivate(true);
+ EXPECT_TRUE(bubble_focus_manager_->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(parent_return_target.accelerator_count(), 2);
+ EXPECT_TRUE(bubble_widget->IsClosed());
}
// Test that when an accelerator is sent to a bubble that is registered on both
@@ -1286,6 +1302,7 @@ TEST_F(RedirectToParentFocusManagerTest, BubbleHandlesRegisteredAccelerators) {
ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
ui::TestAcceleratorTarget parent_return_target(true);
ui::TestAcceleratorTarget bubble_return_target(true);
+ Widget* bubble_widget = bubble_->GetWidget();
EXPECT_EQ(0, bubble_return_target.accelerator_count());
EXPECT_EQ(0, parent_return_target.accelerator_count());
@@ -1297,10 +1314,32 @@ TEST_F(RedirectToParentFocusManagerTest, BubbleHandlesRegisteredAccelerators) {
return_accelerator, ui::AcceleratorManager::kNormalPriority,
&parent_return_target);
- // Accelerator was proccesed by the bubble and not by the parent.
+ // The bubble shouldn't be closed after it processed accelerator without
+ // passing it to the parent.
+ bubble_->set_close_on_deactivate(true);
+ // Accelerator was processed by the bubble and not by the parent.
EXPECT_TRUE(bubble_focus_manager_->ProcessAccelerator(return_accelerator));
EXPECT_EQ(1, bubble_return_target.accelerator_count());
EXPECT_EQ(0, parent_return_target.accelerator_count());
+ EXPECT_FALSE(bubble_widget->IsClosed());
+}
+
+// Test that when an accelerator is sent to a bubble that isn't registered
+// for either the bubble or the bubble's parent, the bubble isn't closed.
+TEST_F(RedirectToParentFocusManagerTest, NotProcessedAccelerator) {
+ ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE);
+ Widget* bubble_widget = bubble_->GetWidget();
+
+ EXPECT_TRUE(
+ !bubble_focus_manager_->IsAcceleratorRegistered(return_accelerator));
+ EXPECT_TRUE(
+ !parent_focus_manager_->IsAcceleratorRegistered(return_accelerator));
+
+ // The bubble shouldn't be closed if the accelerator was passed to the parent
+ // but the parent didn't process it.
+ bubble_->set_close_on_deactivate(true);
+ EXPECT_FALSE(bubble_focus_manager_->ProcessAccelerator(return_accelerator));
+ EXPECT_FALSE(bubble_widget->IsClosed());
}
#endif
diff --git a/chromium/ui/views/focus/focus_search.cc b/chromium/ui/views/focus/focus_search.cc
index 58f9b621965..1cbf0b156d7 100644
--- a/chromium/ui/views/focus/focus_search.cc
+++ b/chromium/ui/views/focus/focus_search.cc
@@ -203,7 +203,7 @@ View* FocusSearch::FindNextFocusableViewImpl(
// Check to see if we should navigate into a dialog anchored at this view.
if (can_go_into_anchored_dialog ==
AnchoredDialogPolicy::kCanGoIntoAnchoredDialog) {
- BubbleDialogDelegateView* bubble =
+ BubbleDialogDelegate* bubble =
starting_view->GetProperty(kAnchoredDialogKey);
if (bubble) {
*focus_traversable = bubble->GetWidget()->GetFocusTraversable();
@@ -230,8 +230,7 @@ View* FocusSearch::FindNextFocusableViewImpl(
while (parent && parent != root_) {
if (can_go_into_anchored_dialog ==
AnchoredDialogPolicy::kCanGoIntoAnchoredDialog) {
- BubbleDialogDelegateView* bubble =
- parent->GetProperty(kAnchoredDialogKey);
+ BubbleDialogDelegate* bubble = parent->GetProperty(kAnchoredDialogKey);
if (bubble) {
*focus_traversable = bubble->GetWidget()->GetFocusTraversable();
*focus_traversable_view = starting_view;
@@ -302,7 +301,7 @@ View* FocusSearch::FindPreviousFocusableViewImpl(
// Check to see if we should navigate into a dialog anchored at this view.
if (can_go_into_anchored_dialog ==
AnchoredDialogPolicy::kCanGoIntoAnchoredDialog) {
- BubbleDialogDelegateView* bubble =
+ BubbleDialogDelegate* bubble =
starting_view->GetProperty(kAnchoredDialogKey);
if (bubble) {
*focus_traversable = bubble->GetWidget()->GetFocusTraversable();
diff --git a/chromium/ui/views/focus/focus_traversal_unittest.cc b/chromium/ui/views/focus/focus_traversal_unittest.cc
index cf5540c9325..c8dcc449f51 100644
--- a/chromium/ui/views/focus/focus_traversal_unittest.cc
+++ b/chromium/ui/views/focus/focus_traversal_unittest.cc
@@ -94,7 +94,7 @@ class DummyComboboxModel : public ui::ComboboxModel {
public:
// Overridden from ui::ComboboxModel:
int GetItemCount() const override { return 10; }
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
return ASCIIToUTF16("Item ") + base::NumberToString16(index);
}
};
diff --git a/chromium/ui/views/layout/grid_layout.h b/chromium/ui/views/layout/grid_layout.h
index 5e64d647a62..9c6a82cd127 100644
--- a/chromium/ui/views/layout/grid_layout.h
+++ b/chromium/ui/views/layout/grid_layout.h
@@ -11,7 +11,7 @@
#include <utility>
#include <vector>
-#include "base/logging.h"
+#include "base/check.h"
#include "base/macros.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/layout/layout_manager.h"
diff --git a/chromium/ui/views/linux_ui/linux_ui.cc b/chromium/ui/views/linux_ui/linux_ui.cc
index 27ae4e29d04..585fea624f8 100644
--- a/chromium/ui/views/linux_ui/linux_ui.cc
+++ b/chromium/ui/views/linux_ui/linux_ui.cc
@@ -28,7 +28,6 @@ void LinuxUI::SetInstance(LinuxUI* instance) {
SkiaFontDelegate::SetInstance(instance);
ShellDialogLinux::SetInstance(instance);
ui::SetTextEditKeyBindingsDelegate(instance);
- ui::CursorThemeManagerLinux::SetInstance(instance);
}
LinuxUI* LinuxUI::instance() {
diff --git a/chromium/ui/views/linux_ui/linux_ui.h b/chromium/ui/views/linux_ui/linux_ui.h
index 55d73138880..7641a91abf0 100644
--- a/chromium/ui/views/linux_ui/linux_ui.h
+++ b/chromium/ui/views/linux_ui/linux_ui.h
@@ -12,7 +12,7 @@
#include "base/macros.h"
#include "build/buildflag.h"
#include "third_party/skia/include/core/SkColor.h"
-#include "ui/base/cursor/cursor_theme_manager_linux.h"
+#include "ui/base/cursor/cursor_theme_manager.h"
#include "ui/base/ime/linux/linux_input_method_context_factory.h"
#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
#include "ui/gfx/animation/animation_settings_provider_linux.h"
@@ -55,7 +55,7 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory,
public gfx::SkiaFontDelegate,
public ui::ShellDialogLinux,
public ui::TextEditKeyBindingsDelegateAuraLinux,
- public ui::CursorThemeManagerLinux,
+ public ui::CursorThemeManager,
public gfx::AnimationSettingsProviderLinux {
public:
using UseSystemThemeCallback =
diff --git a/chromium/ui/views/native_cursor_mac.mm b/chromium/ui/views/native_cursor_mac.mm
index 967484deb72..9d9c1c46abd 100644
--- a/chromium/ui/views/native_cursor_mac.mm
+++ b/chromium/ui/views/native_cursor_mac.mm
@@ -6,6 +6,8 @@
#include <Cocoa/Cocoa.h>
+#include "base/notreached.h"
+
namespace views {
gfx::NativeCursor GetNativeIBeamCursor() {
diff --git a/chromium/ui/views/touchui/touch_selection_controller_impl.cc b/chromium/ui/views/touchui/touch_selection_controller_impl.cc
index a2da9320fb2..a5dbd5b1750 100644
--- a/chromium/ui/views/touchui/touch_selection_controller_impl.cc
+++ b/chromium/ui/views/touchui/touch_selection_controller_impl.cc
@@ -71,21 +71,6 @@ constexpr int kSelectionHandleBarMinHeight = 5;
// boundaries.
constexpr int kSelectionHandleBarBottomAllowance = 3;
-// Creates a widget to host SelectionHandleView.
-views::Widget* CreateTouchSelectionPopupWidget(
- gfx::NativeView parent,
- views::WidgetDelegate* widget_delegate) {
- views::Widget* widget = new views::Widget;
- views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
- params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
- params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
- params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- params.parent = parent;
- params.delegate = widget_delegate;
- widget->Init(std::move(params));
- return widget;
-}
-
gfx::Image* GetCenterHandleImage() {
static gfx::Image* handle_image = nullptr;
if (!handle_image) {
@@ -209,8 +194,7 @@ namespace views {
using EditingHandleView = TouchSelectionControllerImpl::EditingHandleView;
// A View that displays the text selection handle.
-class TouchSelectionControllerImpl::EditingHandleView
- : public WidgetDelegateView {
+class TouchSelectionControllerImpl::EditingHandleView : public View {
public:
EditingHandleView(TouchSelectionControllerImpl* controller,
gfx::NativeView parent,
@@ -218,28 +202,33 @@ class TouchSelectionControllerImpl::EditingHandleView
: controller_(controller),
image_(GetCenterHandleImage()),
is_cursor_handle_(is_cursor_handle),
- draw_invisible_(false) {
- widget_.reset(CreateTouchSelectionPopupWidget(parent, this));
+ draw_invisible_(false),
+ widget_(new views::Widget) {
+ // Create a widget to host EditingHandleView.
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
+ params.shadow_type = views::Widget::InitParams::ShadowType::kNone;
+ params.parent = parent;
+ widget_->Init(std::move(params));
+
+ widget_->GetNativeWindow()->SetEventTargeter(
+ std::make_unique<aura::WindowTargeter>());
+ widget_->SetContentsView(this);
+ }
- targeter_ = new aura::WindowTargeter();
- aura::Window* window = widget_->GetNativeWindow();
- window->SetEventTargeter(std::unique_ptr<aura::WindowTargeter>(targeter_));
+ EditingHandleView(const EditingHandleView&) = delete;
+ EditingHandleView& operator=(const EditingHandleView&) = delete;
+ ~EditingHandleView() override = default;
- // We are owned by the TouchSelectionControllerImpl.
- set_owned_by_client();
+ void CloseHandleWidget() {
+ SetWidgetVisible(false);
+ widget_->CloseNow();
}
- ~EditingHandleView() override { SetWidgetVisible(false, false); }
-
gfx::SelectionBound::Type selection_bound_type() {
return selection_bound_.type();
}
- // WidgetDelegateView:
- void DeleteDelegate() override {
- // We are owned and deleted by TouchSelectionControllerImpl.
- }
-
// View:
void OnPaint(gfx::Canvas* canvas) override {
if (draw_invisible_)
@@ -271,14 +260,7 @@ class TouchSelectionControllerImpl::EditingHandleView
}
case ui::ET_GESTURE_SCROLL_END:
case ui::ET_SCROLL_FLING_START: {
- // Use a weak pointer to the handle to make sure the handle and its
- // owning selection controller is not destroyed by the capture release
- // to diagnose a crash on Windows (see crbug.com/459423)
- // TODO(mohsen): Delete the diagnostics code when the crash is fixed.
- base::WeakPtr<EditingHandleView> weak_ptr =
- weak_ptr_factory_.GetWeakPtr();
widget_->ReleaseCapture();
- CHECK(weak_ptr);
controller_->SetDraggingHandle(nullptr);
break;
}
@@ -297,11 +279,9 @@ class TouchSelectionControllerImpl::EditingHandleView
bool IsWidgetVisible() const { return widget_->IsVisible(); }
- void SetWidgetVisible(bool visible, bool quick) {
+ void SetWidgetVisible(bool visible) {
if (widget_->IsVisible() == visible)
return;
- widget_->SetVisibilityAnimationDuration(
- quick ? base::TimeDelta::FromMilliseconds(50) : base::TimeDelta());
if (visible)
widget_->Show();
else
@@ -349,7 +329,10 @@ class TouchSelectionControllerImpl::EditingHandleView
const gfx::Insets insets(
selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset, 0,
0, 0);
- targeter_->SetInsets(insets, insets);
+
+ // Shifts the hit-test target below the apparent bounds to make dragging
+ // easier.
+ widget_->GetNativeWindow()->targeter()->SetInsets(insets, insets);
}
void SetDrawInvisible(bool draw_invisible) {
@@ -360,15 +343,8 @@ class TouchSelectionControllerImpl::EditingHandleView
}
private:
- std::unique_ptr<Widget> widget_;
TouchSelectionControllerImpl* controller_;
- // A WindowTargeter that shifts the hit-test target below the apparent bounds
- // to make dragging easier. The |widget_|'s NativeWindow takes ownership over
- // the |targeter_| but since the |widget_|'s lifetime is known to this class,
- // it can safely access the |targeter_|.
- aura::WindowTargeter* targeter_;
-
// In local coordinates
gfx::SelectionBound selection_bound_;
gfx::Image* image_;
@@ -388,9 +364,8 @@ class TouchSelectionControllerImpl::EditingHandleView
// handle.
bool draw_invisible_;
- base::WeakPtrFactory<EditingHandleView> weak_ptr_factory_{this};
-
- DISALLOW_COPY_AND_ASSIGN(EditingHandleView);
+ // Owning widget.
+ Widget* widget_ = nullptr;
};
TouchSelectionControllerImpl::TouchSelectionControllerImpl(
@@ -423,6 +398,11 @@ TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
aura::Env::GetInstance()->RemoveEventObserver(this);
if (client_widget_)
client_widget_->RemoveObserver(this);
+ // Close the owning Widgets to clean up the EditingHandleViews.
+ selection_handle_1_->CloseHandleWidget();
+ selection_handle_2_->CloseHandleWidget();
+ cursor_handle_->CloseHandleWidget();
+ CHECK(!IsInObserverList());
}
void TouchSelectionControllerImpl::SelectionChanged() {
@@ -477,11 +457,11 @@ void TouchSelectionControllerImpl::SelectionChanged() {
// TODO(varunjain): Fix this: crbug.com/269003
dragging_handle_->SetDrawInvisible(!ShouldShowHandleFor(focus));
- if (dragging_handle_ != cursor_handle_.get()) {
+ if (dragging_handle_ != cursor_handle_) {
// The non-dragging-handle might have recently become visible.
- EditingHandleView* non_dragging_handle = selection_handle_1_.get();
- if (dragging_handle_ == selection_handle_1_.get()) {
- non_dragging_handle = selection_handle_2_.get();
+ EditingHandleView* non_dragging_handle = selection_handle_1_;
+ if (dragging_handle_ == selection_handle_1_) {
+ non_dragging_handle = selection_handle_2_;
// if handle 1 is being dragged, it is corresponding to the end of
// selection and the other handle to the start of selection.
selection_bound_1_ = screen_bound_focus;
@@ -497,30 +477,18 @@ void TouchSelectionControllerImpl::SelectionChanged() {
// Check if there is any selection at all.
if (screen_bound_anchor.edge_start() == screen_bound_focus.edge_start() &&
screen_bound_anchor.edge_end() == screen_bound_focus.edge_end()) {
- selection_handle_1_->SetWidgetVisible(false, false);
- selection_handle_2_->SetWidgetVisible(false, false);
- SetHandleBound(cursor_handle_.get(), anchor, screen_bound_anchor_clipped);
+ selection_handle_1_->SetWidgetVisible(false);
+ selection_handle_2_->SetWidgetVisible(false);
+ SetHandleBound(cursor_handle_, anchor, screen_bound_anchor_clipped);
return;
}
- cursor_handle_->SetWidgetVisible(false, false);
- SetHandleBound(selection_handle_1_.get(), anchor,
- screen_bound_anchor_clipped);
- SetHandleBound(selection_handle_2_.get(), focus,
- screen_bound_focus_clipped);
+ cursor_handle_->SetWidgetVisible(false);
+ SetHandleBound(selection_handle_1_, anchor, screen_bound_anchor_clipped);
+ SetHandleBound(selection_handle_2_, focus, screen_bound_focus_clipped);
}
}
-bool TouchSelectionControllerImpl::IsHandleDragInProgress() {
- return !!dragging_handle_;
-}
-
-void TouchSelectionControllerImpl::HideHandles(bool quick) {
- selection_handle_1_->SetWidgetVisible(false, quick);
- selection_handle_2_->SetWidgetVisible(false, quick);
- cursor_handle_->SetWidgetVisible(false, quick);
-}
-
void TouchSelectionControllerImpl::ShowQuickMenuImmediatelyForTesting() {
if (quick_menu_timer_.IsRunning()) {
quick_menu_timer_.Stop();
@@ -543,15 +511,15 @@ void TouchSelectionControllerImpl::SelectionHandleDragged(
gfx::Point drag_pos_in_client = drag_pos;
ConvertPointToClientView(dragging_handle_, &drag_pos_in_client);
- if (dragging_handle_ == cursor_handle_.get()) {
+ if (dragging_handle_ == cursor_handle_) {
client_view_->MoveCaretTo(drag_pos_in_client);
return;
}
// Find the stationary selection handle.
- gfx::SelectionBound anchor_bound =
- selection_handle_1_.get() == dragging_handle_ ? selection_bound_2_
- : selection_bound_1_;
+ gfx::SelectionBound anchor_bound = selection_handle_1_ == dragging_handle_
+ ? selection_bound_2_
+ : selection_bound_1_;
// Find selection end points in client_view's coordinate system.
gfx::Point p2 = anchor_bound.edge_start_rounded();
@@ -575,7 +543,7 @@ void TouchSelectionControllerImpl::SetHandleBound(
EditingHandleView* handle,
const gfx::SelectionBound& bound,
const gfx::SelectionBound& bound_in_screen) {
- handle->SetWidgetVisible(ShouldShowHandleFor(bound), false);
+ handle->SetWidgetVisible(ShouldShowHandleFor(bound));
handle->SetBoundInScreen(bound_in_screen, handle->IsWidgetVisible());
}
@@ -750,12 +718,12 @@ gfx::Rect TouchSelectionControllerImpl::GetExpectedHandleBounds(
return GetSelectionWidgetBounds(bound);
}
-WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() {
- return selection_handle_1_.get();
+View* TouchSelectionControllerImpl::GetHandle1View() {
+ return selection_handle_1_;
}
-WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() {
- return selection_handle_2_.get();
+View* TouchSelectionControllerImpl::GetHandle2View() {
+ return selection_handle_2_;
}
} // namespace views
diff --git a/chromium/ui/views/touchui/touch_selection_controller_impl.h b/chromium/ui/views/touchui/touch_selection_controller_impl.h
index af43216b179..a1e3b6291fc 100644
--- a/chromium/ui/views/touchui/touch_selection_controller_impl.h
+++ b/chromium/ui/views/touchui/touch_selection_controller_impl.h
@@ -19,7 +19,6 @@
#include "ui/views/widget/widget_observer.h"
namespace views {
-class WidgetDelegateView;
// Touch specific implementation of TouchEditingControllerDeprecated.
// Responsible for displaying selection handles and menu elements relevant in a
@@ -34,12 +33,13 @@ class VIEWS_EXPORT TouchSelectionControllerImpl
// Use ui::TouchEditingControllerFactory::Create() instead.
explicit TouchSelectionControllerImpl(ui::TouchEditable* client_view);
+ TouchSelectionControllerImpl(const TouchSelectionControllerImpl&) = delete;
+ TouchSelectionControllerImpl& operator=(const TouchSelectionControllerImpl&) =
+ delete;
~TouchSelectionControllerImpl() override;
// ui::TouchEditingControllerDeprecated:
void SelectionChanged() override;
- bool IsHandleDragInProgress() override;
- void HideHandles(bool quick) override;
void ShowQuickMenuImmediatelyForTesting();
@@ -108,14 +108,16 @@ class VIEWS_EXPORT TouchSelectionControllerImpl
bool IsSelectionHandle2Visible();
bool IsCursorHandleVisible();
gfx::Rect GetExpectedHandleBounds(const gfx::SelectionBound& bound);
- WidgetDelegateView* GetHandle1View();
- WidgetDelegateView* GetHandle2View();
+ View* GetHandle1View();
+ View* GetHandle2View();
ui::TouchEditable* client_view_;
Widget* client_widget_ = nullptr;
- std::unique_ptr<EditingHandleView> selection_handle_1_;
- std::unique_ptr<EditingHandleView> selection_handle_2_;
- std::unique_ptr<EditingHandleView> cursor_handle_;
+ // Non-owning pointers to EditingHandleViews. These views are owned by their
+ // Widget and cleaned up when their Widget closes.
+ EditingHandleView* selection_handle_1_;
+ EditingHandleView* selection_handle_2_;
+ EditingHandleView* cursor_handle_;
bool command_executed_ = false;
base::TimeTicks selection_start_time_;
@@ -138,8 +140,6 @@ class VIEWS_EXPORT TouchSelectionControllerImpl
// Selection bounds, clipped to client view's boundaries.
gfx::SelectionBound selection_bound_1_clipped_;
gfx::SelectionBound selection_bound_2_clipped_;
-
- DISALLOW_COPY_AND_ASSIGN(TouchSelectionControllerImpl);
};
} // namespace views
diff --git a/chromium/ui/views/touchui/touch_selection_controller_impl_unittest.cc b/chromium/ui/views/touchui/touch_selection_controller_impl_unittest.cc
index b2f2d5c4b99..c54289850cc 100644
--- a/chromium/ui/views/touchui/touch_selection_controller_impl_unittest.cc
+++ b/chromium/ui/views/touchui/touch_selection_controller_impl_unittest.cc
@@ -147,7 +147,7 @@ class TouchSelectionControllerImplTest : public ViewsTestBase {
void SimulateSelectionHandleDrag(gfx::Vector2d v, int selection_handle) {
TouchSelectionControllerImpl* controller = GetSelectionController();
- views::WidgetDelegateView* handle = nullptr;
+ views::View* handle = nullptr;
if (selection_handle == 1)
handle = controller->GetHandle1View();
else
diff --git a/chromium/ui/views/touchui/touch_selection_menu_views.cc b/chromium/ui/views/touchui/touch_selection_menu_views.cc
index 8b014f1386d..6cc5d4b6ce4 100644
--- a/chromium/ui/views/touchui/touch_selection_menu_views.cc
+++ b/chromium/ui/views/touchui/touch_selection_menu_views.cc
@@ -36,7 +36,6 @@ struct MenuCommand {
};
constexpr int kSpacingBetweenButtons = 2;
-constexpr int kEllipsesButtonTag = -1;
} // namespace
@@ -126,18 +125,21 @@ void TouchSelectionMenuViews::CreateButtons() {
if (!client_->IsCommandIdEnabled(command.command_id))
continue;
- Button* button = CreateButton(l10n_util::GetStringUTF16(command.message_id),
- command.command_id);
+ Button* button =
+ CreateButton(l10n_util::GetStringUTF16(command.message_id));
+ button->set_tag(command.command_id);
AddChildView(button);
}
- // Finally, add ellipses button.
- AddChildView(CreateButton(base::ASCIIToUTF16("..."), kEllipsesButtonTag));
+ // Finally, add ellipsis button.
+ LabelButton* ellipsis_button = CreateButton(base::ASCIIToUTF16("..."));
+ ellipsis_button->SetID(ButtonViewId::kEllipsisButton);
+ AddChildView(ellipsis_button);
InvalidateLayout();
}
-LabelButton* TouchSelectionMenuViews::CreateButton(const base::string16& title,
- int tag) {
+LabelButton* TouchSelectionMenuViews::CreateButton(
+ const base::string16& title) {
base::string16 label =
gfx::RemoveAcceleratorChar(title, '&', nullptr, nullptr);
LabelButton* button = new LabelButton(this, label, style::CONTEXT_TOUCH_MENU);
@@ -145,7 +147,6 @@ LabelButton* TouchSelectionMenuViews::CreateButton(const base::string16& title,
button->SetMinSize(kMenuButtonMinSize);
button->SetFocusForPlatform();
button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
- button->set_tag(tag);
return button;
}
@@ -180,7 +181,7 @@ void TouchSelectionMenuViews::WindowClosing() {
void TouchSelectionMenuViews::ButtonPressed(Button* sender,
const ui::Event& event) {
CloseMenu();
- if (sender->tag() != kEllipsesButtonTag)
+ if (sender->GetID() != ButtonViewId::kEllipsisButton)
client_->ExecuteCommand(sender->tag(), event.flags());
else
client_->RunContextMenu();
diff --git a/chromium/ui/views/touchui/touch_selection_menu_views.h b/chromium/ui/views/touchui/touch_selection_menu_views.h
index ad1819ee56a..77121939b18 100644
--- a/chromium/ui/views/touchui/touch_selection_menu_views.h
+++ b/chromium/ui/views/touchui/touch_selection_menu_views.h
@@ -24,6 +24,8 @@ class VIEWS_EXPORT TouchSelectionMenuViews : public BubbleDialogDelegateView,
public:
METADATA_HEADER(TouchSelectionMenuViews);
+ enum ButtonViewId : int { kEllipsisButton = 1 };
+
TouchSelectionMenuViews(TouchSelectionMenuRunnerViews* owner,
ui::TouchSelectionMenuClient* client,
aura::Window* context);
@@ -45,7 +47,7 @@ class VIEWS_EXPORT TouchSelectionMenuViews : public BubbleDialogDelegateView,
virtual void CreateButtons();
// Helper method to create a single button.
- LabelButton* CreateButton(const base::string16& title, int tag);
+ LabelButton* CreateButton(const base::string16& title);
// ButtonListener:
void ButtonPressed(Button* sender, const ui::Event& event) override;
diff --git a/chromium/ui/views/vector_icons/vector_icons.cc.template b/chromium/ui/views/vector_icons/vector_icons.cc.template
index df1e9f7613d..7ebccd79e63 100644
--- a/chromium/ui/views/vector_icons/vector_icons.cc.template
+++ b/chromium/ui/views/vector_icons/vector_icons.cc.template
@@ -7,7 +7,6 @@
#include "ui/views/vector_icons.h"
-#include "base/logging.h"
#include "components/vector_icons/cc_macros.h"
#include "ui/gfx/vector_icon_types.h"
diff --git a/chromium/ui/views/view.cc b/chromium/ui/views/view.cc
index 00b654c8e4f..186b6c5f5cd 100644
--- a/chromium/ui/views/view.cc
+++ b/chromium/ui/views/view.cc
@@ -192,6 +192,13 @@ View::~View() {
if (parent_)
parent_->RemoveChildView(this);
+ // Need to remove layout manager before deleting children because if we do not
+ // it is possible for layout-related calls (e.g. CalculatePreferredSize()) to
+ // be called on this view during one of the callbacks below. Since most
+ // layout managers access child view properties, this would result in a
+ // use-after-free error.
+ layout_manager_.reset();
+
{
internal::ScopedChildrenLock lock(this);
for (auto* child : children_) {
@@ -511,8 +518,10 @@ void View::SetVisible(bool visible) {
// Notify the parent.
if (parent_) {
parent_->ChildVisibilityChanged(this);
- parent_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- true);
+ if (!view_accessibility_ || !view_accessibility_->IsIgnored()) {
+ parent_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
+ true);
+ }
}
// This notifies all sub-views recursively.
@@ -1973,7 +1982,13 @@ void View::OnFocus() {
// TODO(beng): Investigate whether it's possible for us to move this to
// Focus().
// Notify assistive technologies of the focus change.
- NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
+ AXVirtualView* focused_virtual_child =
+ view_accessibility_ ? view_accessibility_->FocusedVirtualChild()
+ : nullptr;
+ if (focused_virtual_child)
+ focused_virtual_child->NotifyAccessibilityEvent(ax::mojom::Event::kFocus);
+ else
+ NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
void View::OnBlur() {}
@@ -2288,7 +2303,7 @@ void View::AddChildViewAtImpl(View* view, int index) {
ViewHierarchyChangedDetails details(true, this, view, parent);
for (View* v = this; v; v = v->parent_)
- v->ViewHierarchyChangedImpl(false, details);
+ v->ViewHierarchyChangedImpl(details);
view->PropagateAddNotifications(details, widget && widget != old_widget);
@@ -2378,9 +2393,13 @@ void View::PropagateRemoveNotifications(View* old_parent,
}
}
+ // When a view is removed from a hierarchy, UnregisterAccelerators() is called
+ // for the removed view and all descendant views in post-order.
+ UnregisterAccelerators(true);
+
ViewHierarchyChangedDetails details(false, old_parent, this, new_parent);
for (View* v = this; v; v = v->parent_)
- v->ViewHierarchyChangedImpl(true, details);
+ v->ViewHierarchyChangedImpl(details);
if (is_removed_from_widget) {
RemovedFromWidget();
@@ -2391,12 +2410,22 @@ void View::PropagateRemoveNotifications(View* old_parent,
void View::PropagateAddNotifications(const ViewHierarchyChangedDetails& details,
bool is_added_to_widget) {
+ // When a view is added to a Widget hierarchy, RegisterPendingAccelerators()
+ // will be called for the added view and all its descendants in pre-order.
+ // This means that descendant views will register their accelerators after
+ // their parents. This allows children to override accelerators registered by
+ // their parents as accelerators registered later take priority over those
+ // registered earlier.
+ if (GetFocusManager())
+ RegisterPendingAccelerators();
+
{
internal::ScopedChildrenLock lock(this);
for (auto* child : children_)
child->PropagateAddNotifications(details, is_added_to_widget);
}
- ViewHierarchyChangedImpl(true, details);
+
+ ViewHierarchyChangedImpl(details);
if (is_added_to_widget) {
AddedToWidget();
for (ViewObserver& observer : observers_)
@@ -2414,21 +2443,9 @@ void View::PropagateNativeViewHierarchyChanged() {
}
void View::ViewHierarchyChangedImpl(
- bool register_accelerators,
const ViewHierarchyChangedDetails& details) {
- if (register_accelerators) {
- if (details.is_add) {
- // If you get this registration, you are part of a subtree that has been
- // added to the view hierarchy.
- if (GetFocusManager())
- RegisterPendingAccelerators();
- } else {
- if (details.child == this)
- UnregisterAccelerators(true);
- }
- }
-
ViewHierarchyChanged(details);
+
for (ViewObserver& observer : observers_)
observer.OnViewHierarchyChanged(this, details);
diff --git a/chromium/ui/views/view.h b/chromium/ui/views/view.h
index 1f4f1eb78fe..23b5d3ffd8b 100644
--- a/chromium/ui/views/view.h
+++ b/chromium/ui/views/view.h
@@ -1683,10 +1683,8 @@ class VIEWS_EXPORT View : public ui::LayerDelegate,
// children.
void PropagateNativeViewHierarchyChanged();
- // Takes care of registering/unregistering accelerators if
- // |register_accelerators| true and calls ViewHierarchyChanged().
- void ViewHierarchyChangedImpl(bool register_accelerators,
- const ViewHierarchyChangedDetails& details);
+ // Calls ViewHierarchyChanged() and notifies observers.
+ void ViewHierarchyChangedImpl(const ViewHierarchyChangedDetails& details);
// Size and disposition ------------------------------------------------------
diff --git a/chromium/ui/views/view_class_properties.cc b/chromium/ui/views/view_class_properties.cc
index 0409ce2d73b..1dec102808a 100644
--- a/chromium/ui/views/view_class_properties.cc
+++ b/chromium/ui/views/view_class_properties.cc
@@ -20,7 +20,7 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, int)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
- views::BubbleDialogDelegateView*)
+ views::BubbleDialogDelegate*)
DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
views::HighlightPathGenerator*)
@@ -31,7 +31,7 @@ namespace views {
DEFINE_UI_CLASS_PROPERTY_KEY(int, kHitTestComponentKey, HTNOWHERE)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Insets, kMarginsKey, nullptr)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Insets, kInternalPaddingKey, nullptr)
-DEFINE_UI_CLASS_PROPERTY_KEY(views::BubbleDialogDelegateView*,
+DEFINE_UI_CLASS_PROPERTY_KEY(views::BubbleDialogDelegate*,
kAnchoredDialogKey,
nullptr)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(views::HighlightPathGenerator,
diff --git a/chromium/ui/views/view_class_properties.h b/chromium/ui/views/view_class_properties.h
index c815cd936d3..f106354a6ad 100644
--- a/chromium/ui/views/view_class_properties.h
+++ b/chromium/ui/views/view_class_properties.h
@@ -14,7 +14,7 @@ class Insets;
namespace views {
-class BubbleDialogDelegateView;
+class BubbleDialogDelegate;
class FlexSpecification;
class HighlightPathGenerator;
@@ -41,7 +41,7 @@ VIEWS_EXPORT extern const ui::ClassProperty<gfx::Insets*>* const
// A property to store the bubble dialog anchored to this view, to
// enable the bubble's contents to be included in the focus order.
-VIEWS_EXPORT extern const ui::ClassProperty<BubbleDialogDelegateView*>* const
+VIEWS_EXPORT extern const ui::ClassProperty<BubbleDialogDelegate*>* const
kAnchoredDialogKey;
// A property to store a highlight-path generator. This generator is used to
@@ -63,7 +63,7 @@ VIEWS_EXPORT extern const ui::ClassProperty<FlexSpecification*>* const
// translation unit is a C++ error.
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
- views::BubbleDialogDelegateView*)
+ views::BubbleDialogDelegate*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT,
views::HighlightPathGenerator*)
DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::FlexSpecification*)
diff --git a/chromium/ui/views/view_model.h b/chromium/ui/views/view_model.h
index 4a46bab09b6..578c22709fd 100644
--- a/chromium/ui/views/view_model.h
+++ b/chromium/ui/views/view_model.h
@@ -7,7 +7,7 @@
#include <vector>
-#include "base/logging.h"
+#include "base/check_op.h"
#include "base/macros.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/views_export.h"
diff --git a/chromium/ui/views/view_targeter_unittest.cc b/chromium/ui/views/view_targeter_unittest.cc
index edae0b1b093..f2994f9a157 100644
--- a/chromium/ui/views/view_targeter_unittest.cc
+++ b/chromium/ui/views/view_targeter_unittest.cc
@@ -128,11 +128,10 @@ TEST_F(ViewTargeterTest, ViewTargeterForKeyEvents) {
widget.Init(std::move(init_params));
widget.Show();
- View* content = new View;
View* child = new View;
View* grandchild = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
content->AddChildView(child);
child->AddChildView(grandchild);
@@ -174,14 +173,14 @@ TEST_F(ViewTargeterTest, ViewTargeterForScrollEvents) {
widget.Init(std::move(init_params));
// The coordinates used for SetBounds() are in the parent coordinate space.
- View* content = new View;
- content->SetBounds(0, 0, 100, 100);
+ auto owning_content = std::make_unique<View>();
+ owning_content->SetBounds(0, 0, 100, 100);
View* child = new View;
child->SetBounds(50, 50, 20, 20);
View* grandchild = new View;
grandchild->SetBounds(0, 0, 5, 5);
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::move(owning_content));
content->AddChildView(child);
child->AddChildView(grandchild);
@@ -245,14 +244,13 @@ TEST_F(ViewTargeterTest, ViewTargeterForGestureEvents) {
widget.Init(std::move(init_params));
// The coordinates used for SetBounds() are in the parent coordinate space.
- View* content = new View;
- content->SetBounds(0, 0, 100, 100);
View* child = new View;
child->SetBounds(50, 50, 20, 20);
View* grandchild = new View;
grandchild->SetBounds(0, 0, 5, 5);
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
+ content->SetBounds(0, 0, 100, 100);
content->AddChildView(child);
child->AddChildView(grandchild);
@@ -355,9 +353,9 @@ TEST_F(ViewTargeterTest, TargetContentsAndRootView) {
widget.Init(std::move(init_params));
// The coordinates used for SetBounds() are in the parent coordinate space.
- View* content = new View;
- content->SetBounds(0, 0, 100, 100);
- widget.SetContentsView(content);
+ auto owning_content = std::make_unique<View>();
+ owning_content->SetBounds(0, 0, 100, 100);
+ View* content = widget.SetContentsView(std::move(owning_content));
internal::RootView* root_view =
static_cast<internal::RootView*>(widget.GetRootView());
@@ -437,8 +435,6 @@ TEST_F(ViewTargeterTest, GestureEventCoordinateConversion) {
widget.Init(std::move(init_params));
// The coordinates used for SetBounds() are in the parent coordinate space.
- View* content = new View;
- content->SetBounds(0, 0, 100, 100);
View* child = new View;
child->SetBounds(50, 50, 20, 20);
View* grandchild = new View;
@@ -446,7 +442,8 @@ TEST_F(ViewTargeterTest, GestureEventCoordinateConversion) {
View* great_grandchild = new View;
great_grandchild->SetBounds(3, 3, 4, 4);
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
+ content->SetBounds(0, 0, 100, 100);
content->AddChildView(child);
child->AddChildView(grandchild);
grandchild->AddChildView(great_grandchild);
diff --git a/chromium/ui/views/view_unittest.cc b/chromium/ui/views/view_unittest.cc
index dd69ffe1c53..7613c1939c8 100644
--- a/chromium/ui/views/view_unittest.cc
+++ b/chromium/ui/views/view_unittest.cc
@@ -53,6 +53,7 @@
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/metadata/metadata_types.h"
#include "ui/views/paint_info.h"
+#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view_observer.h"
#include "ui/views/views_features.h"
@@ -285,6 +286,15 @@ class TestView : public View {
};
////////////////////////////////////////////////////////////////////////////////
+// Metadata
+////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(ViewTest, MetadataTest) {
+ auto test_view = std::make_unique<TestView>();
+ test::TestViewMetadata(test_view.get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
// Layout
////////////////////////////////////////////////////////////////////////////////
diff --git a/chromium/ui/views/views_features.cc b/chromium/ui/views/views_features.cc
index 13bf1c9f0e2..b3702c04028 100644
--- a/chromium/ui/views/views_features.cc
+++ b/chromium/ui/views/views_features.cc
@@ -34,5 +34,10 @@ const base::Feature kEnableViewPaintOptimization{
const base::Feature kTextfieldFocusOnTapUp{"TextfieldFocusOnTapUp",
base::FEATURE_DISABLED_BY_DEFAULT};
+// Allows a "New" badge to be displayed on menu items that provide access to new
+// features.
+const base::Feature kEnableNewBadgeOnMenuItems{
+ "EnableNewBadgeOnMenuItems", base::FEATURE_DISABLED_BY_DEFAULT};
+
} // namespace features
} // namespace views
diff --git a/chromium/ui/views/views_features.h b/chromium/ui/views/views_features.h
index e4340bbb13a..f0f77154191 100644
--- a/chromium/ui/views/views_features.h
+++ b/chromium/ui/views/views_features.h
@@ -18,6 +18,7 @@ VIEWS_EXPORT extern const base::Feature kEnableMDRoundedCornersOnDialogs;
VIEWS_EXPORT extern const base::Feature kEnablePlatformHighContrastInkDrop;
VIEWS_EXPORT extern const base::Feature kEnableViewPaintOptimization;
VIEWS_EXPORT extern const base::Feature kTextfieldFocusOnTapUp;
+VIEWS_EXPORT extern const base::Feature kEnableNewBadgeOnMenuItems;
} // namespace features
} // namespace views
diff --git a/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm b/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm
index faf79e0e6c7..ca7575056b0 100644
--- a/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm
+++ b/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm
@@ -772,7 +772,7 @@ class TestComboboxModel : public ui::ComboboxModel {
// ui::ComboboxModel:
int GetItemCount() const override { return 2; }
- base::string16 GetItemAt(int index) override {
+ base::string16 GetItemAt(int index) const override {
return index == 0 ? base::SysNSStringToUTF16(kTestStringValue)
: base::ASCIIToUTF16("Second Item");
}
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc
index 3ee330f3598..80090c42e7d 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc
@@ -11,7 +11,6 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/drag_drop_client.h"
@@ -24,6 +23,7 @@
#include "ui/base/dragdrop/os_exchange_data_provider_x11.h"
#include "ui/base/layout.h"
#include "ui/base/x/selection_utils.h"
+#include "ui/base/x/x11_cursor.h"
#include "ui/base/x/x11_drag_context.h"
#include "ui/base/x/x11_util.h"
#include "ui/base/x/x11_whole_screen_move_loop.h"
@@ -32,6 +32,7 @@
#include "ui/events/event_utils.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/x/x11.h"
+#include "ui/gfx/x/xproto.h"
#include "ui/platform_window/x11/x11_topmost_window_finder.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
@@ -115,9 +116,8 @@ DesktopDragDropClientAuraX11*
DesktopDragDropClientAuraX11::DesktopDragDropClientAuraX11(
aura::Window* root_window,
views::DesktopNativeCursorManager* cursor_manager,
- ::Display* display,
- XID window)
- : XDragDropClient(this, display, window),
+ x11::Window window)
+ : XDragDropClient(this, window),
root_window_(root_window),
cursor_manager_(cursor_manager) {}
@@ -140,9 +140,6 @@ int DesktopDragDropClientAuraX11::StartDragAndDrop(
const gfx::Point& /*screen_location*/,
int operation,
ui::DragDropTypes::DragEventSource source) {
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Start", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
-
DCHECK(!g_current_drag_drop_client);
g_current_drag_drop_client = this;
@@ -171,28 +168,24 @@ int DesktopDragDropClientAuraX11::StartDragAndDrop(
// Windows has a specific method, DoDragDrop(), which performs the entire
// drag. We have to emulate this, so we spin off a nested runloop which will
// track all cursor movement and reroute events to a specific handler.
+ auto* last_cursor = static_cast<ui::X11Cursor*>(
+ source_window->GetHost()->last_cursor().platform());
move_loop_->RunMoveLoop(
!source_window->HasCapture(),
- source_window->GetHost()->last_cursor().platform(),
- cursor_manager_->GetInitializedCursor(ui::mojom::CursorType::kGrabbing)
- .platform());
+ last_cursor ? last_cursor->xcursor() : x11::None,
+ static_cast<ui::X11Cursor*>(
+ cursor_manager_
+ ->GetInitializedCursor(ui::mojom::CursorType::kGrabbing)
+ .platform())
+ ->xcursor());
if (alive) {
auto resulting_operation = negotiated_operation();
- if (resulting_operation == ui::DragDropTypes::DRAG_NONE) {
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
- } else {
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Drop", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
- }
drag_widget_.reset();
g_current_drag_drop_client = nullptr;
CleanupDrag();
return resulting_operation;
}
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
return ui::DragDropTypes::DRAG_NONE;
}
@@ -214,12 +207,13 @@ void DesktopDragDropClientAuraX11::RemoveObserver(
NOTIMPLEMENTED();
}
-bool DesktopDragDropClientAuraX11::DispatchXEvent(XEvent* event) {
- if (!target_current_context() ||
- event->xany.window != target_current_context()->source_window()) {
+bool DesktopDragDropClientAuraX11::DispatchXEvent(x11::Event* event) {
+ auto* prop = event->As<x11::PropertyNotifyEvent>();
+ if (!target_current_context() || !prop ||
+ prop->window != target_current_context()->source_window()) {
return false;
}
- return target_current_context()->DispatchXEvent(event);
+ return target_current_context()->DispatchPropertyNotifyEvent(*prop);
}
void DesktopDragDropClientAuraX11::OnWindowDestroyed(aura::Window* window) {
@@ -339,13 +333,8 @@ int DesktopDragDropClientAuraX11::UpdateDrag(const gfx::Point& screen_point) {
std::unique_ptr<ui::DropTargetEvent> drop_target_event;
DragDropDelegate* delegate = nullptr;
DragTranslate(screen_point, &data, &drop_target_event, &delegate);
- int drag_operation =
- delegate ? drag_operation = delegate->OnDragUpdated(*drop_target_event)
- : ui::DragDropTypes::DRAG_NONE;
- UMA_HISTOGRAM_BOOLEAN("Event.DragDrop.AcceptDragUpdate",
- drag_operation != ui::DragDropTypes::DRAG_NONE);
-
- return drag_operation;
+ return delegate ? delegate->OnDragUpdated(*drop_target_event)
+ : ui::DragDropTypes::DRAG_NONE;
}
void DesktopDragDropClientAuraX11::UpdateCursor(
@@ -366,10 +355,12 @@ void DesktopDragDropClientAuraX11::UpdateCursor(
break;
}
move_loop_->UpdateCursor(
- cursor_manager_->GetInitializedCursor(cursor_type).platform());
+ static_cast<ui::X11Cursor*>(
+ cursor_manager_->GetInitializedCursor(cursor_type).platform())
+ ->xcursor());
}
-void DesktopDragDropClientAuraX11::OnBeginForeignDrag(XID window) {
+void DesktopDragDropClientAuraX11::OnBeginForeignDrag(x11::Window window) {
DCHECK(target_current_context());
DCHECK(!target_current_context()->source_client());
@@ -413,10 +404,6 @@ int DesktopDragDropClientAuraX11::PerformDrop() {
drop_event.set_flags(ui::XGetMaskAsEventFlags());
}
- if (!IsDragDropInProgress()) {
- UMA_HISTOGRAM_COUNTS_1M("Event.DragDrop.ExternalOriginDrop", 1);
- }
-
drag_operation = delegate->OnPerformDrop(drop_event, std::move(data));
}
@@ -426,7 +413,7 @@ int DesktopDragDropClientAuraX11::PerformDrop() {
return drag_operation;
}
-void DesktopDragDropClientAuraX11::EndMoveLoop() {
+void DesktopDragDropClientAuraX11::EndDragLoop() {
move_loop_->EndMoveLoop();
}
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h
index a06220b1864..b8f6cceafbf 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h
@@ -22,6 +22,7 @@
#include "ui/events/x/x11_window_event_manager.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/x/event.h"
#include "ui/gfx/x/x11.h"
#include "ui/views/views_export.h"
@@ -61,8 +62,7 @@ class VIEWS_EXPORT DesktopDragDropClientAuraX11
DesktopDragDropClientAuraX11(
aura::Window* root_window,
views::DesktopNativeCursorManager* cursor_manager,
- Display* xdisplay,
- XID xwindow);
+ x11::Window xwindow);
~DesktopDragDropClientAuraX11() override;
void Init();
@@ -80,7 +80,7 @@ class VIEWS_EXPORT DesktopDragDropClientAuraX11
void RemoveObserver(aura::client::DragDropClientObserver* observer) override;
// XEventDispatcher:
- bool DispatchXEvent(XEvent* event) override;
+ bool DispatchXEvent(x11::Event* event) override;
// aura::WindowObserver:
void OnWindowDestroyed(aura::Window* window) override;
@@ -118,11 +118,11 @@ class VIEWS_EXPORT DesktopDragDropClientAuraX11
int UpdateDrag(const gfx::Point& screen_point) override;
void UpdateCursor(
ui::DragDropTypes::DragOperation negotiated_operation) override;
- void OnBeginForeignDrag(XID window) override;
+ void OnBeginForeignDrag(x11::Window window) override;
void OnEndForeignDrag() override;
void OnBeforeDragLeave() override;
int PerformDrop() override;
- void EndMoveLoop() override;
+ void EndDragLoop() override;
// A nested run loop that notifies this object of events through the
// ui::X11MoveLoopDelegate interface.
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc
index 68c1ba2c47d..1ab88c73c14 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
+
#include <map>
#include <memory>
#include <utility>
@@ -26,23 +28,26 @@
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
+#include "ui/gfx/x/xproto.h"
#include "ui/views/test/views_test_base.h"
-#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/widget.h"
+// TODO(crbug.com/990756): Transfer all tests from this file to better places
+// when DDDClientAuraX11 goes away.
+
namespace views {
namespace {
class TestDragDropClient;
-// Collects messages which would otherwise be sent to |xid_| via
+// Collects messages which would otherwise be sent to |window_| via
// SendXClientEvent().
class ClientMessageEventCollector {
public:
- ClientMessageEventCollector(::Window xid, TestDragDropClient* client);
+ ClientMessageEventCollector(x11::Window window, TestDragDropClient* client);
virtual ~ClientMessageEventCollector();
// Returns true if |events_| is non-empty.
@@ -50,18 +55,18 @@ class ClientMessageEventCollector {
// Pops all of |events_| and returns the popped events in the order that they
// were on the stack
- std::vector<XClientMessageEvent> PopAllEvents();
+ std::vector<x11::ClientMessageEvent> PopAllEvents();
// Adds |event| to the stack.
- void RecordEvent(const XClientMessageEvent& event);
+ void RecordEvent(const x11::ClientMessageEvent& event);
private:
- ::Window xid_;
+ x11::Window window_;
// Not owned.
TestDragDropClient* client_;
- std::vector<XClientMessageEvent> events_;
+ std::vector<x11::ClientMessageEvent> events_;
DISALLOW_COPY_AND_ASSIGN(ClientMessageEventCollector);
};
@@ -101,8 +106,8 @@ class SimpleTestDragDropClient : public DesktopDragDropClientAuraX11 {
DesktopNativeCursorManager* cursor_manager);
~SimpleTestDragDropClient() override;
- // Sets |xid| as the topmost window for all mouse positions.
- void SetTopmostXWindow(XID xid);
+ // Sets |window| as the topmost window for all mouse positions.
+ void SetTopmostXWindow(x11::Window window);
// Returns true if the move loop is running.
bool IsMoveLoopRunning();
@@ -113,10 +118,10 @@ class SimpleTestDragDropClient : public DesktopDragDropClientAuraX11 {
// DesktopDragDropClientAuraX11:
std::unique_ptr<ui::X11MoveLoop> CreateMoveLoop(
ui::X11MoveLoopDelegate* delegate) override;
- XID FindWindowFor(const gfx::Point& screen_point) override;
+ x11::Window FindWindowFor(const gfx::Point& screen_point) override;
- // The XID of the window which is simulated to be the topmost window.
- XID target_xid_ = x11::None;
+ // The x11::Window of the window which is simulated to be the topmost window.
+ x11::Window target_window_ = x11::Window::None;
// The move loop. Not owned.
TestMoveLoop* loop_ = nullptr;
@@ -137,46 +142,47 @@ class TestDragDropClient : public SimpleTestDragDropClient {
DesktopNativeCursorManager* cursor_manager);
~TestDragDropClient() override;
- // Returns the XID of the window which initiated the drag.
- ::Window source_xwindow() { return source_xid_; }
+ // Returns the x11::Window of the window which initiated the drag.
+ x11::Window source_xwindow() { return source_window_; }
// Returns the Atom with |name|.
- Atom GetAtom(const char* name);
+ x11::Atom GetAtom(const char* name);
// Returns true if the event's message has |type|.
- bool MessageHasType(const XClientMessageEvent& event, const char* type);
+ bool MessageHasType(const x11::ClientMessageEvent& event, const char* type);
- // Sets |collector| to collect XClientMessageEvents which would otherwise
+ // Sets |collector| to collect x11::ClientMessageEvents which would otherwise
// have been sent to the drop target window.
- void SetEventCollectorFor(::Window xid,
+ void SetEventCollectorFor(x11::Window window,
ClientMessageEventCollector* collector);
// Builds an XdndStatus message and sends it to
// DesktopDragDropClientAuraX11.
- void OnStatus(XID target_window,
+ void OnStatus(x11::Window target_window,
bool will_accept_drop,
- ::Atom accepted_action);
+ x11::Atom accepted_action);
// Builds an XdndFinished message and sends it to
// DesktopDragDropClientAuraX11.
- void OnFinished(XID target_window,
+ void OnFinished(x11::Window target_window,
bool accepted_drop,
- ::Atom performed_action);
+ x11::Atom performed_action);
- // Sets |xid| as the topmost window at the current mouse position and
+ // Sets |window| as the topmost window at the current mouse position and
// generates a synthetic mouse move.
- void SetTopmostXWindowAndMoveMouse(::Window xid);
+ void SetTopmostXWindowAndMoveMouse(x11::Window window);
private:
// DesktopDragDropClientAuraX11:
- void SendXClientEvent(::Window xid, XEvent* event) override;
+ void SendXClientEvent(x11::Window window,
+ const x11::ClientMessageEvent& event) override;
- // The XID of the window which initiated the drag.
- ::Window source_xid_;
+ // The x11::Window of the window which initiated the drag.
+ x11::Window source_window_;
- // Map of ::Windows to the collector which intercepts XClientMessageEvents
- // for that window.
- std::map<::Window, ClientMessageEventCollector*> collectors_;
+ // Map of x11::Windows to the collector which intercepts
+ // x11::ClientMessageEvents for that window.
+ std::map<x11::Window, ClientMessageEventCollector*> collectors_;
DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
};
@@ -185,24 +191,25 @@ class TestDragDropClient : public SimpleTestDragDropClient {
// ClientMessageEventCollector
ClientMessageEventCollector::ClientMessageEventCollector(
- ::Window xid,
+ x11::Window window,
TestDragDropClient* client)
- : xid_(xid), client_(client) {
- client->SetEventCollectorFor(xid, this);
+ : window_(window), client_(client) {
+ client->SetEventCollectorFor(window, this);
}
ClientMessageEventCollector::~ClientMessageEventCollector() {
- client_->SetEventCollectorFor(xid_, nullptr);
+ client_->SetEventCollectorFor(window_, nullptr);
}
-std::vector<XClientMessageEvent> ClientMessageEventCollector::PopAllEvents() {
- std::vector<XClientMessageEvent> to_return;
+std::vector<x11::ClientMessageEvent>
+ClientMessageEventCollector::PopAllEvents() {
+ std::vector<x11::ClientMessageEvent> to_return;
to_return.swap(events_);
return to_return;
}
void ClientMessageEventCollector::RecordEvent(
- const XClientMessageEvent& event) {
+ const x11::ClientMessageEvent& event) {
events_.push_back(event);
}
@@ -246,13 +253,12 @@ SimpleTestDragDropClient::SimpleTestDragDropClient(
DesktopNativeCursorManager* cursor_manager)
: DesktopDragDropClientAuraX11(window,
cursor_manager,
- gfx::GetXDisplay(),
window->GetHost()->GetAcceleratedWidget()) {}
SimpleTestDragDropClient::~SimpleTestDragDropClient() = default;
-void SimpleTestDragDropClient::SetTopmostXWindow(XID xid) {
- target_xid_ = xid;
+void SimpleTestDragDropClient::SetTopmostXWindow(x11::Window window) {
+ target_window_ = window;
}
bool SimpleTestDragDropClient::IsMoveLoopRunning() {
@@ -265,8 +271,9 @@ std::unique_ptr<ui::X11MoveLoop> SimpleTestDragDropClient::CreateMoveLoop(
return base::WrapUnique(loop_);
}
-XID SimpleTestDragDropClient::FindWindowFor(const gfx::Point& screen_point) {
- return target_xid_;
+x11::Window SimpleTestDragDropClient::FindWindowFor(
+ const gfx::Point& screen_point) {
+ return target_window_;
}
///////////////////////////////////////////////////////////////////////////////
@@ -276,68 +283,70 @@ TestDragDropClient::TestDragDropClient(
aura::Window* window,
DesktopNativeCursorManager* cursor_manager)
: SimpleTestDragDropClient(window, cursor_manager),
- source_xid_(window->GetHost()->GetAcceleratedWidget()) {}
+ source_window_(window->GetHost()->GetAcceleratedWidget()) {}
TestDragDropClient::~TestDragDropClient() = default;
-Atom TestDragDropClient::GetAtom(const char* name) {
+x11::Atom TestDragDropClient::GetAtom(const char* name) {
return gfx::GetAtom(name);
}
-bool TestDragDropClient::MessageHasType(const XClientMessageEvent& event,
+bool TestDragDropClient::MessageHasType(const x11::ClientMessageEvent& event,
const char* type) {
- return event.message_type == GetAtom(type);
+ return event.type == GetAtom(type);
}
void TestDragDropClient::SetEventCollectorFor(
- ::Window xid,
+ x11::Window window,
ClientMessageEventCollector* collector) {
if (collector)
- collectors_[xid] = collector;
+ collectors_[window] = collector;
else
- collectors_.erase(xid);
+ collectors_.erase(window);
}
-void TestDragDropClient::OnStatus(XID target_window,
+void TestDragDropClient::OnStatus(x11::Window target_window,
bool will_accept_drop,
- ::Atom accepted_action) {
- XClientMessageEvent event;
- event.message_type = GetAtom("XdndStatus");
+ x11::Atom accepted_action) {
+ x11::ClientMessageEvent event;
+ event.type = GetAtom("XdndStatus");
event.format = 32;
- event.window = source_xid_;
- event.data.l[0] = target_window;
- event.data.l[1] = will_accept_drop ? 1 : 0;
- event.data.l[2] = 0;
- event.data.l[3] = 0;
- event.data.l[4] = accepted_action;
+ event.window = source_window_;
+ event.data.data32[0] = static_cast<uint32_t>(target_window);
+ event.data.data32[1] = will_accept_drop ? 1 : 0;
+ event.data.data32[2] = 0;
+ event.data.data32[3] = 0;
+ event.data.data32[4] = static_cast<uint32_t>(accepted_action);
HandleXdndEvent(event);
}
-void TestDragDropClient::OnFinished(XID target_window,
+void TestDragDropClient::OnFinished(x11::Window target_window,
bool accepted_drop,
- ::Atom performed_action) {
- XClientMessageEvent event;
- event.message_type = GetAtom("XdndFinished");
+ x11::Atom performed_action) {
+ x11::ClientMessageEvent event;
+ event.type = GetAtom("XdndFinished");
event.format = 32;
- event.window = source_xid_;
- event.data.l[0] = target_window;
- event.data.l[1] = accepted_drop ? 1 : 0;
- event.data.l[2] = performed_action;
- event.data.l[3] = 0;
- event.data.l[4] = 0;
+ event.window = source_window_;
+ event.data.data32[0] = static_cast<uint32_t>(target_window);
+ event.data.data32[1] = accepted_drop ? 1 : 0;
+ event.data.data32[2] = static_cast<uint32_t>(performed_action);
+ event.data.data32[3] = 0;
+ event.data.data32[4] = 0;
HandleXdndEvent(event);
}
-void TestDragDropClient::SetTopmostXWindowAndMoveMouse(::Window xid) {
- SetTopmostXWindow(xid);
+void TestDragDropClient::SetTopmostXWindowAndMoveMouse(x11::Window window) {
+ SetTopmostXWindow(window);
OnMouseMovement(gfx::Point(kMouseMoveX, kMouseMoveY), ui::EF_NONE,
ui::EventTimeForNow());
}
-void TestDragDropClient::SendXClientEvent(::Window xid, XEvent* event) {
- auto it = collectors_.find(xid);
+void TestDragDropClient::SendXClientEvent(
+ x11::Window window,
+ const x11::ClientMessageEvent& event) {
+ auto it = collectors_.find(window);
if (it != collectors_.end())
- it->second->RecordEvent(event->xclient);
+ it->second->RecordEvent(event);
}
} // namespace
@@ -402,103 +411,6 @@ class DesktopDragDropClientAuraX11Test : public ViewsTestBase {
DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientAuraX11Test);
};
-namespace {
-
-void BasicStep2(TestDragDropClient* client, XID toplevel) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- ClientMessageEventCollector collector(toplevel, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
-
- // XdndEnter should have been sent to |toplevel| before the XdndPosition
- // message.
- std::vector<XClientMessageEvent> events = collector.PopAllEvents();
- ASSERT_EQ(2u, events.size());
-
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
- EXPECT_EQ(client->source_xwindow(), static_cast<XID>(events[0].data.l[0]));
- EXPECT_EQ(1, events[0].data.l[1] & 1);
- std::vector<Atom> targets;
- ui::GetAtomArrayProperty(client->source_xwindow(), "XdndTypeList", &targets);
- EXPECT_FALSE(targets.empty());
-
- EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
- EXPECT_EQ(client->source_xwindow(), static_cast<XID>(events[0].data.l[0]));
- const int kCoords =
- TestDragDropClient::kMouseMoveX << 16 | TestDragDropClient::kMouseMoveY;
- EXPECT_EQ(kCoords, events[1].data.l[2]);
- EXPECT_EQ(client->GetAtom("XdndActionCopy"),
- static_cast<Atom>(events[1].data.l[4]));
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
-
- // Because there is no unprocessed XdndPosition, the drag drop client should
- // send XdndDrop immediately after the mouse is released.
- client->OnMouseReleased();
-
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
- EXPECT_EQ(client->source_xwindow(), static_cast<XID>(events[0].data.l[0]));
-
- // Send XdndFinished to indicate that the drag drop client can cleanup any
- // data related to this drag. The move loop should end only after the
- // XdndFinished message was received.
- EXPECT_TRUE(client->IsMoveLoopRunning());
- client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-void BasicStep3(TestDragDropClient* client, XID toplevel) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- ClientMessageEventCollector collector(toplevel, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
-
- std::vector<XClientMessageEvent> events = collector.PopAllEvents();
- ASSERT_EQ(2u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
- client->SetTopmostXWindowAndMoveMouse(toplevel);
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
-
- // We have not received an XdndStatus ack for the second XdndPosition message.
- // Test that sending XdndDrop is delayed till the XdndStatus ack is received.
- client->OnMouseReleased();
- EXPECT_FALSE(collector.HasEvents());
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
-
- EXPECT_TRUE(client->IsMoveLoopRunning());
- client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-} // namespace
-
-TEST_F(DesktopDragDropClientAuraX11Test, Basic) {
- XID toplevel = 1;
-
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&BasicStep2, client(), toplevel));
- int result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
-
- // Do another drag and drop to test that the data is properly cleaned up as a
- // result of the XdndFinished message.
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&BasicStep3, client(), toplevel));
- result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
-}
-
void HighDPIStep(TestDragDropClient* client) {
float scale =
display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
@@ -520,6 +432,8 @@ void HighDPIStep(TestDragDropClient* client) {
client->OnMouseReleased();
}
+// TODO(crbug.com/990756): Turn this into tests of DesktopDragDropClientOzone
+// or its equivalent.
TEST_F(DesktopDragDropClientAuraX11Test, HighDPI200) {
aura::TestScreen* screen =
static_cast<aura::TestScreen*>(display::Screen::GetScreen());
@@ -531,6 +445,8 @@ TEST_F(DesktopDragDropClientAuraX11Test, HighDPI200) {
EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
}
+// TODO(crbug.com/990756): Turn this into tests of DesktopDragDropClientOzone
+// or its equivalent.
TEST_F(DesktopDragDropClientAuraX11Test, HighDPI150) {
aura::TestScreen* screen =
static_cast<aura::TestScreen*>(display::Screen::GetScreen());
@@ -544,212 +460,6 @@ TEST_F(DesktopDragDropClientAuraX11Test, HighDPI150) {
namespace {
-void TargetDoesNotRespondStep2(TestDragDropClient* client) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- XID toplevel = 1;
- ClientMessageEventCollector collector(toplevel, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
-
- std::vector<XClientMessageEvent> events = collector.PopAllEvents();
- ASSERT_EQ(2u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
-
- client->OnMouseReleased();
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-} // namespace
-
-// Test that we do not wait for the target to send XdndStatus if we have not
-// received any XdndStatus messages at all from the target. The Unity
-// DNDCollectionWindow is an example of an XdndAware target which does not
-// respond to XdndPosition messages at all.
-TEST_F(DesktopDragDropClientAuraX11Test, TargetDoesNotRespond) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&TargetDoesNotRespondStep2, client()));
- int result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
-}
-
-namespace {
-
-void QueuePositionStep2(TestDragDropClient* client) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- XID toplevel = 1;
- ClientMessageEventCollector collector(toplevel, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
-
- std::vector<XClientMessageEvent> events = collector.PopAllEvents();
- ASSERT_EQ(2u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(collector.HasEvents());
-
- client->OnMouseReleased();
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
-
- EXPECT_TRUE(client->IsMoveLoopRunning());
- client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-} // namespace
-
-// Test that XdndPosition messages are queued till the pending XdndPosition
-// message is acked via an XdndStatus message.
-TEST_F(DesktopDragDropClientAuraX11Test, QueuePosition) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&QueuePositionStep2, client()));
- int result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
-}
-
-namespace {
-
-void TargetChangesStep2(TestDragDropClient* client) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- XID toplevel1 = 1;
- ClientMessageEventCollector collector1(toplevel1, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel1);
-
- std::vector<XClientMessageEvent> events1 = collector1.PopAllEvents();
- ASSERT_EQ(2u, events1.size());
- EXPECT_TRUE(client->MessageHasType(events1[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events1[1], "XdndPosition"));
-
- XID toplevel2 = 2;
- ClientMessageEventCollector collector2(toplevel2, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel2);
-
- // It is possible for |toplevel1| to send XdndStatus after the source has sent
- // XdndLeave but before |toplevel1| has received the XdndLeave message. The
- // XdndStatus message should be ignored.
- client->OnStatus(toplevel1, true, client->GetAtom("XdndActionCopy"));
- events1 = collector1.PopAllEvents();
- ASSERT_EQ(1u, events1.size());
- EXPECT_TRUE(client->MessageHasType(events1[0], "XdndLeave"));
-
- std::vector<XClientMessageEvent> events2 = collector2.PopAllEvents();
- ASSERT_EQ(2u, events2.size());
- EXPECT_TRUE(client->MessageHasType(events2[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events2[1], "XdndPosition"));
-
- client->OnStatus(toplevel2, true, client->GetAtom("XdndActionCopy"));
- client->OnMouseReleased();
- events2 = collector2.PopAllEvents();
- ASSERT_EQ(1u, events2.size());
- EXPECT_TRUE(client->MessageHasType(events2[0], "XdndDrop"));
-
- EXPECT_TRUE(client->IsMoveLoopRunning());
- client->OnFinished(toplevel2, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-} // namespace
-
-// Test the behavior when the target changes during a drag.
-TEST_F(DesktopDragDropClientAuraX11Test, TargetChanges) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&TargetChangesStep2, client()));
- int result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
-}
-
-namespace {
-
-void RejectAfterMouseReleaseStep2(TestDragDropClient* client) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- XID toplevel = 1;
- ClientMessageEventCollector collector(toplevel, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
-
- std::vector<XClientMessageEvent> events = collector.PopAllEvents();
- ASSERT_EQ(2u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(collector.HasEvents());
-
- // Send another mouse move such that there is a pending XdndPosition.
- client->SetTopmostXWindowAndMoveMouse(toplevel);
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
-
- client->OnMouseReleased();
- // Reject the drop.
- client->OnStatus(toplevel, false, x11::None);
-
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-void RejectAfterMouseReleaseStep3(TestDragDropClient* client) {
- EXPECT_TRUE(client->IsMoveLoopRunning());
-
- XID toplevel = 2;
- ClientMessageEventCollector collector(toplevel, client);
- client->SetTopmostXWindowAndMoveMouse(toplevel);
-
- std::vector<XClientMessageEvent> events = collector.PopAllEvents();
- ASSERT_EQ(2u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
- EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
-
- client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
- EXPECT_FALSE(collector.HasEvents());
-
- client->OnMouseReleased();
- events = collector.PopAllEvents();
- ASSERT_EQ(1u, events.size());
- EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
-
- EXPECT_TRUE(client->IsMoveLoopRunning());
- client->OnFinished(toplevel, false, x11::None);
- EXPECT_FALSE(client->IsMoveLoopRunning());
-}
-
-} // namespace
-
-// Test that the source sends XdndLeave instead of XdndDrop if the drag
-// operation is rejected after the mouse is released.
-TEST_F(DesktopDragDropClientAuraX11Test, RejectAfterMouseRelease) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&RejectAfterMouseReleaseStep2, client()));
- int result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
-
- // Repeat the test but reject the drop in the XdndFinished message instead.
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&RejectAfterMouseReleaseStep3, client()));
- result = StartDragAndDrop();
- EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
-}
-
-namespace {
-
// DragDropDelegate which counts the number of each type of drag-drop event and
// keeps track of the most recent drag-drop event.
class TestDragDropDelegate : public aura::client::DragDropDelegate {
@@ -928,6 +638,8 @@ void ChromeSourceTargetStep2(SimpleTestDragDropClient* client,
} // namespace
+// TODO(crbug.com/990756): Turn this into tests of DesktopDragDropClientOzone
+// or its equivalent.
TEST_F(DesktopDragDropClientAuraX11ChromeSourceTargetTest, Basic) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
@@ -936,6 +648,8 @@ TEST_F(DesktopDragDropClientAuraX11ChromeSourceTargetTest, Basic) {
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
}
+// TODO(crbug.com/990756): Turn this into tests of DesktopDragDropClientOzone
+// or its equivalent.
// Test that if 'Ctrl' is pressed during a drag and drop operation, that
// the aura::client::DragDropDelegate is properly notified.
TEST_F(DesktopDragDropClientAuraX11ChromeSourceTargetTest, CtrlPressed) {
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
index 24a959b321d..dd9232bb7e9 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
@@ -21,10 +21,13 @@
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drop_target_event.h"
-#include "ui/base/dragdrop/os_exchange_data_provider_aura.h"
+#include "ui/base/layout.h"
+#include "ui/display/screen.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/platform_window_handler/wm_drag_handler.h"
+#include "ui/views/controls/image_view.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
+#include "ui/views/widget/widget.h"
namespace views {
@@ -37,8 +40,64 @@ aura::Window* GetTargetWindow(aura::Window* root_window,
return root_window->GetEventHandlerForPoint(root_location);
}
+// The minimum alpha required so we would treat the pixel as visible.
+constexpr uint32_t kMinAlpha = 32;
+
+// Returns true if |image| has any visible regions (defined as having a pixel
+// with alpha > |kMinAlpha|).
+bool IsValidDragImage(const gfx::ImageSkia& image) {
+ if (image.isNull())
+ return false;
+
+ // Because we need a GL context per window, we do a quick check so that we
+ // don't make another context if the window would just be displaying a mostly
+ // transparent image.
+ const SkBitmap* in_bitmap = image.bitmap();
+ for (int y = 0; y < in_bitmap->height(); ++y) {
+ uint32_t* in_row = in_bitmap->getAddr32(0, y);
+
+ for (int x = 0; x < in_bitmap->width(); ++x) {
+ if (SkColorGetA(in_row[x]) > kMinAlpha)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::unique_ptr<views::Widget> CreateDragWidget(
+ const gfx::Point& root_location,
+ const gfx::ImageSkia& image,
+ const gfx::Vector2d& drag_widget_offset) {
+ auto widget = std::make_unique<views::Widget>();
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_DRAG);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.accept_events = false;
+
+ gfx::Point location = root_location - drag_widget_offset;
+ params.bounds = gfx::Rect(location, image.size());
+ widget->set_focus_on_creation(false);
+ widget->set_frame_type(views::Widget::FrameType::kForceNative);
+ widget->Init(std::move(params));
+ widget->GetNativeWindow()->SetName("DragWindow");
+
+ std::unique_ptr<views::ImageView> image_view =
+ std::make_unique<views::ImageView>();
+ image_view->SetImage(image);
+ widget->SetContentsView(std::move(image_view));
+ widget->Show();
+ widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
+ widget->StackAtTop();
+
+ return widget;
+}
+
} // namespace
+DesktopDragDropClientOzone::DragContext::DragContext() = default;
+
+DesktopDragDropClientOzone::DragContext::~DragContext() = default;
+
DesktopDragDropClientOzone::DesktopDragDropClientOzone(
aura::Window* root_window,
views::DesktopNativeCursorManager* cursor_manager,
@@ -50,7 +109,7 @@ DesktopDragDropClientOzone::DesktopDragDropClientOzone(
DesktopDragDropClientOzone::~DesktopDragDropClientOzone() {
ResetDragDropTarget();
- if (in_move_loop_)
+ if (IsDragDropInProgress())
DragCancel();
}
@@ -64,9 +123,11 @@ int DesktopDragDropClientOzone::StartDragAndDrop(
if (!drag_handler_)
return ui::DragDropTypes::DragOperation::DRAG_NONE;
- DCHECK(!in_move_loop_);
+ DCHECK(!drag_context_);
+ drag_context_ = std::make_unique<DragContext>();
+
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
- quit_closure_ = run_loop.QuitClosure();
+ drag_context_->quit_closure = run_loop.QuitClosure();
// Chrome expects starting drag and drop to release capture.
aura::Window* capture_window =
@@ -77,18 +138,38 @@ int DesktopDragDropClientOzone::StartDragAndDrop(
aura::client::CursorClient* cursor_client =
aura::client::GetCursorClient(root_window);
- initial_cursor_ = source_window->GetHost()->last_cursor();
+ auto initial_cursor = source_window->GetHost()->last_cursor();
drag_operation_ = operation;
- cursor_client->SetCursor(
- cursor_manager_->GetInitializedCursor(ui::mojom::CursorType::kGrabbing));
-
- drag_handler_->StartDrag(
- *data.get(), operation, cursor_client->GetCursor(),
- base::BindOnce(&DesktopDragDropClientOzone::OnDragSessionClosed,
- base::Unretained(this)));
- in_move_loop_ = true;
+ if (cursor_client) {
+ cursor_client->SetCursor(cursor_manager_->GetInitializedCursor(
+ ui::mojom::CursorType::kGrabbing));
+ }
+
+ const auto& provider = data->provider();
+ gfx::ImageSkia drag_image = provider.GetDragImage();
+ if (IsValidDragImage(drag_image)) {
+ drag_context_->size = drag_image.size();
+ drag_context_->offset = provider.GetDragImageOffset();
+ drag_context_->widget =
+ CreateDragWidget(root_location, drag_image, drag_context_->offset);
+ }
+
+ // This object is owned by a DesktopNativeWidgetAura that can be destroyed
+ // during the drag loop, which will also destroy this object. So keep track
+ // of whether we are still alive after the drag ends.
+ auto alive = weak_factory_.GetWeakPtr();
+
+ drag_handler_->StartDrag(*data.get(), operation, cursor_client->GetCursor(),
+ this);
run_loop.Run();
- DragDropSessionCompleted();
+
+ if (!alive)
+ return ui::DragDropTypes::DRAG_NONE;
+
+ if (cursor_client)
+ cursor_client->SetCursor(initial_cursor);
+ drag_context_.reset();
+
return drag_operation_;
}
@@ -97,7 +178,7 @@ void DesktopDragDropClientOzone::DragCancel() {
}
bool DesktopDragDropClientOzone::IsDragDropInProgress() {
- return in_move_loop_;
+ return bool(drag_context_) && bool(drag_context_->quit_closure);
}
void DesktopDragDropClientOzone::AddObserver(
@@ -117,129 +198,193 @@ void DesktopDragDropClientOzone::OnDragEnter(
last_drag_point_ = point;
drag_operation_ = operation;
- // If it doesn't have |data|, it defers sending events to
- // |drag_drop_delegate_|. It will try again before handling drop.
+ // If |data| is empty, we defer sending any events to the
+ // |drag_drop_delegate_|. All necessary events will be sent on dropping.
if (!data)
return;
- os_exchange_data_ = std::move(data);
- std::unique_ptr<ui::DropTargetEvent> event = CreateDropTargetEvent(point);
- if (drag_drop_delegate_ && event)
- drag_drop_delegate_->OnDragEntered(*event);
+ data_to_drop_ = std::move(data);
+ UpdateTargetAndCreateDropEvent(point);
}
int DesktopDragDropClientOzone::OnDragMotion(const gfx::PointF& point,
int operation) {
last_drag_point_ = point;
drag_operation_ = operation;
- int client_operation =
- ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE;
-
- if (os_exchange_data_) {
- std::unique_ptr<ui::DropTargetEvent> event = CreateDropTargetEvent(point);
- // If |os_exchange_data_| has a valid data, |drag_drop_delegate_| returns
- // the operation which it expects.
- if (drag_drop_delegate_ && event)
- client_operation = drag_drop_delegate_->OnDragUpdated(*event);
- }
+
+ // If |data_to_drop_| doesn't have data, return that we accept everything.
+ if (!data_to_drop_)
+ return ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE;
+
+ // Ask the delegate what operation it would accept for the current data.
+ int client_operation = ui::DragDropTypes::DRAG_NONE;
+ std::unique_ptr<ui::DropTargetEvent> event =
+ UpdateTargetAndCreateDropEvent(point);
+ if (drag_drop_delegate_ && event)
+ client_operation = drag_drop_delegate_->OnDragUpdated(*event);
return client_operation;
}
void DesktopDragDropClientOzone::OnDragDrop(
std::unique_ptr<ui::OSExchangeData> data) {
- // If it doesn't have |os_exchange_data_|, it needs to update it with |data|.
- if (!os_exchange_data_) {
- DCHECK(data);
- os_exchange_data_ = std::move(data);
- std::unique_ptr<ui::DropTargetEvent> event =
- CreateDropTargetEvent(last_drag_point_);
- // Sends the deferred drag events to |drag_drop_delegate_| before handling
- // drop.
- if (drag_drop_delegate_ && event) {
- drag_drop_delegate_->OnDragEntered(*event);
- // TODO(jkim): It doesn't use the return value from 'OnDragUpdated' and
- // doesn't have a chance to update the expected operation.
- // https://crbug.com/875164
+ // If we didn't have |data_to_drop_|, then |drag_drop_delegate_| had never
+ // been updated, and now it needs to receive deferred enter and update events
+ // before handling the actual drop.
+ const bool posponed_enter_and_update = !data_to_drop_;
+
+ // If we had |data_to_drop_| already since the drag had entered the window,
+ // then we don't expect new data to come now, and vice versa.
+ DCHECK((data_to_drop_ && !data) || (!data_to_drop_ && data));
+ if (!data_to_drop_)
+ data_to_drop_ = std::move(data);
+
+ // This will call the delegate's OnDragEntered if needed.
+ auto event = UpdateTargetAndCreateDropEvent(last_drag_point_);
+ if (drag_drop_delegate_ && event) {
+ if (posponed_enter_and_update) {
+ // TODO(https://crbug.com/1014860): deal with drop refusals.
+ // The delegate's OnDragUpdated returns an operation that the delegate
+ // would accept. Normally the accepted operation would be propagated
+ // properly, and if the delegate didn't accept it, the drop would never
+ // be called, but in this scenario of postponed updates we send all events
+ // at once. Now we just drop, but perhaps we could call OnDragLeave
+ // and quit?
drag_drop_delegate_->OnDragUpdated(*event);
}
- } else {
- // If it has |os_exchange_data_|, it doesn't expect |data| on OnDragDrop.
- DCHECK(!data);
+ drag_operation_ =
+ drag_drop_delegate_->OnPerformDrop(*event, std::move(data_to_drop_));
}
- PerformDrop();
+ ResetDragDropTarget();
}
void DesktopDragDropClientOzone::OnDragLeave() {
- os_exchange_data_.reset();
+ data_to_drop_.reset();
ResetDragDropTarget();
}
-void DesktopDragDropClientOzone::OnDragSessionClosed(int dnd_action) {
- drag_operation_ = dnd_action;
- QuitRunLoop();
+void DesktopDragDropClientOzone::OnWindowDestroyed(aura::Window* window) {
+ DCHECK_EQ(window, current_window_);
+
+ current_window_->RemoveObserver(this);
+ current_window_ = nullptr;
+ drag_drop_delegate_ = nullptr;
}
-void DesktopDragDropClientOzone::DragDropSessionCompleted() {
+void DesktopDragDropClientOzone::OnDragLocationChanged(
+ const gfx::Point& screen_point_px) {
+ DCHECK(drag_context_);
+
+ if (!drag_context_->widget)
+ return;
+
+ const bool dispatch_mouse_event = !drag_context_->last_screen_location_px;
+ drag_context_->last_screen_location_px = screen_point_px;
+ if (dispatch_mouse_event) {
+ // Post a task to dispatch mouse movement event when control returns to the
+ // message loop. This allows smoother dragging since the events are
+ // dispatched without waiting for the drag widget updates.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&DesktopDragDropClientOzone::UpdateDragWidgetLocation,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void DesktopDragDropClientOzone::OnDragOperationChanged(
+ ui::DragDropTypes::DragOperation operation) {
aura::client::CursorClient* cursor_client =
aura::client::GetCursorClient(root_window_);
if (!cursor_client)
return;
- cursor_client->SetCursor(initial_cursor_);
+ ui::mojom::CursorType cursor_type = ui::mojom::CursorType::kNull;
+ switch (operation) {
+ case ui::DragDropTypes::DRAG_NONE:
+ cursor_type = ui::mojom::CursorType::kDndNone;
+ break;
+ case ui::DragDropTypes::DRAG_MOVE:
+ cursor_type = ui::mojom::CursorType::kDndMove;
+ break;
+ case ui::DragDropTypes::DRAG_COPY:
+ cursor_type = ui::mojom::CursorType::kDndCopy;
+ break;
+ case ui::DragDropTypes::DRAG_LINK:
+ cursor_type = ui::mojom::CursorType::kDndLink;
+ break;
+ }
+ cursor_client->SetCursor(cursor_manager_->GetInitializedCursor(cursor_type));
+}
+
+void DesktopDragDropClientOzone::OnDragFinished(int dnd_action) {
+ drag_operation_ = dnd_action;
+ QuitRunLoop();
}
void DesktopDragDropClientOzone::QuitRunLoop() {
- in_move_loop_ = false;
- if (quit_closure_.is_null())
+ if (!drag_context_->quit_closure)
return;
- std::move(quit_closure_).Run();
+ std::move(drag_context_->quit_closure).Run();
}
std::unique_ptr<ui::DropTargetEvent>
-DesktopDragDropClientOzone::CreateDropTargetEvent(const gfx::PointF& location) {
+DesktopDragDropClientOzone::UpdateTargetAndCreateDropEvent(
+ const gfx::PointF& location) {
const gfx::Point point(location.x(), location.y());
aura::Window* window = GetTargetWindow(root_window_, point);
- if (!window)
+ if (!window) {
+ ResetDragDropTarget();
+ return nullptr;
+ }
+
+ auto* new_delegate = aura::client::GetDragDropDelegate(window);
+ const bool delegate_has_changed = (new_delegate != drag_drop_delegate_);
+ if (delegate_has_changed) {
+ ResetDragDropTarget();
+ drag_drop_delegate_ = new_delegate;
+ current_window_ = window;
+ current_window_->AddObserver(this);
+ }
+
+ if (!drag_drop_delegate_)
return nullptr;
- UpdateDragDropDelegate(window);
gfx::Point root_location(location.x(), location.y());
root_window_->GetHost()->ConvertScreenInPixelsToDIP(&root_location);
gfx::PointF target_location(root_location);
aura::Window::ConvertPointToTarget(root_window_, window, &target_location);
- return std::make_unique<ui::DropTargetEvent>(
- *os_exchange_data_, target_location, gfx::PointF(root_location),
+ auto event = std::make_unique<ui::DropTargetEvent>(
+ *data_to_drop_, target_location, gfx::PointF(root_location),
drag_operation_);
+ if (delegate_has_changed)
+ drag_drop_delegate_->OnDragEntered(*event);
+ return event;
}
-void DesktopDragDropClientOzone::UpdateDragDropDelegate(aura::Window* window) {
- aura::client::DragDropDelegate* delegate =
- aura::client::GetDragDropDelegate(window);
-
- if (drag_drop_delegate_ == delegate)
+void DesktopDragDropClientOzone::UpdateDragWidgetLocation() {
+ if (!drag_context_)
return;
- ResetDragDropTarget();
- if (delegate)
- drag_drop_delegate_ = delegate;
-}
+ float scale_factor =
+ ui::GetScaleFactorForNativeView(drag_context_->widget->GetNativeWindow());
+ gfx::Point scaled_point = gfx::ScaleToRoundedPoint(
+ *drag_context_->last_screen_location_px, 1.f / scale_factor);
+ drag_context_->widget->SetBounds(
+ gfx::Rect(scaled_point - drag_context_->offset, drag_context_->size));
+ drag_context_->widget->StackAtTop();
-void DesktopDragDropClientOzone::ResetDragDropTarget() {
- if (!drag_drop_delegate_)
- return;
- drag_drop_delegate_->OnDragExited();
- drag_drop_delegate_ = nullptr;
+ drag_context_->last_screen_location_px.reset();
}
-void DesktopDragDropClientOzone::PerformDrop() {
- std::unique_ptr<ui::DropTargetEvent> event =
- CreateDropTargetEvent(last_drag_point_);
- if (drag_drop_delegate_ && event)
- drag_operation_ = drag_drop_delegate_->OnPerformDrop(
- *event, std::move(os_exchange_data_));
- DragDropSessionCompleted();
- ResetDragDropTarget();
+void DesktopDragDropClientOzone::ResetDragDropTarget() {
+ if (drag_drop_delegate_) {
+ drag_drop_delegate_->OnDragExited();
+ drag_drop_delegate_ = nullptr;
+ }
+ if (current_window_) {
+ current_window_->RemoveObserver(this);
+ current_window_ = nullptr;
+ }
}
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
index 05b2fd24af6..d4e93838750 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
@@ -8,7 +8,9 @@
#include <memory>
#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
#include "ui/aura/client/drag_drop_client.h"
+#include "ui/aura/window_observer.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/gfx/geometry/point_f.h"
@@ -29,17 +31,50 @@ class DropTargetEvent;
namespace views {
class DesktopNativeCursorManager;
+class Widget;
class VIEWS_EXPORT DesktopDragDropClientOzone
: public aura::client::DragDropClient,
- public ui::WmDropHandler {
+ public ui::WmDragHandler::Delegate,
+ public ui::WmDropHandler,
+ public aura::WindowObserver {
public:
DesktopDragDropClientOzone(aura::Window* root_window,
views::DesktopNativeCursorManager* cursor_manager,
ui::WmDragHandler* drag_handler);
~DesktopDragDropClientOzone() override;
- // Overridden from aura::client::DragDropClient:
+ private:
+ friend class DesktopDragDropClientOzoneTest;
+
+ // Holds data related to the drag operation started by this client.
+ struct DragContext {
+ DragContext();
+ ~DragContext();
+
+ // Exits the drag loop.
+ base::OnceClosure quit_closure;
+
+ // Widget that the user drags around. May be nullptr.
+ std::unique_ptr<Widget> widget;
+
+ // The size of drag image.
+ gfx::Size size;
+
+ // The offset of |drag_widget_| relative to the mouse position.
+ gfx::Vector2d offset;
+
+ // The last received drag location. The drag widget is moved asynchronously
+ // so its position is updated when the UI thread has time for that. When
+ // the first change to the location happens, a call to UpdateDragWidget()
+ // is posted, and this location is set. The location can be updated a few
+ // more times until the posted task is executed, but no more than a single
+ // call to UpdateDragWidget() is scheduled at any time; this optional is set
+ // means that the task is scheduled.
+ base::Optional<gfx::Point> last_screen_location_px;
+ };
+
+ // aura::client::DragDropClient
int StartDragAndDrop(std::unique_ptr<ui::OSExchangeData> data,
aura::Window* root_window,
aura::Window* source_window,
@@ -51,7 +86,7 @@ class VIEWS_EXPORT DesktopDragDropClientOzone
void AddObserver(aura::client::DragDropClientObserver* observer) override;
void RemoveObserver(aura::client::DragDropClientObserver* observer) override;
- // Overridden from void ui::WmDropHandler:
+ // ui::WmDropHandler
void OnDragEnter(const gfx::PointF& point,
std::unique_ptr<ui::OSExchangeData> data,
int operation) override;
@@ -59,24 +94,33 @@ class VIEWS_EXPORT DesktopDragDropClientOzone
void OnDragDrop(std::unique_ptr<ui::OSExchangeData> data) override;
void OnDragLeave() override;
- void OnDragSessionClosed(int operation);
+ // aura::WindowObserver
+ void OnWindowDestroyed(aura::Window* window) override;
+
+ // ui::WmDragHandler::Delegate
+ void OnDragLocationChanged(const gfx::Point& screen_point_px) override;
+ void OnDragOperationChanged(
+ ui::DragDropTypes::DragOperation operation) override;
+ void OnDragFinished(int operation) override;
- private:
- void DragDropSessionCompleted();
void QuitRunLoop();
- // Returns a DropTargetEvent to be passed to the DragDropDelegate, or null to
- // abort the drag.
- std::unique_ptr<ui::DropTargetEvent> CreateDropTargetEvent(
+ // Returns a DropTargetEvent to be passed to the DragDropDelegate.
+ // Updates the delegate if needed, which in its turn calls their
+ // OnDragExited/OnDragEntered, so after getting the event the delegate
+ // is ready to accept OnDragUpdated or OnPerformDrop. Returns nullptr if
+ // drop is not possible.
+ std::unique_ptr<ui::DropTargetEvent> UpdateTargetAndCreateDropEvent(
const gfx::PointF& point);
// Updates |drag_drop_delegate_| along with |window|.
void UpdateDragDropDelegate(aura::Window* window);
- // Resets |drag_drop_delegate_|.
- void ResetDragDropTarget();
+ // Updates |drag_widget_| so it is aligned with the last drag location.
+ void UpdateDragWidgetLocation();
- void PerformDrop();
+ // Resets |drag_drop_delegate_|. Calls OnDragExited() before resetting.
+ void ResetDragDropTarget();
aura::Window* const root_window_;
@@ -84,26 +128,24 @@ class VIEWS_EXPORT DesktopDragDropClientOzone
ui::WmDragHandler* const drag_handler_;
+ // Last window under the mouse.
+ aura::Window* current_window_ = nullptr;
// The delegate corresponding to the window located at the mouse position.
aura::client::DragDropDelegate* drag_drop_delegate_ = nullptr;
// The data to be delivered through the drag and drop.
- std::unique_ptr<ui::OSExchangeData> os_exchange_data_ = nullptr;
+ std::unique_ptr<ui::OSExchangeData> data_to_drop_;
- // The most recent native coordinates of a drag.
+ // The most recent native coordinates of an incoming drag. Updated while
+ // the mouse is moved, and used at dropping.
gfx::PointF last_drag_point_;
- // Cursor in use prior to the move loop starting. Restored when the move loop
- // quits.
- gfx::NativeCursor initial_cursor_;
-
- base::OnceClosure quit_closure_;
-
// The operation bitfield.
int drag_operation_ = 0;
- // The flag that controls whether it has a nested run loop.
- bool in_move_loop_ = false;
+ std::unique_ptr<DragContext> drag_context_;
+
+ base::WeakPtrFactory<DesktopDragDropClientOzone> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientOzone);
};
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
index d159e5130b8..cfb7bcca00b 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
@@ -66,8 +66,8 @@ class FakePlatformWindow : public ui::PlatformWindow, public ui::WmDragHandler {
void StartDrag(const OSExchangeData& data,
int operation,
gfx::NativeCursor cursor,
- base::OnceCallback<void(int)> callback) override {
- drag_closed_callback_ = std::move(callback);
+ WmDragHandler::Delegate* delegate) override {
+ drag_handler_delegate_ = delegate;
source_data_ = std::make_unique<OSExchangeData>(data.provider().Clone());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
@@ -107,7 +107,7 @@ class FakePlatformWindow : public ui::PlatformWindow, public ui::WmDragHandler {
}
void CloseDrag(uint32_t dnd_action) {
- std::move(drag_closed_callback_).Run(dnd_action);
+ drag_handler_delegate_->OnDragFinished(dnd_action);
}
void ProcessDrag(std::unique_ptr<OSExchangeData> data, int operation) {
@@ -119,7 +119,7 @@ class FakePlatformWindow : public ui::PlatformWindow, public ui::WmDragHandler {
}
private:
- base::OnceCallback<void(int)> drag_closed_callback_;
+ WmDragHandler::Delegate* drag_handler_delegate_ = nullptr;
std::unique_ptr<ui::OSExchangeData> source_data_;
DISALLOW_COPY_AND_ASSIGN(FakePlatformWindow);
@@ -288,10 +288,77 @@ TEST_F(DesktopDragDropClientOzoneTest, ReceiveDrag) {
EXPECT_EQ(sample_data, string_data);
EXPECT_EQ(1, dragdrop_delegate_->num_enters());
- EXPECT_EQ(1, dragdrop_delegate_->num_enters());
EXPECT_EQ(1, dragdrop_delegate_->num_updates());
EXPECT_EQ(1, dragdrop_delegate_->num_drops());
EXPECT_EQ(1, dragdrop_delegate_->num_exits());
}
+TEST_F(DesktopDragDropClientOzoneTest, TargetDestroyedDuringDrag) {
+ const int suggested_operation =
+ ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE;
+
+ // Set the operation which the destination can accept.
+ dragdrop_delegate_->SetOperation(ui::DragDropTypes::DRAG_MOVE);
+
+ // Set the data which will be delivered.
+ const base::string16 sample_data = base::ASCIIToUTF16("ReceiveDrag");
+ std::unique_ptr<ui::OSExchangeData> data =
+ std::make_unique<ui::OSExchangeData>();
+ data->SetString(sample_data);
+
+ // Simulate that the drag enter/motion/leave events happen with the
+ // |suggested_operation| in the main window.
+ platform_window_->OnDragEnter(gfx::PointF(), std::move(data),
+ suggested_operation);
+ platform_window_->OnDragMotion(gfx::PointF(), suggested_operation);
+ platform_window_->OnDragLeave();
+
+ // Create another window with its own DnD facility and simulate that the drag
+ // enters it and then the window is destroyed.
+ auto another_widget = std::make_unique<Widget>();
+ Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(100, 100);
+ another_widget->Init(std::move(params));
+ another_widget->Show();
+
+ aura::Window* another_window = another_widget->GetNativeWindow();
+ auto another_dragdrop_delegate = std::make_unique<FakeDragDropDelegate>();
+ aura::client::SetDragDropDelegate(another_window,
+ another_dragdrop_delegate.get());
+ another_dragdrop_delegate->SetOperation(ui::DragDropTypes::DRAG_COPY);
+
+ auto another_cursor_manager = std::make_unique<DesktopNativeCursorManager>();
+ auto another_platform_window = std::make_unique<FakePlatformWindow>();
+ ui::WmDragHandler* drag_handler =
+ ui::GetWmDragHandler(*(another_platform_window));
+ auto another_client = std::make_unique<DesktopDragDropClientOzone>(
+ another_window, another_cursor_manager.get(), drag_handler);
+ SetWmDropHandler(another_platform_window.get(), another_client.get());
+
+ std::unique_ptr<ui::OSExchangeData> another_data =
+ std::make_unique<ui::OSExchangeData>();
+ another_data->SetString(sample_data);
+ another_platform_window->OnDragEnter(gfx::PointF(), std::move(another_data),
+ suggested_operation);
+ another_platform_window->OnDragMotion(gfx::PointF(), suggested_operation);
+
+ another_widget->CloseWithReason(Widget::ClosedReason::kUnspecified);
+ another_widget.reset();
+
+ // The main window should have the typical record of a drag started and left.
+ EXPECT_EQ(1, dragdrop_delegate_->num_enters());
+ EXPECT_EQ(1, dragdrop_delegate_->num_updates());
+ EXPECT_EQ(0, dragdrop_delegate_->num_drops());
+ EXPECT_EQ(1, dragdrop_delegate_->num_exits());
+
+ // As the target window has closed and we have never provided another one,
+ // the number of exits should be zero despite that the platform window has
+ // notified the client about leaving the drag.
+ EXPECT_EQ(1, another_dragdrop_delegate->num_enters());
+ EXPECT_EQ(1, another_dragdrop_delegate->num_updates());
+ EXPECT_EQ(0, another_dragdrop_delegate->num_drops());
+ EXPECT_EQ(0, another_dragdrop_delegate->num_exits());
+}
+
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
index 806e4a883a0..e3bd6355071 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
@@ -6,19 +6,24 @@
#include <memory>
-#include "base/metrics/histogram_macros.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drag_source_win.h"
#include "ui/base/dragdrop/drop_target_event.h"
#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
+#include "ui/base/win/event_creation_utils.h"
+#include "ui/display/win/screen_win.h"
#include "ui/views/widget/desktop_aura/desktop_drop_target_win.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"
namespace views {
-DesktopDragDropClientWin::DesktopDragDropClientWin(aura::Window* root_window,
- HWND window)
- : drag_drop_in_progress_(false), drag_operation_(0) {
+DesktopDragDropClientWin::DesktopDragDropClientWin(
+ aura::Window* root_window,
+ HWND window,
+ DesktopWindowTreeHostWin* desktop_host)
+ : drag_drop_in_progress_(false),
+ drag_operation_(0),
+ desktop_host_(desktop_host) {
drop_target_ = new DesktopDropTargetWin(root_window);
drop_target_->Init(window);
}
@@ -37,24 +42,36 @@ int DesktopDragDropClientWin::StartDragAndDrop(
ui::DragDropTypes::DragEventSource source) {
drag_drop_in_progress_ = true;
drag_operation_ = operation;
-
+ if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
+ gfx::Point screen_point = display::win::ScreenWin::DIPToScreenPoint(
+ {screen_location.x(), screen_location.y()});
+ // Send a mouse down and mouse move before do drag drop runs its own event
+ // loop. This is required for ::DoDragDrop to start the drag.
+ ui::SendMouseEvent(screen_point,
+ MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE);
+ ui::SendMouseEvent(screen_point, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
+ desktop_host_->SetInTouchDrag(true);
+ }
base::WeakPtr<DesktopDragDropClientWin> alive(weak_factory_.GetWeakPtr());
drag_source_ = ui::DragSourceWin::Create();
Microsoft::WRL::ComPtr<ui::DragSourceWin> drag_source_copy = drag_source_;
drag_source_copy->set_data(data.get());
- ui::OSExchangeDataProviderWin::GetDataObjectImpl(*data.get())
- ->set_in_drag_loop(true);
+ ui::OSExchangeDataProviderWin::GetDataObjectImpl(*data)->set_in_drag_loop(
+ true);
DWORD effect;
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Start", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
-
- HRESULT result = DoDragDrop(
+ HRESULT result = ::DoDragDrop(
ui::OSExchangeDataProviderWin::GetIDataObject(*data.get()),
drag_source_.Get(),
ui::DragDropTypes::DragOperationToDropEffect(operation), &effect);
+ if (alive && source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
+ desktop_host_->SetInTouchDrag(false);
+ // Gesture state gets left in a state where you can't start
+ // another drag, unless it's cleaned up.
+ source_window->CleanupGestureState();
+ }
drag_source_copy->set_data(nullptr);
if (alive)
@@ -63,17 +80,7 @@ int DesktopDragDropClientWin::StartDragAndDrop(
if (result != DRAGDROP_S_DROP)
effect = DROPEFFECT_NONE;
- int drag_operation = ui::DragDropTypes::DropEffectToDragOperation(effect);
-
- if (drag_operation == ui::DragDropTypes::DRAG_NONE) {
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
- } else {
- UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Drop", source,
- ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
- }
-
- return drag_operation;
+ return ui::DragDropTypes::DropEffectToDragOperation(effect);
}
void DesktopDragDropClientWin::DragCancel() {
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
index 74c40adff7e..a55fe963a04 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
@@ -28,11 +28,14 @@ class DragSourceWin;
namespace views {
class DesktopDropTargetWin;
+class DesktopWindowTreeHostWin;
class VIEWS_EXPORT DesktopDragDropClientWin
: public aura::client::DragDropClient {
public:
- DesktopDragDropClientWin(aura::Window* root_window, HWND window);
+ DesktopDragDropClientWin(aura::Window* root_window,
+ HWND window,
+ DesktopWindowTreeHostWin* desktop_host);
~DesktopDragDropClientWin() override;
// Overridden from aura::client::DragDropClient:
@@ -58,6 +61,11 @@ class VIEWS_EXPORT DesktopDragDropClientWin
scoped_refptr<DesktopDropTargetWin> drop_target_;
+ // |this| will get deleted DesktopNativeWidgetAura is notified that the
+ // DesktopWindowTreeHost is being destroyed. So desktop_host_ should outlive
+ // |this|.
+ DesktopWindowTreeHostWin* desktop_host_ = nullptr;
+
base::WeakPtrFactory<DesktopDragDropClientWin> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(DesktopDragDropClientWin);
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc b/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc
index a1703ed51c4..eac8953797e 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc
@@ -6,7 +6,6 @@
#include <utility>
-#include "base/metrics/histogram_macros.h"
#include "base/win/win_util.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/drag_drop_delegate.h"
@@ -80,8 +79,6 @@ DWORD DesktopDropTargetWin::OnDragOver(IDataObject* data_object,
if (delegate)
drag_operation = delegate->OnDragUpdated(*event);
- UMA_HISTOGRAM_BOOLEAN("Event.DragDrop.AcceptDragUpdate",
- drag_operation != ui::DragDropTypes::DRAG_NONE);
return ui::DragDropTypes::DragOperationToDropEffect(drag_operation);
}
@@ -98,14 +95,8 @@ DWORD DesktopDropTargetWin::OnDrop(IDataObject* data_object,
std::unique_ptr<ui::DropTargetEvent> event;
DragDropDelegate* delegate;
Translate(data_object, key_state, position, effect, &data, &event, &delegate);
- if (delegate) {
+ if (delegate)
drag_operation = delegate->OnPerformDrop(*event, std::move(data));
- DragDropClient* client = aura::client::GetDragDropClient(root_window_);
- if (client && !client->IsDragDropInProgress() &&
- drag_operation != ui::DragDropTypes::DRAG_NONE) {
- UMA_HISTOGRAM_COUNTS_1M("Event.DragDrop.ExternalOriginDrop", 1);
- }
- }
if (target_window_) {
target_window_->RemoveObserver(this);
target_window_ = nullptr;
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen.cc
index b656c14e4f7..50ac4c71dbf 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_screen.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen.cc
@@ -17,7 +17,7 @@ void InstallDesktopScreenIfNecessary() {
// The screen may have already been set in test initialization.
if (!display::Screen::GetScreen())
- display::Screen::SetScreenInstance(CreateDesktopScreen());
+ CreateDesktopScreen();
}
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_linux.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_linux.cc
new file mode 100644
index 00000000000..6a11abce534
--- /dev/null
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_linux.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/widget/desktop_aura/desktop_screen.h"
+
+#include "base/notreached.h"
+
+#if defined(USE_X11)
+#include "ui/views/widget/desktop_aura/desktop_screen_x11.h"
+#endif
+
+#if defined(USE_OZONE)
+#include "ui/base/ui_base_features.h"
+#include "ui/views/widget/desktop_aura/desktop_screen_ozone.h"
+#endif
+
+namespace views {
+
+display::Screen* CreateDesktopScreen() {
+#if defined(USE_OZONE)
+ if (features::IsUsingOzonePlatform())
+ return new DesktopScreenOzone();
+#endif
+#if defined(USE_X11)
+ auto* screen = new DesktopScreenX11();
+ screen->Init();
+ return screen;
+#else
+ NOTREACHED();
+ return nullptr;
+#endif
+}
+
+} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc
index 6f9d29fbcbb..05fc9cde057 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc
@@ -4,6 +4,7 @@
#include "ui/views/widget/desktop_aura/desktop_screen_ozone.h"
+#include "build/build_config.h"
#include "ui/aura/screen_ozone.h"
#include "ui/views/widget/desktop_aura/desktop_screen.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h"
@@ -22,8 +23,13 @@ gfx::NativeWindow DesktopScreenOzone::GetNativeWindowFromAcceleratedWidget(
widget);
}
+// To avoid multiple definitions when use_x11 && use_ozone is true, disable this
+// factory method for OS_LINUX as Linux has a factory method that decides what
+// screen to use based on IsUsingOzonePlatform feature flag.
+#if !defined(OS_LINUX)
display::Screen* CreateDesktopScreen() {
return new DesktopScreenOzone();
}
+#endif
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_win.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_win.cc
index 963e7053e7a..e9bb45ad238 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_screen_win.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_win.cc
@@ -13,7 +13,9 @@ namespace views {
DesktopScreenWin::DesktopScreenWin() = default;
-DesktopScreenWin::~DesktopScreenWin() = default;
+DesktopScreenWin::~DesktopScreenWin() {
+ display::Screen::SetScreenInstance(old_screen_);
+}
HWND DesktopScreenWin::GetHWNDFromNativeWindow(gfx::NativeWindow window) const {
aura::WindowTreeHost* host = window->GetHost();
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_win.h b/chromium/ui/views/widget/desktop_aura/desktop_screen_win.h
index 97442c424d8..52f54f2e6b8 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_screen_win.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_win.h
@@ -21,6 +21,8 @@ class VIEWS_EXPORT DesktopScreenWin : public display::win::ScreenWin {
// display::win::ScreenWin:
HWND GetHWNDFromNativeWindow(gfx::NativeWindow window) const override;
gfx::NativeWindow GetNativeWindowFromHWND(HWND hwnd) const override;
+
+ display::Screen* const old_screen_ = display::Screen::SetScreenInstance(this);
};
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc
index 4f4a5d1c395..084ba94bd9a 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc
@@ -32,7 +32,9 @@ DesktopScreenX11::DesktopScreenX11() {
display_scale_factor_observer_.Add(LinuxUI::instance());
}
-DesktopScreenX11::~DesktopScreenX11() = default;
+DesktopScreenX11::~DesktopScreenX11() {
+ display::Screen::SetScreenInstance(old_screen_);
+}
void DesktopScreenX11::Init() {
if (x11_display_manager_->IsXrandrAvailable() &&
@@ -60,12 +62,11 @@ bool DesktopScreenX11::IsWindowUnderCursor(gfx::NativeWindow window) {
gfx::NativeWindow DesktopScreenX11::GetWindowAtScreenPoint(
const gfx::Point& point) {
- auto accelerated_widget =
- ui::X11TopmostWindowFinder().FindLocalProcessWindowAt(
- gfx::ConvertPointToPixel(GetXDisplayScaleFactor(), point), {});
- return accelerated_widget
+ auto window = ui::X11TopmostWindowFinder().FindLocalProcessWindowAt(
+ gfx::ConvertPointToPixel(GetXDisplayScaleFactor(), point), {});
+ return window != x11::Window::None
? views::DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
- static_cast<gfx::AcceleratedWidget>(accelerated_widget))
+ window)
: nullptr;
}
@@ -75,13 +76,12 @@ gfx::NativeWindow DesktopScreenX11::GetLocalProcessWindowAtPoint(
std::set<gfx::AcceleratedWidget> ignore_widgets;
for (auto* const window : ignore)
ignore_widgets.emplace(window->GetHost()->GetAcceleratedWidget());
- auto accelerated_widget =
- ui::X11TopmostWindowFinder().FindLocalProcessWindowAt(
- gfx::ConvertPointToPixel(GetXDisplayScaleFactor(), point),
- ignore_widgets);
- return accelerated_widget
+ auto window = ui::X11TopmostWindowFinder().FindLocalProcessWindowAt(
+ gfx::ConvertPointToPixel(GetXDisplayScaleFactor(), point),
+ ignore_widgets);
+ return window != x11::Window::None
? views::DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
- static_cast<gfx::AcceleratedWidget>(accelerated_widget))
+ window)
: nullptr;
}
@@ -148,7 +148,7 @@ std::string DesktopScreenX11::GetCurrentWorkspace() {
return x11_display_manager_->GetCurrentWorkspace();
}
-bool DesktopScreenX11::DispatchXEvent(XEvent* event) {
+bool DesktopScreenX11::DispatchXEvent(x11::Event* event) {
return x11_display_manager_->CanProcessEvent(*event) &&
x11_display_manager_->ProcessEvent(event);
}
@@ -176,12 +176,4 @@ float DesktopScreenX11::GetXDisplayScaleFactor() const {
: 1.0f;
}
-////////////////////////////////////////////////////////////////////////////////
-
-display::Screen* CreateDesktopScreen() {
- auto* screen = new DesktopScreenX11;
- screen->Init();
- return screen;
-}
-
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.h b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.h
index b391f41caf2..54954d61993 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.h
@@ -12,6 +12,7 @@
#include "ui/base/x/x11_display_manager.h"
#include "ui/display/screen.h"
#include "ui/events/platform/x11/x11_event_source.h"
+#include "ui/gfx/x/event.h"
#include "ui/views/linux_ui/device_scale_factor_observer.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/views_export.h"
@@ -19,10 +20,6 @@
namespace views {
class DesktopScreenX11Test;
-namespace test {
-class DesktopScreenX11TestApi;
-}
-
// Screen implementation that talks to XRandR
class VIEWS_EXPORT DesktopScreenX11 : public display::Screen,
public ui::XEventDispatcher,
@@ -57,7 +54,7 @@ class VIEWS_EXPORT DesktopScreenX11 : public display::Screen,
std::string GetCurrentWorkspace() override;
// ui::XEventDispatcher:
- bool DispatchXEvent(XEvent* event) override;
+ bool DispatchXEvent(x11::Event* event) override;
// DeviceScaleFactorObserver:
void OnDeviceScaleFactorChanged() override;
@@ -66,12 +63,12 @@ class VIEWS_EXPORT DesktopScreenX11 : public display::Screen,
private:
friend class DesktopScreenX11Test;
- friend class test::DesktopScreenX11TestApi;
// ui::XDisplayManager::Delegate:
void OnXDisplayListUpdated() override;
float GetXDisplayScaleFactor() const override;
+ display::Screen* const old_screen_ = display::Screen::SetScreenInstance(this);
std::unique_ptr<ui::XDisplayManager> x11_display_manager_ =
std::make_unique<ui::XDisplayManager>(this);
ScopedObserver<LinuxUI,
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc
index 4168fa1a51f..61d8b3a0678 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc
@@ -184,23 +184,6 @@ Widget::MoveLoopResult DesktopWindowTreeHostLinux::RunMoveLoop(
escape_behavior);
}
-void DesktopWindowTreeHostLinux::OnDisplayMetricsChanged(
- const display::Display& display,
- uint32_t changed_metrics) {
- aura::WindowTreeHost::OnDisplayMetricsChanged(display, changed_metrics);
-
- if ((changed_metrics & DISPLAY_METRIC_DEVICE_SCALE_FACTOR) &&
- display::Screen::GetScreen()->GetDisplayNearestWindow(window()).id() ==
- display.id()) {
- // When the scale factor changes, also pretend that a resize
- // occurred so that the window layout will be refreshed and a
- // compositor redraw will be scheduled. This is weird, but works.
- // TODO(thomasanderson): Figure out a more direct way of doing
- // this.
- OnHostResizedInPixels(GetBoundsInPixels().size());
- }
-}
-
void DesktopWindowTreeHostLinux::DispatchEvent(ui::Event* event) {
// The input can be disabled and the widget marked as non-active in case of
// opened file-dialogs.
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h
index 79bef5fe949..bec4d76adb1 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h
@@ -89,12 +89,10 @@ class VIEWS_EXPORT DesktopWindowTreeHostLinux
const ui::X11Extension* GetX11Extension() const;
private:
- friend class DesktopWindowTreeHostX11Test;
FRIEND_TEST_ALL_PREFIXES(DesktopWindowTreeHostLinuxTest, HitTest);
-
- // Overridden from display::DisplayObserver via aura::WindowTreeHost:
- void OnDisplayMetricsChanged(const display::Display& display,
- uint32_t changed_metrics) override;
+ FRIEND_TEST_ALL_PREFIXES(DesktopWindowTreeHostLinuxTest, MouseNCEvents);
+ FRIEND_TEST_ALL_PREFIXES(DesktopWindowTreeHostLinuxHighDPITest,
+ MouseNCEvents);
// DesktopWindowTreeHostPlatform overrides:
void AddAdditionalInitProperties(
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_unittest.cc
new file mode 100644
index 00000000000..15d8c29db55
--- /dev/null
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_unittest.cc
@@ -0,0 +1,228 @@
+// 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/widget/desktop_aura/desktop_window_tree_host_linux.h"
+
+#include "base/command_line.h"
+#include "ui/base/hit_test.h"
+#include "ui/display/display_switches.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace views {
+
+namespace {
+// A NonClientFrameView with a window mask with the bottom right corner cut out.
+class ShapedNonClientFrameView : public NonClientFrameView {
+ public:
+ ShapedNonClientFrameView() = default;
+
+ ~ShapedNonClientFrameView() override = default;
+
+ // NonClientFrameView:
+ gfx::Rect GetBoundsForClientView() const override { return bounds(); }
+ gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const override {
+ return client_bounds;
+ }
+ int NonClientHitTest(const gfx::Point& point) override {
+ // Fake bottom for non client event test.
+ if (point == gfx::Point(500, 500))
+ return HTBOTTOM;
+ return HTNOWHERE;
+ }
+ void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {
+ int right = size.width();
+ int bottom = size.height();
+
+ window_mask->moveTo(0, 0);
+ window_mask->lineTo(0, bottom);
+ window_mask->lineTo(right, bottom);
+ window_mask->lineTo(right, 10);
+ window_mask->lineTo(right - 10, 10);
+ window_mask->lineTo(right - 10, 0);
+ window_mask->close();
+ }
+ void ResetWindowControls() override {}
+ void UpdateWindowIcon() override {}
+ void UpdateWindowTitle() override {}
+ void SizeConstraintsChanged() override {}
+
+ bool GetAndResetLayoutRequest() {
+ bool layout_requested = layout_requested_;
+ layout_requested_ = false;
+ return layout_requested;
+ }
+
+ private:
+ void Layout() override { layout_requested_ = true; }
+
+ bool layout_requested_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
+};
+
+class ShapedWidgetDelegate : public WidgetDelegateView {
+ public:
+ ShapedWidgetDelegate() = default;
+
+ ~ShapedWidgetDelegate() override = default;
+
+ // WidgetDelegateView:
+ NonClientFrameView* CreateNonClientFrameView(Widget* widget) override {
+ return new ShapedNonClientFrameView;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate);
+};
+
+class MouseEventRecorder : public ui::EventHandler {
+ public:
+ MouseEventRecorder() = default;
+ ~MouseEventRecorder() override = default;
+
+ void Reset() { mouse_events_.clear(); }
+
+ const std::vector<ui::MouseEvent>& mouse_events() const {
+ return mouse_events_;
+ }
+
+ private:
+ // ui::EventHandler:
+ void OnMouseEvent(ui::MouseEvent* mouse) override {
+ mouse_events_.push_back(*mouse);
+ }
+
+ std::vector<ui::MouseEvent> mouse_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(MouseEventRecorder);
+};
+
+} // namespace
+
+class DesktopWindowTreeHostLinuxTest : public ViewsTestBase {
+ public:
+ DesktopWindowTreeHostLinuxTest() = default;
+ DesktopWindowTreeHostLinuxTest(const DesktopWindowTreeHostLinuxTest&) =
+ delete;
+ DesktopWindowTreeHostLinuxTest& operator=(
+ const DesktopWindowTreeHostLinuxTest&) = delete;
+ ~DesktopWindowTreeHostLinuxTest() override = default;
+
+ void SetUp() override {
+ set_native_widget_type(NativeWidgetType::kDesktop);
+
+ ViewsTestBase::SetUp();
+ }
+
+ protected:
+ // Creates a widget of size 100x100.
+ std::unique_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
+ std::unique_ptr<Widget> widget(new Widget);
+ Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
+ params.delegate = delegate;
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.remove_standard_frame = true;
+ params.bounds = gfx::Rect(100, 100, 100, 100);
+ widget->Init(std::move(params));
+ return widget;
+ }
+};
+
+TEST_F(DesktopWindowTreeHostLinuxTest, ChildWindowDestructionDuringTearDown) {
+ Widget parent_widget;
+ Widget::InitParams parent_params =
+ CreateParams(Widget::InitParams::TYPE_WINDOW);
+ parent_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ parent_widget.Init(std::move(parent_params));
+ parent_widget.Show();
+
+ Widget child_widget;
+ Widget::InitParams child_params =
+ CreateParams(Widget::InitParams::TYPE_WINDOW);
+ child_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ child_params.parent = parent_widget.GetNativeWindow();
+ child_widget.Init(std::move(child_params));
+ child_widget.Show();
+
+ // Sanity check that the two widgets each have their own XID.
+ ASSERT_NE(parent_widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget(),
+ child_widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+ Widget::CloseAllSecondaryWidgets();
+ EXPECT_TRUE(DesktopWindowTreeHostLinux::GetAllOpenWindows().empty());
+}
+
+TEST_F(DesktopWindowTreeHostLinuxTest, MouseNCEvents) {
+ std::unique_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
+ widget->Show();
+
+ base::RunLoop().RunUntilIdle();
+
+ widget->SetBounds(gfx::Rect(100, 100, 501, 501));
+
+ base::RunLoop().RunUntilIdle();
+
+ MouseEventRecorder recorder;
+ widget->GetNativeWindow()->AddPreTargetHandler(&recorder);
+
+ auto* host_linux = static_cast<DesktopWindowTreeHostLinux*>(
+ widget->GetNativeWindow()->GetHost());
+ ASSERT_TRUE(host_linux);
+
+ ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::PointF(500, 500),
+ gfx::PointF(500, 500), base::TimeTicks::Now(), 0, 0, {});
+ host_linux->DispatchEvent(&event);
+
+ ASSERT_EQ(1u, recorder.mouse_events().size());
+ EXPECT_EQ(ui::ET_MOUSE_PRESSED, recorder.mouse_events()[0].type());
+ EXPECT_TRUE(recorder.mouse_events()[0].flags() & ui::EF_IS_NON_CLIENT);
+
+ widget->GetNativeWindow()->RemovePreTargetHandler(&recorder);
+}
+
+class DesktopWindowTreeHostLinuxHighDPITest
+ : public DesktopWindowTreeHostLinuxTest {
+ public:
+ DesktopWindowTreeHostLinuxHighDPITest() = default;
+ ~DesktopWindowTreeHostLinuxHighDPITest() override = default;
+
+ private:
+ void SetUp() override {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ command_line->AppendSwitchASCII(switches::kForceDeviceScaleFactor, "2");
+
+ DesktopWindowTreeHostLinuxTest::SetUp();
+ }
+};
+
+TEST_F(DesktopWindowTreeHostLinuxHighDPITest, MouseNCEvents) {
+ std::unique_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
+ widget->Show();
+
+ widget->SetBounds(gfx::Rect(100, 100, 1000, 1000));
+ base::RunLoop().RunUntilIdle();
+
+ MouseEventRecorder recorder;
+ widget->GetNativeWindow()->AddPreTargetHandler(&recorder);
+
+ auto* host_linux = static_cast<DesktopWindowTreeHostLinux*>(
+ widget->GetNativeWindow()->GetHost());
+ ASSERT_TRUE(host_linux);
+
+ ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::PointF(1001, 1001),
+ gfx::PointF(1001, 1001), base::TimeTicks::Now(), 0, 0,
+ {});
+ host_linux->DispatchEvent(&event);
+
+ EXPECT_EQ(1u, recorder.mouse_events().size());
+ EXPECT_EQ(gfx::Point(500, 500), recorder.mouse_events()[0].location());
+ EXPECT_EQ(ui::ET_MOUSE_PRESSED, recorder.mouse_events()[0].type());
+ EXPECT_TRUE(recorder.mouse_events()[0].flags() & ui::EF_IS_NON_CLIENT);
+
+ widget->GetNativeWindow()->RemovePreTargetHandler(&recorder);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
index 62e2a8fd5ab..c9119b8606c 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
@@ -143,4 +143,68 @@ TEST_F(DesktopWindowTreeHostPlatformTest, CallOnNativeWidgetVisibilityChanged) {
EXPECT_TRUE(observer.visible());
}
+// Tests that the minimization information is propagated to the content window.
+TEST_F(DesktopWindowTreeHostPlatformTest,
+ ToggleMinimizePropogateToContentWindow) {
+ std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+ widget->Show();
+
+ auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+ widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+ ASSERT_TRUE(host_platform);
+
+ EXPECT_TRUE(widget->GetNativeWindow()->IsVisible());
+
+ // Pretend a PlatformWindow enters the minimized state.
+ host_platform->OnWindowStateChanged(ui::PlatformWindowState::kMinimized);
+
+ EXPECT_FALSE(widget->GetNativeWindow()->IsVisible());
+
+ // Pretend a PlatformWindow exits the minimized state.
+ host_platform->OnWindowStateChanged(ui::PlatformWindowState::kNormal);
+ EXPECT_TRUE(widget->GetNativeWindow()->IsVisible());
+}
+
+// A Widget that allows setting the min/max size for the widget.
+class CustomSizeWidget : public Widget {
+ public:
+ CustomSizeWidget() = default;
+ ~CustomSizeWidget() override = default;
+
+ void set_min_size(const gfx::Size& size) { min_size_ = size; }
+ void set_max_size(const gfx::Size& size) { max_size_ = size; }
+
+ // Widget:
+ gfx::Size GetMinimumSize() const override { return min_size_; }
+ gfx::Size GetMaximumSize() const override { return max_size_; }
+
+ private:
+ gfx::Size min_size_;
+ gfx::Size max_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomSizeWidget);
+};
+
+TEST_F(DesktopWindowTreeHostPlatformTest, SetBoundsWithMinMax) {
+ CustomSizeWidget widget;
+ Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(200, 100);
+ widget.Init(std::move(params));
+ widget.Show();
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(gfx::Size(200, 100).ToString(),
+ widget.GetWindowBoundsInScreen().size().ToString());
+ widget.SetBounds(gfx::Rect(300, 200));
+ EXPECT_EQ(gfx::Size(300, 200).ToString(),
+ widget.GetWindowBoundsInScreen().size().ToString());
+
+ widget.set_min_size(gfx::Size(100, 100));
+ widget.SetBounds(gfx::Rect(50, 500));
+ EXPECT_EQ(gfx::Size(100, 500).ToString(),
+ widget.GetWindowBoundsInScreen().size().ToString());
+}
+
} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 6051aadee62..b5430b871cf 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -25,6 +25,7 @@
#include "ui/base/cursor/cursor_loader_win.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ui_base_features.h"
+#include "ui/base/win/event_creation_utils.h"
#include "ui/base/win/shell.h"
#include "ui/compositor/paint_context.h"
#include "ui/display/win/dpi.h"
@@ -32,6 +33,7 @@
#include "ui/events/keyboard_hook.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_keyboard_layout_map.h"
+#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/native_widget_types.h"
@@ -118,6 +120,10 @@ aura::Window* DesktopWindowTreeHostWin::GetContentWindowForHWND(HWND hwnd) {
return host ? host->window()->GetProperty(kContentWindowForRootWindow) : NULL;
}
+void DesktopWindowTreeHostWin::SetInTouchDrag(bool in_touch_drag) {
+ in_touch_drag_ = in_touch_drag;
+}
+
////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostWin, DesktopWindowTreeHost implementation:
@@ -185,7 +191,7 @@ std::unique_ptr<corewm::Tooltip> DesktopWindowTreeHostWin::CreateTooltip() {
std::unique_ptr<aura::client::DragDropClient>
DesktopWindowTreeHostWin::CreateDragDropClient(
DesktopNativeCursorManager* cursor_manager) {
- drag_drop_client_ = new DesktopDragDropClientWin(window(), GetHWND());
+ drag_drop_client_ = new DesktopDragDropClientWin(window(), GetHWND(), this);
return base::WrapUnique(drag_drop_client_);
}
@@ -920,6 +926,9 @@ void DesktopWindowTreeHostWin::HandleNativeBlur(HWND focused_window) {
}
bool DesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
+ // Ignore native platform events for test purposes
+ if (ui::PlatformEventSource::ShouldIgnoreNativePlatformEvents())
+ return true;
// Mouse events in occluded windows should be very rare. If this stat isn't
// very close to 0, that would indicate that windows are incorrectly getting
// marked occluded, or getting stuck in the occluded state. Event can cause
@@ -967,6 +976,21 @@ void DesktopWindowTreeHostWin::HandleTouchEvent(ui::TouchEvent* event) {
if (!GetWidget()->GetNativeView())
return;
+ if (in_touch_drag_) {
+ POINT event_point;
+ event_point.x = event->location().x();
+ event_point.y = event->location().y();
+ ::ClientToScreen(GetHWND(), &event_point);
+ gfx::Point screen_point(event_point);
+ // Send equivalent mouse events, because Ole32 drag drop doesn't seem to
+ // handle pointer events.
+ if (event->type() == ui::ET_TOUCH_MOVED) {
+ ui::SendMouseEvent(screen_point, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
+ } else if (event->type() == ui::ET_TOUCH_RELEASED) {
+ ui::SendMouseEvent(screen_point,
+ MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE);
+ }
+ }
// Currently we assume the window that has capture gets touch events too.
aura::WindowTreeHost* host =
aura::WindowTreeHost::GetForAcceleratedWidget(GetCapture());
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index 75ca9e99f2c..c0f00b6d86d 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -59,6 +59,12 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
// A way of converting an HWND into a content window.
static aura::Window* GetContentWindowForHWND(HWND hwnd);
+ // Set to true when DesktopDragDropClientWin starts a touch-initiated drag
+ // drop and false when it finishes. While in touch drag, if pointer events are
+ // received, the equivalent mouse events are generated, because ole32
+ // ::DoDragDrop does not seem to handle pointer events.
+ void SetInTouchDrag(bool in_touch_drag);
+
protected:
// Overridden from DesktopWindowTreeHost:
void Init(const Widget::InitParams& params) override;
@@ -312,6 +318,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
// when that stat is no longer tracked.
gfx::Point occluded_window_mouse_event_loc_;
+ bool in_touch_drag_ = false;
+
// The z-order level of the window; the window exhibits "always on top"
// behavior if > 0.
ui::ZOrderLevel z_order_ = ui::ZOrderLevel::kNormal;
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 44e9eef3492..af81a254b0c 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -52,6 +52,7 @@
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_path.h"
+#include "ui/gfx/x/xproto.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/views_switches.h"
#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
@@ -82,9 +83,9 @@ void DesktopWindowTreeHostX11::Init(const Widget::InitParams& params) {
// Set XEventDelegate to receive selection, drag&drop and raw key events.
//
// TODO(https://crbug.com/990756): There are two cases of this delegate:
- // XEvents for DragAndDrop client and raw key events. DragAndDrop could be
- // unified so that DragAndrDropClientOzone is used and XEvent are handled on
- // platform level.
+ // x11::Events for DragAndDrop client and raw key events. DragAndDrop could be
+ // unified so that DragAndrDropClientOzone is used and x11::Event are handled
+ // on platform level.
static_cast<ui::X11Window*>(platform_window())->SetXEventDelegate(this);
}
@@ -92,7 +93,6 @@ std::unique_ptr<aura::client::DragDropClient>
DesktopWindowTreeHostX11::CreateDragDropClient(
DesktopNativeCursorManager* cursor_manager) {
drag_drop_client_ = new DesktopDragDropClientAuraX11(window(), cursor_manager,
- GetXWindow()->display(),
GetXWindow()->window());
drag_drop_client_->Init();
return base::WrapUnique(drag_drop_client_);
@@ -101,16 +101,16 @@ DesktopWindowTreeHostX11::CreateDragDropClient(
////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostX11 implementation:
-void DesktopWindowTreeHostX11::OnXWindowSelectionEvent(XEvent* xev) {
+void DesktopWindowTreeHostX11::OnXWindowSelectionEvent(x11::Event* xev) {
DCHECK(xev);
DCHECK(drag_drop_client_);
- drag_drop_client_->OnSelectionNotify(xev->xselection);
+ drag_drop_client_->OnSelectionNotify(*xev->As<x11::SelectionNotifyEvent>());
}
-void DesktopWindowTreeHostX11::OnXWindowDragDropEvent(XEvent* xev) {
+void DesktopWindowTreeHostX11::OnXWindowDragDropEvent(x11::Event* xev) {
DCHECK(xev);
DCHECK(drag_drop_client_);
- drag_drop_client_->HandleXdndEvent(xev->xclient);
+ drag_drop_client_->HandleXdndEvent(*xev->As<x11::ClientMessageEvent>());
}
const ui::XWindow* DesktopWindowTreeHostX11::GetXWindow() const {
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
index 8cd6b80ee1e..69501945464 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
@@ -8,6 +8,7 @@
#include <memory>
#include "base/macros.h"
+#include "ui/gfx/x/event.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/x11/x11_window.h"
@@ -40,8 +41,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 : public DesktopWindowTreeHostLinux,
friend class DesktopWindowTreeHostX11HighDPITest;
// Overridden from ui::XEventDelegate.
- void OnXWindowSelectionEvent(XEvent* xev) override;
- void OnXWindowDragDropEvent(XEvent* xev) override;
+ void OnXWindowSelectionEvent(x11::Event* xev) override;
+ void OnXWindowDragDropEvent(x11::Event* xev) override;
// Casts PlatformWindow into XWindow and returns the result. This is a temp
// solution to access XWindow, which is subclassed by the X11Window, which is
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_interactive_uitest.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_interactive_uitest.cc
index c63bfe26350..b7c96511462 100644
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_interactive_uitest.cc
+++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_interactive_uitest.cc
@@ -4,6 +4,8 @@
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
+#include <xcb/xproto.h>
+
#include <memory>
#include "base/macros.h"
@@ -17,6 +19,7 @@
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/events/x/x11_event_translation.h"
#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/x/event.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/views/controls/textfield/textfield.h"
@@ -30,7 +33,7 @@ namespace {
// Blocks till |window| gets activated.
class ActivationWaiter : public ui::X11PropertyChangeWaiter {
public:
- explicit ActivationWaiter(XID window)
+ explicit ActivationWaiter(x11::Window window)
: ui::X11PropertyChangeWaiter(ui::GetX11RootWindow(),
"_NET_ACTIVE_WINDOW"),
window_(window) {}
@@ -39,13 +42,14 @@ class ActivationWaiter : public ui::X11PropertyChangeWaiter {
private:
// ui::X11PropertyChangeWaiter:
- bool ShouldKeepOnWaiting(XEvent* event) override {
- XID xid = 0;
- ui::GetXIDProperty(ui::GetX11RootWindow(), "_NET_ACTIVE_WINDOW", &xid);
- return xid != window_;
+ bool ShouldKeepOnWaiting(x11::Event* event) override {
+ x11::Window window = x11::Window::None;
+ ui::GetProperty(ui::GetX11RootWindow(), gfx::GetAtom("_NET_ACTIVE_WINDOW"),
+ &window);
+ return window != window_;
}
- XID window_;
+ x11::Window window_;
DISALLOW_COPY_AND_ASSIGN(ActivationWaiter);
};
@@ -88,23 +92,25 @@ void DispatchMouseMotionEvent(DesktopWindowTreeHostX11* desktop_host,
const gfx::Point& point_in_screen) {
gfx::Rect bounds_in_screen = desktop_host->window()->GetBoundsInScreen();
- Display* display = gfx::GetXDisplay();
- XEvent xev;
- xev.xmotion.type = MotionNotify;
- xev.xmotion.display = display;
- xev.xmotion.window = desktop_host->GetAcceleratedWidget();
- xev.xmotion.root = DefaultRootWindow(display);
- xev.xmotion.subwindow = 0;
- xev.xmotion.time = x11::CurrentTime;
- xev.xmotion.x = point_in_screen.x() - bounds_in_screen.x();
- xev.xmotion.y = point_in_screen.y() - bounds_in_screen.y();
- xev.xmotion.x_root = point_in_screen.x();
- xev.xmotion.y_root = point_in_screen.y();
- xev.xmotion.state = 0;
- xev.xmotion.is_hint = NotifyNormal;
- xev.xmotion.same_screen = x11::True;
-
- ui::X11EventSource::GetInstance()->ProcessXEvent(&xev);
+ auto* connection = x11::Connection::Get();
+ xcb_generic_event_t ge;
+ memset(&ge, 0, sizeof(ge));
+ auto* xev = reinterpret_cast<xcb_motion_notify_event_t*>(&ge);
+ xev->response_type = MotionNotify;
+ xev->event = static_cast<uint32_t>(desktop_host->GetAcceleratedWidget());
+ xev->root = static_cast<uint32_t>(connection->default_screen().root);
+ xev->child = 0;
+ xev->time = x11::CurrentTime;
+ xev->event_x = point_in_screen.x() - bounds_in_screen.x();
+ xev->event_y = point_in_screen.y() - bounds_in_screen.y();
+ xev->root_x = point_in_screen.x();
+ xev->root_y = point_in_screen.y();
+ xev->state = 0;
+ xev->detail = NotifyNormal;
+ xev->same_screen = x11::True;
+
+ x11::Event x11_event(&ge, connection);
+ ui::X11EventSource::GetInstance()->ProcessXEvent(&x11_event);
}
} // namespace
diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc
deleted file mode 100644
index 1f4b3a5244c..00000000000
--- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc
+++ /dev/null
@@ -1,675 +0,0 @@
-// Copyright 2014 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 <stddef.h>
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "base/command_line.h"
-#include "base/macros.h"
-#include "base/run_loop.h"
-#include "base/stl_util.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_tree_host.h"
-#include "ui/base/hit_test.h"
-#include "ui/base/x/test/x11_property_change_waiter.h"
-#include "ui/base/x/x11_util.h"
-#include "ui/display/display_switches.h"
-#include "ui/events/devices/x11/touch_factory_x11.h"
-#include "ui/events/platform/x11/x11_event_source.h"
-#include "ui/events/test/events_test_utils_x11.h"
-#include "ui/events/test/platform_event_source_test_api.h"
-#include "ui/events/x/x11_event_translation.h"
-#include "ui/gfx/geometry/point.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/x/x11.h"
-#include "ui/gfx/x/x11_atom_cache.h"
-#include "ui/views/test/views_test_base.h"
-#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
-#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
-#include "ui/views/widget/widget_delegate.h"
-#include "ui/views/window/non_client_view.h"
-
-namespace views {
-
-namespace {
-
-const int kPointerDeviceId = 1;
-
-// Blocks till the window state hint, |hint|, is set or unset.
-class WMStateWaiter : public ui::X11PropertyChangeWaiter {
- public:
- WMStateWaiter(XID window, const char* hint, bool wait_till_set)
- : ui::X11PropertyChangeWaiter(window, "_NET_WM_STATE"),
- hint_(hint),
- wait_till_set_(wait_till_set) {}
-
- ~WMStateWaiter() override = default;
-
- private:
- // X11PropertyChangeWaiter:
- bool ShouldKeepOnWaiting(XEvent* event) override {
- std::vector<Atom> hints;
- if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints))
- return base::Contains(hints, gfx::GetAtom(hint_)) != wait_till_set_;
- return true;
- }
-
- // The name of the hint to wait to get set or unset.
- const char* hint_;
-
- // Whether we are waiting for |hint| to be set or unset.
- bool wait_till_set_;
-
- DISALLOW_COPY_AND_ASSIGN(WMStateWaiter);
-};
-
-// A NonClientFrameView with a window mask with the bottom right corner cut out.
-class ShapedNonClientFrameView : public NonClientFrameView {
- public:
- ShapedNonClientFrameView() = default;
-
- ~ShapedNonClientFrameView() override = default;
-
- // NonClientFrameView:
- gfx::Rect GetBoundsForClientView() const override { return bounds(); }
- gfx::Rect GetWindowBoundsForClientBounds(
- const gfx::Rect& client_bounds) const override {
- return client_bounds;
- }
- int NonClientHitTest(const gfx::Point& point) override {
- // Fake bottom for non client event test.
- if (point == gfx::Point(500, 500))
- return HTBOTTOM;
- return HTNOWHERE;
- }
- void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {
- int right = size.width();
- int bottom = size.height();
-
- window_mask->moveTo(0, 0);
- window_mask->lineTo(0, bottom);
- window_mask->lineTo(right, bottom);
- window_mask->lineTo(right, 10);
- window_mask->lineTo(right - 10, 10);
- window_mask->lineTo(right - 10, 0);
- window_mask->close();
- }
- void ResetWindowControls() override {}
- void UpdateWindowIcon() override {}
- void UpdateWindowTitle() override {}
- void SizeConstraintsChanged() override {}
-
- bool GetAndResetLayoutRequest() {
- bool layout_requested = layout_requested_;
- layout_requested_ = false;
- return layout_requested;
- }
-
- private:
- void Layout() override { layout_requested_ = true; }
-
- bool layout_requested_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
-};
-
-class ShapedWidgetDelegate : public WidgetDelegateView {
- public:
- ShapedWidgetDelegate() = default;
-
- ~ShapedWidgetDelegate() override = default;
-
- // WidgetDelegateView:
- NonClientFrameView* CreateNonClientFrameView(Widget* widget) override {
- return new ShapedNonClientFrameView;
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate);
-};
-
-// Creates a widget of size 100x100.
-std::unique_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
- std::unique_ptr<Widget> widget(new Widget);
- Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
- params.delegate = delegate;
- params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- params.remove_standard_frame = true;
- params.bounds = gfx::Rect(100, 100, 100, 100);
- widget->Init(std::move(params));
- return widget;
-}
-
-// Returns the list of rectangles which describe |xid|'s bounding region via the
-// X shape extension.
-std::vector<gfx::Rect> GetShapeRects(XID xid) {
- int dummy;
- int shape_rects_size;
- gfx::XScopedPtr<XRectangle[]> shape_rects(XShapeGetRectangles(
- gfx::GetXDisplay(), xid, ShapeBounding, &shape_rects_size, &dummy));
-
- std::vector<gfx::Rect> shape_vector;
- for (int i = 0; i < shape_rects_size; ++i) {
- const XRectangle& rect = shape_rects[i];
- shape_vector.emplace_back(rect.x, rect.y, rect.width, rect.height);
- }
- return shape_vector;
-}
-
-// Returns true if one of |rects| contains point (x,y).
-bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects,
- int x,
- int y) {
- gfx::Point point(x, y);
- return std::any_of(
- shape_rects.cbegin(), shape_rects.cend(),
- [&point](const auto& rect) { return rect.Contains(point); });
-}
-
-} // namespace
-
-class DesktopWindowTreeHostX11Test : public ViewsTestBase {
- public:
- DesktopWindowTreeHostX11Test()
- : event_source_(ui::X11EventSource::GetInstance()) {}
- ~DesktopWindowTreeHostX11Test() override = default;
-
- void SetUp() override {
- set_native_widget_type(NativeWidgetType::kDesktop);
-
- std::vector<int> pointer_devices;
- pointer_devices.push_back(kPointerDeviceId);
- ui::TouchFactory::GetInstance()->SetPointerDeviceForTest(pointer_devices);
-
- ViewsTestBase::SetUp();
-
- // Make X11 synchronous for our display connection. This does not force the
- // window manager to behave synchronously.
- XSynchronize(gfx::GetXDisplay(), x11::True);
- }
-
- void TearDown() override {
- XSynchronize(gfx::GetXDisplay(), x11::False);
- ViewsTestBase::TearDown();
- }
-
- void DispatchSingleEventToWidget(XEvent* xev, Widget* widget) {
- DCHECK_EQ(GenericEvent, xev->type);
- XIDeviceEvent* device_event =
- static_cast<XIDeviceEvent*>(xev->xcookie.data);
- device_event->event =
- widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
- event_source_->ProcessXEvent(xev);
- }
-
- private:
- ui::X11EventSource* event_source_;
- DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test);
-};
-
-// https://crbug.com/898742: Test is flaky.
-// Tests that the shape is properly set on the x window.
-TEST_F(DesktopWindowTreeHostX11Test, DISABLED_Shape) {
- if (!ui::IsShapeExtensionAvailable())
- return;
-
- // 1) Test setting the window shape via the NonClientFrameView. This technique
- // is used to get rounded corners on Chrome windows when not using the native
- // window frame.
- std::unique_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate());
- widget1->Show();
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
- std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1);
- ASSERT_FALSE(shape_rects.empty());
-
- // The widget was supposed to be 100x100, but the WM might have ignored this
- // suggestion.
- int widget_width = widget1->GetWindowBoundsInScreen().width();
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15));
-
- // Changing widget's size should update the shape.
- widget1->SetBounds(gfx::Rect(100, 100, 200, 200));
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- if (widget1->GetWindowBoundsInScreen().width() == 200) {
- shape_rects = GetShapeRects(xid1);
- ASSERT_FALSE(shape_rects.empty());
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15));
- }
-
- if (ui::WmSupportsHint(gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
- // The shape should be changed to a rectangle which fills the entire screen
- // when |widget1| is maximized.
- {
- WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true);
- widget1->Maximize();
- waiter.Wait();
- }
-
- // Ensure that the task which is posted when a window is resized is run.
- base::RunLoop().RunUntilIdle();
-
- // xvfb does not support Xrandr so we cannot check the maximized window's
- // bounds.
- gfx::Rect maximized_bounds;
- ui::GetOuterWindowBounds(xid1, &maximized_bounds);
-
- shape_rects = GetShapeRects(xid1);
- ASSERT_FALSE(shape_rects.empty());
- EXPECT_TRUE(
- ShapeRectContainsPoint(shape_rects, maximized_bounds.width() - 1, 5));
- EXPECT_TRUE(
- ShapeRectContainsPoint(shape_rects, maximized_bounds.width() - 1, 15));
- }
-
- // 2) Test setting the window shape via Widget::SetShape().
- auto shape_region = std::make_unique<Widget::ShapeRects>();
- shape_region->emplace_back(10, 0, 90, 10);
- shape_region->emplace_back(0, 10, 10, 90);
- shape_region->emplace_back(10, 10, 90, 90);
-
- std::unique_ptr<Widget> widget2(CreateWidget(nullptr));
- widget2->Show();
- widget2->SetShape(std::move(shape_region));
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
- shape_rects = GetShapeRects(xid2);
- ASSERT_FALSE(shape_rects.empty());
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
-
- // Changing the widget's size should not affect the shape.
- widget2->SetBounds(gfx::Rect(100, 100, 200, 200));
- shape_rects = GetShapeRects(xid2);
- ASSERT_FALSE(shape_rects.empty());
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
-
- // Setting the shape to NULL resets the shape back to the entire
- // window bounds.
- widget2->SetShape(nullptr);
- shape_rects = GetShapeRects(xid2);
- ASSERT_FALSE(shape_rects.empty());
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 5, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
- EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 105, 15));
- EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 500, 500));
-}
-
-// Test that the widget reacts on changes in fullscreen state initiated by the
-// window manager (e.g. via a window manager accelerator key).
-TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) {
- if (!ui::WmSupportsHint(gfx::GetAtom("_NET_WM_STATE_FULLSCREEN")))
- return;
-
- Display* display = gfx::GetXDisplay();
-
- std::unique_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
- auto* non_client_view = static_cast<ShapedNonClientFrameView*>(
- widget->non_client_view()->frame_view());
- ASSERT_TRUE(non_client_view);
- XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
- widget->Show();
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
- {
- WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true);
- widget->SetFullscreen(true);
- waiter.Wait();
- }
- EXPECT_TRUE(widget->IsFullscreen());
-
- // After the fullscreen state has been set, there must be a relayout request
- EXPECT_TRUE(non_client_view->GetAndResetLayoutRequest());
-
- // Ensure there is not request before we proceed.
- EXPECT_FALSE(non_client_view->GetAndResetLayoutRequest());
-
- // Emulate the window manager exiting fullscreen via a window manager
- // accelerator key.
- {
- XEvent xclient;
- memset(&xclient, 0, sizeof(xclient));
- xclient.type = ClientMessage;
- xclient.xclient.window = xid;
- xclient.xclient.message_type = gfx::GetAtom("_NET_WM_STATE");
- xclient.xclient.format = 32;
- xclient.xclient.data.l[0] = 0;
- xclient.xclient.data.l[1] = gfx::GetAtom("_NET_WM_STATE_FULLSCREEN");
- xclient.xclient.data.l[2] = 0;
- xclient.xclient.data.l[3] = 1;
- xclient.xclient.data.l[4] = 0;
- XSendEvent(display, DefaultRootWindow(display), x11::False,
- SubstructureRedirectMask | SubstructureNotifyMask, &xclient);
-
- WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false);
- waiter.Wait();
- }
- // Ensure it continues in browser fullscreen mode and bounds are restored to
- // |initial_bounds|.
- EXPECT_TRUE(widget->IsFullscreen());
- EXPECT_EQ(initial_bounds.ToString(),
- widget->GetWindowBoundsInScreen().ToString());
-
- // Emulate window resize (through X11 configure events) while in browser
- // fullscreen mode and ensure bounds are tracked correctly.
- initial_bounds.set_size({400, 400});
- {
- XWindowChanges changes = {0};
- changes.width = initial_bounds.width();
- changes.height = initial_bounds.height();
- XConfigureWindow(display, xid, CWHeight | CWWidth, &changes);
- // Ensure that the task which is posted when a window is resized is run.
- base::RunLoop().RunUntilIdle();
- }
- EXPECT_TRUE(widget->IsFullscreen());
- EXPECT_EQ(initial_bounds.ToString(),
- widget->GetWindowBoundsInScreen().ToString());
-
- // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
- // state and clean things up.
- widget->SetFullscreen(false);
- EXPECT_FALSE(widget->IsFullscreen());
- EXPECT_EQ(initial_bounds.ToString(),
- widget->GetWindowBoundsInScreen().ToString());
-
- // Even though the unfullscreen request came from the window manager, we must
- // still react and relayout.
- EXPECT_TRUE(non_client_view->GetAndResetLayoutRequest());
-}
-
-// Tests that the minimization information is propagated to the content window.
-TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) {
- Widget widget;
- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
- params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- widget.Init(std::move(params));
- widget.Show();
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
- Display* display = gfx::GetXDisplay();
-
- // Minimize by sending _NET_WM_STATE_HIDDEN
- {
- std::vector<::Atom> atom_list;
- atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_HIDDEN"));
- ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
-
- XEvent xevent;
- memset(&xevent, 0, sizeof(xevent));
- xevent.type = PropertyNotify;
- xevent.xproperty.type = PropertyNotify;
- xevent.xproperty.send_event = 1;
- xevent.xproperty.display = display;
- xevent.xproperty.window = xid;
- xevent.xproperty.atom = gfx::GetAtom("_NET_WM_STATE");
- xevent.xproperty.state = 0;
- XSendEvent(display, DefaultRootWindow(display), x11::False,
- SubstructureRedirectMask | SubstructureNotifyMask, &xevent);
-
- WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true);
- waiter.Wait();
- }
- EXPECT_FALSE(widget.GetNativeWindow()->IsVisible());
-
- // Show from minimized by sending _NET_WM_STATE_FOCUSED
- {
- std::vector<::Atom> atom_list;
- atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_FOCUSED"));
- ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
-
- XEvent xevent;
- memset(&xevent, 0, sizeof(xevent));
- xevent.type = PropertyNotify;
- xevent.xproperty.type = PropertyNotify;
- xevent.xproperty.send_event = 1;
- xevent.xproperty.display = display;
- xevent.xproperty.window = xid;
- xevent.xproperty.atom = gfx::GetAtom("_NET_WM_STATE");
- xevent.xproperty.state = 0;
- XSendEvent(display, DefaultRootWindow(display), x11::False,
- SubstructureRedirectMask | SubstructureNotifyMask, &xevent);
-
- WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true);
- waiter.Wait();
- }
- EXPECT_TRUE(widget.GetNativeWindow()->IsVisible());
-}
-
-TEST_F(DesktopWindowTreeHostX11Test, ChildWindowDestructionDuringTearDown) {
- Widget parent_widget;
- Widget::InitParams parent_params =
- CreateParams(Widget::InitParams::TYPE_WINDOW);
- parent_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- parent_widget.Init(std::move(parent_params));
- parent_widget.Show();
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- Widget child_widget;
- Widget::InitParams child_params =
- CreateParams(Widget::InitParams::TYPE_WINDOW);
- child_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- child_params.parent = parent_widget.GetNativeWindow();
- child_widget.Init(std::move(child_params));
- child_widget.Show();
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- // Sanity check that the two widgets each have their own XID.
- ASSERT_NE(parent_widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget(),
- child_widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget());
- Widget::CloseAllSecondaryWidgets();
- EXPECT_TRUE(DesktopWindowTreeHostLinux::GetAllOpenWindows().empty());
-}
-
-// A Widget that allows setting the min/max size for the widget.
-class CustomSizeWidget : public Widget {
- public:
- CustomSizeWidget() = default;
- ~CustomSizeWidget() override = default;
-
- void set_min_size(const gfx::Size& size) { min_size_ = size; }
- void set_max_size(const gfx::Size& size) { max_size_ = size; }
-
- // Widget:
- gfx::Size GetMinimumSize() const override { return min_size_; }
- gfx::Size GetMaximumSize() const override { return max_size_; }
-
- private:
- gfx::Size min_size_;
- gfx::Size max_size_;
-
- DISALLOW_COPY_AND_ASSIGN(CustomSizeWidget);
-};
-
-TEST_F(DesktopWindowTreeHostX11Test, SetBoundsWithMinMax) {
- CustomSizeWidget widget;
- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
- params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- params.bounds = gfx::Rect(200, 100);
- widget.Init(std::move(params));
- widget.Show();
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- EXPECT_EQ(gfx::Size(200, 100).ToString(),
- widget.GetWindowBoundsInScreen().size().ToString());
- widget.SetBounds(gfx::Rect(300, 200));
- EXPECT_EQ(gfx::Size(300, 200).ToString(),
- widget.GetWindowBoundsInScreen().size().ToString());
-
- widget.set_min_size(gfx::Size(100, 100));
- widget.SetBounds(gfx::Rect(50, 500));
- EXPECT_EQ(gfx::Size(100, 500).ToString(),
- widget.GetWindowBoundsInScreen().size().ToString());
-}
-
-class MouseEventRecorder : public ui::EventHandler {
- public:
- MouseEventRecorder() = default;
- ~MouseEventRecorder() override = default;
-
- void Reset() { mouse_events_.clear(); }
-
- const std::vector<ui::MouseEvent>& mouse_events() const {
- return mouse_events_;
- }
-
- private:
- // ui::EventHandler:
- void OnMouseEvent(ui::MouseEvent* mouse) override {
- mouse_events_.push_back(*mouse);
- }
-
- std::vector<ui::MouseEvent> mouse_events_;
-
- DISALLOW_COPY_AND_ASSIGN(MouseEventRecorder);
-};
-
-class DesktopWindowTreeHostX11HighDPITest
- : public DesktopWindowTreeHostX11Test {
- public:
- DesktopWindowTreeHostX11HighDPITest() = default;
- ~DesktopWindowTreeHostX11HighDPITest() override = default;
-
- void PretendCapture(views::Widget* capture_widget) {
- if (capture_widget)
- capture_widget->GetNativeWindow()->SetCapture();
- }
-
- private:
- void SetUp() override {
- base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
- command_line->AppendSwitchASCII(switches::kForceDeviceScaleFactor, "2");
-
- DesktopWindowTreeHostX11Test::SetUp();
- }
-
- DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11HighDPITest);
-};
-
-// https://crbug.com/702687
-TEST_F(DesktopWindowTreeHostX11HighDPITest,
- DISABLED_LocatedEventDispatchWithCapture) {
- Widget first;
- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
- params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- params.bounds = gfx::Rect(0, 0, 50, 50);
- first.Init(std::move(params));
- first.Show();
-
- Widget second;
- params = CreateParams(Widget::InitParams::TYPE_WINDOW);
- params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
- params.bounds = gfx::Rect(50, 50, 50, 50);
- second.Init(std::move(params));
- second.Show();
-
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- MouseEventRecorder first_recorder, second_recorder;
- first.GetNativeWindow()->AddPreTargetHandler(&first_recorder);
- second.GetNativeWindow()->AddPreTargetHandler(&second_recorder);
-
- // Dispatch an event on |first|. Verify it gets the event.
- ui::ScopedXI2Event event;
- event.InitGenericButtonEvent(kPointerDeviceId, ui::ET_MOUSEWHEEL,
- gfx::Point(50, 50), ui::EF_NONE);
- DispatchSingleEventToWidget(event, &first);
- ASSERT_EQ(1u, first_recorder.mouse_events().size());
- EXPECT_EQ(ui::ET_MOUSEWHEEL, first_recorder.mouse_events()[0].type());
- EXPECT_EQ(gfx::Point(25, 25).ToString(),
- first_recorder.mouse_events()[0].location().ToString());
- ASSERT_EQ(0u, second_recorder.mouse_events().size());
-
- first_recorder.Reset();
- second_recorder.Reset();
-
- // Set a capture on |second|, and dispatch the same event to |first|. This
- // event should reach |second| instead.
- PretendCapture(&second);
- event.InitGenericButtonEvent(kPointerDeviceId, ui::ET_MOUSEWHEEL,
- gfx::Point(50, 50), ui::EF_NONE);
- DispatchSingleEventToWidget(event, &first);
-
- ASSERT_EQ(0u, first_recorder.mouse_events().size());
- ASSERT_EQ(1u, second_recorder.mouse_events().size());
- EXPECT_EQ(ui::ET_MOUSEWHEEL, second_recorder.mouse_events()[0].type());
- EXPECT_EQ(gfx::Point(-25, -25).ToString(),
- second_recorder.mouse_events()[0].location().ToString());
-
- PretendCapture(nullptr);
- first.GetNativeWindow()->RemovePreTargetHandler(&first_recorder);
- second.GetNativeWindow()->RemovePreTargetHandler(&second_recorder);
-}
-
-TEST_F(DesktopWindowTreeHostX11Test, MouseNCEvents) {
- std::unique_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
- widget->Show();
-
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- widget->SetBounds(gfx::Rect(100, 100, 501, 501));
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- MouseEventRecorder recorder;
- widget->GetNativeWindow()->AddPreTargetHandler(&recorder);
-
- ui::ScopedXI2Event event;
- event.InitGenericButtonEvent(kPointerDeviceId, ui::ET_MOUSE_PRESSED,
- gfx::Point(500, 500), ui::EF_LEFT_MOUSE_BUTTON);
-
- DispatchSingleEventToWidget(event, widget.get());
- ASSERT_EQ(1u, recorder.mouse_events().size());
- EXPECT_EQ(ui::ET_MOUSE_PRESSED, recorder.mouse_events()[0].type());
- EXPECT_TRUE(recorder.mouse_events()[0].flags() & ui::EF_IS_NON_CLIENT);
-
- widget->GetNativeWindow()->RemovePreTargetHandler(&recorder);
-}
-
-TEST_F(DesktopWindowTreeHostX11HighDPITest, MouseNCEvents) {
- std::unique_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
- widget->Show();
-
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- widget->SetBounds(gfx::Rect(100, 100, 1000, 1000));
- ui::X11EventSource::GetInstance()->DispatchXEvents();
-
- MouseEventRecorder recorder;
- widget->GetNativeWindow()->AddPreTargetHandler(&recorder);
-
- ui::ScopedXI2Event event;
- event.InitGenericButtonEvent(kPointerDeviceId, ui::ET_MOUSE_PRESSED,
- gfx::Point(1001, 1001),
- ui::EF_LEFT_MOUSE_BUTTON);
- DispatchSingleEventToWidget(event, widget.get());
- ASSERT_EQ(1u, recorder.mouse_events().size());
- EXPECT_EQ(ui::ET_MOUSE_PRESSED, recorder.mouse_events()[0].type());
- EXPECT_TRUE(recorder.mouse_events()[0].flags() & ui::EF_IS_NON_CLIENT);
-
- widget->GetNativeWindow()->RemovePreTargetHandler(&recorder);
-}
-
-} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/x11_drag_drop_client_unittest.cc b/chromium/ui/views/widget/desktop_aura/x11_drag_drop_client_unittest.cc
new file mode 100644
index 00000000000..c9946d29596
--- /dev/null
+++ b/chromium/ui/views/widget/desktop_aura/x11_drag_drop_client_unittest.cc
@@ -0,0 +1,827 @@
+// Copyright 2014 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/base/x/x11_drag_drop_client.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ui/aura/client/drag_drop_client.h"
+#include "ui/aura/client/drag_drop_delegate.h"
+#include "ui/aura/test/test_screen.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/x/x11_cursor.h"
+#include "ui/base/x/x11_move_loop.h"
+#include "ui/base/x/x11_move_loop_delegate.h"
+#include "ui/base/x/x11_os_exchange_data_provider.h"
+#include "ui/base/x/x11_util.h"
+#include "ui/events/event_utils.h"
+#include "ui/gfx/x/x11.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+#include "ui/gfx/x/x11_types.h"
+#include "ui/gfx/x/xproto.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
+#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+namespace {
+
+class TestDragDropClient;
+
+// Collects messages which would otherwise be sent to |window_| via
+// SendXClientEvent().
+class ClientMessageEventCollector {
+ public:
+ ClientMessageEventCollector(x11::Window window, TestDragDropClient* client);
+ virtual ~ClientMessageEventCollector();
+
+ // Returns true if |events_| is non-empty.
+ bool HasEvents() const { return !events_.empty(); }
+
+ // Pops all of |events_| and returns the popped events in the order that they
+ // were on the stack
+ std::vector<x11::ClientMessageEvent> PopAllEvents();
+
+ // Adds |event| to the stack.
+ void RecordEvent(const x11::ClientMessageEvent& event);
+
+ private:
+ x11::Window window_;
+
+ // Not owned.
+ TestDragDropClient* client_;
+
+ std::vector<x11::ClientMessageEvent> events_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientMessageEventCollector);
+};
+
+// An implementation of ui::X11MoveLoop where RunMoveLoop() always starts the
+// move loop.
+class TestMoveLoop : public ui::X11MoveLoop {
+ public:
+ explicit TestMoveLoop(ui::X11MoveLoopDelegate* delegate);
+ ~TestMoveLoop() override;
+
+ // Returns true if the move loop is running.
+ bool IsRunning() const;
+
+ // ui::X11MoveLoop:
+ bool RunMoveLoop(bool can_grab_pointer,
+ ::Cursor old_cursor,
+ ::Cursor new_cursor) override;
+ void UpdateCursor(::Cursor cursor) override;
+ void EndMoveLoop() override;
+
+ private:
+ // Not owned.
+ ui::X11MoveLoopDelegate* delegate_;
+
+ // Ends the move loop.
+ base::OnceClosure quit_closure_;
+
+ bool is_running_ = false;
+};
+
+// Implementation of XDragDropClient which short circuits FindWindowFor().
+class SimpleTestDragDropClient : public aura::client::DragDropClient,
+ public ui::XDragDropClient,
+ public ui::XDragDropClient::Delegate,
+ public ui::X11MoveLoopDelegate {
+ public:
+ SimpleTestDragDropClient(aura::Window*,
+ DesktopNativeCursorManager* cursor_manager);
+ ~SimpleTestDragDropClient() override;
+
+ // Sets |window| as the topmost window for all mouse positions.
+ void SetTopmostXWindow(x11::Window window);
+
+ // Returns true if the move loop is running.
+ bool IsMoveLoopRunning();
+
+ // aura::client::DragDropClient:
+ int StartDragAndDrop(std::unique_ptr<ui::OSExchangeData> data,
+ aura::Window* root_window,
+ aura::Window* source_window,
+ const gfx::Point& screen_location,
+ int operation,
+ ui::DragDropTypes::DragEventSource source) override;
+ void DragCancel() override;
+ bool IsDragDropInProgress() override;
+ void AddObserver(aura::client::DragDropClientObserver* observer) override;
+ void RemoveObserver(aura::client::DragDropClientObserver* observer) override;
+
+ private:
+ // ui::XDragDropClient::Delegate:
+ std::unique_ptr<ui::XTopmostWindowFinder> CreateWindowFinder() override;
+ int UpdateDrag(const gfx::Point& screen_point) override;
+ void UpdateCursor(
+ ui::DragDropTypes::DragOperation negotiated_operation) override;
+ void OnBeginForeignDrag(x11::Window window) override;
+ void OnEndForeignDrag() override;
+ void OnBeforeDragLeave() override;
+ int PerformDrop() override;
+ void EndDragLoop() override;
+
+ // XDragDropClient:
+ x11::Window FindWindowFor(const gfx::Point& screen_point) override;
+
+ // ui::X11MoveLoopDelegate:
+ void OnMouseMovement(const gfx::Point& screen_point,
+ int flags,
+ base::TimeTicks event_time) override;
+ void OnMouseReleased() override;
+ void OnMoveLoopEnded() override;
+
+ std::unique_ptr<ui::X11MoveLoop> CreateMoveLoop(
+ ui::X11MoveLoopDelegate* delegate);
+
+ // The x11::Window of the window which is simulated to be the topmost window.
+ x11::Window target_window_ = x11::Window::None;
+
+ // The move loop. Not owned.
+ TestMoveLoop* loop_ = nullptr;
+
+ base::OnceClosure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleTestDragDropClient);
+};
+
+// Implementation of XDragDropClient which works with a fake
+// |XDragDropClient::source_current_window_|.
+class TestDragDropClient : public SimpleTestDragDropClient {
+ public:
+ // The location in screen coordinates used for the synthetic mouse moves
+ // generated in SetTopmostXWindowAndMoveMouse().
+ static constexpr int kMouseMoveX = 100;
+ static constexpr int kMouseMoveY = 200;
+
+ TestDragDropClient(aura::Window* window,
+ DesktopNativeCursorManager* cursor_manager);
+ ~TestDragDropClient() override;
+
+ // Returns the x11::Window of the window which initiated the drag.
+ x11::Window source_xwindow() { return source_window_; }
+
+ // Returns the Atom with |name|.
+ x11::Atom GetAtom(const char* name);
+
+ // Returns true if the event's message has |type|.
+ bool MessageHasType(const x11::ClientMessageEvent& event, const char* type);
+
+ // Sets |collector| to collect x11::ClientMessageEvents which would otherwise
+ // have been sent to the drop target window.
+ void SetEventCollectorFor(x11::Window window,
+ ClientMessageEventCollector* collector);
+
+ // Builds an XdndStatus message and sends it to
+ // XDragDropClient.
+ void OnStatus(x11::Window target_window,
+ bool will_accept_drop,
+ x11::Atom accepted_action);
+
+ // Builds an XdndFinished message and sends it to
+ // XDragDropClient.
+ void OnFinished(x11::Window target_window,
+ bool accepted_drop,
+ x11::Atom performed_action);
+
+ // Sets |window| as the topmost window at the current mouse position and
+ // generates a synthetic mouse move.
+ void SetTopmostXWindowAndMoveMouse(x11::Window window);
+
+ private:
+ // XDragDropClient:
+ void SendXClientEvent(x11::Window window,
+ const x11::ClientMessageEvent& event) override;
+
+ // The x11::Window of the window which initiated the drag.
+ x11::Window source_window_;
+
+ // Map of x11::Windows to the collector which intercepts
+ // x11::ClientMessageEvents for that window.
+ std::map<x11::Window, ClientMessageEventCollector*> collectors_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// ClientMessageEventCollector
+
+ClientMessageEventCollector::ClientMessageEventCollector(
+ x11::Window window,
+ TestDragDropClient* client)
+ : window_(window), client_(client) {
+ client->SetEventCollectorFor(window, this);
+}
+
+ClientMessageEventCollector::~ClientMessageEventCollector() {
+ client_->SetEventCollectorFor(window_, nullptr);
+}
+
+std::vector<x11::ClientMessageEvent>
+ClientMessageEventCollector::PopAllEvents() {
+ std::vector<x11::ClientMessageEvent> to_return;
+ to_return.swap(events_);
+ return to_return;
+}
+
+void ClientMessageEventCollector::RecordEvent(
+ const x11::ClientMessageEvent& event) {
+ events_.push_back(event);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TestMoveLoop
+
+TestMoveLoop::TestMoveLoop(ui::X11MoveLoopDelegate* delegate)
+ : delegate_(delegate) {}
+
+TestMoveLoop::~TestMoveLoop() = default;
+
+bool TestMoveLoop::IsRunning() const {
+ return is_running_;
+}
+
+bool TestMoveLoop::RunMoveLoop(bool can_grab_pointer,
+ ::Cursor old_cursor,
+ ::Cursor new_cursor) {
+ is_running_ = true;
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+ return true;
+}
+
+void TestMoveLoop::UpdateCursor(::Cursor cursor) {}
+
+void TestMoveLoop::EndMoveLoop() {
+ if (is_running_) {
+ delegate_->OnMoveLoopEnded();
+ is_running_ = false;
+ std::move(quit_closure_).Run();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SimpleTestDragDropClient
+
+SimpleTestDragDropClient::SimpleTestDragDropClient(
+ aura::Window* window,
+ DesktopNativeCursorManager* cursor_manager)
+ : ui::XDragDropClient(this, window->GetHost()->GetAcceleratedWidget()) {}
+
+SimpleTestDragDropClient::~SimpleTestDragDropClient() = default;
+
+void SimpleTestDragDropClient::SetTopmostXWindow(x11::Window window) {
+ target_window_ = window;
+}
+
+bool SimpleTestDragDropClient::IsMoveLoopRunning() {
+ return loop_->IsRunning();
+}
+
+std::unique_ptr<ui::X11MoveLoop> SimpleTestDragDropClient::CreateMoveLoop(
+ ui::X11MoveLoopDelegate* delegate) {
+ loop_ = new TestMoveLoop(delegate);
+ return base::WrapUnique(loop_);
+}
+
+int SimpleTestDragDropClient::StartDragAndDrop(
+ std::unique_ptr<ui::OSExchangeData> data,
+ aura::Window* root_window,
+ aura::Window* source_window,
+ const gfx::Point& screen_location,
+ int operation,
+ ui::DragDropTypes::DragEventSource source) {
+ InitDrag(operation, data.get());
+
+ auto loop = CreateMoveLoop(this);
+
+ // Windows has a specific method, DoDragDrop(), which performs the entire
+ // drag. We have to emulate this, so we spin off a nested runloop which will
+ // track all cursor movement and reroute events to a specific handler.
+ auto cursor_manager_ = std::make_unique<DesktopNativeCursorManager>();
+ auto* last_cursor = static_cast<ui::X11Cursor*>(
+ source_window->GetHost()->last_cursor().platform());
+ loop_->RunMoveLoop(
+ !source_window->HasCapture(),
+ last_cursor ? last_cursor->xcursor() : x11::None,
+ static_cast<ui::X11Cursor*>(
+ cursor_manager_
+ ->GetInitializedCursor(ui::mojom::CursorType::kGrabbing)
+ .platform())
+ ->xcursor());
+
+ auto resulting_operation = negotiated_operation();
+ CleanupDrag();
+ return resulting_operation;
+}
+
+void SimpleTestDragDropClient::DragCancel() {}
+bool SimpleTestDragDropClient::IsDragDropInProgress() {
+ return false;
+}
+void SimpleTestDragDropClient::AddObserver(
+ aura::client::DragDropClientObserver* observer) {}
+void SimpleTestDragDropClient::RemoveObserver(
+ aura::client::DragDropClientObserver* observer) {}
+
+int SimpleTestDragDropClient::UpdateDrag(const gfx::Point& screen_point) {
+ return 0;
+}
+
+std::unique_ptr<ui::XTopmostWindowFinder>
+SimpleTestDragDropClient::CreateWindowFinder() {
+ return {};
+}
+void SimpleTestDragDropClient::UpdateCursor(
+ ui::DragDropTypes::DragOperation negotiated_operation) {}
+void SimpleTestDragDropClient::OnBeginForeignDrag(x11::Window window) {}
+void SimpleTestDragDropClient::OnEndForeignDrag() {}
+void SimpleTestDragDropClient::OnBeforeDragLeave() {}
+int SimpleTestDragDropClient::PerformDrop() {
+ return 0;
+}
+void SimpleTestDragDropClient::EndDragLoop() {
+ // std::move(quit_closure_).Run();
+ loop_->EndMoveLoop();
+}
+
+x11::Window SimpleTestDragDropClient::FindWindowFor(
+ const gfx::Point& screen_point) {
+ return target_window_;
+}
+
+void SimpleTestDragDropClient::OnMouseMovement(const gfx::Point& screen_point,
+ int flags,
+ base::TimeTicks event_time) {
+ HandleMouseMovement(screen_point, flags, event_time);
+}
+
+void SimpleTestDragDropClient::OnMouseReleased() {
+ HandleMouseReleased();
+}
+
+void SimpleTestDragDropClient::OnMoveLoopEnded() {
+ HandleMoveLoopEnded();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TestDragDropClient
+
+TestDragDropClient::TestDragDropClient(
+ aura::Window* window,
+ DesktopNativeCursorManager* cursor_manager)
+ : SimpleTestDragDropClient(window, cursor_manager),
+ source_window_(window->GetHost()->GetAcceleratedWidget()) {}
+
+TestDragDropClient::~TestDragDropClient() = default;
+
+x11::Atom TestDragDropClient::GetAtom(const char* name) {
+ return gfx::GetAtom(name);
+}
+
+bool TestDragDropClient::MessageHasType(const x11::ClientMessageEvent& event,
+ const char* type) {
+ return event.type == GetAtom(type);
+}
+
+void TestDragDropClient::SetEventCollectorFor(
+ x11::Window window,
+ ClientMessageEventCollector* collector) {
+ if (collector)
+ collectors_[window] = collector;
+ else
+ collectors_.erase(window);
+}
+
+void TestDragDropClient::OnStatus(x11::Window target_window,
+ bool will_accept_drop,
+ x11::Atom accepted_action) {
+ x11::ClientMessageEvent event;
+ event.type = GetAtom("XdndStatus");
+ event.format = 32;
+ event.window = source_window_;
+ event.data.data32[0] = static_cast<uint32_t>(target_window);
+ event.data.data32[1] = will_accept_drop ? 1 : 0;
+ event.data.data32[2] = 0;
+ event.data.data32[3] = 0;
+ event.data.data32[4] = static_cast<uint32_t>(accepted_action);
+ HandleXdndEvent(event);
+}
+
+void TestDragDropClient::OnFinished(x11::Window target_window,
+ bool accepted_drop,
+ x11::Atom performed_action) {
+ x11::ClientMessageEvent event;
+ event.type = GetAtom("XdndFinished");
+ event.format = 32;
+ event.window = source_window_;
+ event.data.data32[0] = static_cast<uint32_t>(target_window);
+ event.data.data32[1] = accepted_drop ? 1 : 0;
+ event.data.data32[2] = static_cast<uint32_t>(performed_action);
+ event.data.data32[3] = 0;
+ event.data.data32[4] = 0;
+ HandleXdndEvent(event);
+}
+
+void TestDragDropClient::SetTopmostXWindowAndMoveMouse(x11::Window window) {
+ SetTopmostXWindow(window);
+ HandleMouseMovement(gfx::Point(kMouseMoveX, kMouseMoveY), ui::EF_NONE,
+ ui::EventTimeForNow());
+}
+
+void TestDragDropClient::SendXClientEvent(
+ x11::Window window,
+ const x11::ClientMessageEvent& event) {
+ auto it = collectors_.find(window);
+ if (it != collectors_.end())
+ it->second->RecordEvent(event);
+}
+
+} // namespace
+
+class X11DragDropClientTest : public ViewsTestBase {
+ public:
+ X11DragDropClientTest() = default;
+ ~X11DragDropClientTest() override = default;
+
+ int StartDragAndDrop() {
+ auto data(std::make_unique<ui::OSExchangeData>());
+ data->SetString(base::ASCIIToUTF16("Test"));
+ SkBitmap drag_bitmap;
+ drag_bitmap.allocN32Pixels(10, 10);
+ drag_bitmap.eraseARGB(0xFF, 0, 0, 0);
+ gfx::ImageSkia drag_image(gfx::ImageSkia::CreateFrom1xBitmap(drag_bitmap));
+ data->provider().SetDragImage(drag_image, gfx::Vector2d());
+
+ return client_->StartDragAndDrop(
+ std::move(data), widget_->GetNativeWindow()->GetRootWindow(),
+ widget_->GetNativeWindow(), gfx::Point(), ui::DragDropTypes::DRAG_COPY,
+ ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
+ }
+
+ // ViewsTestBase:
+ void SetUp() override {
+ set_native_widget_type(NativeWidgetType::kDesktop);
+
+ ViewsTestBase::SetUp();
+
+ // Create widget to initiate the drags.
+ widget_ = std::make_unique<Widget>();
+ Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(100, 100);
+ widget_->Init(std::move(params));
+ widget_->Show();
+
+ cursor_manager_ = std::make_unique<DesktopNativeCursorManager>();
+
+ client_ = std::make_unique<TestDragDropClient>(widget_->GetNativeWindow(),
+ cursor_manager_.get());
+ // client_->Init();
+ }
+
+ void TearDown() override {
+ client_.reset();
+ cursor_manager_.reset();
+ widget_.reset();
+ ViewsTestBase::TearDown();
+ }
+
+ TestDragDropClient* client() { return client_.get(); }
+
+ private:
+ std::unique_ptr<TestDragDropClient> client_;
+ std::unique_ptr<DesktopNativeCursorManager> cursor_manager_;
+
+ // The widget used to initiate drags.
+ std::unique_ptr<Widget> widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(X11DragDropClientTest);
+};
+
+namespace {
+
+void BasicStep2(TestDragDropClient* client, x11::Window toplevel) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ ClientMessageEventCollector collector(toplevel, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+
+ // XdndEnter should have been sent to |toplevel| before the XdndPosition
+ // message.
+ std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
+ ASSERT_EQ(2u, events.size());
+
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
+ EXPECT_EQ(client->source_xwindow(),
+ static_cast<x11::Window>(events[0].data.data32[0]));
+ EXPECT_EQ(1u, events[0].data.data32[1] & 1);
+ std::vector<x11::Atom> targets;
+ ui::GetAtomArrayProperty(client->source_xwindow(), "XdndTypeList", &targets);
+ EXPECT_FALSE(targets.empty());
+
+ EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
+ EXPECT_EQ(client->source_xwindow(),
+ static_cast<x11::Window>(events[0].data.data32[0]));
+ const uint32_t kCoords =
+ TestDragDropClient::kMouseMoveX << 16 | TestDragDropClient::kMouseMoveY;
+ EXPECT_EQ(kCoords, events[1].data.data32[2]);
+ EXPECT_EQ(client->GetAtom("XdndActionCopy"),
+ static_cast<x11::Atom>(events[1].data.data32[4]));
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+
+ // Because there is no unprocessed XdndPosition, the drag drop client should
+ // send XdndDrop immediately after the mouse is released.
+ client->HandleMouseReleased();
+
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
+ EXPECT_EQ(client->source_xwindow(),
+ static_cast<x11::Window>(events[0].data.data32[0]));
+
+ // Send XdndFinished to indicate that the drag drop client can cleanup any
+ // data related to this drag. The move loop should end only after the
+ // XdndFinished message was received.
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+ client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+void BasicStep3(TestDragDropClient* client, x11::Window toplevel) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ ClientMessageEventCollector collector(toplevel, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+
+ std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
+ ASSERT_EQ(2u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
+
+ // We have not received an XdndStatus ack for the second XdndPosition message.
+ // Test that sending XdndDrop is delayed till the XdndStatus ack is received.
+ client->HandleMouseReleased();
+ EXPECT_FALSE(collector.HasEvents());
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
+
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+ client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+} // namespace
+
+TEST_F(X11DragDropClientTest, Basic) {
+ x11::Window toplevel = static_cast<x11::Window>(1);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&BasicStep2, client(), toplevel));
+ int result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
+
+ // Do another drag and drop to test that the data is properly cleaned up as a
+ // result of the XdndFinished message.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&BasicStep3, client(), toplevel));
+ result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
+}
+
+namespace {
+
+void TargetDoesNotRespondStep2(TestDragDropClient* client) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ x11::Window toplevel = static_cast<x11::Window>(1);
+ ClientMessageEventCollector collector(toplevel, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+
+ std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
+ ASSERT_EQ(2u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
+
+ client->HandleMouseReleased();
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+} // namespace
+
+// Test that we do not wait for the target to send XdndStatus if we have not
+// received any XdndStatus messages at all from the target. The Unity
+// DNDCollectionWindow is an example of an XdndAware target which does not
+// respond to XdndPosition messages at all.
+TEST_F(X11DragDropClientTest, TargetDoesNotRespond) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&TargetDoesNotRespondStep2, client()));
+ int result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
+}
+
+namespace {
+
+void QueuePositionStep2(TestDragDropClient* client) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ x11::Window toplevel = static_cast<x11::Window>(1);
+ ClientMessageEventCollector collector(toplevel, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+
+ std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
+ ASSERT_EQ(2u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(collector.HasEvents());
+
+ client->HandleMouseReleased();
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
+
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+ client->OnFinished(toplevel, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+} // namespace
+
+// Test that XdndPosition messages are queued till the pending XdndPosition
+// message is acked via an XdndStatus message.
+TEST_F(X11DragDropClientTest, QueuePosition) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&QueuePositionStep2, client()));
+ int result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
+}
+
+namespace {
+
+void TargetChangesStep2(TestDragDropClient* client) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ x11::Window toplevel1 = static_cast<x11::Window>(1);
+ ClientMessageEventCollector collector1(toplevel1, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel1);
+
+ std::vector<x11::ClientMessageEvent> events1 = collector1.PopAllEvents();
+ ASSERT_EQ(2u, events1.size());
+ EXPECT_TRUE(client->MessageHasType(events1[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events1[1], "XdndPosition"));
+
+ x11::Window toplevel2 = static_cast<x11::Window>(2);
+ ClientMessageEventCollector collector2(toplevel2, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel2);
+
+ // It is possible for |toplevel1| to send XdndStatus after the source has sent
+ // XdndLeave but before |toplevel1| has received the XdndLeave message. The
+ // XdndStatus message should be ignored.
+ client->OnStatus(toplevel1, true, client->GetAtom("XdndActionCopy"));
+ events1 = collector1.PopAllEvents();
+ ASSERT_EQ(1u, events1.size());
+ EXPECT_TRUE(client->MessageHasType(events1[0], "XdndLeave"));
+
+ std::vector<x11::ClientMessageEvent> events2 = collector2.PopAllEvents();
+ ASSERT_EQ(2u, events2.size());
+ EXPECT_TRUE(client->MessageHasType(events2[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events2[1], "XdndPosition"));
+
+ client->OnStatus(toplevel2, true, client->GetAtom("XdndActionCopy"));
+ client->HandleMouseReleased();
+ events2 = collector2.PopAllEvents();
+ ASSERT_EQ(1u, events2.size());
+ EXPECT_TRUE(client->MessageHasType(events2[0], "XdndDrop"));
+
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+ client->OnFinished(toplevel2, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+} // namespace
+
+// Test the behavior when the target changes during a drag.
+TEST_F(X11DragDropClientTest, TargetChanges) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&TargetChangesStep2, client()));
+ int result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, result);
+}
+
+namespace {
+
+void RejectAfterMouseReleaseStep2(TestDragDropClient* client) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ x11::Window toplevel = static_cast<x11::Window>(1);
+ ClientMessageEventCollector collector(toplevel, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+
+ std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
+ ASSERT_EQ(2u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(collector.HasEvents());
+
+ // Send another mouse move such that there is a pending XdndPosition.
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndPosition"));
+
+ client->HandleMouseReleased();
+ // Reject the drop.
+ client->OnStatus(toplevel, false, x11::Atom::None);
+
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndLeave"));
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+void RejectAfterMouseReleaseStep3(TestDragDropClient* client) {
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+
+ x11::Window toplevel = static_cast<x11::Window>(2);
+ ClientMessageEventCollector collector(toplevel, client);
+ client->SetTopmostXWindowAndMoveMouse(toplevel);
+
+ std::vector<x11::ClientMessageEvent> events = collector.PopAllEvents();
+ ASSERT_EQ(2u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndEnter"));
+ EXPECT_TRUE(client->MessageHasType(events[1], "XdndPosition"));
+
+ client->OnStatus(toplevel, true, client->GetAtom("XdndActionCopy"));
+ EXPECT_FALSE(collector.HasEvents());
+
+ client->HandleMouseReleased();
+ events = collector.PopAllEvents();
+ ASSERT_EQ(1u, events.size());
+ EXPECT_TRUE(client->MessageHasType(events[0], "XdndDrop"));
+
+ EXPECT_TRUE(client->IsMoveLoopRunning());
+ client->OnFinished(toplevel, false, x11::Atom::None);
+ EXPECT_FALSE(client->IsMoveLoopRunning());
+}
+
+} // namespace
+
+// Test that the source sends XdndLeave instead of XdndDrop if the drag
+// operation is rejected after the mouse is released.
+TEST_F(X11DragDropClientTest, RejectAfterMouseRelease) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&RejectAfterMouseReleaseStep2, client()));
+ int result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
+
+ // Repeat the test but reject the drop in the XdndFinished message instead.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&RejectAfterMouseReleaseStep3, client()));
+ result = StartDragAndDrop();
+ EXPECT_EQ(ui::DragDropTypes::DRAG_NONE, result);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc b/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
index aa249625d3f..fadecc97d9d 100644
--- a/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
+++ b/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
@@ -15,7 +15,12 @@
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/x/test/x11_property_change_waiter.h"
+#include "ui/base/x/x11_util.h"
#include "ui/events/platform/x11/x11_event_source.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/x/connection.h"
+#include "ui/gfx/x/event.h"
+#include "ui/gfx/x/shape.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_path.h"
@@ -31,15 +36,15 @@ namespace {
// Waits till |window| is minimized.
class MinimizeWaiter : public ui::X11PropertyChangeWaiter {
public:
- explicit MinimizeWaiter(XID window)
+ explicit MinimizeWaiter(x11::Window window)
: ui::X11PropertyChangeWaiter(window, "_NET_WM_STATE") {}
~MinimizeWaiter() override = default;
private:
// ui::X11PropertyChangeWaiter:
- bool ShouldKeepOnWaiting(XEvent* event) override {
- std::vector<Atom> wm_states;
+ bool ShouldKeepOnWaiting(x11::Event* event) override {
+ std::vector<x11::Atom> wm_states;
if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) {
return !base::Contains(wm_states, gfx::GetAtom("_NET_WM_STATE_HIDDEN"));
}
@@ -53,7 +58,7 @@ class MinimizeWaiter : public ui::X11PropertyChangeWaiter {
// |expected_windows|.
class StackingClientListWaiter : public ui::X11PropertyChangeWaiter {
public:
- StackingClientListWaiter(XID* expected_windows, size_t count)
+ StackingClientListWaiter(x11::Window* expected_windows, size_t count)
: ui::X11PropertyChangeWaiter(ui::GetX11RootWindow(),
"_NET_CLIENT_LIST_STACKING"),
expected_windows_(expected_windows, expected_windows + count) {}
@@ -72,15 +77,15 @@ class StackingClientListWaiter : public ui::X11PropertyChangeWaiter {
private:
// ui::X11PropertyChangeWaiter:
- bool ShouldKeepOnWaiting(XEvent* event) override {
- std::vector<XID> stack;
+ bool ShouldKeepOnWaiting(x11::Event* event) override {
+ std::vector<x11::Window> stack;
ui::GetXWindowStack(ui::GetX11RootWindow(), &stack);
return !std::all_of(
expected_windows_.cbegin(), expected_windows_.cend(),
- [&stack](XID window) { return base::Contains(stack, window); });
+ [&stack](x11::Window window) { return base::Contains(stack, window); });
}
- std::vector<XID> expected_windows_;
+ std::vector<x11::Window> expected_windows_;
DISALLOW_COPY_AND_ASSIGN(StackingClientListWaiter);
};
@@ -120,34 +125,36 @@ class X11TopmostWindowFinderTest : public test::DesktopWidgetTestInteractive {
}
// Creates and shows an X window with |bounds|.
- XID CreateAndShowXWindow(const gfx::Rect& bounds) {
- XID root = DefaultRootWindow(xdisplay());
- XID xid = XCreateSimpleWindow(xdisplay(), root, 0, 0, 1, 1,
- 0, // border_width
- 0, // border
- 0); // background
-
- ui::SetUseOSWindowFrame(xid, false);
- ShowAndSetXWindowBounds(xid, bounds);
- return xid;
+ x11::Window CreateAndShowXWindow(const gfx::Rect& bounds) {
+ x11::Window root = ui::GetX11RootWindow();
+ x11::Window window = static_cast<x11::Window>(
+ XCreateSimpleWindow(xdisplay(), static_cast<uint32_t>(root), 0, 0, 1, 1,
+ 0, // border_width
+ 0, // border
+ 0)); // background
+
+ ui::SetUseOSWindowFrame(window, false);
+ ShowAndSetXWindowBounds(window, bounds);
+ return window;
}
- // Shows |xid| and sets its bounds.
- void ShowAndSetXWindowBounds(XID xid, const gfx::Rect& bounds) {
- XMapWindow(xdisplay(), xid);
+ // Shows |window| and sets its bounds.
+ void ShowAndSetXWindowBounds(x11::Window window, const gfx::Rect& bounds) {
+ XMapWindow(xdisplay(), static_cast<uint32_t>(window));
XWindowChanges changes = {0};
changes.x = bounds.x();
changes.y = bounds.y();
changes.width = bounds.width();
changes.height = bounds.height();
- XConfigureWindow(xdisplay(), xid, CWX | CWY | CWWidth | CWHeight, &changes);
+ XConfigureWindow(xdisplay(), static_cast<uint32_t>(window),
+ CWX | CWY | CWWidth | CWHeight, &changes);
}
Display* xdisplay() { return gfx::GetXDisplay(); }
// Returns the topmost X window at the passed in screen position.
- XID FindTopmostXWindowAt(int screen_x, int screen_y) {
+ x11::Window FindTopmostXWindowAt(int screen_x, int screen_y) {
ui::X11TopmostWindowFinder finder;
return finder.FindWindowAt(gfx::Point(screen_x, screen_y));
}
@@ -158,9 +165,10 @@ class X11TopmostWindowFinderTest : public test::DesktopWidgetTestInteractive {
ui::X11TopmostWindowFinder finder;
auto widget =
finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), {});
- return widget ? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
- static_cast<gfx::AcceleratedWidget>(widget))
- : nullptr;
+ return widget != gfx::kNullAcceleratedWidget
+ ? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
+ static_cast<gfx::AcceleratedWidget>(widget))
+ : nullptr;
}
// Returns the topmost aura::Window at the passed in screen position ignoring
@@ -175,9 +183,10 @@ class X11TopmostWindowFinderTest : public test::DesktopWidgetTestInteractive {
ui::X11TopmostWindowFinder finder;
auto widget =
finder.FindLocalProcessWindowAt(gfx::Point(screen_x, screen_y), ignore);
- return widget ? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
- static_cast<gfx::AcceleratedWidget>(widget))
- : nullptr;
+ return widget != gfx::kNullAcceleratedWidget
+ ? DesktopWindowTreeHostPlatform::GetContentWindowForWidget(
+ static_cast<gfx::AcceleratedWidget>(widget))
+ : nullptr;
}
private:
@@ -191,38 +200,38 @@ TEST_F(X11TopmostWindowFinderTest, Basic) {
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 200, 100)));
aura::Window* window1 = widget1->GetNativeWindow();
- XID xid1 = window1->GetHost()->GetAcceleratedWidget();
+ x11::Window x11_window1 = window1->GetHost()->GetAcceleratedWidget();
- XID xid2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
+ x11::Window x11_window2 = CreateAndShowXWindow(gfx::Rect(200, 100, 100, 200));
std::unique_ptr<Widget> widget3(
CreateAndShowWidget(gfx::Rect(100, 190, 200, 110)));
aura::Window* window3 = widget3->GetNativeWindow();
- XID xid3 = window3->GetHost()->GetAcceleratedWidget();
+ x11::Window x11_window3 = window3->GetHost()->GetAcceleratedWidget();
- XID xids[] = {xid1, xid2, xid3};
- StackingClientListWaiter waiter(xids, base::size(xids));
+ x11::Window windows[] = {x11_window1, x11_window2, x11_window3};
+ StackingClientListWaiter waiter(windows, base::size(windows));
waiter.Wait();
ui::X11EventSource::GetInstance()->DispatchXEvents();
- EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150));
+ EXPECT_EQ(x11_window1, FindTopmostXWindowAt(150, 150));
EXPECT_EQ(window1, FindTopmostLocalProcessWindowAt(150, 150));
- EXPECT_EQ(xid2, FindTopmostXWindowAt(250, 150));
+ EXPECT_EQ(x11_window2, FindTopmostXWindowAt(250, 150));
EXPECT_FALSE(FindTopmostLocalProcessWindowAt(250, 150));
- EXPECT_EQ(xid3, FindTopmostXWindowAt(250, 250));
+ EXPECT_EQ(x11_window3, FindTopmostXWindowAt(250, 250));
EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(250, 250));
- EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 250));
+ EXPECT_EQ(x11_window3, FindTopmostXWindowAt(150, 250));
EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 250));
- EXPECT_EQ(xid3, FindTopmostXWindowAt(150, 195));
+ EXPECT_EQ(x11_window3, FindTopmostXWindowAt(150, 195));
EXPECT_EQ(window3, FindTopmostLocalProcessWindowAt(150, 195));
- EXPECT_NE(xid1, FindTopmostXWindowAt(1000, 1000));
- EXPECT_NE(xid2, FindTopmostXWindowAt(1000, 1000));
- EXPECT_NE(xid3, FindTopmostXWindowAt(1000, 1000));
+ EXPECT_NE(x11_window1, FindTopmostXWindowAt(1000, 1000));
+ EXPECT_NE(x11_window2, FindTopmostXWindowAt(1000, 1000));
+ EXPECT_NE(x11_window3, FindTopmostXWindowAt(1000, 1000));
EXPECT_FALSE(FindTopmostLocalProcessWindowAt(1000, 1000));
EXPECT_EQ(window1,
@@ -232,7 +241,7 @@ TEST_F(X11TopmostWindowFinderTest, Basic) {
EXPECT_EQ(window1,
FindTopmostLocalProcessWindowWithIgnore(150, 195, window3));
- XDestroyWindow(xdisplay(), xid2);
+ XDestroyWindow(xdisplay(), static_cast<uint32_t>(x11_window2));
}
// Test that the minimized state is properly handled.
@@ -240,35 +249,35 @@ TEST_F(X11TopmostWindowFinderTest, Minimized) {
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
aura::Window* window1 = widget1->GetNativeWindow();
- XID xid1 = window1->GetHost()->GetAcceleratedWidget();
- XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
+ x11::Window x11_window1 = window1->GetHost()->GetAcceleratedWidget();
+ x11::Window x11_window2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
- XID xids[] = {xid1, xid2};
- StackingClientListWaiter stack_waiter(xids, base::size(xids));
+ x11::Window windows[] = {x11_window1, x11_window2};
+ StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
ui::X11EventSource::GetInstance()->DispatchXEvents();
- EXPECT_EQ(xid1, FindTopmostXWindowAt(150, 150));
+ EXPECT_EQ(x11_window1, FindTopmostXWindowAt(150, 150));
{
- MinimizeWaiter minimize_waiter(xid1);
- XIconifyWindow(xdisplay(), xid1, 0);
+ MinimizeWaiter minimize_waiter(x11_window1);
+ XIconifyWindow(xdisplay(), static_cast<uint32_t>(x11_window1), 0);
minimize_waiter.Wait();
}
- EXPECT_NE(xid1, FindTopmostXWindowAt(150, 150));
- EXPECT_NE(xid2, FindTopmostXWindowAt(150, 150));
+ EXPECT_NE(x11_window1, FindTopmostXWindowAt(150, 150));
+ EXPECT_NE(x11_window2, FindTopmostXWindowAt(150, 150));
// Repeat test for an X window which does not belong to a views::Widget
// because the code path is different.
- EXPECT_EQ(xid2, FindTopmostXWindowAt(350, 150));
+ EXPECT_EQ(x11_window2, FindTopmostXWindowAt(350, 150));
{
- MinimizeWaiter minimize_waiter(xid2);
- XIconifyWindow(xdisplay(), xid2, 0);
+ MinimizeWaiter minimize_waiter(x11_window2);
+ XIconifyWindow(xdisplay(), static_cast<uint32_t>(x11_window2), 0);
minimize_waiter.Wait();
}
- EXPECT_NE(xid1, FindTopmostXWindowAt(350, 150));
- EXPECT_NE(xid2, FindTopmostXWindowAt(350, 150));
+ EXPECT_NE(x11_window1, FindTopmostXWindowAt(350, 150));
+ EXPECT_NE(x11_window2, FindTopmostXWindowAt(350, 150));
- XDestroyWindow(xdisplay(), xid2);
+ XDestroyWindow(xdisplay(), static_cast<uint32_t>(x11_window2));
}
// Test that non-rectangular windows are properly handled.
@@ -278,7 +287,8 @@ TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
- XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
+ x11::Window window1 =
+ widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
auto shape1 = std::make_unique<Widget::ShapeRects>();
shape1->emplace_back(0, 10, 10, 90);
shape1->emplace_back(10, 0, 90, 100);
@@ -287,27 +297,31 @@ TEST_F(X11TopmostWindowFinderTest, NonRectangular) {
SkRegion skregion2;
skregion2.op(SkIRect::MakeXYWH(0, 10, 10, 90), SkRegion::kUnion_Op);
skregion2.op(SkIRect::MakeXYWH(10, 0, 90, 100), SkRegion::kUnion_Op);
- XID xid2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
- gfx::XScopedPtr<REGION, gfx::XObjectDeleter<REGION, int, XDestroyRegion>>
- region2(gfx::CreateRegionFromSkRegion(skregion2));
- XShapeCombineRegion(xdisplay(), xid2, ShapeBounding, 0, 0, region2.get(),
- false);
- XID xids[] = {xid1, xid2};
- StackingClientListWaiter stack_waiter(xids, base::size(xids));
+ x11::Window window2 = CreateAndShowXWindow(gfx::Rect(300, 100, 100, 100));
+ auto region2 = gfx::CreateRegionFromSkRegion(skregion2);
+ x11::Connection::Get()->shape().Rectangles({
+ .operation = x11::Shape::So::Set,
+ .destination_kind = x11::Shape::Sk::Bounding,
+ .ordering = x11::ClipOrdering::YXBanded,
+ .destination_window = window2,
+ .rectangles = *region2,
+ });
+ x11::Window windows[] = {window1, window2};
+ StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
ui::X11EventSource::GetInstance()->DispatchXEvents();
- EXPECT_EQ(xid1, FindTopmostXWindowAt(105, 120));
- EXPECT_NE(xid1, FindTopmostXWindowAt(105, 105));
- EXPECT_NE(xid2, FindTopmostXWindowAt(105, 105));
+ EXPECT_EQ(window1, FindTopmostXWindowAt(105, 120));
+ EXPECT_NE(window1, FindTopmostXWindowAt(105, 105));
+ EXPECT_NE(window2, FindTopmostXWindowAt(105, 105));
// Repeat test for an X window which does not belong to a views::Widget
// because the code path is different.
- EXPECT_EQ(xid2, FindTopmostXWindowAt(305, 120));
- EXPECT_NE(xid1, FindTopmostXWindowAt(305, 105));
- EXPECT_NE(xid2, FindTopmostXWindowAt(305, 105));
+ EXPECT_EQ(window2, FindTopmostXWindowAt(305, 120));
+ EXPECT_NE(window1, FindTopmostXWindowAt(305, 105));
+ EXPECT_NE(window2, FindTopmostXWindowAt(305, 105));
- XDestroyWindow(xdisplay(), xid2);
+ XDestroyWindow(xdisplay(), static_cast<uint32_t>(window2));
}
// Test that a window with an empty shape are properly handled.
@@ -317,18 +331,19 @@ TEST_F(X11TopmostWindowFinderTest, NonRectangularEmptyShape) {
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
- XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
+ x11::Window window1 =
+ widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
auto shape1 = std::make_unique<Widget::ShapeRects>();
shape1->emplace_back();
// Widget takes ownership of |shape1|.
widget1->SetShape(std::move(shape1));
- XID xids[] = {xid1};
- StackingClientListWaiter stack_waiter(xids, base::size(xids));
+ x11::Window windows[] = {window1};
+ StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
ui::X11EventSource::GetInstance()->DispatchXEvents();
- EXPECT_NE(xid1, FindTopmostXWindowAt(105, 105));
+ EXPECT_NE(window1, FindTopmostXWindowAt(105, 105));
}
// Test that setting a Null shape removes the shape.
@@ -338,7 +353,8 @@ TEST_F(X11TopmostWindowFinderTest, NonRectangularNullShape) {
std::unique_ptr<Widget> widget1(
CreateAndShowWidget(gfx::Rect(100, 100, 100, 100)));
- XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
+ x11::Window window1 =
+ widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
auto shape1 = std::make_unique<Widget::ShapeRects>();
shape1->emplace_back();
widget1->SetShape(std::move(shape1));
@@ -346,47 +362,48 @@ TEST_F(X11TopmostWindowFinderTest, NonRectangularNullShape) {
// Remove the shape - this is now just a normal window.
widget1->SetShape(nullptr);
- XID xids[] = {xid1};
- StackingClientListWaiter stack_waiter(xids, base::size(xids));
+ x11::Window windows[] = {window1};
+ StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
ui::X11EventSource::GetInstance()->DispatchXEvents();
- EXPECT_EQ(xid1, FindTopmostXWindowAt(105, 105));
+ EXPECT_EQ(window1, FindTopmostXWindowAt(105, 105));
}
// Test that the TopmostWindowFinder finds windows which belong to menus
// (which may or may not belong to Chrome).
TEST_F(X11TopmostWindowFinderTest, Menu) {
- XID xid = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
+ x11::Window window = CreateAndShowXWindow(gfx::Rect(100, 100, 100, 100));
- XID root = DefaultRootWindow(xdisplay());
+ x11::Window root = ui::GetX11RootWindow();
XSetWindowAttributes swa;
swa.override_redirect = x11::True;
- XID menu_xid = XCreateWindow(xdisplay(), root, 0, 0, 1, 1,
- 0, // border width
- CopyFromParent, // depth
- InputOutput,
- CopyFromParent, // visual
- CWOverrideRedirect, &swa);
+ x11::Window menu_window = static_cast<x11::Window>(XCreateWindow(
+ xdisplay(), static_cast<uint32_t>(root), 0, 0, 1, 1,
+ 0, // border width
+ static_cast<int>(x11::WindowClass::CopyFromParent), // depth
+ static_cast<int>(x11::WindowClass::InputOutput),
+ nullptr, // visual
+ CWOverrideRedirect, &swa));
{
- ui::SetAtomProperty(menu_xid, "_NET_WM_WINDOW_TYPE", "ATOM",
+ ui::SetAtomProperty(menu_window, "_NET_WM_WINDOW_TYPE", "ATOM",
gfx::GetAtom("_NET_WM_WINDOW_TYPE_MENU"));
}
- ui::SetUseOSWindowFrame(menu_xid, false);
- ShowAndSetXWindowBounds(menu_xid, gfx::Rect(140, 110, 100, 100));
+ ui::SetUseOSWindowFrame(menu_window, false);
+ ShowAndSetXWindowBounds(menu_window, gfx::Rect(140, 110, 100, 100));
ui::X11EventSource::GetInstance()->DispatchXEvents();
- // |menu_xid| is never added to _NET_CLIENT_LIST_STACKING.
- XID xids[] = {xid};
- StackingClientListWaiter stack_waiter(xids, base::size(xids));
+ // |menu_window| is never added to _NET_CLIENT_LIST_STACKING.
+ x11::Window windows[] = {window};
+ StackingClientListWaiter stack_waiter(windows, base::size(windows));
stack_waiter.Wait();
- EXPECT_EQ(xid, FindTopmostXWindowAt(110, 110));
- EXPECT_EQ(menu_xid, FindTopmostXWindowAt(150, 120));
- EXPECT_EQ(menu_xid, FindTopmostXWindowAt(210, 120));
+ EXPECT_EQ(window, FindTopmostXWindowAt(110, 110));
+ EXPECT_EQ(menu_window, FindTopmostXWindowAt(150, 120));
+ EXPECT_EQ(menu_window, FindTopmostXWindowAt(210, 120));
- XDestroyWindow(xdisplay(), xid);
- XDestroyWindow(xdisplay(), menu_xid);
+ XDestroyWindow(xdisplay(), static_cast<uint32_t>(window));
+ XDestroyWindow(xdisplay(), static_cast<uint32_t>(menu_window));
}
} // namespace views
diff --git a/chromium/ui/views/widget/native_widget_aura_unittest.cc b/chromium/ui/views/widget/native_widget_aura_unittest.cc
index 4e9ce929364..a0a3fb4a69e 100644
--- a/chromium/ui/views/widget/native_widget_aura_unittest.cc
+++ b/chromium/ui/views/widget/native_widget_aura_unittest.cc
@@ -318,14 +318,13 @@ class PropertyTestLayoutManager : public TestLayoutManagerBase {
class PropertyTestWidgetDelegate : public WidgetDelegate {
public:
- explicit PropertyTestWidgetDelegate(Widget* widget) : widget_(widget) {}
+ explicit PropertyTestWidgetDelegate(Widget* widget) : widget_(widget) {
+ SetHasWindowSizeControls(true);
+ }
~PropertyTestWidgetDelegate() override = default;
private:
// WidgetDelegate:
- bool CanMaximize() const override { return true; }
- bool CanMinimize() const override { return true; }
- bool CanResize() const override { return true; }
void DeleteDelegate() override { delete this; }
Widget* GetWidget() override { return widget_; }
const Widget* GetWidget() const override { return widget_; }
@@ -399,18 +398,18 @@ class GestureTrackingView : public View {
TEST_F(NativeWidgetAuraTest, DontCaptureOnGesture) {
// Create two views (both sized the same). |child| is configured not to
// consume the gesture event.
- GestureTrackingView* view = new GestureTrackingView();
+ auto content_view = std::make_unique<GestureTrackingView>();
GestureTrackingView* child = new GestureTrackingView();
child->set_consume_gesture_event(false);
- view->SetLayoutManager(std::make_unique<FillLayout>());
- view->AddChildView(child);
+ content_view->SetLayoutManager(std::make_unique<FillLayout>());
+ content_view->AddChildView(child);
std::unique_ptr<TestWidget> widget(new TestWidget());
Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.context = root_window();
params.bounds = gfx::Rect(0, 0, 100, 200);
widget->Init(std::move(params));
- widget->SetContentsView(view);
+ GestureTrackingView* view = widget->SetContentsView(std::move(content_view));
widget->Show();
ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(41, 51),
@@ -441,13 +440,12 @@ TEST_F(NativeWidgetAuraTest, DontCaptureOnGesture) {
// Verifies views with layers are targeted for events properly.
TEST_F(NativeWidgetAuraTest, PreferViewLayersToChildWindows) {
// Create two widgets: |parent| and |child|. |child| is a child of |parent|.
- View* parent_root = new View;
std::unique_ptr<Widget> parent(new Widget());
Widget::InitParams parent_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
parent_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
parent_params.context = root_window();
parent->Init(std::move(parent_params));
- parent->SetContentsView(parent_root);
+ View* parent_root = parent->SetContentsView(std::make_unique<View>());
parent->SetBounds(gfx::Rect(0, 0, 400, 400));
parent->Show();
@@ -499,13 +497,12 @@ TEST_F(NativeWidgetAuraTest, PreferViewLayersToChildWindows) {
TEST_F(NativeWidgetAuraTest,
ShouldDescendIntoChildForEventHandlingChecksVisibleBounds) {
// Create two widgets: |parent| and |child|. |child| is a child of |parent|.
- View* parent_root_view = new View;
Widget parent;
Widget::InitParams parent_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
parent_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
parent_params.context = root_window();
parent.Init(std::move(parent_params));
- parent.SetContentsView(parent_root_view);
+ View* parent_root_view = parent.SetContentsView(std::make_unique<View>());
parent.SetBounds(gfx::Rect(0, 0, 400, 400));
parent.Show();
diff --git a/chromium/ui/views/widget/root_view_unittest.cc b/chromium/ui/views/widget/root_view_unittest.cc
index a0640142341..6994e4864bf 100644
--- a/chromium/ui/views/widget/root_view_unittest.cc
+++ b/chromium/ui/views/widget/root_view_unittest.cc
@@ -56,8 +56,7 @@ TEST_F(RootViewTest, DeleteViewDuringKeyEventDispatch) {
bool got_key_event = false;
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
View* child = new DeleteOnKeyEventView(&got_key_event);
content->AddChildView(child);
@@ -413,8 +412,7 @@ TEST_F(RootViewTest, DeleteViewOnMouseExitDispatch) {
widget.Init(std::move(init_params));
widget.SetBounds(gfx::Rect(10, 10, 500, 500));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
bool view_destroyed = false;
View* child = new DeleteViewOnEvent(ui::ET_MOUSE_EXITED, &view_destroyed);
@@ -450,8 +448,7 @@ TEST_F(RootViewTest, DeleteViewOnMouseEnterDispatch) {
widget.Init(std::move(init_params));
widget.SetBounds(gfx::Rect(10, 10, 500, 500));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
bool view_destroyed = false;
View* child = new DeleteViewOnEvent(ui::ET_MOUSE_ENTERED, &view_destroyed);
@@ -489,8 +486,7 @@ TEST_F(RootViewTest, RemoveViewOnMouseEnterDispatch) {
widget.Init(std::move(init_params));
widget.SetBounds(gfx::Rect(10, 10, 500, 500));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
// |child| gets removed without being deleted, so make it a local
// to prevent test memory leak.
@@ -529,8 +525,7 @@ TEST_F(RootViewTest, ClearMouseMoveHandlerOnMouseExitDispatch) {
widget.Init(std::move(init_params));
widget.SetBounds(gfx::Rect(10, 10, 500, 500));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
View* root_view = widget.GetRootView();
@@ -565,8 +560,7 @@ TEST_F(RootViewTest,
widget.Init(std::move(init_params));
widget.SetBounds(gfx::Rect(10, 10, 500, 500));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
View* root_view = widget.GetRootView();
@@ -603,8 +597,7 @@ TEST_F(RootViewTest, ClearMouseMoveHandlerOnMouseEnterDispatch) {
widget.Init(std::move(init_params));
widget.SetBounds(gfx::Rect(10, 10, 500, 500));
- View* content = new View;
- widget.SetContentsView(content);
+ View* content = widget.SetContentsView(std::make_unique<View>());
View* root_view = widget.GetRootView();
@@ -657,10 +650,10 @@ TEST_F(RootViewTest, DeleteWidgetOnMouseExitDispatch) {
widget->SetBounds(gfx::Rect(10, 10, 500, 500));
WidgetDeletionObserver widget_deletion_observer(widget);
- View* content = new View();
+ auto content = std::make_unique<View>();
View* child = new DeleteWidgetOnMouseExit(widget);
content->AddChildView(child);
- widget->SetContentsView(content);
+ widget->SetContentsView(std::move(content));
// Make |child| smaller than the containing Widget and RootView.
child->SetBounds(100, 100, 100, 100);
@@ -693,10 +686,9 @@ TEST_F(RootViewTest, DeleteWidgetOnMouseExitDispatchFromChild) {
widget->SetBounds(gfx::Rect(10, 10, 500, 500));
WidgetDeletionObserver widget_deletion_observer(widget);
- View* content = new View();
View* child = new DeleteWidgetOnMouseExit(widget);
View* subchild = new View();
- widget->SetContentsView(content);
+ View* content = widget->SetContentsView(std::make_unique<View>());
content->AddChildView(child);
child->AddChildView(subchild);
diff --git a/chromium/ui/views/widget/unique_widget_ptr.cc b/chromium/ui/views/widget/unique_widget_ptr.cc
new file mode 100644
index 00000000000..558c9f35b23
--- /dev/null
+++ b/chromium/ui/views/widget/unique_widget_ptr.cc
@@ -0,0 +1,97 @@
+// 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/widget/unique_widget_ptr.h"
+
+#include <utility>
+
+#include "base/scoped_observer.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace views {
+
+namespace {
+
+struct WidgetAutoCloser {
+ void operator()(Widget* widget) {
+ widget->CloseWithReason(Widget::ClosedReason::kUnspecified);
+ }
+};
+
+using WidgetAutoClosePtr = std::unique_ptr<Widget, WidgetAutoCloser>;
+
+} // namespace
+
+class UniqueWidgetPtr::UniqueWidgetPtrImpl : public WidgetObserver {
+ public:
+ UniqueWidgetPtrImpl() = default;
+ // Deliberately implicit
+ // NOLINTNEXTLINE(runtime/explicit)
+ UniqueWidgetPtrImpl(std::unique_ptr<Widget> widget)
+ : widget_closer_(widget.release()) {
+ widget_observer_.Add(widget_closer_.get());
+ }
+
+ UniqueWidgetPtrImpl(const UniqueWidgetPtrImpl&) = delete;
+
+ UniqueWidgetPtrImpl& operator=(const UniqueWidgetPtrImpl&) = delete;
+
+ ~UniqueWidgetPtrImpl() override = default;
+
+ Widget* Get() const { return widget_closer_.get(); }
+
+ void Reset() {
+ if (!widget_closer_)
+ return;
+ widget_observer_.RemoveAll();
+ widget_closer_.reset();
+ }
+
+ // WidgetObserver overrides.
+ void OnWidgetDestroying(Widget* widget) override {
+ DCHECK_EQ(widget, widget_closer_.get());
+ widget_observer_.RemoveAll();
+ widget_closer_.release();
+ }
+
+ private:
+ ScopedObserver<Widget, WidgetObserver> widget_observer_{this};
+ WidgetAutoClosePtr widget_closer_;
+};
+
+UniqueWidgetPtr::UniqueWidgetPtr() = default;
+
+UniqueWidgetPtr::UniqueWidgetPtr(std::unique_ptr<Widget> widget)
+ : unique_widget_ptr_impl_(
+ std::make_unique<UniqueWidgetPtrImpl>(std::move(widget))) {}
+
+UniqueWidgetPtr::UniqueWidgetPtr(UniqueWidgetPtr&& other) = default;
+
+UniqueWidgetPtr& UniqueWidgetPtr::operator=(UniqueWidgetPtr&& other) = default;
+
+UniqueWidgetPtr::~UniqueWidgetPtr() = default;
+
+UniqueWidgetPtr::operator bool() const {
+ return !!get();
+}
+
+Widget& UniqueWidgetPtr::operator*() const {
+ return *get();
+}
+
+Widget* UniqueWidgetPtr::operator->() const {
+ return get();
+}
+
+void UniqueWidgetPtr::reset() {
+ unique_widget_ptr_impl_.reset();
+}
+
+Widget* UniqueWidgetPtr::get() const {
+ return unique_widget_ptr_impl_ ? unique_widget_ptr_impl_->Get() : nullptr;
+}
+
+} // namespace views
diff --git a/chromium/ui/views/widget/unique_widget_ptr.h b/chromium/ui/views/widget/unique_widget_ptr.h
new file mode 100644
index 00000000000..f493c367a73
--- /dev/null
+++ b/chromium/ui/views/widget/unique_widget_ptr.h
@@ -0,0 +1,43 @@
+// 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_WIDGET_UNIQUE_WIDGET_PTR_H_
+#define UI_VIEWS_WIDGET_UNIQUE_WIDGET_PTR_H_
+
+#include <memory>
+
+#include "ui/views/views_export.h"
+
+namespace views {
+
+class Widget;
+
+// Ensures the Widget is properly closed when this special
+// auto pointer goes out of scope.
+
+class VIEWS_EXPORT UniqueWidgetPtr {
+ public:
+ UniqueWidgetPtr();
+ // Deliberately implicit since it's supposed to resemble a std::unique_ptr.
+ // NOLINTNEXTLINE(runtime/explicit)
+ UniqueWidgetPtr(std::unique_ptr<Widget> widget);
+ UniqueWidgetPtr(UniqueWidgetPtr&&);
+ UniqueWidgetPtr& operator=(UniqueWidgetPtr&&);
+ ~UniqueWidgetPtr();
+
+ explicit operator bool() const;
+ Widget& operator*() const;
+ Widget* operator->() const;
+ void reset();
+ Widget* get() const;
+
+ private:
+ class UniqueWidgetPtrImpl;
+
+ std::unique_ptr<UniqueWidgetPtrImpl> unique_widget_ptr_impl_;
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_WIDGET_UNIQUE_WIDGET_PTR_H_
diff --git a/chromium/ui/views/widget/unique_widget_ptr_unittest.cc b/chromium/ui/views/widget/unique_widget_ptr_unittest.cc
new file mode 100644
index 00000000000..df8804ab1f9
--- /dev/null
+++ b/chromium/ui/views/widget/unique_widget_ptr_unittest.cc
@@ -0,0 +1,133 @@
+// 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/widget/unique_widget_ptr.h"
+
+#include <memory>
+
+#include "base/scoped_observer.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace views {
+
+class UniqueWidgetPtrTest : public ViewsTestBase, public WidgetObserver {
+ public:
+ UniqueWidgetPtrTest() = default;
+ ~UniqueWidgetPtrTest() override = default;
+
+ // ViewsTestBase overrides.
+ void TearDown() override {
+ ViewsTestBase::TearDown();
+ ASSERT_EQ(widget_, nullptr);
+ }
+
+ protected:
+ std::unique_ptr<Widget> AllocateTestWidget() override {
+ auto widget = ViewsTestBase::AllocateTestWidget();
+ widget->Init(CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS));
+ widget_observer_.Add(widget.get());
+ return widget;
+ }
+
+ UniqueWidgetPtr CreateUniqueWidgetPtr() {
+ auto widget = UniqueWidgetPtr(AllocateTestWidget());
+ widget->SetContentsView(std::make_unique<View>());
+ widget_ = widget.get();
+ return widget;
+ }
+
+ Widget* widget() { return widget_; }
+
+ // WidgetObserver overrides.
+ void OnWidgetDestroying(Widget* widget) override {
+ ASSERT_NE(widget_, nullptr);
+ ASSERT_EQ(widget_, widget);
+ widget_observer_.Remove(widget_);
+ widget_ = nullptr;
+ }
+
+ private:
+ Widget* widget_ = nullptr;
+ ScopedObserver<Widget, WidgetObserver> widget_observer_{this};
+};
+
+// Make sure explicitly resetting the |unique_widget_ptr| variable properly
+// closes the widget. TearDown() will ensure |widget_| has been cleared.
+TEST_F(UniqueWidgetPtrTest, TestCloseContent) {
+ UniqueWidgetPtr unique_widget_ptr = CreateUniqueWidgetPtr();
+ EXPECT_EQ(unique_widget_ptr->GetContentsView(), widget()->GetContentsView());
+ unique_widget_ptr.reset();
+}
+
+// Same as above, only testing that going out of scope will accomplish the same
+// thing.
+TEST_F(UniqueWidgetPtrTest, TestScopeDestruct) {
+ UniqueWidgetPtr unique_widget_ptr = CreateUniqueWidgetPtr();
+ EXPECT_EQ(unique_widget_ptr->GetContentsView(), widget()->GetContentsView());
+ // Just go out of scope to close the view;
+}
+
+// Check that proper move semantics for assignments work.
+TEST_F(UniqueWidgetPtrTest, TestMoveAssign) {
+ UniqueWidgetPtr unique_widget_ptr2 = CreateUniqueWidgetPtr();
+ {
+ UniqueWidgetPtr unique_widget_ptr;
+ EXPECT_EQ(unique_widget_ptr2->GetContentsView(),
+ widget()->GetContentsView());
+ unique_widget_ptr = std::move(unique_widget_ptr2);
+ EXPECT_EQ(unique_widget_ptr->GetContentsView(),
+ widget()->GetContentsView());
+ EXPECT_FALSE(unique_widget_ptr2);
+ unique_widget_ptr.reset();
+ EXPECT_FALSE(unique_widget_ptr);
+ }
+ RunPendingMessages();
+ EXPECT_EQ(widget(), nullptr);
+}
+
+// Check that move construction functions correctly.
+TEST_F(UniqueWidgetPtrTest, TestMoveConstruct) {
+ UniqueWidgetPtr unique_widget_ptr2 = CreateUniqueWidgetPtr();
+ {
+ EXPECT_EQ(unique_widget_ptr2->GetContentsView(),
+ widget()->GetContentsView());
+ UniqueWidgetPtr unique_widget_ptr = std::move(unique_widget_ptr2);
+ EXPECT_EQ(unique_widget_ptr->GetContentsView(),
+ widget()->GetContentsView());
+ EXPECT_FALSE(unique_widget_ptr2);
+ unique_widget_ptr.reset();
+ EXPECT_FALSE(unique_widget_ptr);
+ }
+ RunPendingMessages();
+ EXPECT_EQ(widget(), nullptr);
+}
+
+// Make sure that any external closing of the widget is properly tracked in the
+// |unique_widget_ptr|.
+TEST_F(UniqueWidgetPtrTest, TestCloseWidget) {
+ UniqueWidgetPtr unique_widget_ptr = CreateUniqueWidgetPtr();
+ EXPECT_EQ(unique_widget_ptr->GetContentsView(), widget()->GetContentsView());
+ // Initiate widget destruction.
+ widget()->CloseWithReason(Widget::ClosedReason::kUnspecified);
+ // Cycle the run loop to allow the deferred destruction to happen.
+ RunPendingMessages();
+ // The UniqueWidgetPtr should have dropped its reference to the content view.
+ EXPECT_FALSE(unique_widget_ptr);
+}
+
+// When the NativeWidget is destroyed, ensure that the Widget is also destroyed
+// which in turn clears the |unique_widget_ptr|.
+TEST_F(UniqueWidgetPtrTest, TestCloseNativeWidget) {
+ UniqueWidgetPtr unique_widget_ptr = CreateUniqueWidgetPtr();
+ EXPECT_EQ(unique_widget_ptr->GetContentsView(), widget()->GetContentsView());
+ // Initiate an OS level native widget destruction.
+ SimulateNativeDestroy(widget());
+ // The UniqueWidgetPtr should have dropped its reference to the content view.
+ EXPECT_FALSE(unique_widget_ptr);
+}
+
+} // namespace views
diff --git a/chromium/ui/views/widget/widget.cc b/chromium/ui/views/widget/widget.cc
index ecd7be9185f..a06ebe82c33 100644
--- a/chromium/ui/views/widget/widget.cc
+++ b/chromium/ui/views/widget/widget.cc
@@ -93,7 +93,7 @@ bool Widget::g_disable_activation_change_handling_ = false;
// WidgetDelegate is supplied.
class DefaultWidgetDelegate : public WidgetDelegate {
public:
- explicit DefaultWidgetDelegate(Widget* widget) : widget_(widget) {
+ DefaultWidgetDelegate() {
// In most situations where a Widget is used without a delegate the Widget
// is used as a container, so that we want focus to advance to the top-level
// widget. A good example of this is the find bar.
@@ -103,12 +103,8 @@ class DefaultWidgetDelegate : public WidgetDelegate {
// WidgetDelegate:
void DeleteDelegate() override { delete this; }
- Widget* GetWidget() override { return widget_; }
- const Widget* GetWidget() const override { return widget_; }
private:
- Widget* widget_;
-
DISALLOW_COPY_AND_ASSIGN(DefaultWidgetDelegate);
};
@@ -179,6 +175,8 @@ Widget::Widget(InitParams params) {
}
Widget::~Widget() {
+ if (widget_delegate_)
+ widget_delegate_->WidgetDestroying();
DestroyRootView();
if (ownership_ == InitParams::WIDGET_OWNS_NATIVE_WIDGET) {
delete native_widget_;
@@ -298,8 +296,7 @@ void Widget::Init(InitParams params) {
// ViewsDelegate::OnBeforeWidgetInit() may change `params.delegate` either
// by setting it to null or assigning a different value to it, so handle
// both cases.
- auto default_widget_delegate =
- std::make_unique<DefaultWidgetDelegate>(this);
+ auto default_widget_delegate = std::make_unique<DefaultWidgetDelegate>();
widget_delegate_ =
params.delegate ? params.delegate : default_widget_delegate.get();
@@ -322,6 +319,9 @@ void Widget::Init(InitParams params) {
// Henceforth, ensure the delegate outlives the Widget.
widget_delegate_->can_delete_this_ = false;
+ if (params.delegate)
+ params.delegate->WidgetInitializing(this);
+
ownership_ = params.ownership;
native_widget_ = CreateNativeWidget(params, this)->AsNativeWidgetPrivate();
root_view_.reset(CreateRootView());
@@ -377,7 +377,7 @@ void Widget::Init(InitParams params) {
native_widget_->OnWidgetInitDone();
if (delegate)
- delegate->OnWidgetInitialized();
+ delegate->WidgetInitialized();
internal::AnyWidgetObserverSingleton::GetInstance()->OnAnyWidgetInitialized(
this);
@@ -1162,6 +1162,8 @@ gfx::Size Widget::GetMaximumSize() const {
}
void Widget::OnNativeWidgetMove() {
+ TRACE_EVENT0("ui", "Widget::OnNativeWidgetMove");
+
widget_delegate_->OnWidgetMove();
NotifyCaretBoundsChanged(GetInputMethod());
@@ -1170,6 +1172,8 @@ void Widget::OnNativeWidgetMove() {
}
void Widget::OnNativeWidgetSizeChanged(const gfx::Size& new_size) {
+ TRACE_EVENT0("ui", "Widget::OnNativeWidgetSizeChanged");
+
View* root = GetRootView();
if (root)
root->SetSize(new_size);
@@ -1229,6 +1233,8 @@ void Widget::OnKeyEvent(ui::KeyEvent* event) {
// RootView from anywhere in Widget. Use
// SendEventToSink() instead. See crbug.com/348087.
void Widget::OnMouseEvent(ui::MouseEvent* event) {
+ TRACE_EVENT0("ui", "Widget::OnMouseEvent");
+
View* root_view = GetRootView();
switch (event->type()) {
case ui::ET_MOUSE_PRESSED: {
@@ -1472,6 +1478,8 @@ View* Widget::GetFocusTraversableParentView() {
// Widget, ui::NativeThemeObserver implementation:
void Widget::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) {
+ TRACE_EVENT0("ui", "Widget::OnNativeThemeUpdated");
+
DCHECK(observer_manager_.IsObserving(observed_theme));
#if defined(OS_MACOSX) || defined(OS_WIN)
diff --git a/chromium/ui/views/widget/widget_delegate.cc b/chromium/ui/views/widget/widget_delegate.cc
index 4ba80f860b3..6916ad1c393 100644
--- a/chromium/ui/views/widget/widget_delegate.cc
+++ b/chromium/ui/views/widget/widget_delegate.cc
@@ -7,6 +7,7 @@
#include "base/check.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/image/image_skia.h"
@@ -46,7 +47,7 @@ View* WidgetDelegate::GetInitiallyFocusedView() {
return nullptr;
}
-BubbleDialogDelegateView* WidgetDelegate::AsBubbleDialogDelegate() {
+BubbleDialogDelegate* WidgetDelegate::AsBubbleDialogDelegate() {
return nullptr;
}
@@ -75,11 +76,12 @@ ui::ModalType WidgetDelegate::GetModalType() const {
}
ax::mojom::Role WidgetDelegate::GetAccessibleWindowRole() {
- return ax::mojom::Role::kWindow;
+ return params_.accessible_role;
}
base::string16 WidgetDelegate::GetAccessibleWindowTitle() const {
- return GetWindowTitle();
+ return params_.accessible_title.empty() ? GetWindowTitle()
+ : params_.accessible_title;
}
base::string16 WidgetDelegate::GetWindowTitle() const {
@@ -152,6 +154,19 @@ bool WidgetDelegate::ShouldRestoreWindowSize() const {
return true;
}
+void WidgetDelegate::WidgetInitializing(Widget* widget) {
+ widget_ = widget;
+ OnWidgetInitializing();
+}
+
+void WidgetDelegate::WidgetInitialized() {
+ OnWidgetInitialized();
+}
+
+void WidgetDelegate::WidgetDestroying() {
+ widget_ = nullptr;
+}
+
void WidgetDelegate::WindowWillClose() {
// TODO(ellyjones): For this and the other callback methods, establish whether
// any other code calls these methods. If not, DCHECK here and below that
@@ -170,6 +185,14 @@ void WidgetDelegate::DeleteDelegate() {
std::move(callback).Run();
}
+Widget* WidgetDelegate::GetWidget() {
+ return widget_;
+}
+
+const Widget* WidgetDelegate::GetWidget() const {
+ return widget_;
+}
+
View* WidgetDelegate::GetContentsView() {
if (!default_contents_view_)
default_contents_view_ = new View;
@@ -206,6 +229,14 @@ bool WidgetDelegate::ShouldDescendIntoChildForEventHandling(
return true;
}
+void WidgetDelegate::SetAccessibleRole(ax::mojom::Role role) {
+ params_.accessible_role = role;
+}
+
+void WidgetDelegate::SetAccessibleTitle(base::string16 title) {
+ params_.accessible_title = std::move(title);
+}
+
void WidgetDelegate::SetCanMaximize(bool can_maximize) {
params_.can_maximize = can_maximize;
}
@@ -246,12 +277,22 @@ void WidgetDelegate::SetTitle(const base::string16& title) {
GetWidget()->UpdateWindowTitle();
}
+void WidgetDelegate::SetTitle(int title_message_id) {
+ SetTitle(l10n_util::GetStringUTF16(title_message_id));
+}
+
#if defined(USE_AURA)
void WidgetDelegate::SetCenterTitle(bool center_title) {
params_.center_title = center_title;
}
#endif
+void WidgetDelegate::SetHasWindowSizeControls(bool has_controls) {
+ SetCanMaximize(has_controls);
+ SetCanMinimize(has_controls);
+ SetCanResize(has_controls);
+}
+
void WidgetDelegate::RegisterWindowWillCloseCallback(
base::OnceClosure callback) {
window_will_close_callbacks_.emplace_back(std::move(callback));
diff --git a/chromium/ui/views/widget/widget_delegate.h b/chromium/ui/views/widget/widget_delegate.h
index 23396ad8c3a..6b4ef19ee72 100644
--- a/chromium/ui/views/widget/widget_delegate.h
+++ b/chromium/ui/views/widget/widget_delegate.h
@@ -9,7 +9,7 @@
#include <vector>
#include "base/macros.h"
-#include "ui/accessibility/ax_enums.mojom-forward.h"
+#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/ui_base_types.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
@@ -20,7 +20,7 @@ class Rect;
} // namespace gfx
namespace views {
-class BubbleDialogDelegateView;
+class BubbleDialogDelegate;
class ClientView;
class DialogDelegate;
class NonClientFrameView;
@@ -33,6 +33,17 @@ class VIEWS_EXPORT WidgetDelegate {
Params();
~Params();
+ // The window's role. Useful values include kWindow (a plain window),
+ // kDialog (a dialog), and kAlertDialog (a high-priority dialog whose body
+ // is read when it appears). Using a role outside this set is not likely to
+ // work across platforms.
+ ax::mojom::Role accessible_role = ax::mojom::Role::kWindow;
+
+ // The accessible title for the window, often more descriptive than the
+ // plain title. If no accessible title is present the result of
+ // GetWindowTitle() will be used.
+ base::string16 accessible_title;
+
// Whether the window should display controls for the user to minimize,
// maximize, or resize it.
bool can_maximize = false;
@@ -68,6 +79,7 @@ class VIEWS_EXPORT WidgetDelegate {
};
WidgetDelegate();
+ virtual ~WidgetDelegate();
// Sets the return value of CanActivate(). Default is true.
void SetCanActivate(bool can_activate);
@@ -82,6 +94,10 @@ class VIEWS_EXPORT WidgetDelegate {
// menu bars, etc.) changes in size.
virtual void OnWorkAreaChanged();
+ // Called when the widget's initialization is beginning, right after the
+ // ViewsDelegate decides to use this WidgetDelegate for a Widget.
+ virtual void OnWidgetInitializing() {}
+
// Called when the widget's initialization is complete.
virtual void OnWidgetInitialized() {}
@@ -98,7 +114,7 @@ class VIEWS_EXPORT WidgetDelegate {
// NULL no view is focused.
virtual View* GetInitiallyFocusedView();
- virtual BubbleDialogDelegateView* AsBubbleDialogDelegate();
+ virtual BubbleDialogDelegate* AsBubbleDialogDelegate();
virtual DialogDelegate* AsDialogDelegate();
// Returns true if the window can be resized.
@@ -193,10 +209,28 @@ class VIEWS_EXPORT WidgetDelegate {
virtual void OnWindowEndUserBoundsChange() {}
// Returns the Widget associated with this delegate.
- virtual Widget* GetWidget() = 0;
- virtual const Widget* GetWidget() const = 0;
+ virtual Widget* GetWidget();
+ virtual const Widget* GetWidget() const;
- // Returns the View that is contained within this Widget.
+ // Get the view that is contained within this widget.
+ //
+ // WARNING: This method has unusual ownership behavior:
+ // * If the returned view is owned_by_client(), then the returned pointer is
+ // never an owning pointer;
+ // * If the returned view is !owned_by_client() (the default & the
+ // recommendation), then the returned pointer is *sometimes* an owning
+ // pointer and sometimes not. Specifically, it is an owning pointer exactly
+ // once, when this method is being used to construct the ClientView, which
+ // takes ownership of the ContentsView() when !owned_by_client().
+ //
+ // Apart from being difficult to reason about this introduces a problem: a
+ // WidgetDelegate can't know whether it owns its contents view or not, so
+ // constructing a WidgetDelegate which one does not then use to construct a
+ // Widget (often done in tests) leaks memory in a way that can't be locally
+ // fixed.
+ //
+ // TODO(ellyjones): This is not tenable - figure out how this should work and
+ // replace it.
virtual View* GetContentsView();
// Called by the Widget to create the Client View used to host the contents
@@ -237,6 +271,8 @@ class VIEWS_EXPORT WidgetDelegate {
// Setters for data parameters of the WidgetDelegate. If you use these
// setters, there is no need to override the corresponding virtual getters.
+ void SetAccessibleRole(ax::mojom::Role role);
+ void SetAccessibleTitle(base::string16 title);
void SetCanMaximize(bool can_maximize);
void SetCanMinimize(bool can_minimize);
void SetCanResize(bool can_resize);
@@ -246,16 +282,24 @@ class VIEWS_EXPORT WidgetDelegate {
void SetShowIcon(bool show_icon);
void SetShowTitle(bool show_title);
void SetTitle(const base::string16& title);
+ void SetTitle(int title_message_id);
#if defined(USE_AURA)
void SetCenterTitle(bool center_title);
#endif
+ // A convenience wrapper that does all three of SetCanMaximize,
+ // SetCanMinimize, and SetCanResize.
+ void SetHasWindowSizeControls(bool has_controls);
+
void RegisterWindowWillCloseCallback(base::OnceClosure callback);
void RegisterWindowClosingCallback(base::OnceClosure callback);
void RegisterDeleteDelegateCallback(base::OnceClosure callback);
- // Call this to notify the WidgetDelegate that its Widget is about to start
- // closing.
+ // Called to notify the WidgetDelegate of changes to the state of its Widget.
+ // It is not usually necessary to call these from client code.
+ void WidgetInitializing(Widget* widget);
+ void WidgetInitialized();
+ void WidgetDestroying();
void WindowWillClose();
// Returns true if the title text should be centered.
@@ -263,12 +307,12 @@ class VIEWS_EXPORT WidgetDelegate {
bool focus_traverses_out() const { return params_.focus_traverses_out; }
- protected:
- virtual ~WidgetDelegate();
-
private:
friend class Widget;
+ // The Widget that was initialized with this instance as its WidgetDelegate,
+ // if any.
+ Widget* widget_ = nullptr;
Params params_;
View* default_contents_view_ = nullptr;
@@ -299,7 +343,7 @@ class VIEWS_EXPORT WidgetDelegateView : public WidgetDelegate, public View {
void DeleteDelegate() override;
Widget* GetWidget() override;
const Widget* GetWidget() const override;
- views::View* GetContentsView() override;
+ View* GetContentsView() override;
private:
DISALLOW_COPY_AND_ASSIGN(WidgetDelegateView);
diff --git a/chromium/ui/views/widget/widget_deletion_observer.cc b/chromium/ui/views/widget/widget_deletion_observer.cc
index 15df9c391ce..3e64f1613b6 100644
--- a/chromium/ui/views/widget/widget_deletion_observer.cc
+++ b/chromium/ui/views/widget/widget_deletion_observer.cc
@@ -16,6 +16,7 @@ WidgetDeletionObserver::WidgetDeletionObserver(Widget* widget)
WidgetDeletionObserver::~WidgetDeletionObserver() {
CleanupWidget();
+ CHECK(!IsInObserverList());
}
void WidgetDeletionObserver::OnWidgetDestroying(Widget* widget) {
diff --git a/chromium/ui/views/widget/widget_hwnd_utils.cc b/chromium/ui/views/widget/widget_hwnd_utils.cc
index 40e66a212e3..89f5b62f95d 100644
--- a/chromium/ui/views/widget/widget_hwnd_utils.cc
+++ b/chromium/ui/views/widget/widget_hwnd_utils.cc
@@ -118,6 +118,8 @@ void CalculateWindowStylesFromInitParams(
else
*style |= WS_BORDER;
}
+ if (!params.force_show_in_taskbar)
+ *ex_style |= WS_EX_TOOLWINDOW;
break;
case Widget::InitParams::TYPE_TOOLTIP:
*style |= WS_POPUP;
diff --git a/chromium/ui/views/widget/widget_interactive_uitest.cc b/chromium/ui/views/widget/widget_interactive_uitest.cc
index f4ba5ba6cad..a333ca057ae 100644
--- a/chromium/ui/views/widget/widget_interactive_uitest.cc
+++ b/chromium/ui/views/widget/widget_interactive_uitest.cc
@@ -4,6 +4,7 @@
#include <stddef.h>
+#include <memory>
#include <utility>
#include "base/bind.h"
@@ -603,7 +604,7 @@ TEST_F(WidgetTestInteractive, ChildStackedRelativeToParent) {
// Test view focus retention when a widget's HWND is disabled and re-enabled.
TEST_F(WidgetTestInteractive, ViewFocusOnHWNDEnabledChanges) {
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
- widget->SetContentsView(new View);
+ widget->SetContentsView(std::make_unique<View>());
for (size_t i = 0; i < 2; ++i) {
auto child = std::make_unique<View>();
child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
@@ -1456,10 +1457,10 @@ TEST_F(WidgetCaptureTest, FailedCaptureRequestIsNoop) {
MouseView* mouse_view1 = new MouseView;
MouseView* mouse_view2 = new MouseView;
- View* contents_view = new View;
+ auto contents_view = std::make_unique<View>();
contents_view->AddChildView(mouse_view1);
contents_view->AddChildView(mouse_view2);
- widget.SetContentsView(contents_view);
+ widget.SetContentsView(std::move(contents_view));
mouse_view1->SetBounds(0, 0, 200, 400);
mouse_view2->SetBounds(200, 0, 200, 400);
@@ -1480,8 +1481,7 @@ TEST_F(WidgetCaptureTest, FailedCaptureRequestIsNoop) {
TEST_F(WidgetCaptureTest, CaptureAutoReset) {
WidgetAutoclosePtr toplevel(CreateTopLevelFramelessPlatformWidget());
- View* container = new View;
- toplevel->SetContentsView(container);
+ toplevel->SetContentsView(std::make_unique<View>());
EXPECT_FALSE(toplevel->HasCapture());
toplevel->SetCapture(nullptr);
@@ -1507,8 +1507,7 @@ TEST_F(WidgetCaptureTest, CaptureAutoReset) {
TEST_F(WidgetCaptureTest, ResetCaptureOnGestureEnd) {
WidgetAutoclosePtr toplevel(CreateTopLevelFramelessPlatformWidget());
- View* container = new View;
- toplevel->SetContentsView(container);
+ View* container = toplevel->SetContentsView(std::make_unique<View>());
View* gesture = new GestureCaptureView;
gesture->SetBounds(0, 0, 30, 30);
@@ -1570,10 +1569,11 @@ TEST_F(WidgetCaptureTest, DisableCaptureWidgetFromMousePress) {
WidgetAutoclosePtr first(CreateTopLevelFramelessPlatformWidget());
Widget* second = CreateTopLevelFramelessPlatformWidget();
- NestedLoopCaptureView* container = new NestedLoopCaptureView(second);
- first->SetContentsView(container);
+ NestedLoopCaptureView* container =
+ first->SetContentsView(std::make_unique<NestedLoopCaptureView>(second));
- second->SetContentsView(new ExitLoopOnRelease(container->GetQuitClosure()));
+ second->SetContentsView(
+ std::make_unique<ExitLoopOnRelease>(container->GetQuitClosure()));
first->SetSize(gfx::Size(100, 100));
first->Show();
@@ -1597,21 +1597,21 @@ TEST_F(WidgetCaptureTest, DisableCaptureWidgetFromMousePress) {
// time.
TEST_F(WidgetCaptureTest, GrabUngrab) {
auto top_level = CreateTestWidget();
- top_level->SetContentsView(new MouseView());
+ top_level->SetContentsView(std::make_unique<MouseView>());
Widget* child1 = new Widget;
Widget::InitParams params1 = CreateParams(Widget::InitParams::TYPE_CONTROL);
params1.parent = top_level->GetNativeView();
params1.bounds = gfx::Rect(10, 10, 100, 100);
child1->Init(std::move(params1));
- child1->SetContentsView(new MouseView());
+ child1->SetContentsView(std::make_unique<MouseView>());
Widget* child2 = new Widget;
Widget::InitParams params2 = CreateParams(Widget::InitParams::TYPE_CONTROL);
params2.parent = top_level->GetNativeView();
params2.bounds = gfx::Rect(110, 10, 100, 100);
child2->Init(std::move(params2));
- child2->SetContentsView(new MouseView());
+ child2->SetContentsView(std::make_unique<MouseView>());
top_level->Show();
RunPendingMessages();
@@ -1732,8 +1732,8 @@ TEST_F(WidgetCaptureTest, MAYBE_MouseExitOnCaptureGrab) {
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params1.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget1.Init(std::move(params1));
- MouseView* mouse_view1 = new MouseView;
- widget1.SetContentsView(mouse_view1);
+ MouseView* mouse_view1 =
+ widget1.SetContentsView(std::make_unique<MouseView>());
widget1.Show();
widget1.SetBounds(gfx::Rect(300, 300));
diff --git a/chromium/ui/views/widget/window_reorderer_unittest.cc b/chromium/ui/views/widget/window_reorderer_unittest.cc
index 670cc0f1238..c46fda38a8e 100644
--- a/chromium/ui/views/widget/window_reorderer_unittest.cc
+++ b/chromium/ui/views/widget/window_reorderer_unittest.cc
@@ -59,8 +59,7 @@ TEST_F(WindowReordererTest, Basic) {
parent->Show();
aura::Window* parent_window = parent->GetNativeWindow();
- View* contents_view = new View();
- parent->SetContentsView(contents_view);
+ View* contents_view = parent->SetContentsView(std::make_unique<View>());
// 1) Test that layers for views and layers for windows associated to a host
// view are stacked below the layers for any windows not associated to a host
@@ -130,8 +129,7 @@ TEST_F(WindowReordererTest, Association) {
parent->Show();
aura::Window* parent_window = parent->GetNativeWindow();
- View* contents_view = new View();
- parent->SetContentsView(contents_view);
+ View* contents_view = parent->SetContentsView(std::make_unique<View>());
aura::Window* w1 =
aura::test::CreateTestWindowWithId(0, parent->GetNativeWindow());
@@ -185,8 +183,7 @@ TEST_F(WindowReordererTest, HostViewParentHasLayer) {
parent->Show();
aura::Window* parent_window = parent->GetNativeWindow();
- View* contents_view = new View();
- parent->SetContentsView(contents_view);
+ View* contents_view = parent->SetContentsView(std::make_unique<View>());
// Create the following view hierarchy. (*) denotes views which paint to a
// layer.
@@ -251,8 +248,7 @@ TEST_F(WindowReordererTest, ViewWithLayerBeneath) {
aura::Window* parent_window = parent->GetNativeWindow();
- View* contents_view = new View;
- parent->SetContentsView(contents_view);
+ View* contents_view = parent->SetContentsView(std::make_unique<View>());
View* view_with_layer_beneath =
contents_view->AddChildView(std::make_unique<View>());
diff --git a/chromium/ui/views/win/hwnd_message_handler.cc b/chromium/ui/views/win/hwnd_message_handler.cc
index c981cfc6b0a..db4fe784c7e 100644
--- a/chromium/ui/views/win/hwnd_message_handler.cc
+++ b/chromium/ui/views/win/hwnd_message_handler.cc
@@ -421,6 +421,7 @@ HWNDMessageHandler::HWNDMessageHandler(HWNDMessageHandlerDelegate* delegate,
dwm_transition_desired_(false),
dwm_composition_enabled_(ui::win::IsDwmCompositionEnabled()),
sent_window_size_changing_(false),
+ did_return_uia_object_(false),
left_button_down_on_caption_(false),
background_fullscreen_hack_(false),
pointer_events_for_touch_(::features::IsUsingWMPointerForTouch()) {}
@@ -987,6 +988,8 @@ HICON HWNDMessageHandler::GetSmallWindowIcon() const {
LRESULT HWNDMessageHandler::OnWndProc(UINT message,
WPARAM w_param,
LPARAM l_param) {
+ TRACE_EVENT1("ui", "HWNDMessageHandler::OnWndProc", "message_id", message);
+
HWND window = hwnd();
LRESULT result = 0;
if (delegate_ && delegate_->PreHandleMSG(message, w_param, l_param, &result))
@@ -1647,15 +1650,17 @@ void HWNDMessageHandler::OnDestroy() {
if (i != map.end())
map.erase(i);
- if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled()) {
- // Signal to UIA that all objects associated with this HWND can be
- // discarded.
+ // If we have ever returned a UIA object via WM_GETOBJECT, signal that all
+ // objects associated with this HWND can be discarded. See:
+ // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcoreapi/nf-uiautomationcoreapi-uiareturnrawelementprovider#remarks
+ if (did_return_uia_object_)
UiaReturnRawElementProvider(hwnd(), 0, 0, nullptr);
- }
}
void HWNDMessageHandler::OnDisplayChange(UINT bits_per_pixel,
const gfx::Size& screen_size) {
+ TRACE_EVENT0("ui", "HWNDMessageHandler::OnDisplayChange");
+
delegate_->HandleDisplayChange();
// Force a WM_NCCALCSIZE to occur to ensure that we handle auto hide
// taskbars correctly.
@@ -1663,8 +1668,10 @@ void HWNDMessageHandler::OnDisplayChange(UINT bits_per_pixel,
}
LRESULT HWNDMessageHandler::OnDwmCompositionChanged(UINT msg,
- WPARAM w_param,
- LPARAM l_param) {
+ WPARAM /* w_param */,
+ LPARAM /* l_param */) {
+ TRACE_EVENT0("ui", "HWNDMessageHandler::OnDwmCompositionChanged");
+
if (!delegate_->HasNonClientView()) {
SetMsgHandled(FALSE);
return 0;
@@ -1689,6 +1696,9 @@ LRESULT HWNDMessageHandler::OnDpiChanged(UINT msg,
if (LOWORD(w_param) != HIWORD(w_param))
NOTIMPLEMENTED() << "Received non-square scaling factors";
+ TRACE_EVENT1("ui", "HWNDMessageHandler::OnDwmCompositionChanged", "dpi",
+ LOWORD(w_param));
+
int dpi;
float scaling_factor;
if (display::Display::HasForceDeviceScaleFactor()) {
@@ -1811,6 +1821,10 @@ LRESULT HWNDMessageHandler::OnGetObject(UINT message,
Microsoft::WRL::ComPtr<IRawElementProviderSimple> root;
ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
IID_PPV_ARGS(&root));
+
+ // Return the UIA object via UiaReturnRawElementProvider(). See:
+ // https://docs.microsoft.com/en-us/windows/win32/winauto/wm-getobject
+ did_return_uia_object_ = true;
reference_result =
UiaReturnRawElementProvider(hwnd(), w_param, l_param, root.Get());
} else if (is_msaa_request) {
@@ -1979,12 +1993,27 @@ LRESULT HWNDMessageHandler::OnPointerEvent(UINT message,
return -1;
}
+ // |HandlePointerEventTypePenClient| assumes all pen events happen on the
+ // client area, so WM_NCPOINTER messages sent to it would eventually be
+ // dropped and the native frame wouldn't be able to respond to pens.
+ // |HandlePointerEventTypeTouchOrNonClient| handles non-client area messages
+ // properly. Since we don't need to distinguish between pens and fingers in
+ // non-client area, route the messages to that method.
+ if (pointer_type == PT_PEN &&
+ (message == WM_NCPOINTERDOWN ||
+ message == WM_NCPOINTERUP ||
+ message == WM_NCPOINTERUPDATE)) {
+ pointer_type = PT_TOUCH;
+ }
+
switch (pointer_type) {
case PT_PEN:
- return HandlePointerEventTypePen(message, w_param, l_param);
+ return HandlePointerEventTypePenClient(message, w_param, l_param);
case PT_TOUCH:
- if (pointer_events_for_touch_)
- return HandlePointerEventTypeTouch(message, w_param, l_param);
+ if (pointer_events_for_touch_) {
+ return HandlePointerEventTypeTouchOrNonClient(
+ message, w_param, l_param);
+ }
FALLTHROUGH;
default:
break;
@@ -2390,23 +2419,28 @@ void HWNDMessageHandler::OnPaint(HDC dc) {
// flicker opaque black. http://crbug.com/586454
FillRect(ps.hdc, &ps.rcPaint, brush);
- } else if (exposed_pixels_.height() > 0 || exposed_pixels_.width() > 0) {
+ } else if (exposed_pixels_ != gfx::Size()) {
// Fill in newly exposed window client area with black to ensure Windows
// doesn't put something else there (eg. copying existing pixels). This
// isn't needed if we've just cleared the whole client area outside the
// child window above.
RECT cr;
if (GetClientRect(hwnd(), &cr)) {
+ // GetClientRect() always returns a rect with top/left at 0.
+ const gfx::Size client_area = gfx::Rect(cr).size();
+
+ // It's possible that |exposed_pixels_| height and/or width is larger
+ // than the client area if the window frame size changed. This isn't an
+ // issue since FillRect() is clipped by |ps.rcPaint|.
if (exposed_pixels_.height() > 0) {
- DCHECK_GE(cr.bottom, exposed_pixels_.height());
- RECT rect = {cr.left, cr.bottom - exposed_pixels_.height(), cr.right,
- cr.bottom};
+ RECT rect = {0, client_area.height() - exposed_pixels_.height(),
+ client_area.width(), client_area.height()};
FillRect(ps.hdc, &rect, brush);
}
if (exposed_pixels_.width() > 0) {
- DCHECK_GE(cr.right, exposed_pixels_.width());
- RECT rect = {cr.right - exposed_pixels_.width(), cr.top, cr.right,
- cr.bottom - exposed_pixels_.height()};
+ RECT rect = {client_area.width() - exposed_pixels_.width(), 0,
+ client_area.width(),
+ client_area.height() - exposed_pixels_.height()};
FillRect(ps.hdc, &rect, brush);
}
}
@@ -2721,6 +2755,8 @@ LRESULT HWNDMessageHandler::OnTouchEvent(UINT message,
}
void HWNDMessageHandler::OnWindowPosChanging(WINDOWPOS* window_pos) {
+ TRACE_EVENT0("ui", "HWNDMessageHandler::OnWindowPosChanging");
+
if (ignore_window_pos_changes_) {
// If somebody's trying to toggle our visibility, change the nonclient area,
// change our Z-order, or activate us, we should probably let it go through.
@@ -2872,6 +2908,8 @@ void HWNDMessageHandler::OnWindowPosChanging(WINDOWPOS* window_pos) {
}
void HWNDMessageHandler::OnWindowPosChanged(WINDOWPOS* window_pos) {
+ TRACE_EVENT0("ui", "HWNDMessageHandler::OnWindowPosChanged");
+
if (DidClientAreaSizeChange(window_pos))
ClientAreaSizeChanged();
if (window_pos->flags & SWP_FRAMECHANGED)
@@ -3084,9 +3122,8 @@ LRESULT HWNDMessageHandler::HandleMouseEventInternal(UINT message,
return 0;
}
-LRESULT HWNDMessageHandler::HandlePointerEventTypeTouch(UINT message,
- WPARAM w_param,
- LPARAM l_param) {
+LRESULT HWNDMessageHandler::HandlePointerEventTypeTouchOrNonClient(
+ UINT message, WPARAM w_param, LPARAM l_param) {
UINT32 pointer_id = GET_POINTERID_WPARAM(w_param);
using GetPointerTouchInfoFn = BOOL(WINAPI*)(UINT32, POINTER_TOUCH_INFO*);
POINTER_TOUCH_INFO pointer_touch_info;
@@ -3194,9 +3231,9 @@ LRESULT HWNDMessageHandler::HandlePointerEventTypeTouch(UINT message,
return 0;
}
-LRESULT HWNDMessageHandler::HandlePointerEventTypePen(UINT message,
- WPARAM w_param,
- LPARAM l_param) {
+LRESULT HWNDMessageHandler::HandlePointerEventTypePenClient(UINT message,
+ WPARAM w_param,
+ LPARAM l_param) {
UINT32 pointer_id = GET_POINTERID_WPARAM(w_param);
using GetPointerPenInfoFn = BOOL(WINAPI*)(UINT32, POINTER_PEN_INFO*);
POINTER_PEN_INFO pointer_pen_info;
@@ -3296,6 +3333,8 @@ void HWNDMessageHandler::PerformDwmTransition() {
}
void HWNDMessageHandler::UpdateDwmFrame() {
+ TRACE_EVENT0("ui", "HWNDMessageHandler::UpdateDwmFrame");
+
gfx::Insets insets;
if (ui::win::IsAeroGlassEnabled() &&
delegate_->GetDwmFrameInsetsInPixels(&insets)) {
diff --git a/chromium/ui/views/win/hwnd_message_handler.h b/chromium/ui/views/win/hwnd_message_handler.h
index a84076658b4..7118ff56f0d 100644
--- a/chromium/ui/views/win/hwnd_message_handler.h
+++ b/chromium/ui/views/win/hwnd_message_handler.h
@@ -543,13 +543,17 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl,
LPARAM l_param,
bool track_mouse);
- LRESULT HandlePointerEventTypeTouch(UINT message,
- WPARAM w_param,
- LPARAM l_param);
-
- LRESULT HandlePointerEventTypePen(UINT message,
- WPARAM w_param,
- LPARAM l_param);
+ // We handle 2 kinds of WM_POINTER events: PT_TOUCH and PT_PEN. This helper
+ // handles client area events of PT_TOUCH, and non-client area events of both
+ // kinds.
+ LRESULT HandlePointerEventTypeTouchOrNonClient(UINT message,
+ WPARAM w_param,
+ LPARAM l_param);
+
+ // Helper to handle client area events of PT_PEN.
+ LRESULT HandlePointerEventTypePenClient(UINT message,
+ WPARAM w_param,
+ LPARAM l_param);
// Returns true if the mouse message passed in is an OS synthesized mouse
// message.
@@ -742,9 +746,15 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl,
// Some assistive software need to track the location of the caret.
std::unique_ptr<ui::AXSystemCaretWin> ax_system_caret_;
- // Implements IRawElementProviderFragmentRoot when UIA is enabled
+ // Implements IRawElementProviderFragmentRoot when UIA is enabled.
std::unique_ptr<ui::AXFragmentRootWin> ax_fragment_root_;
+ // Set to true when we return a UIA object. Determines whether we need to
+ // call UIA to clean up object references on window destruction.
+ // This is important to avoid triggering a cross-thread COM call which could
+ // cause re-entrancy during teardown. https://crbug.com/1087553
+ bool did_return_uia_object_;
+
// The location where the user clicked on the caption. We cache this when we
// receive the WM_NCLBUTTONDOWN message. We use this in the subsequent
// WM_NCMOUSEMOVE message to see if the mouse actually moved.
diff --git a/chromium/ui/views/window/custom_frame_view_unittest.cc b/chromium/ui/views/window/custom_frame_view_unittest.cc
index d04752c1b4d..37ed1a60201 100644
--- a/chromium/ui/views/window/custom_frame_view_unittest.cc
+++ b/chromium/ui/views/window/custom_frame_view_unittest.cc
@@ -17,33 +17,6 @@
namespace views {
-namespace {
-
-// Allows for the control of whether or not the widget can minimize/maximize or
-// not. This can be set after initial setup in order to allow testing of both
-// forms of delegates. By default this can minimize and maximize.
-class MinimizeAndMaximizeStateControlDelegate : public WidgetDelegateView {
- public:
- MinimizeAndMaximizeStateControlDelegate() = default;
- ~MinimizeAndMaximizeStateControlDelegate() override = default;
-
- void set_can_maximize(bool can_maximize) { can_maximize_ = can_maximize; }
-
- void set_can_minimize(bool can_minimize) { can_minimize_ = can_minimize; }
-
- // WidgetDelegate:
- bool CanMaximize() const override { return can_maximize_; }
- bool CanMinimize() const override { return can_minimize_; }
-
- private:
- bool can_maximize_ = true;
- bool can_minimize_ = true;
-
- DISALLOW_COPY_AND_ASSIGN(MinimizeAndMaximizeStateControlDelegate);
-};
-
-} // namespace
-
class CustomFrameViewTest : public ViewsTestBase {
public:
CustomFrameViewTest() = default;
@@ -51,11 +24,6 @@ class CustomFrameViewTest : public ViewsTestBase {
CustomFrameView* custom_frame_view() { return custom_frame_view_; }
- MinimizeAndMaximizeStateControlDelegate*
- minimize_and_maximize_state_control_delegate() {
- return minimize_and_maximize_state_control_delegate_;
- }
-
Widget* widget() { return widget_; }
// ViewsTestBase:
@@ -90,27 +58,26 @@ class CustomFrameViewTest : public ViewsTestBase {
const std::vector<views::FrameButton> trailing_buttons);
private:
+ std::unique_ptr<WidgetDelegate> widget_delegate_;
+
// Parent container for |custom_frame_view_|
Widget* widget_;
// Owned by |widget_|
CustomFrameView* custom_frame_view_;
- // Delegate of |widget_| which controls minimizing and maximizing
- MinimizeAndMaximizeStateControlDelegate*
- minimize_and_maximize_state_control_delegate_;
-
DISALLOW_COPY_AND_ASSIGN(CustomFrameViewTest);
};
void CustomFrameViewTest::SetUp() {
ViewsTestBase::SetUp();
- minimize_and_maximize_state_control_delegate_ =
- new MinimizeAndMaximizeStateControlDelegate;
widget_ = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
- params.delegate = minimize_and_maximize_state_control_delegate_;
+ widget_delegate_ = std::make_unique<WidgetDelegate>();
+ params.delegate = widget_delegate_.get();
+ params.delegate->SetCanMaximize(true);
+ params.delegate->SetCanMinimize(true);
params.remove_standard_frame = true;
widget_->Init(std::move(params));
@@ -214,9 +181,7 @@ TEST_F(CustomFrameViewTest, MaximizeRevealsRestoreButton) {
TEST_F(CustomFrameViewTest, CannotMaximizeHidesButton) {
Widget* parent = widget();
CustomFrameView* view = custom_frame_view();
- MinimizeAndMaximizeStateControlDelegate* delegate =
- minimize_and_maximize_state_control_delegate();
- delegate->set_can_maximize(false);
+ widget()->widget_delegate()->SetCanMaximize(false);
view->Init(parent);
parent->SetBounds(gfx::Rect(0, 0, 300, 100));
@@ -231,9 +196,7 @@ TEST_F(CustomFrameViewTest, CannotMaximizeHidesButton) {
TEST_F(CustomFrameViewTest, CannotMinimizeHidesButton) {
Widget* parent = widget();
CustomFrameView* view = custom_frame_view();
- MinimizeAndMaximizeStateControlDelegate* delegate =
- minimize_and_maximize_state_control_delegate();
- delegate->set_can_minimize(false);
+ widget()->widget_delegate()->SetCanMinimize(false);
view->Init(parent);
parent->SetBounds(gfx::Rect(0, 0, 300, 100));
diff --git a/chromium/ui/views/window/dialog_delegate.cc b/chromium/ui/views/window/dialog_delegate.cc
index 6afc661f5a2..21fea394e44 100644
--- a/chromium/ui/views/window/dialog_delegate.cc
+++ b/chromium/ui/views/window/dialog_delegate.cc
@@ -162,6 +162,9 @@ void DialogDelegate::RunCloseCallback(base::OnceClosure callback) {
}
View* DialogDelegate::GetInitiallyFocusedView() {
+ if (params_.initially_focused_view.has_value())
+ return *params_.initially_focused_view;
+
// Focus the default button if any.
const DialogClientView* dcv = GetDialogClientView();
if (!dcv)
@@ -370,6 +373,10 @@ void DialogDelegate::SetCloseCallback(base::OnceClosure callback) {
close_callback_ = std::move(callback);
}
+void DialogDelegate::SetInitiallyFocusedView(View* view) {
+ params_.initially_focused_view = view;
+}
+
std::unique_ptr<View> DialogDelegate::DisownExtraView() {
return std::move(extra_view_);
}
diff --git a/chromium/ui/views/window/dialog_delegate.h b/chromium/ui/views/window/dialog_delegate.h
index 04bc5b4cdce..2b35fd9e2c2 100644
--- a/chromium/ui/views/window/dialog_delegate.h
+++ b/chromium/ui/views/window/dialog_delegate.h
@@ -64,6 +64,11 @@ class VIEWS_EXPORT DialogDelegate : public WidgetDelegate {
// dialog. It's legal for a button to be marked enabled that isn't present
// in |buttons| (see above).
int enabled_buttons = ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
+
+ // The view that should receive initial focus in the dialog. If not set, the
+ // default button will receive initial focus. If explicitly set to nullptr,
+ // no view will receive focus.
+ base::Optional<View*> initially_focused_view;
};
DialogDelegate();
@@ -192,8 +197,23 @@ class VIEWS_EXPORT DialogDelegate : public WidgetDelegate {
void SetButtons(int buttons);
void SetButtonLabel(ui::DialogButton button, base::string16 label);
void SetButtonEnabled(ui::DialogButton button, bool enabled);
+ void SetInitiallyFocusedView(View* view);
+
+ // Called when the user presses the dialog's "OK" button or presses the dialog
+ // accept accelerator, if there is one.
void SetAcceptCallback(base::OnceClosure callback);
+
+ // Called when the user presses the dialog's "Cancel" button or presses the
+ // dialog close accelerator (which is always VKEY_ESCAPE).
void SetCancelCallback(base::OnceClosure callback);
+
+ // Called when:
+ // * The user presses the dialog's close button, if it has one
+ // * The dialog's widget is closed via Widget::Close()
+ // NOT called when the dialog's widget is closed via Widget::CloseNow() - in
+ // that case, the normal widget close path is skipped, so no orderly teardown
+ // of the dialog's widget happens. The main way that can happen in production
+ // use is if the dialog's parent widget is closed.
void SetCloseCallback(base::OnceClosure callback);
// Returns ownership of the extra view for this dialog, if one was provided
diff --git a/chromium/ui/views/window/dialog_delegate_unittest.cc b/chromium/ui/views/window/dialog_delegate_unittest.cc
index 6096431dc6b..33e49328132 100644
--- a/chromium/ui/views/window/dialog_delegate_unittest.cc
+++ b/chromium/ui/views/window/dialog_delegate_unittest.cc
@@ -515,4 +515,23 @@ TEST_F(DialogDelegateCloseTest, OldClosePathDoesNotDoubleClose) {
EXPECT_FALSE(cancelled);
}
+TEST_F(DialogDelegateCloseTest, CloseParentWidgetDoesNotInvokeCloseCallback) {
+ auto* dialog = new DialogDelegateView();
+ std::unique_ptr<Widget> parent = CreateTestWidget();
+ Widget* widget = DialogDelegate::CreateDialogWidget(dialog, GetContext(),
+ parent->GetNativeView());
+
+ bool closed = false;
+ dialog->SetCloseCallback(
+ base::BindLambdaForTesting([&closed]() { closed = true; }));
+
+ views::test::WidgetDestroyedWaiter parent_waiter(parent.get());
+ views::test::WidgetDestroyedWaiter dialog_waiter(widget);
+ parent->Close();
+ parent_waiter.Wait();
+ dialog_waiter.Wait();
+
+ EXPECT_FALSE(closed);
+}
+
} // namespace views
diff --git a/chromium/ui/views/window/vector_icons/vector_icons.cc.template b/chromium/ui/views/window/vector_icons/vector_icons.cc.template
index e3457f998b2..ca986041784 100644
--- a/chromium/ui/views/window/vector_icons/vector_icons.cc.template
+++ b/chromium/ui/views/window/vector_icons/vector_icons.cc.template
@@ -7,7 +7,6 @@
#include "ui/views/window/vector_icons/vector_icons.h"
-#include "base/logging.h"
#include "components/vector_icons/cc_macros.h"
#include "ui/gfx/vector_icon_types.h"