diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-10-24 11:30:15 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-10-30 12:56:19 +0000 |
commit | 6036726eb981b6c4b42047513b9d3f4ac865daac (patch) | |
tree | 673593e70678e7789766d1f732eb51f613a2703b /chromium/ui/views | |
parent | 466052c4e7c052268fd931888cd58961da94c586 (diff) | |
download | qtwebengine-chromium-6036726eb981b6c4b42047513b9d3f4ac865daac.tar.gz |
BASELINE: Update Chromium to 70.0.3538.78
Change-Id: Ie634710bf039e26c1957f4ae45e101bd4c434ae7
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/ui/views')
285 files changed, 5170 insertions, 3914 deletions
diff --git a/chromium/ui/views/BUILD.gn b/chromium/ui/views/BUILD.gn index 961f2b06534..3265121c103 100644 --- a/chromium/ui/views/BUILD.gn +++ b/chromium/ui/views/BUILD.gn @@ -27,6 +27,7 @@ aggregate_vector_icons("views_vector_icons") { "info.icon", "launch.icon", "menu_check.icon", + "menu_drop_arrow.icon", "menu_radio_empty.icon", "menu_radio_selected.icon", "new_incognito_window.icon", @@ -78,23 +79,13 @@ jumbo_component("views") { "background.h", "border.h", "bubble/bubble_border.h", - "bubble/bubble_dialog_delegate.h", + "bubble/bubble_dialog_delegate_view.h", "bubble/bubble_frame_view.h", "bubble/info_bubble.h", "bubble/tooltip_icon.h", "button_drag_utils.h", - "cocoa/bridged_content_view.h", "cocoa/bridged_native_widget.h", - "cocoa/bridged_native_widget_owner.h", - "cocoa/cocoa_mouse_capture.h", - "cocoa/cocoa_mouse_capture_delegate.h", - "cocoa/cocoa_window_move_loop.h", - "cocoa/drag_drop_client_mac.h", "cocoa/native_widget_mac_nswindow.h", - "cocoa/tooltip_manager_mac.h", - "cocoa/views_nswindow_delegate.h", - "cocoa/views_scrollbar_bridge.h", - "cocoa/widget_owner_nswindow_adapter.h", "cocoa/window_touch_bar_delegate.h", "color_chooser/color_chooser_listener.h", "color_chooser/color_chooser_view.h", @@ -121,6 +112,7 @@ jumbo_component("views") { "controls/link.h", "controls/link_listener.h", "controls/menu/menu_closure_animation_mac.h", + "controls/menu/menu_cocoa_watcher_mac.h", "controls/menu/menu_config.h", "controls/menu/menu_controller.h", "controls/menu/menu_controller_delegate.h", @@ -253,12 +245,13 @@ jumbo_component("views") { "window/dialog_observer.h", "window/frame_background.h", "window/frame_buttons.h", + "window/hit_test_utils.h", "window/native_frame_view.h", "window/non_client_view.h", "window/window_button_order_provider.h", + "window/window_resize_utils.h", "window/window_resources.h", "window/window_shape.h", - "window/window_resize_utils.h", "word_lookup_client.h", ] @@ -283,22 +276,13 @@ jumbo_component("views") { "background.cc", "border.cc", "bubble/bubble_border.cc", - "bubble/bubble_dialog_delegate.cc", + "bubble/bubble_dialog_delegate_view.cc", "bubble/bubble_frame_view.cc", "bubble/info_bubble.cc", "bubble/tooltip_icon.cc", "button_drag_utils.cc", - "cocoa/bridged_content_view.mm", - "cocoa/bridged_content_view_touch_bar.mm", "cocoa/bridged_native_widget.mm", - "cocoa/cocoa_mouse_capture.mm", - "cocoa/cocoa_window_move_loop.mm", - "cocoa/drag_drop_client_mac.mm", "cocoa/native_widget_mac_nswindow.mm", - "cocoa/tooltip_manager_mac.mm", - "cocoa/views_nswindow_delegate.mm", - "cocoa/views_scrollbar_bridge.mm", - "cocoa/widget_owner_nswindow_adapter.mm", "color_chooser/color_chooser_view.cc", "controls/animated_icon_view.cc", "controls/button/blue_button.cc", @@ -320,6 +304,7 @@ jumbo_component("views") { "controls/link.cc", "controls/menu/display_change_listener_mac.cc", "controls/menu/menu_closure_animation_mac.mm", + "controls/menu/menu_cocoa_watcher_mac.mm", "controls/menu/menu_config.cc", "controls/menu/menu_config_chromeos.cc", "controls/menu/menu_config_linux.cc", @@ -435,6 +420,7 @@ jumbo_component("views") { "window/dialog_client_view.cc", "window/dialog_delegate.cc", "window/frame_background.cc", + "window/hit_test_utils.cc", "window/native_frame_view.cc", "window/non_client_view.cc", "window/window_button_order_provider.cc", @@ -442,14 +428,37 @@ jumbo_component("views") { "window/window_shape.cc", ] - # Internal sources. If a header in this list is used by another target under - # //ui/views:* the header can be listed again in that target's sources. See - # http://crbug/com/732993 for a possible build system feature to avoid the - # repetition. TODO(tapted): Move more headers from public into this list with - # the implementation file. + # Other targets in this file (e.g. tests) get access to the internal headers. + friend = [ ":*" ] + + # Internal sources. TODO(https://crbug.com/871123): Move more headers from + # public into this list, along with the implementation file. sources += [ + "cocoa/bridged_content_view.h", + "cocoa/bridged_content_view.mm", + "cocoa/bridged_content_view_touch_bar.mm", + "cocoa/bridged_native_widget_host.h", + "cocoa/bridged_native_widget_host_impl.h", + "cocoa/bridged_native_widget_host_impl.mm", + "cocoa/bridged_native_widget_owner.h", + "cocoa/cocoa_mouse_capture.h", + "cocoa/cocoa_mouse_capture.mm", + "cocoa/cocoa_mouse_capture_delegate.h", + "cocoa/cocoa_window_move_loop.h", + "cocoa/cocoa_window_move_loop.mm", + "cocoa/drag_drop_client_mac.h", + "cocoa/drag_drop_client_mac.mm", + "cocoa/tooltip_manager_mac.h", + "cocoa/tooltip_manager_mac.mm", + "cocoa/views_nswindow_delegate.h", + "cocoa/views_nswindow_delegate.mm", + "cocoa/views_scrollbar_bridge.h", + "cocoa/views_scrollbar_bridge.mm", + "cocoa/widget_owner_nswindow_adapter.h", + "cocoa/widget_owner_nswindow_adapter.mm", "controls/button/label_button_label.cc", "controls/button/label_button_label.h", + "controls/menu/menu_pre_target_handler.h", ] sources += get_target_outputs(":views_vector_icons") @@ -468,7 +477,7 @@ jumbo_component("views") { "//base:i18n", "//base/third_party/dynamic_annotations", "//cc/paint", - "//services/ui/public/interfaces", + "//services/ws/public/mojom", "//skia", "//third_party/icu", "//ui/accessibility", @@ -497,6 +506,7 @@ jumbo_component("views") { "//ui/gfx/animation", "//ui/gfx/geometry", "//ui/views/resources", + "//ui/views_bridge_mac:mojo", ] if (use_x11) { @@ -583,7 +593,6 @@ jumbo_component("views") { "accessibility/ax_widget_obj_wrapper.h", "accessibility/ax_window_obj_wrapper.h", "bubble/tray_bubble_view.h", - "controls/menu/menu_pre_target_handler.h", "controls/native/native_view_host_aura.h", "corewm/cursor_height_provider_win.h", "corewm/tooltip.h", @@ -617,7 +626,8 @@ jumbo_component("views") { "accessibility/ax_window_obj_wrapper.cc", "bubble/tray_bubble_view.cc", "controls/menu/display_change_listener_aura.cc", - "controls/menu/menu_pre_target_handler.cc", + "controls/menu/menu_pre_target_handler_aura.cc", + "controls/menu/menu_pre_target_handler_aura.h", "controls/native/native_view_host_aura.cc", "corewm/cursor_height_provider_win.cc", "corewm/tooltip_aura.cc", @@ -645,7 +655,7 @@ jumbo_component("views") { "widget/window_reorderer.cc", ] deps += [ - "//services/ui/public/interfaces", + "//services/ws/public/mojom", "//ui/aura", "//ui/platform_window", "//ui/touch_selection", @@ -705,18 +715,21 @@ jumbo_component("views") { "widget/desktop_aura/window_event_filter.cc", "widget/desktop_aura/window_event_filter.h", ] - if (!use_x11) { - public += - [ "widget/desktop_aura/desktop_window_tree_host_platform.h" ] - sources += - [ "widget/desktop_aura/desktop_window_tree_host_platform.cc" ] - } + } + if ((is_linux && !use_x11) || is_fuchsia) { + public += [ "widget/desktop_aura/desktop_window_tree_host_platform.h" ] + sources += + [ "widget/desktop_aura/desktop_window_tree_host_platform.cc" ] } } } if (is_mac) { sources -= [ "controls/views_text_services_context_menu.cc" ] + sources += [ + "controls/menu/menu_pre_target_handler_mac.h", + "controls/menu/menu_pre_target_handler_mac.mm", + ] deps += [ "//components/crash/core/common", "//ui/accelerated_widget_mac", @@ -917,16 +930,6 @@ jumbo_static_library("test_support") { source_set("views_unittests_sources") { testonly = true - # Headers used in tests that appear in :views private sources list. Permits - # this target to act as a "friend" of :views. The build system doesn't - # currently have a way to represent this. See http://crbug.com/732993. - public = [ - "controls/button/label_button_label.h", - ] - if (has_native_accessibility) { - public += [ "accessibility/view_ax_platform_node_delegate.h" ] - } - sources = [ "accessible_pane_view_unittest.cc", "animation/bounds_animator_unittest.cc", @@ -939,7 +942,7 @@ source_set("views_unittests_sources") { "animation/square_ink_drop_ripple_unittest.cc", "border_unittest.cc", "bubble/bubble_border_unittest.cc", - "bubble/bubble_dialog_delegate_unittest.cc", + "bubble/bubble_dialog_delegate_view_unittest.cc", "bubble/bubble_frame_view_unittest.cc", "cocoa/bridged_native_widget_unittest.mm", "cocoa/cocoa_mouse_capture_unittest.mm", @@ -971,6 +974,7 @@ source_set("views_unittests_sources") { "controls/resize_area_unittest.cc", "controls/scroll_view_unittest.cc", "controls/scrollbar/scrollbar_unittest.cc", + "controls/separator_unittest.cc", "controls/slider_unittest.cc", "controls/styled_label_unittest.cc", "controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm", @@ -989,6 +993,7 @@ source_set("views_unittests_sources") { "layout/grid_layout_unittest.cc", "paint_info_unittest.cc", "rect_based_targeting_utils_unittest.cc", + "selection_controller_unittest.cc", "test/widget_test_unittest.cc", "view_model_unittest.cc", "view_model_utils_unittest.cc", @@ -1004,6 +1009,7 @@ source_set("views_unittests_sources") { "window/custom_frame_view_unittest.cc", "window/dialog_client_view_unittest.cc", "window/dialog_delegate_unittest.cc", + "window/hit_test_utils_unittest.cc", "window/non_client_view_unittest.cc", "window/window_resize_utils_unittest.cc", ] @@ -1013,7 +1019,7 @@ source_set("views_unittests_sources") { # Make all deps in this target public so both views_unittests and # views_mus_unittests will get them. public_deps = [ - ":test_support", + ":test_support_internal", ":views", "//base", "//base:i18n", @@ -1021,7 +1027,7 @@ source_set("views_unittests_sources") { "//cc", "//cc/paint", "//components/vector_icons", - "//services/ui/public/interfaces", + "//services/ws/public/mojom", "//skia", "//testing/gtest", "//third_party/icu", @@ -1049,8 +1055,6 @@ source_set("views_unittests_sources") { ] if (is_win) { - public += [ "accessibility/view_ax_platform_node_delegate_win.h" ] - public_deps += [ "//build/win:default_exe_manifest", "//third_party/iaccessible2", @@ -1090,8 +1094,10 @@ source_set("views_unittests_sources") { "accessibility/ax_aura_obj_cache_unittest.cc", "accessibility/ax_tree_source_views_unittest.cc", "controls/native/native_view_host_aura_unittest.cc", + "corewm/tooltip_controller_unittest.cc", "touchui/touch_selection_menu_runner_views_unittest.cc", "view_unittest_aura.cc", + "widget/window_reorderer_unittest.cc", ] public_deps += [ "//ui/aura", @@ -1121,16 +1127,10 @@ test("views_unittests") { if (use_aura) { sources += [ - # These tests currently use AuraTestBase, but then try to use views - # objects. Thus, when they are run in views_mus_unittests, they test the - # local aura path instead of the remote mus path, so pull them - # out. crbug.com/710939. - # - # Some of the tests need drag-drop support. crbug.com/614037 - "corewm/tooltip_controller_unittest.cc", + # These tests are expecting a hierarchy as created by classic ash. They + # are only useful for views_unittests, and not in views_mus_unittests. "touchui/touch_selection_controller_impl_unittest.cc", "widget/native_widget_aura_unittest.cc", - "widget/window_reorderer_unittest.cc", ] if (is_mac) { @@ -1153,11 +1153,17 @@ test("views_unittests") { } } + if (is_linux && !is_chromeos && !use_x11) { + sources += + [ "widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc" ] + } + if (!is_chromeos) { sources += [ "widget/desktop_widget_unittest.cc" ] } deps = [ + ":test_support", ":views_unittests_sources", "//mojo/core/embedder", ] diff --git a/chromium/ui/views/DEPS b/chromium/ui/views/DEPS index 39d2ec74e37..f591ea6f71c 100644 --- a/chromium/ui/views/DEPS +++ b/chromium/ui/views/DEPS @@ -2,7 +2,7 @@ include_rules = [ "+cc/paint", "+components/crash/core/common/crash_key.h", "+components/vector_icons", - "+services/ui/public/interfaces", + "+services/ws/public/mojom", "+skia/ext", "+third_party/iaccessible2", "+third_party/skia", diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc index e7bbe189e43..b8ea4c3c754 100644 --- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc +++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc @@ -157,9 +157,16 @@ void AXWindowObjWrapper::OnWindowPropertyChanged(aura::Window* window, void AXWindowObjWrapper::OnWindowVisibilityChanged(aura::Window* window, bool visible) { - AXAuraObjCache::GetInstance()->FireEvent( - AXAuraObjCache::GetInstance()->GetOrCreate(window_), - ax::mojom::Event::kStateChanged); + AXAuraObjCache::GetInstance()->FireEvent(this, + ax::mojom::Event::kStateChanged); +} + +void AXWindowObjWrapper::OnWindowTransformed(aura::Window* window, + ui::PropertyChangeReason reason) { + if (window != window_) + return; + + FireLocationChanges(window_); } } // namespace views diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h index 44f3f64eb3c..e996fa3a124 100644 --- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h +++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h @@ -50,6 +50,8 @@ class AXWindowObjWrapper : public AXAuraObjWrapper, const void* key, intptr_t old) override; void OnWindowVisibilityChanged(aura::Window* window, bool visible) override; + void OnWindowTransformed(aura::Window* window, + ui::PropertyChangeReason reason) override; private: aura::Window* window_; diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc index 8a702690827..09711e4e6f0 100644 --- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc @@ -111,6 +111,10 @@ TEST_F(ViewAXPlatformNodeDelegateTest, BoundsShouldMatch) { } TEST_F(ViewAXPlatformNodeDelegateTest, LabelIsChildOfButton) { + // Disable focus rings for this test: they introduce extra children that can + // be either before or after the label, which complicates correctness testing. + button_->SetInstallFocusRingOnFocus(false); + // |button_| is focusable, so |label_| (as its child) should be ignored. EXPECT_EQ(View::FocusBehavior::ACCESSIBLE_ONLY, button_->focus_behavior()); EXPECT_EQ(1, button_accessibility()->GetChildCount()); diff --git a/chromium/ui/views/animation/bounds_animator.h b/chromium/ui/views/animation/bounds_animator.h index c0a6501fc0d..e72714fefb3 100644 --- a/chromium/ui/views/animation/bounds_animator.h +++ b/chromium/ui/views/animation/bounds_animator.h @@ -161,7 +161,7 @@ class VIEWS_EXPORT BoundsAnimator : public gfx::AnimationDelegate, // Parent of all views being animated. View* parent_; - base::ObserverList<BoundsAnimatorObserver> observers_; + base::ObserverList<BoundsAnimatorObserver>::Unchecked observers_; // All animations we create up with the same container. scoped_refptr<gfx::AnimationContainer> container_; diff --git a/chromium/ui/views/animation/ink_drop.h b/chromium/ui/views/animation/ink_drop.h index 00de74cc3e3..735f0f9a06b 100644 --- a/chromium/ui/views/animation/ink_drop.h +++ b/chromium/ui/views/animation/ink_drop.h @@ -81,7 +81,7 @@ class VIEWS_EXPORT InkDrop { void NotifyInkDropRippleAnimationEnded(InkDropState state); private: - base::ObserverList<InkDropObserver> observers_; + base::ObserverList<InkDropObserver>::Unchecked observers_; DISALLOW_COPY_AND_ASSIGN(InkDrop); }; diff --git a/chromium/ui/views/bubble/bubble_border.cc b/chromium/ui/views/bubble/bubble_border.cc index b746bb4c231..9fc042cd02f 100644 --- a/chromium/ui/views/bubble/bubble_border.cc +++ b/chromium/ui/views/bubble/bubble_border.cc @@ -27,42 +27,13 @@ namespace views { -namespace internal { - -BorderImages::BorderImages(const int border_image_ids[], - const int arrow_image_ids[], - int border_interior_thickness, - int arrow_interior_thickness, - int corner_radius) - : border_thickness(border_interior_thickness), - border_interior_thickness(border_interior_thickness), - arrow_thickness(arrow_interior_thickness), - arrow_interior_thickness(arrow_interior_thickness), - arrow_width(2 * arrow_interior_thickness), - corner_radius(corner_radius) { - if (!border_image_ids) - return; - - border_painter = Painter::CreateImageGridPainter(border_image_ids); - ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - border_thickness = rb.GetImageSkiaNamed(border_image_ids[0])->width(); - - if (arrow_image_ids[0] != 0) { - left_arrow = *rb.GetImageSkiaNamed(arrow_image_ids[0]); - top_arrow = *rb.GetImageSkiaNamed(arrow_image_ids[1]); - right_arrow = *rb.GetImageSkiaNamed(arrow_image_ids[2]); - bottom_arrow = *rb.GetImageSkiaNamed(arrow_image_ids[3]); - arrow_width = top_arrow.width(); - arrow_thickness = top_arrow.height(); - } -} - -BorderImages::~BorderImages() {} - -} // namespace internal - namespace { +// GetShadowValues and GetBorderAndShadowFlags cache their results. The shadow +// values depend on both the shadow elevation and color, so we create a tuple to +// key the cache. +typedef std::tuple<int, SkColor> ShadowCacheKey; + // The border corner radius for material design bubble borders. constexpr int kMaterialDesignCornerRadius = 2; @@ -87,82 +58,6 @@ gfx::Point RightCenter(const gfx::Rect& rect) { return gfx::Point(rect.right(), rect.CenterPoint().y()); } -// Bubble border and arrow image resource ids. They don't use the IMAGE_GRID -// macro because there is no center image. -const int kNoShadowImages[] = { - IDR_BUBBLE_TL, IDR_BUBBLE_T, IDR_BUBBLE_TR, - IDR_BUBBLE_L, 0, IDR_BUBBLE_R, - IDR_BUBBLE_BL, IDR_BUBBLE_B, IDR_BUBBLE_BR }; -const int kNoShadowArrows[] = { - IDR_BUBBLE_L_ARROW, IDR_BUBBLE_T_ARROW, - IDR_BUBBLE_R_ARROW, IDR_BUBBLE_B_ARROW, }; - -const int kBigShadowImages[] = { - IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_LEFT, - IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP, - IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_RIGHT, - IDR_WINDOW_BUBBLE_SHADOW_BIG_LEFT, - 0, - IDR_WINDOW_BUBBLE_SHADOW_BIG_RIGHT, - IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_LEFT, - IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM, - IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_RIGHT }; -const int kBigShadowArrows[] = { - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_LEFT, - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_TOP, - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_RIGHT, - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_BOTTOM }; - -const int kSmallShadowImages[] = { - IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT, - 0, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM, - IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT }; -const int kSmallShadowArrows[] = { - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_LEFT, - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_TOP, - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_RIGHT, - IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_BOTTOM }; - -using internal::BorderImages; - -// Returns the cached BorderImages for the given |shadow| type. -BorderImages* GetBorderImages(BubbleBorder::Shadow shadow) { - // Keep a cache of bubble border image-set painters, arrows, and metrics. - static BorderImages* kBorderImages[BubbleBorder::SHADOW_COUNT] = { NULL }; - - CHECK_LT(shadow, BubbleBorder::SHADOW_COUNT); - struct BorderImages*& set = kBorderImages[shadow]; - if (set) - return set; - - switch (shadow) { - case BubbleBorder::NO_SHADOW: - case BubbleBorder::NO_SHADOW_OPAQUE_BORDER: - set = new BorderImages(kNoShadowImages, kNoShadowArrows, 6, 7, 4); - break; - case BubbleBorder::BIG_SHADOW: - set = new BorderImages(kBigShadowImages, kBigShadowArrows, 23, 9, 2); - break; - case BubbleBorder::SMALL_SHADOW: - set = new BorderImages(kSmallShadowImages, kSmallShadowArrows, 5, 6, 2); - break; - case BubbleBorder::NO_ASSETS: - set = new BorderImages(nullptr, nullptr, 17, 8, 2); - break; - case BubbleBorder::SHADOW_COUNT: - NOTREACHED(); - break; - } - - return set; -} - } // namespace const int BubbleBorder::kStroke = 1; @@ -170,14 +65,10 @@ const int BubbleBorder::kStroke = 1; BubbleBorder::BubbleBorder(Arrow arrow, Shadow shadow, SkColor color) : arrow_(arrow), arrow_offset_(0), - arrow_paint_type_(PAINT_NORMAL), - alignment_(ALIGN_ARROW_TO_MID_ANCHOR), shadow_(shadow), - images_(nullptr), background_color_(color), use_theme_background_color_(false) { DCHECK(shadow_ < SHADOW_COUNT); - Init(); } BubbleBorder::~BubbleBorder() {} @@ -197,23 +88,14 @@ gfx::Insets BubbleBorder::GetBorderAndShadowInsets( void BubbleBorder::SetCornerRadius(int corner_radius) { corner_radius_ = corner_radius; - Init(); -} - -void BubbleBorder::set_paint_arrow(ArrowPaintType value) { - if (UseMaterialDesign()) - return; - arrow_paint_type_ = value; } gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& anchor_rect, const gfx::Size& contents_size) const { // In MD, there are no arrows, so positioning logic is significantly simpler. // TODO(estade): handle more anchor positions. - if (UseMaterialDesign() && - (arrow_ == TOP_RIGHT || arrow_ == TOP_LEFT || arrow_ == BOTTOM_CENTER || - arrow_ == TOP_CENTER || arrow_ == LEFT_CENTER || - arrow_ == RIGHT_CENTER)) { + if (arrow_ == TOP_RIGHT || arrow_ == TOP_LEFT || arrow_ == BOTTOM_CENTER || + arrow_ == TOP_CENTER || arrow_ == LEFT_CENTER || arrow_ == RIGHT_CENTER) { gfx::Rect contents_bounds(contents_size); // Apply the border part of the inset before calculating coordinates because // the border should align with the anchor's border. For the purposes of @@ -258,44 +140,26 @@ gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& anchor_rect, int w = anchor_rect.width(); int h = anchor_rect.height(); const gfx::Size size(GetSizeForContentsSize(contents_size)); - const int arrow_offset = GetArrowOffset(size); const int stroke_width = shadow_ == NO_ASSETS ? 0 : kStroke; - // |arrow_shift| is necessary to visually align the tip of the bubble arrow - // with the anchor point. This shift is an inverse of the shadow thickness. - int arrow_shift = UseMaterialDesign() - ? 0 - : images_->arrow_interior_thickness + stroke_width - - images_->arrow_thickness; - // When arrow is painted transparently the visible border of the bubble needs - // to be positioned at the same bounds as when the arrow is shown. - if (arrow_paint_type_ == PAINT_TRANSPARENT) - arrow_shift += images_->arrow_interior_thickness; - const bool mid_anchor = alignment_ == ALIGN_ARROW_TO_MID_ANCHOR; // Calculate the bubble coordinates based on the border and arrow settings. if (is_arrow_on_horizontal(arrow_)) { if (is_arrow_on_left(arrow_)) { - x += mid_anchor ? w / 2 - arrow_offset - : stroke_width - GetBorderThickness(); + x += stroke_width; } else if (is_arrow_at_center(arrow_)) { - x += w / 2 - arrow_offset; + x += w / 2; } else { - x += mid_anchor ? w / 2 + arrow_offset - size.width() - : w - size.width() + GetBorderThickness() - stroke_width; + x += w - size.width() - stroke_width; } - y += is_arrow_on_top(arrow_) ? h + arrow_shift - : -arrow_shift - size.height(); + y += is_arrow_on_top(arrow_) ? h : -size.height(); } else if (has_arrow(arrow_)) { - x += is_arrow_on_left(arrow_) ? w + arrow_shift - : -arrow_shift - size.width(); + x += is_arrow_on_left(arrow_) ? w : -size.width(); if (is_arrow_on_top(arrow_)) { - y += mid_anchor ? h / 2 - arrow_offset - : stroke_width - GetBorderThickness(); + y += stroke_width; } else if (is_arrow_at_center(arrow_)) { - y += h / 2 - arrow_offset; + y += h / 2; } else { - y += mid_anchor ? h / 2 + arrow_offset - size.height() - : h - size.height() + GetBorderThickness() - stroke_width; + y += h - size.height() - stroke_width; } } else { x += (w - size.width()) / 2; @@ -305,107 +169,28 @@ gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& anchor_rect, return gfx::Rect(x, y, size.width(), size.height()); } -int BubbleBorder::GetBorderThickness() const { - // TODO(estade): this shouldn't be called in MD. - return UseMaterialDesign() - ? 0 - : images_->border_thickness - images_->border_interior_thickness; -} - int BubbleBorder::GetBorderCornerRadius() const { - if (UseMaterialDesign()) - return corner_radius_.value_or(kMaterialDesignCornerRadius); - return images_->corner_radius; -} - -int BubbleBorder::GetArrowOffset(const gfx::Size& border_size) const { - if (UseMaterialDesign()) - return 0; - - const int edge_length = is_arrow_on_horizontal(arrow_) ? - border_size.width() : border_size.height(); - if (is_arrow_at_center(arrow_) && arrow_offset_ == 0) - return edge_length / 2; - - // Calculate the minimum offset to not overlap arrow and corner images. - const int min = images_->border_thickness + (images_->arrow_width / 2); - // Ensure the returned value will not cause image overlap, if possible. - return std::max(min, std::min(arrow_offset_, edge_length - min)); -} - -bool BubbleBorder::GetArrowPath(const gfx::Rect& view_bounds, - gfx::Path* path) const { - if (!has_arrow(arrow_) || arrow_paint_type_ != PAINT_NORMAL) - return false; - - GetArrowPathFromArrowBounds(GetArrowRect(view_bounds), path); - return true; -} - -void BubbleBorder::SetBorderInteriorThickness(int border_interior_thickness) { - // TODO(estade): remove this function. - DCHECK(!UseMaterialDesign()); - images_->border_interior_thickness = border_interior_thickness; - if (!has_arrow(arrow_) || arrow_paint_type_ != PAINT_NORMAL) - images_->border_thickness = border_interior_thickness; -} - -void BubbleBorder::Init() { - if (UseMaterialDesign()) { - // Harmony bubbles don't use arrows. - alignment_ = ALIGN_EDGE_TO_ANCHOR_EDGE; - arrow_paint_type_ = PAINT_NONE; - } else { - images_ = GetBorderImages(shadow_); - } + return corner_radius_.value_or(kMaterialDesignCornerRadius); } void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) { - if (UseMaterialDesign()) - return PaintMd(view, canvas); - - gfx::Rect bounds(view.GetContentsBounds()); - bounds.Inset(-GetBorderThickness(), -GetBorderThickness()); - const gfx::Rect arrow_bounds = GetArrowRect(view.GetLocalBounds()); - if (arrow_bounds.IsEmpty()) { - if (images_->border_painter) - Painter::PaintPainterAt(canvas, images_->border_painter.get(), bounds); - return; - } - if (!images_->border_painter) { - DrawArrow(canvas, arrow_bounds); - return; - } + if (shadow_ == NO_ASSETS) + return PaintNoAssets(view, canvas); - // Clip the arrow bounds out to avoid painting the overlapping edge area. - canvas->Save(); - canvas->ClipRect(arrow_bounds, SkClipOp::kDifference); - Painter::PaintPainterAt(canvas, images_->border_painter.get(), bounds); - canvas->Restore(); + gfx::ScopedCanvas scoped(canvas); + + SkRRect r_rect = GetClientRect(view); + canvas->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference, + true /*doAntiAlias*/); - DrawArrow(canvas, arrow_bounds); + DrawBorderAndShadow(std::move(r_rect), &cc::PaintCanvas::drawRRect, canvas, + md_shadow_elevation_, md_shadow_color_); } gfx::Insets BubbleBorder::GetInsets() const { - if (UseMaterialDesign()) { - return (shadow_ == NO_ASSETS) - ? gfx::Insets() - : GetBorderAndShadowInsets(md_shadow_elevation_); - } - - // The insets contain the stroke and shadow pixels outside the bubble fill. - const int inset = GetBorderThickness(); - if (arrow_paint_type_ != PAINT_NORMAL || !has_arrow(arrow_)) - return gfx::Insets(inset); - - int first_inset = inset; - int second_inset = std::max(inset, images_->arrow_thickness); - if (is_arrow_on_horizontal(arrow_) ? - is_arrow_on_top(arrow_) : is_arrow_on_left(arrow_)) - std::swap(first_inset, second_inset); - return is_arrow_on_horizontal(arrow_) ? - gfx::Insets(first_inset, inset, second_inset, inset) : - gfx::Insets(inset, first_inset, inset, second_inset); + return (shadow_ == NO_ASSETS) + ? gfx::Insets() + : GetBorderAndShadowInsets(md_shadow_elevation_); } gfx::Size BubbleBorder::GetMinimumSize() const { @@ -414,22 +199,26 @@ gfx::Size BubbleBorder::GetMinimumSize() const { // static const gfx::ShadowValues& BubbleBorder::GetShadowValues( - base::Optional<int> elevation) { - // The shadows are always the same for any elevation, so construct them once - // and cache. - static base::NoDestructor<std::map<int, gfx::ShadowValues>> shadow_map; - if (shadow_map->find(elevation.value_or(-1)) != shadow_map->end()) - return shadow_map->find(elevation.value_or(-1))->second; + base::Optional<int> elevation, + SkColor color) { + // The shadows are always the same for any elevation and color combination, so + // construct them once and cache. + static base::NoDestructor<std::map<ShadowCacheKey, gfx::ShadowValues>> + shadow_map; + ShadowCacheKey key(elevation.value_or(-1), color); + + if (shadow_map->find(key) != shadow_map->end()) + return shadow_map->find(key)->second; gfx::ShadowValues shadows; if (elevation.has_value()) { DCHECK(elevation.value() >= 0); - shadows = LayoutProvider::Get()->MakeShadowValues(elevation.value()); + shadows = LayoutProvider::Get()->MakeShadowValues(elevation.value(), color); } else { constexpr int kSmallShadowVerticalOffset = 2; constexpr int kSmallShadowBlur = 4; - constexpr SkColor kSmallShadowColor = SkColorSetA(SK_ColorBLACK, 0x33); - constexpr SkColor kLargeShadowColor = SkColorSetA(SK_ColorBLACK, 0x1A); + SkColor kSmallShadowColor = SkColorSetA(color, 0x33); + SkColor kLargeShadowColor = SkColorSetA(color, 0x1A); // gfx::ShadowValue counts blur pixels both inside and outside the shape, // whereas these blur values only describe the outside portion, hence they // must be doubled. @@ -441,30 +230,31 @@ const gfx::ShadowValues& BubbleBorder::GetShadowValues( }); } - shadow_map->insert( - std::pair<int, gfx::ShadowValues>(elevation.value_or(-1), shadows)); - return shadow_map->find(elevation.value_or(-1))->second; + shadow_map->insert({key, shadows}); + return shadow_map->find(key)->second; } // static const cc::PaintFlags& BubbleBorder::GetBorderAndShadowFlags( - base::Optional<int> elevation) { - // The flags are always the same for any elevation, so construct them once and - // cache. - static base::NoDestructor<std::map<int, cc::PaintFlags>> flag_map; + base::Optional<int> elevation, + SkColor color) { + // The flags are always the same for any elevation and color combination, so + // construct them once and cache. + static base::NoDestructor<std::map<ShadowCacheKey, cc::PaintFlags>> flag_map; + ShadowCacheKey key(elevation.value_or(-1), color); - if (flag_map->find(elevation.value_or(-1)) != flag_map->end()) - return flag_map->find(elevation.value_or(-1))->second; + if (flag_map->find(key) != flag_map->end()) + return flag_map->find(key)->second; cc::PaintFlags flags; constexpr SkColor kBorderColor = SkColorSetA(SK_ColorBLACK, 0x26); flags.setColor(kBorderColor); flags.setAntiAlias(true); - flags.setLooper(gfx::CreateShadowDrawLooper(GetShadowValues(elevation))); - flag_map->insert( - std::pair<int, cc::PaintFlags>(elevation.value_or(-1), flags)); + flags.setLooper( + gfx::CreateShadowDrawLooper(GetShadowValues(elevation, color))); - return flag_map->find(elevation.value_or(-1))->second; + flag_map->insert({key, flags}); + return flag_map->find(key)->second; } gfx::Size BubbleBorder::GetSizeForContentsSize( @@ -473,116 +263,9 @@ gfx::Size BubbleBorder::GetSizeForContentsSize( gfx::Size size(contents_size); const gfx::Insets insets = GetInsets(); size.Enlarge(insets.width(), insets.height()); - if (UseMaterialDesign()) - return size; - - // Ensure the bubble is large enough to not overlap border and arrow images. - const int min = 2 * images_->border_thickness; - // Only take arrow image sizes into account when the bubble tip is shown. - if (arrow_paint_type_ != PAINT_NORMAL || !has_arrow(arrow_)) { - size.SetToMax(gfx::Size(min, min)); - return size; - } - const int min_with_arrow_width = min + images_->arrow_width; - const int min_with_arrow_thickness = images_->border_thickness + - std::max(images_->arrow_thickness + images_->border_interior_thickness, - images_->border_thickness); - if (is_arrow_on_horizontal(arrow_)) - size.SetToMax(gfx::Size(min_with_arrow_width, min_with_arrow_thickness)); - else - size.SetToMax(gfx::Size(min_with_arrow_thickness, min_with_arrow_width)); return size; } -gfx::ImageSkia* BubbleBorder::GetArrowImage() const { - if (!has_arrow(arrow_)) - return NULL; - if (is_arrow_on_horizontal(arrow_)) { - return is_arrow_on_top(arrow_) ? - &images_->top_arrow : &images_->bottom_arrow; - } - return is_arrow_on_left(arrow_) ? - &images_->left_arrow : &images_->right_arrow; -} - -gfx::Rect BubbleBorder::GetArrowRect(const gfx::Rect& bounds) const { - if (!has_arrow(arrow_) || arrow_paint_type_ != PAINT_NORMAL) - return gfx::Rect(); - - gfx::Point origin; - int offset = GetArrowOffset(bounds.size()); - const int half_length = images_->arrow_width / 2; - const gfx::Insets insets = GetInsets(); - - if (is_arrow_on_horizontal(arrow_)) { - origin.set_x(is_arrow_on_left(arrow_) || is_arrow_at_center(arrow_) ? - offset : bounds.width() - offset); - origin.Offset(-half_length, 0); - if (is_arrow_on_top(arrow_)) - origin.set_y(insets.top() - images_->arrow_thickness); - else - origin.set_y(bounds.height() - insets.bottom()); - } else { - origin.set_y(is_arrow_on_top(arrow_) || is_arrow_at_center(arrow_) ? - offset : bounds.height() - offset); - origin.Offset(0, -half_length); - if (is_arrow_on_left(arrow_)) - origin.set_x(insets.left() - images_->arrow_thickness); - else - origin.set_x(bounds.width() - insets.right()); - } - - if (shadow_ != NO_ASSETS) - return gfx::Rect(origin, GetArrowImage()->size()); - - // With no assets, return the size enclosing the path filled in DrawArrow(). - DCHECK_EQ(2 * images_->arrow_interior_thickness, images_->arrow_width); - int width = images_->arrow_width; - int height = images_->arrow_interior_thickness; - if (!is_arrow_on_horizontal(arrow_)) - std::swap(width, height); - return gfx::Rect(origin, gfx::Size(width, height)); -} - -void BubbleBorder::GetArrowPathFromArrowBounds(const gfx::Rect& arrow_bounds, - SkPath* path) const { - DCHECK(!UseMaterialDesign()); - const bool horizontal = is_arrow_on_horizontal(arrow_); - const int thickness = images_->arrow_interior_thickness; - float tip_x = horizontal ? arrow_bounds.CenterPoint().x() : - is_arrow_on_left(arrow_) ? arrow_bounds.right() - thickness : - arrow_bounds.x() + thickness; - float tip_y = !horizontal ? arrow_bounds.CenterPoint().y() + 0.5f : - is_arrow_on_top(arrow_) ? arrow_bounds.bottom() - thickness : - arrow_bounds.y() + thickness; - const bool positive_offset = horizontal ? - is_arrow_on_top(arrow_) : is_arrow_on_left(arrow_); - const int offset_to_next_vertex = positive_offset ? - images_->arrow_interior_thickness : -images_->arrow_interior_thickness; - - path->incReserve(4); - path->moveTo(SkDoubleToScalar(tip_x), SkDoubleToScalar(tip_y)); - path->lineTo(SkDoubleToScalar(tip_x + offset_to_next_vertex), - SkDoubleToScalar(tip_y + offset_to_next_vertex)); - const int multiplier = horizontal ? 1 : -1; - path->lineTo(SkDoubleToScalar(tip_x - multiplier * offset_to_next_vertex), - SkDoubleToScalar(tip_y + multiplier * offset_to_next_vertex)); - path->close(); -} - -void BubbleBorder::DrawArrow(gfx::Canvas* canvas, - const gfx::Rect& arrow_bounds) const { - DCHECK(!UseMaterialDesign()); - canvas->DrawImageInt(*GetArrowImage(), arrow_bounds.x(), arrow_bounds.y()); - SkPath path; - GetArrowPathFromArrowBounds(arrow_bounds, &path); - cc::PaintFlags flags; - flags.setStyle(cc::PaintFlags::kFill_Style); - flags.setColor(background_color_); - - canvas->DrawPath(path, flags); -} - SkRRect BubbleBorder::GetClientRect(const View& view) const { gfx::RectF bounds(view.GetLocalBounds()); bounds.Inset(GetInsets()); @@ -590,20 +273,6 @@ SkRRect BubbleBorder::GetClientRect(const View& view) const { GetBorderCornerRadius(), GetBorderCornerRadius()); } -void BubbleBorder::PaintMd(const View& view, gfx::Canvas* canvas) { - if (shadow_ == NO_ASSETS) - return PaintNoAssets(view, canvas); - - gfx::ScopedCanvas scoped(canvas); - - SkRRect r_rect = GetClientRect(view); - canvas->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference, - true /*doAntiAlias*/); - - DrawBorderAndShadow(std::move(r_rect), &cc::PaintCanvas::drawRRect, canvas, - md_shadow_elevation_); -} - void BubbleBorder::PaintNoAssets(const View& view, gfx::Canvas* canvas) { gfx::ScopedCanvas scoped(canvas); canvas->sk_canvas()->clipRRect(GetClientRect(view), SkClipOp::kDifference, @@ -611,15 +280,6 @@ void BubbleBorder::PaintNoAssets(const View& view, gfx::Canvas* canvas) { canvas->sk_canvas()->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc); } -internal::BorderImages* BubbleBorder::GetImagesForTest() const { - return images_; -} - -bool BubbleBorder::UseMaterialDesign() const { - return ui::MaterialDesignController::IsSecondaryUiMaterial() || - corner_radius_.has_value(); -} - void BubbleBackground::Paint(gfx::Canvas* canvas, views::View* view) const { if (border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) canvas->DrawColor(border_->background_color()); diff --git a/chromium/ui/views/bubble/bubble_border.h b/chromium/ui/views/bubble/bubble_border.h index 778d93070d8..d4a6e2a28a7 100644 --- a/chromium/ui/views/bubble/bubble_border.h +++ b/chromium/ui/views/bubble/bubble_border.h @@ -12,58 +12,19 @@ #include "base/macros.h" #include "build/build_config.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/shadow_value.h" #include "ui/views/background.h" #include "ui/views/border.h" -class SkPath; class SkRRect; namespace gfx { -class Path; class Rect; } namespace views { -class Painter; - -namespace internal { - -// A helper that combines each border image-set painter with arrows and metrics. -struct BorderImages { - BorderImages(const int border_image_ids[], - const int arrow_image_ids[], - int border_interior_thickness, - int arrow_interior_thickness, - int corner_radius); - virtual ~BorderImages(); - - std::unique_ptr<Painter> border_painter; - gfx::ImageSkia left_arrow; - gfx::ImageSkia top_arrow; - gfx::ImageSkia right_arrow; - gfx::ImageSkia bottom_arrow; - - // The thickness of border and arrow images and their interior areas. - // Thickness is the width of left/right and the height of top/bottom images. - // The interior is measured without including stroke or shadow pixels. The tip - // of the arrow is |arrow_interior_thickness| from the border and the base is - // always twice that; drawn in the background color. - int border_thickness; - int border_interior_thickness; - int arrow_thickness; - int arrow_interior_thickness; - - // Width of an arrow (on the horizontal), including any shadows. Defaults to - // the width of the |top_arrow| asset. - int arrow_width; - - // The corner radius of the bubble's rounded-rect interior area. - int corner_radius; -}; - -} // namespace internal // Renders a border, with optional arrow, and a custom dropshadow. // This can be used to produce floating "bubble" objects with rounded corners. @@ -115,26 +76,6 @@ class VIEWS_EXPORT BubbleBorder : public Border { #else DIALOG_SHADOW = SMALL_SHADOW, #endif - - }; - - // The position of the bubble in relation to the anchor. - enum BubbleAlignment { - // The tip of the arrow points to the middle of the anchor. - ALIGN_ARROW_TO_MID_ANCHOR, - // The edge nearest to the arrow is lined up with the edge of the anchor. - ALIGN_EDGE_TO_ANCHOR_EDGE, - }; - - // The way the arrow should be painted. - // TODO(estade): Harmony doesn't use this enum; remove it. - enum ArrowPaintType { - // Fully render the arrow. - PAINT_NORMAL, - // Leave space for the arrow, but do not paint it. - PAINT_TRANSPARENT, - // Neither paint nor leave space for the arrow. - PAINT_NONE, }; // Specific to MD bubbles: size of shadow blur (outside the bubble) and @@ -187,7 +128,8 @@ class VIEWS_EXPORT BubbleBorder : public Border { T rect, void (cc::PaintCanvas::*draw)(const T&, const cc::PaintFlags&), gfx::Canvas* canvas, - base::Optional<int> shadow_elevation = base::nullopt) { + base::Optional<int> shadow_elevation = base::nullopt, + SkColor shadow_base_color = SK_ColorBLACK) { // Borders with custom shadow elevations do not draw the 1px border. if (!shadow_elevation.has_value()) { // Provide a 1 px border outside the bounds. @@ -197,8 +139,8 @@ class VIEWS_EXPORT BubbleBorder : public Border { rect.outset(one_pixel, one_pixel); } - (canvas->sk_canvas()->*draw)(rect, - GetBorderAndShadowFlags(shadow_elevation)); + (canvas->sk_canvas()->*draw)( + rect, GetBorderAndShadowFlags(shadow_elevation, shadow_base_color)); } // Set the corner radius, enables Material Design. @@ -208,10 +150,6 @@ class VIEWS_EXPORT BubbleBorder : public Border { void set_arrow(Arrow arrow) { arrow_ = arrow; } Arrow arrow() const { return arrow_; } - // Get or set the bubble alignment. - void set_alignment(BubbleAlignment alignment) { alignment_ = alignment; } - BubbleAlignment alignment() const { return alignment_; } - // Get the shadow type. Shadow shadow() const { return shadow_; } @@ -233,42 +171,25 @@ class VIEWS_EXPORT BubbleBorder : public Border { // location to place the arrow |offset| pixels from the perpendicular edge. void set_arrow_offset(int offset) { arrow_offset_ = offset; } - // Sets the way the arrow is actually painted. Default is PAINT_NORMAL. - void set_paint_arrow(ArrowPaintType value); - // Sets the shadow elevation for MD shadows. A null |shadow_elevation| will // yield the default BubbleBorder MD shadow. void set_md_shadow_elevation(int shadow_elevation) { - DCHECK(UseMaterialDesign()) << "Setting a non-default MD shadow elevation " - "requires that the BubbleBorder is using MD"; md_shadow_elevation_ = shadow_elevation; } + // Sets the shadow color for MD shadows. Defaults to SK_ColorBLACK. + void set_md_shadow_color(SkColor shadow_color) { + md_shadow_color_ = shadow_color; + } + // Get the desired widget bounds (in screen coordinates) given the anchor rect // and bubble content size; calculated from shadow and arrow image dimensions. virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect, const gfx::Size& contents_size) const; - // Get the border exterior thickness, including stroke and shadow, in pixels. - int GetBorderThickness() const; - // Returns the corner radius of the current image set. int GetBorderCornerRadius() const; - // Gets the arrow offset to use. - int GetArrowOffset(const gfx::Size& border_size) const; - - // Retreives the arrow path given |view_bounds|. |view_bounds| should be local - // bounds of the view. - // Returns false if |path| is unchanged, which is the case when there is no - // painted arrow. - // The returned path does not account for arrow stroke and shadow. - bool GetArrowPath(const gfx::Rect& view_bounds, gfx::Path* path) const; - - // Sets border thickness overriding the thickness set on |images_| creation. - // May only be invoked after |arrow_paint_type_| has been set. - void SetBorderInteriorThickness(int border_interior_thickness); - // Overridden from Border: void Paint(const View& view, gfx::Canvas* canvas) override; gfx::Insets GetInsets() const override; @@ -284,55 +205,41 @@ class VIEWS_EXPORT BubbleBorder : public Border { // bubbles. A null |shadow_elevation| will yield the default BubbleBorder MD // ShadowValues. static const gfx::ShadowValues& GetShadowValues( - base::Optional<int> shadow_elevation = base::nullopt); + base::Optional<int> shadow_elevation = base::nullopt, + SkColor shadow_base_color = SK_ColorBLACK); // Returns the paint flags to use for painting the border and shadow based on // |shadow_elevation|. This is only used for MD bubbles. A null // |shadow_elevation| will yield the default BubbleBorder MD PaintFlags. static const cc::PaintFlags& GetBorderAndShadowFlags( - base::Optional<int> shadow_elevation = base::nullopt); + base::Optional<int> shadow_elevation = base::nullopt, + SkColor shadow_base_color = SK_ColorBLACK); // The border and arrow stroke size used in image assets, in pixels. static const int kStroke; - // Initializes the MD or non-MD BubbleBorder. - void Init(); - gfx::Size GetSizeForContentsSize(const gfx::Size& contents_size) const; - gfx::ImageSkia* GetArrowImage() const; - gfx::Rect GetArrowRect(const gfx::Rect& bounds) const; - void GetArrowPathFromArrowBounds(const gfx::Rect& arrow_bounds, - SkPath* path) const; - void DrawArrow(gfx::Canvas* canvas, const gfx::Rect& arrow_bounds) const; // Returns the region within |view| representing the client area. This can be // set as a canvas clip to ensure any fill or shadow from the border does not // draw over the contents of the bubble. SkRRect GetClientRect(const View& view) const; - // Paints an MD border. Ignores |shadow_|. - void PaintMd(const View& view, gfx::Canvas* canvas); - // Paint for the NO_ASSETS shadow type. This just paints transparent pixels // to make the window shape based on insets and GetBorderCornerRadius(). void PaintNoAssets(const View& view, gfx::Canvas* canvas); - internal::BorderImages* GetImagesForTest() const; - - // Whether to use material design. - bool UseMaterialDesign() const; - Arrow arrow_; int arrow_offset_; // Corner radius for the bubble border. If supplied the border will use // material design. base::Optional<int> corner_radius_; - // Elevation for the MD shadow. Requires material design. - base::Optional<SkColor> md_shadow_elevation_; - ArrowPaintType arrow_paint_type_; - BubbleAlignment alignment_; + Shadow shadow_; - internal::BorderImages* images_; + // Elevation for the MD shadow. + base::Optional<int> md_shadow_elevation_; + // Color for the MD shadow. + SkColor md_shadow_color_ = SK_ColorBLACK; SkColor background_color_; bool use_theme_background_color_; diff --git a/chromium/ui/views/bubble/bubble_border_unittest.cc b/chromium/ui/views/bubble/bubble_border_unittest.cc index 87ca9bc7786..e1420089ab5 100644 --- a/chromium/ui/views/bubble/bubble_border_unittest.cc +++ b/chromium/ui/views/bubble/bubble_border_unittest.cc @@ -16,13 +16,6 @@ #include "ui/views/test/views_test_base.h" namespace views { -namespace { - -bool UseMd() { - return ui::MaterialDesignController::IsSecondaryUiMaterial(); -} - -} // namespace typedef views::ViewsTestBase BubbleBorderTest; @@ -223,7 +216,6 @@ TEST_F(BubbleBorderTest, GetSizeForContentsSizeTest) { BubbleBorder::NO_SHADOW, SK_ColorWHITE); - const views::internal::BorderImages* kImages = border.GetImagesForTest(); const gfx::Insets kInsets = border.GetInsets(); // kSmallSize is smaller than the minimum allowable size and does not @@ -233,106 +225,68 @@ TEST_F(BubbleBorderTest, GetSizeForContentsSizeTest) { // the resulting size. const gfx::Size kMediumSize = gfx::Size(50, 60); - const gfx::Size kSmallHorizArrow( - UseMd() ? kSmallSize.width() + kInsets.width() - : 2 * kImages->border_thickness + kImages->arrow_width, - UseMd() ? kSmallSize.height() + kInsets.height() - : kImages->border_thickness + kImages->arrow_thickness + - kImages->border_interior_thickness); - - const gfx::Size kSmallVertArrow( - UseMd() ? kSmallHorizArrow.width() : kSmallHorizArrow.height(), - UseMd() ? kSmallHorizArrow.height() : kSmallHorizArrow.width()); - - const gfx::Size kSmallNoArrow( - UseMd() ? kSmallHorizArrow.width() : 2 * kImages->border_thickness, - UseMd() ? kSmallHorizArrow.height() : 2 * kImages->border_thickness); - - const gfx::Size kMediumHorizArrow( - UseMd() ? kMediumSize.width() + kInsets.width() - : kMediumSize.width() + 2 * border.GetBorderThickness(), - UseMd() ? kMediumSize.height() + kInsets.height() - : kMediumSize.height() + border.GetBorderThickness() + - kImages->arrow_thickness); - - const gfx::Size kMediumVertArrow( - UseMd() ? kMediumHorizArrow.width() - : kMediumSize.width() + border.GetBorderThickness() + - kImages->arrow_thickness, - UseMd() ? kMediumHorizArrow.height() - : kMediumSize.height() + 2 * border.GetBorderThickness()); - - const gfx::Size kMediumNoArrow( - UseMd() ? kMediumHorizArrow.width() - : kMediumSize.width() + 2 * border.GetBorderThickness(), - UseMd() ? kMediumHorizArrow.height() - : kMediumSize.height() + 2 * border.GetBorderThickness()); + const gfx::Size kSmallHorizArrow(kSmallSize.width() + kInsets.width(), + kSmallSize.height() + kInsets.height()); + + const gfx::Size kSmallVertArrow(kSmallHorizArrow.width(), + kSmallHorizArrow.height()); + + const gfx::Size kSmallNoArrow(kSmallHorizArrow.width(), + kSmallHorizArrow.height()); + + const gfx::Size kMediumHorizArrow(kMediumSize.width() + kInsets.width(), + kMediumSize.height() + kInsets.height()); + + const gfx::Size kMediumVertArrow(kMediumHorizArrow.width(), + kMediumHorizArrow.height()); + + const gfx::Size kMediumNoArrow(kMediumHorizArrow.width(), + kMediumHorizArrow.height()); struct TestCase { BubbleBorder::Arrow arrow; gfx::Size content; - gfx::Size expected_with_arrow; gfx::Size expected_without_arrow; }; TestCase cases[] = { - // Content size: kSmallSize - { BubbleBorder::TOP_LEFT, kSmallSize, kSmallHorizArrow, kSmallNoArrow }, - { BubbleBorder::TOP_CENTER, kSmallSize, kSmallHorizArrow, kSmallNoArrow }, - { BubbleBorder::TOP_RIGHT, kSmallSize, kSmallHorizArrow, kSmallNoArrow }, - { BubbleBorder::BOTTOM_LEFT, kSmallSize, kSmallHorizArrow, kSmallNoArrow }, - { BubbleBorder::BOTTOM_CENTER, kSmallSize, kSmallHorizArrow, - kSmallNoArrow }, - { BubbleBorder::BOTTOM_RIGHT, kSmallSize, kSmallHorizArrow, kSmallNoArrow }, - { BubbleBorder::LEFT_TOP, kSmallSize, kSmallVertArrow, kSmallNoArrow }, - { BubbleBorder::LEFT_CENTER, kSmallSize, kSmallVertArrow, kSmallNoArrow }, - { BubbleBorder::LEFT_BOTTOM, kSmallSize, kSmallVertArrow, kSmallNoArrow }, - { BubbleBorder::RIGHT_TOP, kSmallSize, kSmallVertArrow, kSmallNoArrow }, - { BubbleBorder::RIGHT_CENTER, kSmallSize, kSmallVertArrow, kSmallNoArrow }, - { BubbleBorder::RIGHT_BOTTOM, kSmallSize, kSmallVertArrow, kSmallNoArrow }, - { BubbleBorder::NONE, kSmallSize, kSmallNoArrow, kSmallNoArrow }, - { BubbleBorder::FLOAT, kSmallSize, kSmallNoArrow, kSmallNoArrow }, - - // Content size: kMediumSize - { BubbleBorder::TOP_LEFT, kMediumSize, kMediumHorizArrow, kMediumNoArrow }, - { BubbleBorder::TOP_CENTER, kMediumSize, kMediumHorizArrow, - kMediumNoArrow }, - { BubbleBorder::TOP_RIGHT, kMediumSize, kMediumHorizArrow, kMediumNoArrow }, - { BubbleBorder::BOTTOM_LEFT, kMediumSize, kMediumHorizArrow, - kMediumNoArrow }, - { BubbleBorder::BOTTOM_CENTER, kMediumSize, kMediumHorizArrow, - kMediumNoArrow }, - { BubbleBorder::BOTTOM_RIGHT, kMediumSize, kMediumHorizArrow, - kMediumNoArrow }, - { BubbleBorder::LEFT_TOP, kMediumSize, kMediumVertArrow, kMediumNoArrow }, - { BubbleBorder::LEFT_CENTER, kMediumSize, kMediumVertArrow, - kMediumNoArrow }, - { BubbleBorder::LEFT_BOTTOM, kMediumSize, kMediumVertArrow, - kMediumNoArrow }, - { BubbleBorder::RIGHT_TOP, kMediumSize, kMediumVertArrow, kMediumNoArrow }, - { BubbleBorder::RIGHT_CENTER, kMediumSize, kMediumVertArrow, - kMediumNoArrow }, - { BubbleBorder::RIGHT_BOTTOM, kMediumSize, kMediumVertArrow, - kMediumNoArrow }, - { BubbleBorder::NONE, kMediumSize, kMediumNoArrow, kMediumNoArrow }, - { BubbleBorder::FLOAT, kMediumSize, kMediumNoArrow, kMediumNoArrow } - }; + // Content size: kSmallSize + {BubbleBorder::TOP_LEFT, kSmallSize, kSmallNoArrow}, + {BubbleBorder::TOP_CENTER, kSmallSize, kSmallNoArrow}, + {BubbleBorder::TOP_RIGHT, kSmallSize, kSmallNoArrow}, + {BubbleBorder::BOTTOM_LEFT, kSmallSize, kSmallNoArrow}, + {BubbleBorder::BOTTOM_CENTER, kSmallSize, kSmallNoArrow}, + {BubbleBorder::BOTTOM_RIGHT, kSmallSize, kSmallNoArrow}, + {BubbleBorder::LEFT_TOP, kSmallSize, kSmallNoArrow}, + {BubbleBorder::LEFT_CENTER, kSmallSize, kSmallNoArrow}, + {BubbleBorder::LEFT_BOTTOM, kSmallSize, kSmallNoArrow}, + {BubbleBorder::RIGHT_TOP, kSmallSize, kSmallNoArrow}, + {BubbleBorder::RIGHT_CENTER, kSmallSize, kSmallNoArrow}, + {BubbleBorder::RIGHT_BOTTOM, kSmallSize, kSmallNoArrow}, + {BubbleBorder::NONE, kSmallSize, kSmallNoArrow}, + {BubbleBorder::FLOAT, kSmallSize, kSmallNoArrow}, + + // Content size: kMediumSize + {BubbleBorder::TOP_LEFT, kMediumSize, kMediumNoArrow}, + {BubbleBorder::TOP_CENTER, kMediumSize, kMediumNoArrow}, + {BubbleBorder::TOP_RIGHT, kMediumSize, kMediumNoArrow}, + {BubbleBorder::BOTTOM_LEFT, kMediumSize, kMediumNoArrow}, + {BubbleBorder::BOTTOM_CENTER, kMediumSize, kMediumNoArrow}, + {BubbleBorder::BOTTOM_RIGHT, kMediumSize, kMediumNoArrow}, + {BubbleBorder::LEFT_TOP, kMediumSize, kMediumNoArrow}, + {BubbleBorder::LEFT_CENTER, kMediumSize, kMediumNoArrow}, + {BubbleBorder::LEFT_BOTTOM, kMediumSize, kMediumNoArrow}, + {BubbleBorder::RIGHT_TOP, kMediumSize, kMediumNoArrow}, + {BubbleBorder::RIGHT_CENTER, kMediumSize, kMediumNoArrow}, + {BubbleBorder::RIGHT_BOTTOM, kMediumSize, kMediumNoArrow}, + {BubbleBorder::NONE, kMediumSize, kMediumNoArrow}, + {BubbleBorder::FLOAT, kMediumSize, kMediumNoArrow}}; for (size_t i = 0; i < arraysize(cases); ++i) { SCOPED_TRACE(base::StringPrintf("i=%d arrow=%d", static_cast<int>(i), cases[i].arrow)); border.set_arrow(cases[i].arrow); - - border.set_paint_arrow(BubbleBorder::PAINT_NORMAL); - EXPECT_EQ(cases[i].expected_with_arrow, - border.GetSizeForContentsSize(cases[i].content)); - - border.set_paint_arrow(BubbleBorder::PAINT_TRANSPARENT); - EXPECT_EQ(cases[i].expected_without_arrow, - border.GetSizeForContentsSize(cases[i].content)); - - border.set_paint_arrow(BubbleBorder::PAINT_NONE); EXPECT_EQ(cases[i].expected_without_arrow, border.GetSizeForContentsSize(cases[i].content)); } @@ -348,195 +302,67 @@ TEST_F(BubbleBorderTest, GetBoundsOriginTest) { const gfx::Size kContentSize(500, 600); const gfx::Insets kInsets = border.GetInsets(); - const views::internal::BorderImages* kImages = border.GetImagesForTest(); - border.set_arrow(BubbleBorder::TOP_LEFT); - const gfx::Size kTotalSizeWithHorizArrow = - border.GetSizeForContentsSize(kContentSize); + const gfx::Size kTotalSize = border.GetSizeForContentsSize(kContentSize); border.set_arrow(BubbleBorder::RIGHT_BOTTOM); - const gfx::Size kTotalSizeWithVertArrow = - border.GetSizeForContentsSize(kContentSize); + EXPECT_EQ(kTotalSize, border.GetSizeForContentsSize(kContentSize)); border.set_arrow(BubbleBorder::NONE); - const gfx::Size kTotalSizeWithNoArrow = - border.GetSizeForContentsSize(kContentSize); - - const int kBorderThickness = border.GetBorderThickness(); - - const int kArrowOffsetForHorizCenter = kTotalSizeWithHorizArrow.width() / 2; - const int kArrowOffsetForVertCenter = kTotalSizeWithVertArrow.height() / 2; - const int kArrowOffsetForNotCenter = - UseMd() ? 0 : kImages->border_thickness + (kImages->arrow_width / 2); + EXPECT_EQ(kTotalSize, border.GetSizeForContentsSize(kContentSize)); const int kStrokeWidth = shadow == BubbleBorder::NO_ASSETS ? 0 : BubbleBorder::kStroke; - const int kArrowThickness = UseMd() ? 0 : kImages->arrow_interior_thickness; - const int kArrowShift = - UseMd() ? 0 : kArrowThickness + kStrokeWidth - kImages->arrow_thickness; - const int kHeightDifference = - kTotalSizeWithHorizArrow.height() - kTotalSizeWithNoArrow.height(); - const int kWidthDifference = - kTotalSizeWithVertArrow.width() - kTotalSizeWithNoArrow.width(); - EXPECT_EQ(kHeightDifference, kWidthDifference); - - // The arrow only makes a difference in height if it is longer than the - // shadow. - const int kExpectedHeightDifference = - UseMd() ? 0 - : std::max(kImages->arrow_thickness + - kImages->border_interior_thickness, - kImages->border_thickness) - - std::max(kImages->border_interior_thickness, - kImages->border_thickness); - EXPECT_EQ(kExpectedHeightDifference, kHeightDifference) - << "Size with arrow: " << kTotalSizeWithHorizArrow.ToString() - << " vs. size without arrow: " << kTotalSizeWithNoArrow.ToString(); - - const int kTopHorizArrowY = - UseMd() ? kAnchor.bottom() + kStrokeWidth - kInsets.top() - : kAnchor.bottom() + kArrowShift; - const int kBottomHorizArrowY = - UseMd() ? kAnchor.y() - kTotalSizeWithHorizArrow.height() - : kAnchor.y() - kArrowShift - kTotalSizeWithHorizArrow.height(); - const int kLeftVertArrowX = kAnchor.x() + kAnchor.width() + kArrowShift; - const int kRightVertArrowX = - UseMd() ? kAnchor.x() - kTotalSizeWithHorizArrow.width() - : kAnchor.x() - kArrowShift - kTotalSizeWithVertArrow.width(); + const int kTopHorizArrowY = kAnchor.bottom() + kStrokeWidth - kInsets.top(); + const int kBottomHorizArrowY = kAnchor.y() - kTotalSize.height(); + const int kLeftVertArrowX = kAnchor.x() + kAnchor.width(); + const int kRightVertArrowX = kAnchor.x() - kTotalSize.width(); struct TestCase { BubbleBorder::Arrow arrow; - BubbleBorder::BubbleAlignment alignment; int expected_x; int expected_y; }; TestCase cases[] = { // Horizontal arrow tests. - {BubbleBorder::TOP_LEFT, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - UseMd() ? kAnchor.x() + kStrokeWidth - kInsets.left() - : kAnchor.CenterPoint().x() - kArrowOffsetForNotCenter, + {BubbleBorder::TOP_LEFT, kAnchor.x() + kStrokeWidth - kInsets.left(), kTopHorizArrowY}, - {BubbleBorder::TOP_LEFT, BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE, - UseMd() ? kAnchor.x() + kStrokeWidth - kInsets.left() - : kAnchor.x() + kStrokeWidth - kBorderThickness, - kTopHorizArrowY}, - {BubbleBorder::TOP_CENTER, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - kAnchor.CenterPoint().x() - kArrowOffsetForHorizCenter, - kTopHorizArrowY}, - {BubbleBorder::BOTTOM_RIGHT, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - UseMd() ? kAnchor.CenterPoint().x() - kTotalSizeWithHorizArrow.width() - : kAnchor.CenterPoint().x() + kArrowOffsetForNotCenter - - kTotalSizeWithHorizArrow.width(), - kBottomHorizArrowY}, - {BubbleBorder::BOTTOM_RIGHT, BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE, - kAnchor.x() + kAnchor.width() - kTotalSizeWithHorizArrow.width() + - kBorderThickness - kStrokeWidth, + {BubbleBorder::TOP_CENTER, + kAnchor.CenterPoint().x() - (kTotalSize.width() / 2), kTopHorizArrowY}, + {BubbleBorder::BOTTOM_RIGHT, + kAnchor.x() + kAnchor.width() - kTotalSize.width() - kStrokeWidth, kBottomHorizArrowY}, // Vertical arrow tests. - {BubbleBorder::LEFT_TOP, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - kLeftVertArrowX, kAnchor.CenterPoint().y() - kArrowOffsetForNotCenter}, - {BubbleBorder::LEFT_TOP, BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE, - kLeftVertArrowX, kAnchor.y() + kStrokeWidth - kBorderThickness}, - {BubbleBorder::LEFT_CENTER, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - kLeftVertArrowX - (UseMd() ? kInsets.right() - kStrokeWidth : 0), - kAnchor.CenterPoint().y() - kArrowOffsetForVertCenter + - (UseMd() ? 2 * kStrokeWidth : 0)}, - {BubbleBorder::RIGHT_BOTTOM, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - kRightVertArrowX, - kAnchor.CenterPoint().y() + kArrowOffsetForNotCenter - - kTotalSizeWithVertArrow.height()}, - {BubbleBorder::RIGHT_BOTTOM, BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE, - kRightVertArrowX, - kAnchor.y() + kAnchor.height() - kTotalSizeWithVertArrow.height() + - kBorderThickness - kStrokeWidth}, + {BubbleBorder::LEFT_TOP, kLeftVertArrowX, kAnchor.y() + kStrokeWidth}, + {BubbleBorder::LEFT_CENTER, + kLeftVertArrowX - (kInsets.right() - kStrokeWidth), + kAnchor.CenterPoint().y() - (kTotalSize.height() / 2) + + (2 * kStrokeWidth)}, + {BubbleBorder::RIGHT_BOTTOM, kRightVertArrowX, + kAnchor.y() + kAnchor.height() - kTotalSize.height() - kStrokeWidth}, // No arrow tests. - {BubbleBorder::NONE, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - kAnchor.x() + (kAnchor.width() - kTotalSizeWithNoArrow.width()) / 2, + {BubbleBorder::NONE, + kAnchor.x() + (kAnchor.width() - kTotalSize.width()) / 2, kAnchor.y() + kAnchor.height()}, - {BubbleBorder::FLOAT, BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR, - kAnchor.x() + (kAnchor.width() - kTotalSizeWithNoArrow.width()) / 2, - kAnchor.y() + (kAnchor.height() - kTotalSizeWithNoArrow.height()) / 2}, + {BubbleBorder::FLOAT, + kAnchor.x() + (kAnchor.width() - kTotalSize.width()) / 2, + kAnchor.y() + (kAnchor.height() - kTotalSize.height()) / 2}, }; for (size_t i = 0; i < arraysize(cases); ++i) { - SCOPED_TRACE(base::StringPrintf("i=%d arrow=%d alignment=%d", - static_cast<int>(i), cases[i].arrow, - cases[i].alignment)); + SCOPED_TRACE(base::StringPrintf("shadow=%d i=%d arrow=%d", + static_cast<int>(shadow), + static_cast<int>(i), cases[i].arrow)); const BubbleBorder::Arrow arrow = cases[i].arrow; border.set_arrow(arrow); - border.set_alignment(cases[i].alignment); - - border.set_paint_arrow(BubbleBorder::PAINT_NORMAL); gfx::Point origin = border.GetBounds(kAnchor, kContentSize).origin(); - int expected_x = cases[i].expected_x; - int expected_y = cases[i].expected_y; - EXPECT_EQ(expected_x, origin.x()); - EXPECT_EQ(expected_y, origin.y()); - - border.set_paint_arrow(BubbleBorder::PAINT_TRANSPARENT); - origin = border.GetBounds(kAnchor, kContentSize).origin(); - if (border.is_arrow_on_horizontal(arrow)) { - expected_y += BubbleBorder::is_arrow_on_top(arrow) - ? kArrowThickness - : (-kArrowThickness + kHeightDifference); - } else if (BubbleBorder::has_arrow(arrow)) { - expected_x += BubbleBorder::is_arrow_on_left(arrow) - ? kArrowThickness - : (-kArrowThickness + kWidthDifference); - } - EXPECT_EQ(expected_x, origin.x()); - EXPECT_EQ(expected_y, origin.y()); - - border.set_paint_arrow(BubbleBorder::PAINT_NONE); - origin = border.GetBounds(kAnchor, kContentSize).origin(); - expected_x = cases[i].expected_x; - expected_y = cases[i].expected_y; - if (border.is_arrow_on_horizontal(arrow) && - !BubbleBorder::is_arrow_on_top(arrow)) { - expected_y += kHeightDifference; - } else if (BubbleBorder::has_arrow(arrow) && - !border.is_arrow_on_horizontal(arrow) && - !BubbleBorder::is_arrow_on_left(arrow)) { - expected_x += kWidthDifference; - } - EXPECT_EQ(expected_x, origin.x()); - EXPECT_EQ(expected_y, origin.y()); + EXPECT_EQ(cases[i].expected_x, origin.x()); + EXPECT_EQ(cases[i].expected_y, origin.y()); } -} -} - -// Ensure all the shadow types pass some size validation and paint sanely. -TEST_F(BubbleBorderTest, ShadowTypes) { - if (UseMd()) - return; // This test doesn't mean anything in MD mode. - const gfx::Rect rect(0, 0, 320, 200); - View paint_view; - paint_view.SetBoundsRect(rect); - - for (int i = 0; i < BubbleBorder::SHADOW_COUNT; ++i) { - BubbleBorder::Shadow shadow = static_cast<BubbleBorder::Shadow>(i); - SCOPED_TRACE(testing::Message() << "BubbleBorder::Shadow: " << shadow); - gfx::Canvas canvas(gfx::Size(640, 480), 1.0f, false); - BubbleBorder border(BubbleBorder::TOP_LEFT, shadow, SK_ColorWHITE); - internal::BorderImages* border_images = border.GetImagesForTest(); - - // Arrow assets should always be at least as big as the drawn arrow. - EXPECT_GE(border_images->arrow_thickness, - border_images->arrow_interior_thickness); - EXPECT_GE(border_images->arrow_width, - 2 * border_images->arrow_interior_thickness); - - // Border assets should always be at least as thick as the hittable border. - EXPECT_GE(border_images->border_thickness, - border_images->border_interior_thickness); - - // For a TOP_LEFT arrow, the x-offset always matches the border thickness. - EXPECT_EQ(border.GetArrowRect(rect).x(), border_images->border_thickness); - border.Paint(paint_view, &canvas); } } diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc index 0bfe54962a2..84d9ade9513 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "base/metrics/histogram_macros.h" #include "build/build_config.h" @@ -202,11 +202,24 @@ View* BubbleDialogDelegateView::GetAnchorView() const { return anchor_view_tracker_->view(); } +void BubbleDialogDelegateView::set_arrow(BubbleBorder::Arrow arrow) { + if (arrow_ == arrow) + return; + arrow_ = arrow; + + // If set_arrow() is called before CreateWidget(), there's no need to update + // the BubbleFrameView. + if (GetBubbleFrameView()) { + GetBubbleFrameView()->bubble_border()->set_arrow(arrow); + SizeToContents(); + } +} + gfx::Rect BubbleDialogDelegateView::GetAnchorRect() const { if (!GetAnchorView()) return anchor_rect_; - anchor_rect_ = GetAnchorView()->GetBoundsInScreen(); + anchor_rect_ = GetAnchorView()->GetAnchorBoundsInScreen(); anchor_rect_.Inset(anchor_view_insets_); return anchor_rect_; } @@ -220,23 +233,6 @@ void BubbleDialogDelegateView::UseCompactMargins() { set_margins(gfx::Insets(kCompactMargin)); } -void BubbleDialogDelegateView::SetAlignment( - BubbleBorder::BubbleAlignment alignment) { - GetBubbleFrameView()->bubble_border()->set_alignment(alignment); - SizeToContents(); -} - -void BubbleDialogDelegateView::SetArrowPaintType( - BubbleBorder::ArrowPaintType paint_type) { - GetBubbleFrameView()->bubble_border()->set_paint_arrow(paint_type); - SizeToContents(); -} - -void BubbleDialogDelegateView::SetBorderInteriorThickness(int thickness) { - GetBubbleFrameView()->bubble_border()->SetBorderInteriorThickness(thickness); - SizeToContents(); -} - void BubbleDialogDelegateView::OnAnchorBoundsChanged() { SizeToContents(); } diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate.h b/chromium/ui/views/bubble/bubble_dialog_delegate_view.h index 216578e0961..51a95a0ab17 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate.h +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef UI_VIEWS_BUBBLE_BUBBLE_DIALOG_DELEGATE_H_ -#define UI_VIEWS_BUBBLE_BUBBLE_DIALOG_DELEGATE_H_ +#ifndef UI_VIEWS_BUBBLE_BUBBLE_DIALOG_DELEGATE_VIEW_H_ +#define UI_VIEWS_BUBBLE_BUBBLE_DIALOG_DELEGATE_VIEW_H_ #include <memory> @@ -72,7 +72,7 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, const gfx::Rect& anchor_rect() const { return anchor_rect_; } BubbleBorder::Arrow arrow() const { return arrow_; } - void set_arrow(BubbleBorder::Arrow arrow) { arrow_ = arrow; } + void set_arrow(BubbleBorder::Arrow arrow); void set_mirror_arrow_in_rtl(bool mirror) { mirror_arrow_in_rtl_ = mirror; } @@ -89,6 +89,8 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, title_margins_ = title_margins; } + // 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; } @@ -111,16 +113,6 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, // Sets the content margins to a default picked for smaller bubbles. void UseCompactMargins(); - // Sets the bubble alignment relative to the anchor. This may only be called - // after calling CreateBubble. - void SetAlignment(BubbleBorder::BubbleAlignment alignment); - - // Sets the bubble arrow paint type. - void SetArrowPaintType(BubbleBorder::ArrowPaintType paint_type); - - // Sets the bubble border interior thickness. - void SetBorderInteriorThickness(int thickness); - // 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. @@ -244,4 +236,4 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, } // namespace views -#endif // UI_VIEWS_BUBBLE_BUBBLE_DELEGATE2_H_ +#endif // UI_VIEWS_BUBBLE_BUBBLE_DIALOG_DELEGATE_VIEW_H_ diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_unittest.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc index 6b0e5d89b70..5de626a2bd4 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate_unittest.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include <stddef.h> @@ -86,10 +86,10 @@ class TestBubbleDialogDelegateView : public BubbleDialogDelegateView { DISALLOW_COPY_AND_ASSIGN(TestBubbleDialogDelegateView); }; -class BubbleDialogDelegateTest : public ViewsTestBase { +class BubbleDialogDelegateViewTest : public ViewsTestBase { public: - BubbleDialogDelegateTest() {} - ~BubbleDialogDelegateTest() override {} + BubbleDialogDelegateViewTest() {} + ~BubbleDialogDelegateViewTest() override {} // Creates and shows a test widget that owns its native widget. Widget* CreateTestWidget() { @@ -102,12 +102,12 @@ class BubbleDialogDelegateTest : public ViewsTestBase { } private: - DISALLOW_COPY_AND_ASSIGN(BubbleDialogDelegateTest); + DISALLOW_COPY_AND_ASSIGN(BubbleDialogDelegateViewTest); }; } // namespace -TEST_F(BubbleDialogDelegateTest, CreateDelegate) { +TEST_F(BubbleDialogDelegateViewTest, CreateDelegate) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -127,7 +127,7 @@ TEST_F(BubbleDialogDelegateTest, CreateDelegate) { EXPECT_TRUE(bubble_observer.widget_closed()); } -TEST_F(BubbleDialogDelegateTest, MirrorArrowInRtl) { +TEST_F(BubbleDialogDelegateViewTest, MirrorArrowInRtl) { std::string default_locale = base::i18n::GetConfiguredLocale(); std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); for (bool rtl : {false, true}) { @@ -146,7 +146,7 @@ TEST_F(BubbleDialogDelegateTest, MirrorArrowInRtl) { base::i18n::SetICUDefaultLocale(default_locale); } -TEST_F(BubbleDialogDelegateTest, CloseAnchorWidget) { +TEST_F(BubbleDialogDelegateViewTest, CloseAnchorWidget) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); BubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -177,7 +177,7 @@ TEST_F(BubbleDialogDelegateTest, CloseAnchorWidget) { // This test checks that the bubble delegate is capable to handle an early // destruction of the used anchor view. (Animations and delayed closure of the // bubble will call upon the anchor view to get its location). -TEST_F(BubbleDialogDelegateTest, CloseAnchorViewTest) { +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()); @@ -211,7 +211,7 @@ TEST_F(BubbleDialogDelegateTest, CloseAnchorViewTest) { } // Testing that a move of the anchor view will lead to new bubble locations. -TEST_F(BubbleDialogDelegateTest, TestAnchorRectMovesWithViewTest) { +TEST_F(BubbleDialogDelegateViewTest, TestAnchorRectMovesWithViewTest) { // Create an anchor widget and add a view to be used as anchor view. std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = @@ -226,7 +226,7 @@ TEST_F(BubbleDialogDelegateTest, TestAnchorRectMovesWithViewTest) { EXPECT_NE(view_rect.ToString(), view_rect_2.ToString()); } -TEST_F(BubbleDialogDelegateTest, ResetAnchorWidget) { +TEST_F(BubbleDialogDelegateViewTest, ResetAnchorWidget) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); BubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -267,7 +267,7 @@ TEST_F(BubbleDialogDelegateTest, ResetAnchorWidget) { EXPECT_TRUE(bubble_observer.widget_closed()); } -TEST_F(BubbleDialogDelegateTest, InitiallyFocusedView) { +TEST_F(BubbleDialogDelegateViewTest, InitiallyFocusedView) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); BubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -279,29 +279,28 @@ TEST_F(BubbleDialogDelegateTest, InitiallyFocusedView) { bubble_widget->CloseNow(); } -TEST_F(BubbleDialogDelegateTest, NonClientHitTest) { +TEST_F(BubbleDialogDelegateViewTest, NonClientHitTest) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); BubbleDialogDelegateView::CreateBubble(bubble_delegate); BubbleFrameView* frame = bubble_delegate->GetBubbleFrameView(); - const int border = frame->bubble_border()->GetBorderThickness(); struct { const int point; const int hit; } cases[] = { - {border, HTNOWHERE}, {border + 60, HTCLIENT}, {1000, HTNOWHERE}, + {0, HTNOWHERE}, {60, HTCLIENT}, {1000, HTNOWHERE}, }; for (size_t i = 0; i < arraysize(cases); ++i) { gfx::Point point(cases[i].point, cases[i].point); EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point)) - << " with border: " << border << ", at point " << cases[i].point; + << " at point " << cases[i].point; } } -TEST_F(BubbleDialogDelegateTest, VisibleWhenAnchorWidgetBoundsChanged) { +TEST_F(BubbleDialogDelegateViewTest, VisibleWhenAnchorWidgetBoundsChanged) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); BubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -318,7 +317,7 @@ TEST_F(BubbleDialogDelegateTest, VisibleWhenAnchorWidgetBoundsChanged) { // Test that setting WidgetDelegate::set_can_activate() to false makes the // widget created via BubbleDialogDelegateView::CreateBubble() not activatable. -TEST_F(BubbleDialogDelegateTest, NotActivatable) { +TEST_F(BubbleDialogDelegateViewTest, NotActivatable) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); BubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -329,7 +328,7 @@ TEST_F(BubbleDialogDelegateTest, NotActivatable) { EXPECT_FALSE(bubble_widget->CanActivate()); } -TEST_F(BubbleDialogDelegateTest, CloseMethods) { +TEST_F(BubbleDialogDelegateViewTest, CloseMethods) { { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); BubbleDialogDelegateView* bubble_delegate = @@ -373,7 +372,7 @@ TEST_F(BubbleDialogDelegateTest, CloseMethods) { } } -TEST_F(BubbleDialogDelegateTest, CustomTitle) { +TEST_F(BubbleDialogDelegateViewTest, CustomTitle) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -447,7 +446,7 @@ TEST_F(BubbleDialogDelegateTest, CustomTitle) { // Ensure the BubbleFrameView correctly resizes when the title is provided by a // StyledLabel. -TEST_F(BubbleDialogDelegateTest, StyledLabelTitle) { +TEST_F(BubbleDialogDelegateViewTest, StyledLabelTitle) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(anchor_widget->GetContentsView()); @@ -478,7 +477,7 @@ TEST_F(BubbleDialogDelegateTest, StyledLabelTitle) { bubble_widget->GetWindowBoundsInScreen().height()); } -TEST_F(BubbleDialogDelegateTest, VisibleAnchorChanges) { +TEST_F(BubbleDialogDelegateViewTest, VisibleAnchorChanges) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = new TestBubbleDialogDelegateView(nullptr); diff --git a/chromium/ui/views/bubble/bubble_frame_view.cc b/chromium/ui/views/bubble/bubble_frame_view.cc index 50fce2ddc25..f43e505afb4 100644 --- a/chromium/ui/views/bubble/bubble_frame_view.cc +++ b/chromium/ui/views/bubble/bubble_frame_view.cc @@ -12,7 +12,6 @@ #include "ui/base/default_style.h" #include "ui/base/hit_test.h" #include "ui/base/l10n/l10n_util.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" #include "ui/compositor/paint_recorder.h" #include "ui/display/display.h" @@ -25,7 +24,7 @@ #include "ui/resources/grit/ui_resources.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/bubble/bubble_border.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/image_button_factory.h" #include "ui/views/controls/image_view.h" @@ -127,21 +126,8 @@ std::unique_ptr<Label> BubbleFrameView::CreateDefaultTitleLabel( // static Button* BubbleFrameView::CreateCloseButton(ButtonListener* listener) { ImageButton* close_button = nullptr; - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { - close_button = CreateVectorImageButton(listener); - SetImageFromVectorIcon(close_button, vector_icons::kCloseRoundedIcon); - } else { - ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); - close_button = new ImageButton(listener); - close_button->SetImage(Button::STATE_NORMAL, - *rb->GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia()); - close_button->SetImage( - Button::STATE_HOVERED, - *rb->GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia()); - close_button->SetImage( - Button::STATE_PRESSED, - *rb->GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia()); - } + close_button = CreateVectorImageButton(listener); + SetImageFromVectorIcon(close_button, vector_icons::kCloseRoundedIcon); close_button->SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_CLOSE)); close_button->SizeToPreferredSize(); @@ -249,9 +235,6 @@ void BubbleFrameView::GetWindowMask(const gfx::Size& size, rect.fBottom += SkIntToScalar(kBottomBorderShadowSize); window_mask->addRect(rect); } - gfx::Path arrow_path; - if (bubble_border_->GetArrowPath(gfx::Rect(size), &arrow_path)) - window_mask->addPath(arrow_path, 0, 0); } void BubbleFrameView::ResetWindowControls() { @@ -272,6 +255,7 @@ void BubbleFrameView::UpdateWindowTitle() { !delegate->GetWindowTitle().empty()); default_title_->SetText(delegate->GetWindowTitle()); } // custom_title_'s updates are handled by its creator. + Layout(); } void BubbleFrameView::SizeConstraintsChanged() {} @@ -574,8 +558,7 @@ void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect, // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble // window needs to be moved to the right and that means we need to move arrow // to the left, and that means negative offset. - bubble_border_->set_arrow_offset( - bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust); + bubble_border_->set_arrow_offset(-offscreen_adjust); if (offscreen_adjust) SchedulePaint(); } diff --git a/chromium/ui/views/bubble/bubble_frame_view.h b/chromium/ui/views/bubble/bubble_frame_view.h index 8dedcaf8203..eef3fda7a3e 100644 --- a/chromium/ui/views/bubble/bubble_frame_view.h +++ b/chromium/ui/views/bubble/bubble_frame_view.h @@ -116,7 +116,7 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView, FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, RemoveFootnoteView); FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, LayoutWithIcon); FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CloseReasons); - FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateTest, CloseMethods); + FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest, CloseMethods); // Mirrors the bubble's arrow location on the |vertical| or horizontal axis, // if the generated window bounds don't fit in the monitor bounds. diff --git a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc index bd891dc540b..0a060d2fbf5 100644 --- a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc +++ b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc @@ -9,13 +9,12 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/text_utils.h" #include "ui/views/bubble/bubble_border.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/test/test_layout_provider.h" #include "ui/views/test/test_views.h" @@ -29,10 +28,6 @@ typedef ViewsTestBase BubbleFrameViewTest; namespace { -bool UseMd() { - return ui::MaterialDesignController::IsSecondaryUiMaterial(); -} - const BubbleBorder::Arrow kArrow = BubbleBorder::TOP_LEFT; const SkColor kColor = SK_ColorRED; const int kMargin = 6; @@ -50,7 +45,8 @@ const int kExpectedAdditionalHeight = 12; class TestBubbleFrameViewWidgetDelegate : public WidgetDelegate { public: - TestBubbleFrameViewWidgetDelegate(Widget* widget) : widget_(widget) {} + explicit TestBubbleFrameViewWidgetDelegate(Widget* widget) + : widget_(widget) {} ~TestBubbleFrameViewWidgetDelegate() override {} @@ -77,35 +73,31 @@ class TestBubbleFrameViewWidgetDelegate : public WidgetDelegate { } private: - Widget* widget_; + Widget* const widget_; View* contents_view_ = nullptr; // Owned by |widget_|. bool should_show_close_ = false; }; class TestBubbleFrameView : public BubbleFrameView { public: - TestBubbleFrameView(ViewsTestBase* test_base) + explicit TestBubbleFrameView(ViewsTestBase* test_base) : BubbleFrameView(gfx::Insets(), gfx::Insets(kMargin)), - test_base_(test_base), available_bounds_(gfx::Rect(0, 0, 1000, 1000)) { - SetBubbleBorder(std::unique_ptr<BubbleBorder>( - new BubbleBorder(kArrow, BubbleBorder::NO_SHADOW, kColor))); + SetBubbleBorder(std::make_unique<BubbleBorder>( + kArrow, BubbleBorder::NO_SHADOW, kColor)); + widget_ = std::make_unique<Widget>(); + widget_delegate_ = + std::make_unique<TestBubbleFrameViewWidgetDelegate>(widget_.get()); + Widget::InitParams params = + test_base->CreateParams(Widget::InitParams::TYPE_BUBBLE); + params.delegate = widget_delegate_.get(); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget_->Init(params); } ~TestBubbleFrameView() override {} // View overrides: const Widget* GetWidget() const override { - if (!widget_) { - widget_.reset(new Widget); - widget_delegate_.reset( - new TestBubbleFrameViewWidgetDelegate(widget_.get())); - Widget::InitParams params = - test_base_->CreateParams(Widget::InitParams::TYPE_BUBBLE); - params.delegate = widget_delegate_.get(); - params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - widget_->Init(params); - } - return widget_.get(); } @@ -119,13 +111,10 @@ class TestBubbleFrameView : public BubbleFrameView { } private: - ViewsTestBase* test_base_; - - gfx::Rect available_bounds_; + const gfx::Rect available_bounds_; - // Widget returned by GetWidget(). Only created if GetWidget() is called. - mutable std::unique_ptr<TestBubbleFrameViewWidgetDelegate> widget_delegate_; - mutable std::unique_ptr<Widget> widget_; + std::unique_ptr<TestBubbleFrameViewWidgetDelegate> widget_delegate_; + std::unique_ptr<Widget> widget_; DISALLOW_COPY_AND_ASSIGN(TestBubbleFrameView); }; @@ -159,9 +148,6 @@ TEST_F(BubbleFrameViewTest, RemoveFootnoteView) { TEST_F(BubbleFrameViewTest, GetBoundsForClientViewWithClose) { TestBubbleFrameView frame(this); - // TestBubbleFrameView::GetWidget() is responsible for creating the widget and - // widget delegate at first call, so it is called here for that side-effect. - ignore_result(frame.GetWidget()); frame.widget_delegate()->SetShouldShowCloseButton(true); frame.ResetWindowControls(); EXPECT_EQ(kArrow, frame.bubble_border()->arrow()); @@ -368,7 +354,7 @@ TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsCenterArrows) { gfx::Rect window_bounds; // Bubbles have a thicker shadow on the bottom in MD. // Match definition of kLargeShadowVerticalOffset in bubble_border.cc. - const int kLargeShadowVerticalOffset = UseMd() ? 2 : 0; + constexpr int kLargeShadowVerticalOffset = 2; // Some of these tests may go away once --secondary-ui-md becomes the // default. Under Material Design mode, the BubbleBorder doesn't support all @@ -376,16 +362,6 @@ TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsCenterArrows) { // added for MD mode. // Test that the bubble displays normally when it fits. - if (!UseMd()) { // TOP_CENTER isn't supported by the bubble_border() in MD. - frame.bubble_border()->set_arrow(BubbleBorder::TOP_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(500, 100, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::TOP_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.x() + window_bounds.width() / 2, 525); - } - frame.bubble_border()->set_arrow(BubbleBorder::BOTTOM_CENTER); window_bounds = frame.GetUpdatedWindowBounds( gfx::Rect(500, 900, 50, 50), // |anchor_rect| @@ -413,19 +389,6 @@ TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsCenterArrows) { 425 + kLargeShadowVerticalOffset); // Test bubble not fitting left screen edge. - if (!UseMd()) { // TOP_CENTER isn't supported by the bubble_border() in MD. - frame.bubble_border()->set_arrow(BubbleBorder::TOP_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(100, 100, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::TOP_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.x(), 0); - EXPECT_EQ(window_bounds.x() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 125); - } - frame.bubble_border()->set_arrow(BubbleBorder::BOTTOM_CENTER); window_bounds = frame.GetUpdatedWindowBounds( gfx::Rect(100, 900, 50, 50), // |anchor_rect| @@ -433,26 +396,8 @@ TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsCenterArrows) { true); // |adjust_if_offscreen| EXPECT_EQ(BubbleBorder::BOTTOM_CENTER, frame.bubble_border()->arrow()); EXPECT_EQ(window_bounds.x(), 0); - if (!UseMd()) { // There is no arrow offset in MD mode. - EXPECT_EQ(window_bounds.x() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 125); - } // Test bubble not fitting right screen edge. - if (!UseMd()) { // TOP_CENTER isn't supported by the bubble_border() in MD. - frame.bubble_border()->set_arrow(BubbleBorder::TOP_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(900, 100, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::TOP_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.right(), 1000); - EXPECT_EQ(window_bounds.x() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 925); - } - frame.bubble_border()->set_arrow(BubbleBorder::BOTTOM_CENTER); window_bounds = frame.GetUpdatedWindowBounds( gfx::Rect(900, 900, 50, 50), // |anchor_rect| @@ -460,60 +405,6 @@ TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsCenterArrows) { true); // |adjust_if_offscreen| EXPECT_EQ(BubbleBorder::BOTTOM_CENTER, frame.bubble_border()->arrow()); EXPECT_EQ(window_bounds.right(), 1000); - if (!UseMd()) { // There is no arrow offset in MD mode. - EXPECT_EQ(window_bounds.x() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 925); - } - - // Test bubble not fitting top screen edge. - if (!UseMd()) { // Moving the bubble by setting the arrow offset doesn't work - // in MD mode since there is no arrow displayed. - frame.bubble_border()->set_arrow(BubbleBorder::LEFT_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(100, 100, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::LEFT_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.y(), 0); - EXPECT_EQ(window_bounds.y() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 125); - - frame.bubble_border()->set_arrow(BubbleBorder::RIGHT_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(900, 100, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::RIGHT_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.y(), 0); - EXPECT_EQ(window_bounds.y() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 125); - - // Test bubble not fitting bottom screen edge. - frame.bubble_border()->set_arrow(BubbleBorder::LEFT_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(100, 900, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::LEFT_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.bottom(), 1000); - EXPECT_EQ(window_bounds.y() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 925); - - frame.bubble_border()->set_arrow(BubbleBorder::RIGHT_CENTER); - window_bounds = frame.GetUpdatedWindowBounds( - gfx::Rect(900, 900, 50, 50), // |anchor_rect| - gfx::Size(500, 500), // |client_size| - true); // |adjust_if_offscreen| - EXPECT_EQ(BubbleBorder::RIGHT_CENTER, frame.bubble_border()->arrow()); - EXPECT_EQ(window_bounds.bottom(), 1000); - EXPECT_EQ(window_bounds.y() + - frame.bubble_border()->GetArrowOffset(window_bounds.size()), - 925); - } } TEST_F(BubbleFrameViewTest, GetPreferredSize) { diff --git a/chromium/ui/views/bubble/info_bubble.h b/chromium/ui/views/bubble/info_bubble.h index 1c0e5074bff..c1f092d10d1 100644 --- a/chromium/ui/views/bubble/info_bubble.h +++ b/chromium/ui/views/bubble/info_bubble.h @@ -8,7 +8,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" #include "base/strings/string16.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" namespace views { diff --git a/chromium/ui/views/bubble/tooltip_icon.cc b/chromium/ui/views/bubble/tooltip_icon.cc index 2b88baf6279..8e1969cd449 100644 --- a/chromium/ui/views/bubble/tooltip_icon.cc +++ b/chromium/ui/views/bubble/tooltip_icon.cc @@ -61,7 +61,7 @@ void TooltipIcon::GetAccessibleNodeData(ui::AXNodeData* node_data) { void TooltipIcon::MouseMovedOutOfHost() { if (IsMouseHovered()) { - mouse_watcher_->Start(); + mouse_watcher_->Start(GetWidget()->GetNativeWindow()); return; } @@ -96,7 +96,7 @@ void TooltipIcon::ShowBubble() { View* frame = bubble_->GetWidget()->non_client_view()->frame_view(); mouse_watcher_ = std::make_unique<MouseWatcher>( std::make_unique<MouseWatcherViewHost>(frame, gfx::Insets()), this); - mouse_watcher_->Start(); + mouse_watcher_->Start(GetWidget()->GetNativeWindow()); } } diff --git a/chromium/ui/views/bubble/tray_bubble_view.cc b/chromium/ui/views/bubble/tray_bubble_view.cc index 397b49e9b04..9e2994cfefd 100644 --- a/chromium/ui/views/bubble/tray_bubble_view.cc +++ b/chromium/ui/views/bubble/tray_bubble_view.cc @@ -221,8 +221,6 @@ TrayBubbleView::TrayBubbleView(const InitParams& init_params) DCHECK(params_.parent_window); DCHECK(anchor_widget()); // Computed by BubbleDialogDelegateView(). bubble_border_->set_use_theme_background_color(!init_params.bg_color); - bubble_border_->set_alignment(BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); - bubble_border_->set_paint_arrow(BubbleBorder::PAINT_NONE); if (init_params.corner_radius) bubble_border_->SetCornerRadius(init_params.corner_radius.value()); set_parent_window(params_.parent_window); @@ -421,7 +419,7 @@ void TrayBubbleView::OnMouseEntered(const ui::MouseEvent& event) { // cannot see a lag. mouse_watcher_->set_notify_on_exit_time( base::TimeDelta::FromMilliseconds(kFrameTimeInMS)); - mouse_watcher_->Start(); + mouse_watcher_->Start(GetWidget()->GetNativeWindow()); } } diff --git a/chromium/ui/views/bubble/tray_bubble_view.h b/chromium/ui/views/bubble/tray_bubble_view.h index 346641e9ad5..5ef449781bc 100644 --- a/chromium/ui/views/bubble/tray_bubble_view.h +++ b/chromium/ui/views/bubble/tray_bubble_view.h @@ -12,7 +12,7 @@ #include "ui/accessibility/ax_enums.mojom.h" #include "ui/events/event.h" #include "ui/gfx/native_widget_types.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/mouse_watcher.h" #include "ui/views/views_export.h" diff --git a/chromium/ui/views/cocoa/DEPS b/chromium/ui/views/cocoa/DEPS index 3209b5ac9d8..fcaeee7d596 100644 --- a/chromium/ui/views/cocoa/DEPS +++ b/chromium/ui/views/cocoa/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+components/viz/common", "+ui/accelerated_widget_mac", + "+ui/views_bridge_mac", ] diff --git a/chromium/ui/views/cocoa/bridged_content_view.h b/chromium/ui/views/cocoa/bridged_content_view.h index a5a9b690ef2..a6daf562123 100644 --- a/chromium/ui/views/cocoa/bridged_content_view.h +++ b/chromium/ui/views/cocoa/bridged_content_view.h @@ -10,32 +10,36 @@ #include "base/strings/string16.h" #import "ui/base/cocoa/tool_tip_base_view.h" #import "ui/base/cocoa/tracking_area.h" +#include "ui/views/views_export.h" namespace ui { class TextInputClient; } namespace views { -class View; +class BridgedNativeWidget; } // The NSView that sits as the root contentView of the NSWindow, whilst it has // a views::RootView present. Bridges requests from Cocoa to the hosted // views::View. +VIEWS_EXPORT @interface BridgedContentView : ToolTipBaseView<NSTextInputClient, NSUserInterfaceValidations, NSDraggingSource> { @private - // Weak. The hosted RootView, owned by hostedView_->GetWidget(). - views::View* hostedView_; + // Weak, reset by clearView. + views::BridgedNativeWidget* bridge_; // Weak. If non-null the TextInputClient of the currently focused View in the // hierarchy rooted at |hostedView_|. Owned by the focused View. + // TODO(ccameron): Remove this member. ui::TextInputClient* textInputClient_; // The TextInputClient about to be set. Requests for a new -inputContext will // use this, but while the input is changing, |self| still needs to service // IME requests using the old |textInputClient_|. + // TODO(ccameron): Remove this member. ui::TextInputClient* pendingTextInputClient_; // A tracking area installed to enable mouseMoved events. @@ -51,12 +55,12 @@ class View; base::string16 lastTooltipText_; } -@property(readonly, nonatomic) views::View* hostedView; +@property(readonly, nonatomic) views::BridgedNativeWidget* bridge; @property(assign, nonatomic) ui::TextInputClient* textInputClient; @property(assign, nonatomic) BOOL drawMenuBackgroundForBlur; // Initialize the NSView -> views::View bridge. |viewToHost| must be non-NULL. -- (id)initWithView:(views::View*)viewToHost; +- (id)initWithBridge:(views::BridgedNativeWidget*)bridge bounds:(gfx::Rect)rect; // Clear the hosted view. For example, if it is about to be destroyed. - (void)clearView; diff --git a/chromium/ui/views/cocoa/bridged_content_view.mm b/chromium/ui/views/cocoa/bridged_content_view.mm index d6c0b74b395..9ffa0c183b3 100644 --- a/chromium/ui/views/cocoa/bridged_content_view.mm +++ b/chromium/ui/views/cocoa/bridged_content_view.mm @@ -14,7 +14,6 @@ #include "ui/base/cocoa/cocoa_base_utils.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/os_exchange_data_provider_mac.h" -#include "ui/base/hit_test.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_edit_commands.h" #include "ui/base/ime/text_input_client.h" @@ -32,16 +31,12 @@ #import "ui/gfx/path_mac.h" #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" #import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views/cocoa/bridged_native_widget_host.h" #import "ui/views/cocoa/drag_drop_client_mac.h" #include "ui/views/controls/label.h" -#include "ui/views/controls/menu/menu_config.h" -#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/view.h" #include "ui/views/widget/native_widget_mac.h" #include "ui/views/widget/widget.h" -#include "ui/views/word_lookup_client.h" - -using views::MenuController; namespace { @@ -67,14 +62,6 @@ gfx::Point MovePointToWindow(const NSPoint& point, NSHeight(content_rect) - point_in_window.y); } -// Dispatch |event| to |menu_controller| and return true if |event| is -// swallowed. -bool DispatchEventToMenu(MenuController* menu_controller, ui::KeyEvent* event) { - return menu_controller && - menu_controller->OnWillDispatchKeyEvent(event) == - ui::POST_DISPATCH_NONE; -} - // Returns true if |client| has RTL text. bool IsTextRTL(const ui::TextInputClient* client) { return client && client->GetTextDirection() == base::i18n::RIGHT_TO_LEFT; @@ -84,7 +71,8 @@ bool IsTextRTL(const ui::TextInputClient* client) { // otherwise be ignored by a ui::TextInputClient when inserted. bool IsImeTriggerEvent(NSEvent* event) { ui::KeyboardCode key = ui::KeyboardCodeFromNSEvent(event); - return key == ui::VKEY_RETURN || key == ui::VKEY_TAB; + return key == ui::VKEY_RETURN || key == ui::VKEY_TAB || + key == ui::VKEY_ESCAPE; } // Returns the boundary rectangle for composition characters in the @@ -218,9 +206,16 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { @interface BridgedContentView () -// Returns the active menu controller corresponding to |hostedView_|, -// nil otherwise. -- (MenuController*)activeMenuController; +// Dispatch |event| to |bridge_|'s host. +- (void)dispatchKeyEvent:(ui::KeyEvent*)event; + +// Returns true if active menu controller corresponds to this widget. Note that +// this will synchronously call into the browser process. +- (BOOL)hasActiveMenuController; + +// Dispatch |event| to |menu_controller| and return true if |event| is +// swallowed. +- (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event; // Passes |event| to the InputMethod for dispatch. - (void)handleKeyEvent:(ui::KeyEvent*)event; @@ -273,19 +268,18 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { @implementation BridgedContentView -@synthesize hostedView = hostedView_; +@synthesize bridge = bridge_; @synthesize textInputClient = textInputClient_; @synthesize drawMenuBackgroundForBlur = drawMenuBackgroundForBlur_; -- (id)initWithView:(views::View*)viewToHost { - DCHECK(viewToHost); - gfx::Rect bounds = viewToHost->bounds(); +- (id)initWithBridge:(views::BridgedNativeWidget*)bridge + bounds:(gfx::Rect)bounds { // To keep things simple, assume the origin is (0, 0) until there exists a use // case for something other than that. DCHECK(bounds.origin().IsOrigin()); NSRect initialFrame = NSMakeRect(0, 0, bounds.width(), bounds.height()); if ((self = [super initWithFrame:initialFrame])) { - hostedView_ = viewToHost; + bridge_ = bridge; // Apple's documentation says that NSTrackingActiveAlways is incompatible // with NSTrackingCursorUpdate, so use NSTrackingActiveInActiveApp. @@ -326,7 +320,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { - (void)clearView { [self setTextInputClient:nullptr]; - hostedView_ = nullptr; + bridge_ = nullptr; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; [cursorTrackingArea_.get() clearOwner]; [self removeTrackingArea:cursorTrackingArea_.get()]; @@ -391,20 +385,22 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } } -// If |point| is classified as HTCAPTION (draggable background), return nil so +// If |point| is classified as a draggable background (HTCAPTION), return nil so // that it can lead to a window drag or double-click in the title bar. Dragging // could be optimized by telling the window server which regions should be // instantly draggable without asking (tracked at https://crbug.com/830962). - (NSView*)hitTest:(NSPoint)point { gfx::Point flippedPoint(point.x, NSHeight(self.superview.bounds) - point.y); - int component = hostedView_->GetWidget()->GetNonClientComponent(flippedPoint); - if (component == HTCAPTION) + bool isDraggableBackground = false; + bridge_->host()->GetIsDraggableBackgroundAt(flippedPoint, + &isDraggableBackground); + if (isDraggableBackground) return nil; return [super hitTest:point]; } - (void)processCapturedMouseEvent:(NSEvent*)theEvent { - if (!hostedView_) + if (!bridge_) return; NSWindow* source = [theEvent window]; @@ -430,26 +426,19 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { if (isScrollEvent) { ui::ScrollEvent event(theEvent); event.set_location(event_location); - hostedView_->GetWidget()->OnScrollEvent(&event); + bridge_->host()->OnScrollEvent(event); } else { ui::MouseEvent event(theEvent); event.set_location(event_location); - hostedView_->GetWidget()->OnMouseEvent(&event); + bridge_->host()->OnMouseEvent(event); } } - (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent { - DCHECK(hostedView_); + DCHECK(bridge_); base::string16 newTooltipText; - views::View* view = hostedView_->GetTooltipHandlerForPoint(locationInContent); - if (view) { - gfx::Point viewPoint = locationInContent; - views::View::ConvertPointToScreen(hostedView_, &viewPoint); - views::View::ConvertPointFromScreen(view, &viewPoint); - if (!view->GetTooltipText(viewPoint, &newTooltipText)) - DCHECK(newTooltipText.empty()); - } + bridge_->host()->GetTooltipTextAt(locationInContent, &newTooltipText); if (newTooltipText != lastTooltipText_) { std::swap(newTooltipText, lastTooltipText_); [self setToolTipAtMousePoint:base::SysUTF16ToNSString(lastTooltipText_)]; @@ -457,34 +446,46 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (void)updateFullKeyboardAccess { - if (!hostedView_) + if (!bridge_) return; - - views::FocusManager* focusManager = - hostedView_->GetWidget()->GetFocusManager(); - if (focusManager) - focusManager->SetKeyboardAccessible([NSApp isFullKeyboardAccessEnabled]); + bridge_->host()->SetKeyboardAccessible([NSApp isFullKeyboardAccessEnabled]); } // BridgedContentView private implementation. -- (MenuController*)activeMenuController { - MenuController* menuController = MenuController::GetActiveInstance(); - return menuController && menuController->owner() == hostedView_->GetWidget() - ? menuController - : nullptr; +- (void)dispatchKeyEvent:(ui::KeyEvent*)event { + bool eventHandled = false; + if (bridge_) + bridge_->host()->DispatchKeyEvent(*event, &eventHandled); + if (eventHandled) + event->SetHandled(); } -- (void)handleKeyEvent:(ui::KeyEvent*)event { - if (!hostedView_) - return; +- (BOOL)hasActiveMenuController { + bool hasMenuController = false; + if (bridge_) + bridge_->host()->GetHasMenuController(&hasMenuController); + return hasMenuController; +} + +- (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event { + bool eventSwallowed = false; + bool eventHandled = false; + if (bridge_) { + bridge_->host()->DispatchKeyEventToMenuController(*event, &eventSwallowed, + &eventHandled); + } + if (eventHandled) + event->SetHandled(); + return eventSwallowed; +} +- (void)handleKeyEvent:(ui::KeyEvent*)event { DCHECK(event); - if (DispatchEventToMenu([self activeMenuController], event)) + if ([self dispatchKeyEventToMenuController:event]) return; - ignore_result( - hostedView_->GetWidget()->GetInputMethod()->DispatchKeyEvent(event)); + [self dispatchKeyEvent:event]; } - (BOOL)handleUnhandledKeyDownAsKeyEvent { @@ -501,7 +502,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { keyCode:(ui::KeyboardCode)keyCode domCode:(ui::DomCode)domCode eventFlags:(int)eventFlags { - if (!hostedView_) + if (!bridge_) return; // Always propagate the shift modifier if present. Shift doesn't always alter @@ -513,7 +514,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // Generate a synthetic event with the keycode toolkit-views expects. ui::KeyEvent event(ui::ET_KEY_PRESSED, keyCode, domCode, eventFlags); - if (DispatchEventToMenu([self activeMenuController], &event)) + if ([self dispatchKeyEventToMenuController:&event]) return; // If there's an active TextInputClient, schedule the editing command to be @@ -521,8 +522,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { if (textInputClient_ && textInputClient_->IsTextEditCommandEnabled(command)) textInputClient_->SetTextEditCommandForNextKeyEvent(command); - ignore_result( - hostedView_->GetWidget()->GetInputMethod()->DispatchKeyEvent(&event)); + [self dispatchKeyEvent:&event]; } - (void)adjustUiEventLocation:(ui::LocatedEvent*)event @@ -540,7 +540,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (void)insertTextInternal:(id)text { - if (!hostedView_) + if (!bridge_) return; if ([text isKindOfClass:[NSAttributedString class]]) @@ -591,7 +591,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { if (isFinalInsertForKeyEvent && ![self hasMarkedText]) { ui::KeyEvent charEvent([text characterAtIndex:0], ui::KeyboardCodeFromNSEvent(keyDownEvent_), - ui::EF_NONE); + ui::DomCodeFromNSEvent(keyDownEvent_), ui::EF_NONE); [self handleKeyEvent:&charEvent]; hasUnhandledKeyDownEvent_ = NO; if (charEvent.handled()) @@ -599,7 +599,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } // Forward the |text| to |textInputClient_| if no menu is active. - if (textInputClient_ && ![self activeMenuController]) { + if (textInputClient_ && ![self hasActiveMenuController]) { // If a single character is inserted by keyDown's call to // interpretKeyEvents: then use InsertChar() to allow editing events to be // merged. We use ui::VKEY_UNKNOWN as the key code since it's not feasible @@ -614,8 +614,9 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // |text|. This is because |keyDownEvent_| will correspond to the event that // caused the composition text to be confirmed, say, Return key press. if (isCharacterEvent) { - textInputClient_->InsertChar(ui::KeyEvent([text characterAtIndex:0], - ui::VKEY_UNKNOWN, ui::EF_NONE)); + textInputClient_->InsertChar( + ui::KeyEvent([text characterAtIndex:0], ui::VKEY_UNKNOWN, + ui::DomCode::NONE, ui::EF_NONE)); // Leave character events that may have triggered IME confirmation for // inline IME (e.g. Korean) as "unhandled". There will be no more // -insertText: messages, but we are unable to handle these via @@ -632,9 +633,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (views::DragDropClientMac*)dragDropClient { - views::BridgedNativeWidget* bridge = - views::NativeWidgetMac::GetBridgeForNativeWindow([self window]); - return bridge ? bridge->drag_drop_client() : nullptr; + return bridge_ ? bridge_->drag_drop_client() : nullptr; } - (void)undo:(id)sender { @@ -689,7 +688,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // Translates the location of |theEvent| to toolkit-views coordinates and passes // the event to NativeWidgetMac for handling. - (void)mouseEvent:(NSEvent*)theEvent { - if (!hostedView_) + if (!bridge_) return; DCHECK([theEvent type] != NSScrollWheel); @@ -699,8 +698,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). // Mac hooks in here. [self updateTooltipIfRequiredAt:event.location()]; - - hostedView_->GetWidget()->OnMouseEvent(&event); + bridge_->host()->OnMouseEvent(event); } - (void)forceTouchEvent:(NSEvent*)theEvent { @@ -720,15 +718,15 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { if ([[self window] firstResponder] != self) return NO; BOOL result = [super becomeFirstResponder]; - if (result && hostedView_) - hostedView_->GetWidget()->GetFocusManager()->RestoreFocusedView(); + if (result && bridge_) + bridge_->host()->SetIsFirstResponder(true); return result; } - (BOOL)resignFirstResponder { BOOL result = [super resignFirstResponder]; - if (result && hostedView_) - hostedView_->GetWidget()->GetFocusManager()->StoreFocusedView(true); + if (result && bridge_) + bridge_->host()->SetIsFirstResponder(false); return result; } @@ -747,22 +745,25 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // window containing it, since AppKit requires a titlebar to give frameless // windows correct shadows and rounded corners. NSWindow* window = [self window]; - if (window && [window contentView] == self) + if (window && [window contentView] == self) { newSize = [window contentRectForFrameRect:[window frame]].size; + // Ensure that the window geometry be updated on the host side before the + // view size is updated. + // TODO(ccameron): Consider updating the view size and window size and + // position together in UpdateWindowGeometry. + // https://crbug.com/875776, https://crbug.com/875731 + if (bridge_) + bridge_->UpdateWindowGeometry(); + } [super setFrameSize:newSize]; - if (!hostedView_) - return; - hostedView_->SetSize(gfx::Size(newSize.width, newSize.height)); + if (bridge_) + bridge_->host()->SetViewSize(gfx::Size(newSize.width, newSize.height)); } - (BOOL)isOpaque { - if (!hostedView_) - return NO; - - ui::Layer* layer = hostedView_->GetWidget()->GetLayer(); - return layer && layer->fills_bounds_opaquely(); + return bridge_ ? !bridge_->is_translucent_window() : NO; } // To maximize consistency with the Cocoa browser (mac_views_browser=0), accept @@ -803,7 +804,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // If a menu is active, and -[NSView interpretKeyEvents:] asks for the // input context, return nil. This ensures the action message is sent to // the view, rather than any NSTextInputClient a subview has installed. - if ([self activeMenuController]) + if ([self hasActiveMenuController]) return nil; // When not in an editable mode, or while entering passwords @@ -834,11 +835,24 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (void)keyDown:(NSEvent*)theEvent { + BOOL hadMarkedTextAtKeyDown = [self hasMarkedText]; + // Convert the event into an action message, according to OSX key mappings. keyDownEvent_ = theEvent; hasUnhandledKeyDownEvent_ = YES; [self interpretKeyEvents:@[ theEvent ]]; + // When there is marked text, -[NSView interpretKeyEvents:] may handle the + // event by dismissing the IME window in a way that neither unmarks text, nor + // updates any composition. That is, no signal is given either to the + // NSTextInputClient or the NSTextInputContext that the IME changed state. + // However, we must ensure this key down is not processed as an accelerator. + // TODO(tapted): Investigate removing the IsImeTriggerEvent() check - it's + // probably not required, but helps tests that expect some events to always + // get processed (i.e. TextfieldTest.TextInputClientTest). + if (hadMarkedTextAtKeyDown && IsImeTriggerEvent(theEvent)) + hasUnhandledKeyDownEvent_ = NO; + // If |keyDownEvent_| wasn't cleared during -interpretKeyEvents:, it wasn't // handled. Give Widget accelerators a chance to handle it. [self handleUnhandledKeyDownAsKeyEvent]; @@ -857,7 +871,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (void)scrollWheel:(NSEvent*)theEvent { - if (!hostedView_) + if (!bridge_) return; ui::ScrollEvent event(theEvent); @@ -866,14 +880,13 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). // Mac hooks in here. [self updateTooltipIfRequiredAt:event.location()]; - - hostedView_->GetWidget()->OnScrollEvent(&event); + bridge_->host()->OnScrollEvent(event); } // Called when we get a three-finger swipe, and they're enabled in System // Preferences. - (void)swipeWithEvent:(NSEvent*)event { - if (!hostedView_) + if (!bridge_) return; // themblsha: In my testing all three-finger swipes send only a single event @@ -894,35 +907,24 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { ui::GestureEvent gestureEvent(location.x(), location.y(), ui::EventFlagsFromNative(event), ui::EventTimeFromNative(event), swipeDetails); - - hostedView_->GetWidget()->OnGestureEvent(&gestureEvent); + bridge_->host()->OnGestureEvent(gestureEvent); } - (void)quickLookWithEvent:(NSEvent*)theEvent { - if (!hostedView_) + if (!bridge_) return; const gfx::Point locationInContent = gfx::ToFlooredPoint(ui::EventLocationFromNative(theEvent)); - views::View* target = hostedView_->GetEventHandlerForPoint(locationInContent); - if (!target) - return; - views::WordLookupClient* wordLookupClient = target->GetWordLookupClient(); - if (!wordLookupClient) - return; - - gfx::Point locationInTarget = locationInContent; - views::View::ConvertPointToTarget(hostedView_, target, &locationInTarget); + bool foundWord = false; gfx::DecoratedText decoratedWord; gfx::Point baselinePoint; - if (!wordLookupClient->GetWordLookupDataAtPoint( - locationInTarget, &decoratedWord, &baselinePoint)) { + bridge_->host()->GetWordAt(locationInContent, &foundWord, &decoratedWord, + &baselinePoint); + if (!foundWord) return; - } - // Convert |baselinePoint| to the coordinate system of |hostedView_|. - views::View::ConvertPointToTarget(target, hostedView_, &baselinePoint); NSPoint baselinePointAppKit = NSMakePoint( baselinePoint.x(), NSHeight([self frame]) - baselinePoint.y()); [self showDefinitionForAttributedString: @@ -1387,6 +1389,12 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { - (NSAttributedString*) attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { + // On TouchBar Macs, the IME subsystem sometimes sends an invalid range with a + // non-zero length. This will cause a DCHECK in gfx::Range, so repair it here. + // See https://crbug.com/888782. + if (range.location == NSNotFound) + range.length = 0; + gfx::Range actual_range; base::string16 substring = AttributedSubstringForRangeHelper( textInputClient_, gfx::Range(range), &actual_range); @@ -1442,7 +1450,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (void)insertText:(id)text replacementRange:(NSRange)replacementRange { - if (!hostedView_ || !textInputClient_) + if (!bridge_ || !textInputClient_) return; textInputClient_->DeleteRange(gfx::Range(replacementRange)); @@ -1524,11 +1532,9 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { command != ui::TextEditCommand::SELECT_ALL) return NO; - views::FocusManager* focus_manager = - hostedView_->GetWidget()->GetFocusManager(); - return focus_manager && focus_manager->GetFocusedView() && - focus_manager->GetFocusedView()->GetClassName() == - views::Label::kViewClassName; + bool is_textual = false; + bridge_->host()->GetIsFocusedViewTextual(&is_textual); + return is_textual; } // NSDraggingSource protocol implementation. @@ -1550,20 +1556,22 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { - (id)accessibilityAttributeValue:(NSString*)attribute { if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { - return @[ hostedView_->GetNativeViewAccessible() ]; + return @[ bridge_->host()->GetNativeViewAccessible() ]; } return [super accessibilityAttributeValue:attribute]; } - (id)accessibilityHitTest:(NSPoint)point { - return [hostedView_->GetNativeViewAccessible() accessibilityHitTest:point]; + return + [bridge_->host()->GetNativeViewAccessible() accessibilityHitTest:point]; } - (id)accessibilityFocusedUIElement { - if (!hostedView_) + if (!bridge_) return nil; - return [hostedView_->GetNativeViewAccessible() accessibilityFocusedUIElement]; + return [bridge_->host()->GetNativeViewAccessible() + accessibilityFocusedUIElement]; } @end diff --git a/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm b/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm index 1497d8d63d8..6f1d18d11c4 100644 --- a/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm +++ b/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm @@ -7,11 +7,9 @@ #import "base/mac/sdk_forward_declarations.h" #include "base/strings/sys_string_conversions.h" #import "ui/base/cocoa/touch_bar_forward_declarations.h" -#include "ui/base/models/dialog_model.h" #import "ui/views/cocoa/bridged_content_view.h" -#include "ui/views/widget/widget_delegate.h" -#include "ui/views/window/dialog_client_view.h" -#include "ui/views/window/dialog_delegate.h" +#import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views/cocoa/bridged_native_widget_host.h" namespace { @@ -29,21 +27,9 @@ NSString* const kTouchBarCancelId = @"com.google.chrome-CANCEL"; @implementation BridgedContentView (TouchBarAdditions) - (void)touchBarButtonAction:(id)sender { - if (!hostedView_) - return; - - views::DialogDelegate* dialog = - hostedView_->GetWidget()->widget_delegate()->AsDialogDelegate(); - DCHECK(dialog); - views::DialogClientView* client = dialog->GetDialogClientView(); - - if ([sender tag] == ui::DIALOG_BUTTON_OK) { - client->AcceptWindow(); - return; - } - - DCHECK_EQ([sender tag], ui::DIALOG_BUTTON_CANCEL); - client->CancelWindow(); + ui::DialogButton type = static_cast<ui::DialogButton>([sender tag]); + if (bridge_) + bridge_->host()->DoDialogButtonAction(type); } // NSTouchBarDelegate protocol implementation. @@ -51,7 +37,7 @@ NSString* const kTouchBarCancelId = @"com.google.chrome-CANCEL"; - (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier API_AVAILABLE(macos(10.12.2)) { - if (!hostedView_) + if (!bridge_) return nil; if ([identifier isEqualToString:kTouchBarDialogButtonsGroupId]) { @@ -76,19 +62,22 @@ NSString* const kTouchBarCancelId = @"com.google.chrome-CANCEL"; else return nil; - ui::DialogModel* model = - hostedView_->GetWidget()->widget_delegate()->AsDialogDelegate(); - if (!model || !(model->GetDialogButtons() & type)) + bool buttonExists = false; + base::string16 buttonLabel; + bool isButtonEnabled = false; + bool isButtonDefault = false; + bridge_->host()->GetDialogButtonInfo(type, &buttonExists, &buttonLabel, + &isButtonEnabled, &isButtonDefault); + if (!buttonExists) return nil; base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString( @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); - NSString* title = base::SysUTF16ToNSString(model->GetDialogButtonLabel(type)); NSButton* button = - [NSButton buttonWithTitle:title + [NSButton buttonWithTitle:base::SysUTF16ToNSString(buttonLabel) target:self action:@selector(touchBarButtonAction:)]; - if (type == model->GetDefaultDialogButton()) { + if (isButtonDefault) { // NSAlert uses a private NSButton subclass (_NSTouchBarGroupButton) with // more bells and whistles. It doesn't use -setBezelColor: directly, but // this gives an appearance matching the default _NSTouchBarGroupButton. @@ -97,7 +86,7 @@ NSString* const kTouchBarCancelId = @"com.google.chrome-CANCEL"; blue:0.843 alpha:1.0]]; } - [button setEnabled:model->IsDialogButtonEnabled(type)]; + [button setEnabled:isButtonEnabled]; [button setTag:type]; [item setView:button]; return item.autorelease(); @@ -106,12 +95,12 @@ NSString* const kTouchBarCancelId = @"com.google.chrome-CANCEL"; // NSTouchBarProvider protocol implementation (via NSResponder category). - (NSTouchBar*)makeTouchBar { - if (!hostedView_) + if (!bridge_) return nil; - ui::DialogModel* model = - hostedView_->GetWidget()->widget_delegate()->AsDialogDelegate(); - if (!model || !model->GetDialogButtons()) + bool buttonsExist = false; + bridge_->host()->GetDoDialogButtonsExist(&buttonsExist); + if (!buttonsExist) return nil; base::scoped_nsobject<NSTouchBar> bar( diff --git a/chromium/ui/views/cocoa/bridged_native_widget.h b/chromium/ui/views/cocoa/bridged_native_widget.h index 674a9b78228..6d2d12e6455 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget.h +++ b/chromium/ui/views/cocoa/bridged_native_widget.h @@ -13,32 +13,28 @@ #import "base/mac/scoped_nsobject.h" #include "base/macros.h" #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h" -#import "ui/accelerated_widget_mac/accelerated_widget_mac.h" #include "ui/accelerated_widget_mac/ca_transaction_observer.h" #include "ui/accelerated_widget_mac/display_ca_layer_tree.h" -#include "ui/base/ime/input_method_delegate.h" -#include "ui/compositor/layer_owner.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/display/display_observer.h" #import "ui/views/cocoa/bridged_native_widget_owner.h" #import "ui/views/cocoa/cocoa_mouse_capture_delegate.h" #import "ui/views/focus/focus_manager.h" #include "ui/views/views_export.h" #include "ui/views/widget/widget.h" -#include "ui/views/window/dialog_observer.h" +#include "ui/views_bridge_mac/mojo/bridged_native_widget.mojom.h" @class BridgedContentView; @class ModalShowAnimationWithLayer; +@class NativeWidgetMacNSWindow; @class ViewsNSWindowDelegate; -namespace ui { -class InputMethod; -class RecyclableCompositorMac; -} - namespace views { namespace test { class BridgedNativeWidgetTestApi; } +class BridgedNativeWidgetHost; class CocoaMouseCapture; class CocoaWindowMoveLoop; class DragDropClientMac; @@ -49,46 +45,40 @@ class View; // DesktopNativeWidgetMac. Serves as a helper class to bridge requests from the // NativeWidgetMac to the Cocoa window. Behaves a bit like an aura::Window. class VIEWS_EXPORT BridgedNativeWidget - : public ui::CATransactionCoordinator::PreCommitObserver, - public ui::LayerDelegate, - public ui::LayerOwner, - public ui::internal::InputMethodDelegate, + : public views_bridge_mac::mojom::BridgedNativeWidget, + public display::DisplayObserver, + public ui::CATransactionCoordinator::PreCommitObserver, public CocoaMouseCaptureDelegate, - public FocusChangeListener, - public ui::AcceleratedWidgetMacNSView, - public BridgedNativeWidgetOwner, - public DialogObserver { + public BridgedNativeWidgetOwner { public: // Contains NativeViewHost->gfx::NativeView associations. using AssociatedViews = std::map<const views::View*, NSView*>; - // Ways of changing the visibility of the bridged NSWindow. - enum WindowVisibilityState { - HIDE_WINDOW, // Hides with -[NSWindow orderOut:]. - SHOW_AND_ACTIVATE_WINDOW, // Shows with -[NSWindow makeKeyAndOrderFront:]. - SHOW_INACTIVE, // Shows with -[NSWindow orderWindow:..]. Orders - // the window above its parent if it has one. - }; - // Return the size that |window| will take for the given client area |size|, // based on its current style mask. static gfx::Size GetWindowSizeForClientSize(NSWindow* window, const gfx::Size& size); - // Creates one side of the bridge. |parent| must not be NULL. - explicit BridgedNativeWidget(NativeWidgetMac* parent); + // Creates one side of the bridge. |host| and |parent| must not be NULL. + BridgedNativeWidget(BridgedNativeWidgetHost* host, NativeWidgetMac* parent); ~BridgedNativeWidget() override; - // Initialize the bridge, "retains" ownership of |window|. - void Init(base::scoped_nsobject<NSWindow> window, - const Widget::InitParams& params); + // Initialize the NSWindow by taking ownership of the specified object. + // TODO(ccameron): When a BridgedNativeWidget is allocated across a process + // boundary, it will not be possible to explicitly set an NSWindow in this + // way. + void SetWindow(base::scoped_nsobject<NativeWidgetMacNSWindow> window); - // Invoked at the end of Widget::Init(). - void OnWidgetInitDone(); + // Create the drag drop client for this widget. + // TODO(ccameron): This function takes a views::View (and is not rolled into + // CreateContentView) because drag-drop has not been routed through |host_| + // yet. + void CreateDragDropClient(views::View* view); - // Sets or clears the focus manager to use for tracking focused views. - // This does NOT take ownership of |focus_manager|. - void SetFocusManager(FocusManager* focus_manager); + // Set the parent NSView for the widget. + // TODO(ccameron): Like SetWindow, this will need to pass a handle instead of + // an NSView across processes. + void SetParent(NSView* parent); // Changes the bounds of the window and the hosted layer if present. The // origin is a location in screen coordinates except for "child" windows, @@ -98,20 +88,6 @@ class VIEWS_EXPORT BridgedNativeWidget // views::GetAuraWindowTypeForWidgetType does not consider a "popup" type. void SetBounds(const gfx::Rect& new_bounds); - // Set or clears the views::View bridged by the content view. This does NOT - // take ownership of |view|. - void SetRootView(views::View* view); - - // Sets the desired visibility of the window and updates the visibility of - // descendant windows where necessary. - void SetVisibilityState(WindowVisibilityState new_state); - - // Acquiring mouse capture first steals capture from any existing - // CocoaMouseCaptureDelegate, then captures all mouse events until released. - void AcquireCapture(); - void ReleaseCapture(); - bool HasCapture(); - // Start moving the window, pinned to the mouse cursor, and monitor events. // Return MOVE_LOOP_SUCCESSFUL on mouse up or MOVE_LOOP_CANCELED on premature // termination via EndMoveLoop() or when window is destroyed during the drag. @@ -162,23 +138,10 @@ class VIEWS_EXPORT BridgedNativeWidget // Called by the NSWindowDelegate when the window becomes or resigns key. void OnWindowKeyStatusChangedTo(bool is_key); - // Called by NativeWidgetMac when the window size constraints change. - void OnSizeConstraintsChanged(); - // Called by the window show animation when it completes and wants to destroy // itself. void OnShowAnimationComplete(); - // See widget.h for documentation. - ui::InputMethod* GetInputMethod(); - - // The restored bounds will be derived from the current NSWindow frame unless - // fullscreen or transitioning between fullscreen states. - gfx::Rect GetRestoredBounds() const; - - // Creates a ui::Compositor which becomes responsible for drawing the window. - void CreateLayer(ui::LayerType layer_type, bool translucent); - // Updates |associated_views_| on NativeViewHost::Attach()/Detach(). void SetAssociationForView(const views::View* view, NSView* native_view); void ClearAssociationForView(const views::View* view); @@ -187,11 +150,13 @@ class VIEWS_EXPORT BridgedNativeWidget NativeWidgetMac* native_widget_mac() { return native_widget_mac_; } BridgedContentView* ns_view() { return bridged_view_; } - NSWindow* ns_window() { return window_; } + BridgedNativeWidgetHost* host() { return host_; } + NSWindow* ns_window(); TooltipManager* tooltip_manager() { return tooltip_manager_.get(); } DragDropClientMac* drag_drop_client() { return drag_drop_client_.get(); } + bool is_translucent_window() const { return is_translucent_window_; } // The parent widget specified in Widget::InitParams::parent. If non-null, the // parent will close children before the parent closes, and children will be @@ -227,12 +192,52 @@ class VIEWS_EXPORT BridgedNativeWidget bool ShouldRunCustomAnimationFor( Widget::VisibilityTransition transition) const; - // ui::CATransactionCoordinator::PreCommitObserver implementation + // display::DisplayObserver: + void OnDisplayMetricsChanged(const display::Display& display, + uint32_t metrics) override; + + // ui::CATransactionCoordinator::PreCommitObserver: bool ShouldWaitInPreCommit() override; base::TimeDelta PreCommitTimeout() override; - // Overridden from ui::internal::InputMethodDelegate: - ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override; + // views_bridge_mac::mojom::BridgedNativeWidget: + void InitWindow(views_bridge_mac::mojom::BridgedNativeWidgetInitParamsPtr + params) override; + void InitCompositorView() override; + void CreateContentView(const gfx::Rect& bounds) override; + void DestroyContentView() override; + void CloseWindow() override; + void CloseWindowNow() override; + void SetInitialBounds(const gfx::Rect& new_bounds, + const gfx::Size& minimum_content_size, + const gfx::Vector2d& parent_offset) override; + void SetBounds(const gfx::Rect& new_bounds, + const gfx::Size& minimum_content_size) override; + void SetVisibilityState( + views_bridge_mac::mojom::WindowVisibilityState new_state) override; + void SetVisibleOnAllSpaces(bool always_visible) override; + void SetFullscreen(bool fullscreen) override; + void SetMiniaturized(bool miniaturized) override; + void SetSizeConstraints(const gfx::Size& min_size, + const gfx::Size& max_size, + bool is_resizable, + bool is_maximizable) override; + void SetOpacity(float opacity) override; + void SetContentAspectRatio(const gfx::SizeF& aspect_ratio) override; + void SetCALayerParams(const gfx::CALayerParams& ca_layer_params) override; + void SetWindowTitle(const base::string16& title) override; + void MakeFirstResponder() override; + void ClearTouchBar() override; + void AcquireCapture() override; + void ReleaseCapture() override; + + // TODO(ccameron): This method exists temporarily as we move all direct access + // of TextInputClient out of BridgedContentView. + void SetTextInputClient(ui::TextInputClient* text_input_client); + + // Compute the window and content size, and forward them to |host_|. This will + // update widget and compositor size. + void UpdateWindowGeometry(); private: friend class test::BridgedNativeWidgetTestApi; @@ -243,32 +248,24 @@ class VIEWS_EXPORT BridgedNativeWidget // Notify descendants of a visibility change. void NotifyVisibilityChangeDown(); - // Essentially NativeWidgetMac::GetClientAreaBoundsInScreen().size(), but no - // coordinate transformations are required from AppKit coordinates. - gfx::Size GetClientAreaSize() const; - - // Creates an owned ui::Compositor. For consistency, these functions reflect - // those in aura::WindowTreeHost. - void CreateCompositor(); - void InitCompositor(); - void DestroyCompositor(); - // Installs the NSView for hosting the composited layer. void AddCompositorSuperview(); - // Size the layer to match the client area bounds, taking into account display - // scale factor. - void UpdateLayerProperties(); + // Query the display properties of the monitor that |window_| is on, and + // forward them to |host_|. + void UpdateWindowDisplay(); - // Immediately return if there is a composited frame matching |size_in_dip|. - // Otherwise, asks ui::WindowResizeHelperMac to run tasks until a matching - // frame is ready, or a timeout occurs. - void MaybeWaitForFrame(const gfx::Size& size_in_dip); + // Return true if the delegate's modal type is window-modal. These display as + // a native window "sheet", and have a different lifetime to regular windows. + bool IsWindowModalSheet() const; // Show the window using -[NSApp beginSheet:..], modal for the parent window. void ShowAsModalSheet(); - // Overridden from CocoaMouseCaptureDelegate: + // Returns true if capture exists and is currently active. + bool HasCapture(); + + // CocoaMouseCaptureDelegate: void PostCapturedEvent(NSEvent* event) override; void OnMouseCaptureLost() override; NSWindow* GetWindow() const override; @@ -277,85 +274,64 @@ class VIEWS_EXPORT BridgedNativeWidget // Creates and attaches a new instance if not found. NSMutableDictionary* GetWindowProperties() const; - // Overridden from FocusChangeListener: - void OnWillChangeFocus(View* focused_before, - View* focused_now) override; - void OnDidChangeFocus(View* focused_before, - View* focused_now) override; - - // Overridden from ui::LayerDelegate: - void OnPaintLayer(const ui::PaintContext& context) override; - void OnDeviceScaleFactorChanged(float old_device_scale_factor, - float new_device_scale_factor) override; - - // Overridden from ui::AcceleratedWidgetMac: - void AcceleratedWidgetCALayerParamsUpdated() override; - - // Overridden from BridgedNativeWidgetOwner: + // BridgedNativeWidgetOwner: NSWindow* GetNSWindow() override; gfx::Vector2d GetChildWindowOffset() const override; bool IsVisibleParent() const override; void RemoveChildWindow(BridgedNativeWidget* child) override; - // DialogObserver: - void OnDialogModelChanged() override; - - // Set |layer()| to be visible or not visible based on |window_visible_|. If - // the layer is not visible, then lock the compositor, so we don't draw any - // new frames. - void UpdateLayerVisibility(); - - views::NativeWidgetMac* native_widget_mac_; // Weak. Owns this. - base::scoped_nsobject<NSWindow> window_; + BridgedNativeWidgetHost* const host_; // Weak. Owns this. + NativeWidgetMac* const native_widget_mac_; // Weak. Owns |host_|. + base::scoped_nsobject<NativeWidgetMacNSWindow> window_; base::scoped_nsobject<ViewsNSWindowDelegate> window_delegate_; base::scoped_nsobject<BridgedContentView> bridged_view_; base::scoped_nsobject<ModalShowAnimationWithLayer> show_animation_; - std::unique_ptr<ui::InputMethod> input_method_; std::unique_ptr<CocoaMouseCapture> mouse_capture_; std::unique_ptr<CocoaWindowMoveLoop> window_move_loop_; std::unique_ptr<TooltipManager> tooltip_manager_; std::unique_ptr<DragDropClientMac> drag_drop_client_; - FocusManager* focus_manager_; // Weak. Owned by our Widget. - Widget::InitParams::Type widget_type_; + ui::ModalType modal_type_ = ui::MODAL_TYPE_NONE; + bool is_translucent_window_ = false; - BridgedNativeWidgetOwner* parent_; // Weak. If non-null, owns this. + BridgedNativeWidgetOwner* parent_ = nullptr; // Weak. If non-null, owns this. std::vector<BridgedNativeWidget*> child_windows_; + // The size of the content area of the window most recently sent to |host_| + // (and its compositor). + gfx::Size content_dip_size_; + + // The size of the frame most recently *received from* the compositor. Note + // that during resize (and showing new windows), this will lag behind + // |content_dip_size_|, which is the frame size most recently *sent to* the + // compositor. + gfx::Size compositor_frame_dip_size_; base::scoped_nsobject<NSView> compositor_superview_; std::unique_ptr<ui::DisplayCALayerTree> display_ca_layer_tree_; - std::unique_ptr<ui::RecyclableCompositorMac> compositor_; // Tracks the bounds when the window last started entering fullscreen. Used to // provide an answer for GetRestoredBounds(), but not ever sent to Cocoa (it // has its own copy, but doesn't provide access to it). gfx::Rect bounds_before_fullscreen_; - // Tracks the origin of the window (from the top-left of the screen) when it - // was last reported to toolkit-views. Needed to trigger move notifications - // associated with a window resize since AppKit won't send move notifications - // when the top-left point of the window moves vertically. The origin of the - // window in AppKit coordinates is not actually changing in this case. - gfx::Point last_window_frame_origin_; - // The transition types to animate when not relying on native NSWindow // animation behaviors. Bitmask of Widget::VisibilityTransition. int transitions_to_animate_ = Widget::ANIMATE_BOTH; // Whether this window wants to be fullscreen. If a fullscreen animation is in // progress then it might not be actually fullscreen. - bool target_fullscreen_state_; + bool target_fullscreen_state_ = false; // Whether this window is in a fullscreen transition, and the fullscreen state // can not currently be changed. - bool in_fullscreen_transition_; + bool in_fullscreen_transition_ = false; // Stores the value last read from -[NSWindow isVisible], to detect visibility // changes. - bool window_visible_; + bool window_visible_ = false; // If true, the window is either visible, or wants to be visible but is // currently hidden due to having a hidden parent. - bool wants_to_be_visible_; + bool wants_to_be_visible_ = false; // If true, then ignore interactions with CATransactionCoordinator until the // first frame arrives. diff --git a/chromium/ui/views/cocoa/bridged_native_widget.mm b/chromium/ui/views/cocoa/bridged_native_widget.mm index 69c5f1f44d7..abdbcc45fab 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget.mm @@ -14,48 +14,69 @@ #include "base/mac/mac_util.h" #import "base/mac/sdk_forward_declarations.h" #include "base/single_thread_task_runner.h" -#include "components/viz/common/features.h" -#include "components/viz/common/surfaces/local_surface_id.h" -#include "ui/accelerated_widget_mac/window_resize_helper_mac.h" +#include "base/strings/sys_string_conversions.h" #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" #include "ui/base/hit_test.h" -#include "ui/base/ime/input_method.h" -#include "ui/base/ime/input_method_factory.h" #include "ui/base/layout.h" #include "ui/base/ui_base_switches.h" -#include "ui/compositor/recyclable_compositor_mac.h" +#include "ui/display/screen.h" #include "ui/gfx/geometry/dip_util.h" #import "ui/gfx/mac/coordinate_conversion.h" #import "ui/gfx/mac/nswindow_frame_controls.h" #import "ui/native_theme/native_theme_mac.h" #import "ui/views/cocoa/bridged_content_view.h" +#import "ui/views/cocoa/bridged_native_widget_host.h" #import "ui/views/cocoa/cocoa_mouse_capture.h" #import "ui/views/cocoa/cocoa_window_move_loop.h" #import "ui/views/cocoa/drag_drop_client_mac.h" +#import "ui/views/cocoa/native_widget_mac_nswindow.h" #include "ui/views/cocoa/tooltip_manager_mac.h" #import "ui/views/cocoa/views_nswindow_delegate.h" #import "ui/views/cocoa/widget_owner_nswindow_adapter.h" -#include "ui/views/view.h" -#include "ui/views/views_delegate.h" #include "ui/views/widget/native_widget_mac.h" #include "ui/views/widget/widget.h" -#include "ui/views/widget/widget_aura_utils.h" -#include "ui/views/widget/widget_delegate.h" -#include "ui/views/window/dialog_delegate.h" -extern "C" { +using views_bridge_mac::mojom::WindowVisibilityState; -typedef int32_t CGSConnection; -CGSConnection _CGSDefaultConnection(); -CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection, - NSInteger windowNumber, - int radius); - -} namespace { constexpr auto kUIPaintTimeout = base::TimeDelta::FromSeconds(5); } // namespace +// Self-owning animation delegate that starts a hide animation, then calls +// -[NSWindow close] when the animation ends, releasing itself. +@interface ViewsNSWindowCloseAnimator : NSObject<NSAnimationDelegate> { + @private + base::scoped_nsobject<NSWindow> window_; + base::scoped_nsobject<NSAnimation> animation_; +} ++ (void)closeWindowWithAnimation:(NSWindow*)window; +@end + +@implementation ViewsNSWindowCloseAnimator + +- (id)initWithWindow:(NSWindow*)window { + if ((self = [super init])) { + window_.reset([window retain]); + animation_.reset( + [[ConstrainedWindowAnimationHide alloc] initWithWindow:window]); + [animation_ setDelegate:self]; + [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; + [animation_ startAnimation]; + } + return self; +} + ++ (void)closeWindowWithAnimation:(NSWindow*)window { + [[ViewsNSWindowCloseAnimator alloc] initWithWindow:window]; +} + +- (void)animationDidEnd:(NSAnimation*)animation { + [window_ close]; + [animation_ setDelegate:nil]; + [self release]; +} +@end + // The NSView that hosts the composited CALayer drawing the UI. It fills the // window but is not hittable so that accessibility hit tests always go to the // BridgedContentView. @@ -131,22 +152,6 @@ using NSViewComparatorValue = __kindof NSView*; int kWindowPropertiesKey; -float GetDeviceScaleFactorFromView(NSView* view) { - return ui::GetScaleFactorForNativeView(view); -} - -// Returns true if bounds passed to window in SetBounds should be treated as -// though they are in screen coordinates. -bool PositionWindowInScreenCoordinates(views::Widget* widget, - views::Widget::InitParams::Type type) { - // Replicate the logic in desktop_aura/desktop_screen_position_client.cc. - if (views::GetAuraWindowTypeForWidgetType(type) == - aura::client::WINDOW_TYPE_POPUP) - return true; - - return widget && widget->is_top_level(); -} - // Returns true if the content_view is reparented. bool PositionWindowInNativeViewParent(NSView* content_view) { return [[content_view window] contentView] != content_view; @@ -231,15 +236,9 @@ gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); } -BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) - : native_widget_mac_(parent), - focus_manager_(nullptr), - widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init(). - parent_(nullptr), - target_fullscreen_state_(false), - in_fullscreen_transition_(false), - window_visible_(false), - wants_to_be_visible_(false) { +BridgedNativeWidget::BridgedNativeWidget(BridgedNativeWidgetHost* host, + NativeWidgetMac* parent) + : host_(host), native_widget_mac_(parent) { DCHECK(parent); window_delegate_.reset( [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); @@ -255,18 +254,54 @@ BridgedNativeWidget::~BridgedNativeWidget() { ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this); RemoveOrDestroyChildren(); DCHECK(child_windows_.empty()); - SetFocusManager(nullptr); - SetRootView(nullptr); - DestroyCompositor(); + DestroyContentView(); } -void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window, - const Widget::InitParams& params) { - widget_type_ = params.type; - +void BridgedNativeWidget::SetWindow( + base::scoped_nsobject<NativeWidgetMacNSWindow> window) { DCHECK(!window_); - window_.swap(window); + window_ = std::move(window); + [window_ setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. [window_ setDelegate:window_delegate_]; +} + +void BridgedNativeWidget::SetParent(NSView* new_parent) { + BridgedNativeWidget* bridged_native_widget_parent = + NativeWidgetMac::GetBridgeForNativeWindow([new_parent window]); + // Remove from the old parent. + if (parent_) { + parent_->RemoveChildWindow(this); + parent_ = nullptr; + } + + if (!new_parent) + return; + + // Disallow creating child windows of views not currently in an NSWindow. + CHECK([new_parent window]); + + // If the parent is another BridgedNativeWidget, just add to the collection + // of child windows it owns and manages. Otherwise, create an adapter to + // anchor the child widget and observe when the parent NSWindow is closed. + if (bridged_native_widget_parent) { + parent_ = bridged_native_widget_parent; + bridged_native_widget_parent->child_windows_.push_back(this); + } else { + parent_ = new WidgetOwnerNSWindowAdapter(this, new_parent); + } + + // Widget::ShowInactive() could result in a Space switch when the widget has a + // parent, and we're calling -orderWindow:relativeTo:. Use Transient + // collection behaviour to prevent that. + // https://crbug.com/697829 + [window_ setCollectionBehavior:[window_ collectionBehavior] | + NSWindowCollectionBehaviorTransient]; +} + +void BridgedNativeWidget::InitWindow( + views_bridge_mac::mojom::BridgedNativeWidgetInitParamsPtr params) { + modal_type_ = params->modal_type; + is_translucent_window_ = params->is_translucent; // Register for application hide notifications so that visibility can be // properly tracked. This is not done in the delegate so that the lifetime is @@ -291,64 +326,27 @@ void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window, DCHECK(![window_ isVisible]); DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask); - if (params.parent) { - // Disallow creating child windows of views not currently in an NSWindow. - CHECK([params.parent window]); - BridgedNativeWidget* bridged_native_widget_parent = - NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]); - // If the parent is another BridgedNativeWidget, just add to the collection - // of child windows it owns and manages. Otherwise, create an adapter to - // anchor the child widget and observe when the parent NSWindow is closed. - if (bridged_native_widget_parent) { - parent_ = bridged_native_widget_parent; - bridged_native_widget_parent->child_windows_.push_back(this); - } else { - parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent); - } - // crbug.com/697829: Widget::ShowInactive() could result in a Space switch - // when the widget has a parent, and we're calling -orderWindow:relativeTo:. - // Use Transient collection behaviour to prevent that. - [window_ setCollectionBehavior:[window_ collectionBehavior] | - NSWindowCollectionBehaviorTransient]; - } - // Include "regular" windows without the standard frame in the window cycle. // These use NSBorderlessWindowMask so do not get it by default. - if (widget_type_ == Widget::InitParams::TYPE_WINDOW && - params.remove_standard_frame) { + if (params->force_into_collection_cycle) { [window_ setCollectionBehavior:[window_ collectionBehavior] | NSWindowCollectionBehaviorParticipatesInCycle]; } - // OSX likes to put shadows on most things. However, frameless windows (with - // styleMask = NSBorderlessWindowMask) default to no shadow. So change that. - // SHADOW_TYPE_DROP is used for Menus, which get the same shadow style on Mac. - switch (params.shadow_type) { - case Widget::InitParams::SHADOW_TYPE_NONE: - [window_ setHasShadow:NO]; - break; - case Widget::InitParams::SHADOW_TYPE_DEFAULT: - // Controls should get views shadows instead of native shadows. - [window_ setHasShadow:params.type != Widget::InitParams::TYPE_CONTROL]; - break; - case Widget::InitParams::SHADOW_TYPE_DROP: - [window_ setHasShadow:YES]; - break; - } // No default case, to pick up new types. - - // Set a meaningful initial bounds. Note that except for frameless widgets - // with no WidgetDelegate, the bounds will be set again by Widget after - // initializing the non-client view. In the former case, if bounds were not - // set at all, the creator of the Widget is expected to call SetBounds() - // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized - // (i.e. 1x1) window appearing. - if (!params.bounds.IsEmpty()) { - SetBounds(params.bounds); - } else { + [window_ setHasShadow:params->has_window_server_shadow]; + tooltip_manager_.reset(new TooltipManagerMac(this)); +} + +void BridgedNativeWidget::SetInitialBounds( + const gfx::Rect& new_bounds, + const gfx::Size& minimum_content_size, + const gfx::Vector2d& bounds_offset_for_parent) { + gfx::Rect adjusted_bounds = new_bounds; + if (new_bounds.IsEmpty()) { // If a position is set, but no size, complain. Otherwise, a 1x1 window // would appear there, which might be unexpected. - DCHECK(params.bounds.origin().IsOrigin()) + DCHECK(new_bounds.origin().IsOrigin()) << "Zero-sized windows not supported on Mac."; // Otherwise, bounds is all zeroes. Cocoa will currently have the window at @@ -357,63 +355,28 @@ void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window, // Read back the current frame: it will be a 1x1 context rect but the frame // size also depends on the window style. NSRect frame_rect = [window_ frame]; - SetBounds(gfx::Rect(gfx::Point(), - gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)))); + adjusted_bounds = gfx::Rect( + gfx::Point(), gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))); } - - // Widgets for UI controls (usually layered above web contents) start visible. - if (params.type == Widget::InitParams::TYPE_CONTROL) - SetVisibilityState(SHOW_INACTIVE); - - // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are - // native on Mac, so nothing should ever want one in Widget form. - DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP); - tooltip_manager_.reset(new TooltipManagerMac(this)); -} - -void BridgedNativeWidget::OnWidgetInitDone() { - DialogDelegate* dialog = - native_widget_mac_->GetWidget()->widget_delegate()->AsDialogDelegate(); - if (dialog) - dialog->AddObserver(this); -} - -void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) { - if (focus_manager_ == focus_manager) - return; - - if (focus_manager_) { - // Only the destructor can replace the focus manager (and it passes null). - DCHECK(![window_ delegate]); - DCHECK(!focus_manager); - if (View* old_focus = focus_manager_->GetFocusedView()) - OnDidChangeFocus(old_focus, nullptr); - focus_manager_->RemoveFocusChangeListener(this); - focus_manager_ = nullptr; - return; - } - - focus_manager_ = focus_manager; - focus_manager_->AddFocusChangeListener(this); - if (View* new_focus = focus_manager_->GetFocusedView()) - OnDidChangeFocus(nullptr, new_focus); + adjusted_bounds.Offset(bounds_offset_for_parent); + SetBounds(adjusted_bounds, minimum_content_size); } -void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) { - Widget* widget = native_widget_mac_->GetWidget(); +void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds, + const gfx::Size& minimum_content_size) { // -[NSWindow contentMinSize] is only checked by Cocoa for user-initiated // resizes. This is not what toolkit-views expects, so clamp. Note there is // no check for maximum size (consistent with aura::Window::SetBounds()). gfx::Size clamped_content_size = GetClientSizeForWindowSize(window_, new_bounds.size()); - clamped_content_size.SetToMax(widget->GetMinimumSize()); + clamped_content_size.SetToMax(minimum_content_size); // A contentRect with zero width or height is a banned practice in ChromeMac, // due to unpredictable OSX treatment. DCHECK(!clamped_content_size.IsEmpty()) << "Zero-sized windows not supported on Mac"; - if (!window_visible_ && native_widget_mac_->IsWindowModalSheet()) { + if (!window_visible_ && IsWindowModalSheet()) { // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for // the first time. They also have no frame, so just update the content size. [window_ setContentSize:NSMakeSize(clamped_content_size.width(), @@ -424,9 +387,6 @@ void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) { new_bounds.origin(), GetWindowSizeForClientSize(window_, clamped_content_size)); - if (parent_ && !PositionWindowInScreenCoordinates(widget, widget_type_)) - actual_new_bounds.Offset(parent_->GetChildWindowOffset()); - if (PositionWindowInNativeViewParent(bridged_view_)) actual_new_bounds.Offset(GetNativeViewParentOffset(bridged_view_)); @@ -435,29 +395,26 @@ void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) { animate:NO]; } -void BridgedNativeWidget::SetRootView(views::View* view) { - if (view == [bridged_view_ hostedView]) +void BridgedNativeWidget::DestroyContentView() { + if (!bridged_view_) return; - - // If this is ever false, the compositor will need to be properly torn down - // and replaced, pointing at the new view. - DCHECK(!view || !compositor_); - drag_drop_client_.reset(); [bridged_view_ clearView]; bridged_view_.reset(); - // Note that there can still be references to the old |bridged_view_| - // floating around in Cocoa libraries at this point. However, references to - // the old views::View will be gone, so any method calls will become no-ops. + [window_ setContentView:nil]; +} - if (view) { - bridged_view_.reset([[BridgedContentView alloc] initWithView:view]); - drag_drop_client_.reset(new DragDropClientMac(this, view)); +void BridgedNativeWidget::CreateContentView(const gfx::Rect& bounds) { + DCHECK(!bridged_view_); + // The compositor needs to point at the new content view created here. + DCHECK(!compositor_superview_); - // Objective C initializers can return nil. However, if |view| is non-NULL - // this should be treated as an error and caught early. - CHECK(bridged_view_); - } + bridged_view_.reset( + [[BridgedContentView alloc] initWithBridge:this bounds:bounds]); + + // Objective C initializers can return nil. However, if |view| is non-NULL + // this should be treated as an error and caught early. + CHECK(bridged_view_); // Layer backing the content view improves resize performance, reduces memory // use (no backing store), and clips sublayers to rounded window corners. @@ -466,18 +423,77 @@ void BridgedNativeWidget::SetRootView(views::View* view) { [window_ setContentView:bridged_view_]; } +void BridgedNativeWidget::CreateDragDropClient(views::View* view) { + drag_drop_client_.reset(new DragDropClientMac(this, view)); +} + +void BridgedNativeWidget::CloseWindow() { + // Keep |window| on the stack so that the ObjectiveC block below can capture + // it and properly increment the reference count bound to the posted task. + NSWindow* window = ns_window(); + + if (IsWindowModalSheet()) { + // Sheets can't be closed normally. This starts the sheet closing. Once the + // sheet has finished animating, it will call sheetDidEnd: on the parent + // window's delegate. Note it still needs to be asynchronous, since code + // calling Widget::Close() doesn't expect things to be deleted upon return. + // Ensure |window| is retained by a block. Note in some cases during + // teardown, [window sheetParent] may be nil. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(base::RetainBlock(^{ + [NSApp endSheet:window]; + }))); + return; + } + + // For other modal types, animate the close. + if (ShouldRunCustomAnimationFor(Widget::ANIMATE_HIDE)) { + [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window]; + return; + } + + // Destroy the content view so that it won't call back into |host_| while + // being torn down. + DestroyContentView(); + + // Widget::Close() ensures [Non]ClientView::CanClose() returns true, so there + // is no need to call the NSWindow or its delegate's -windowShouldClose: + // implementation in the manner of -[NSWindow performClose:]. But, + // like -performClose:, first remove the window from AppKit's display + // list to avoid crashes like http://crbug.com/156101. + [window orderOut:nil]; + + // Many tests assume that base::RunLoop().RunUntilIdle() is always sufficient + // to execute a close. However, in rare cases, -performSelector:..afterDelay:0 + // does not do this. So post a regular task. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(base::RetainBlock(^{ + [window close]; + }))); +} + +void BridgedNativeWidget::CloseWindowNow() { + // NSWindows must be retained until -[NSWindow close] returns. + auto window_retain = window_; + + // If there's a bridge at this point, it means there must be a window as well. + DCHECK(window_); + [window_ close]; + // Note: |this| will be deleted here. +} + void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { // Ensure that: // - A window with an invisible parent is not made visible. // - A parent changing visibility updates child window visibility. // * But only when changed via this function - ignore changes via the // NSWindow API, or changes propagating out from here. - wants_to_be_visible_ = new_state != HIDE_WINDOW; + wants_to_be_visible_ = new_state != WindowVisibilityState::kHideWindow; [show_animation_ stopAnimation]; DCHECK(!show_animation_); - if (new_state == HIDE_WINDOW) { + if (new_state == WindowVisibilityState::kHideWindow) { // Calling -orderOut: on a window with an attached sheet encounters broken // AppKit behavior. The sheet effectively becomes "lost". // See http://crbug.com/667602. Alternatives: call -setAlphaValue:0 and @@ -506,12 +522,12 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { if (parent() && !parent()->IsVisibleParent()) return; - if (native_widget_mac_->IsWindowModalSheet()) { + if (IsWindowModalSheet()) { ShowAsModalSheet(); return; } - if (new_state == SHOW_AND_ACTIVATE_WINDOW) { + if (new_state == WindowVisibilityState::kShowAndActivateWindow) { [window_ makeKeyAndOrderFront:nil]; [NSApp activateIgnoringOtherApps:YES]; } else { @@ -560,11 +576,13 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { } void BridgedNativeWidget::AcquireCapture() { - DCHECK(!HasCapture()); + if (HasCapture()) + return; if (!window_visible_) return; // Capture on hidden windows is disallowed. mouse_capture_.reset(new CocoaMouseCapture(this)); + host_->OnMouseCaptureActiveChanged(true); // Initiating global event capture with addGlobalMonitorForEventsMatchingMask: // will reset the mouse cursor to an arrow. Asking the window for an update @@ -637,16 +655,13 @@ void BridgedNativeWidget::SetCursor(NSCursor* cursor) { } void BridgedNativeWidget::OnWindowWillClose() { - Widget* widget = native_widget_mac_->GetWidget(); - if (DialogDelegate* dialog = widget->widget_delegate()->AsDialogDelegate()) - dialog->RemoveObserver(this); - native_widget_mac_->WindowDestroying(); + host_->OnWindowWillClose(); // Ensure BridgedNativeWidget does not have capture, otherwise - // OnMouseCaptureLost() may reference a deleted |native_widget_mac_| when - // called via ~CocoaMouseCapture() upon the destruction of |mouse_capture_|. - // See crbug.com/622201. Also we do this before setting the delegate to nil, - // because this may lead to callbacks to bridge which rely on a valid + // OnMouseCaptureLost() may reference a deleted |host_| when called via + // ~CocoaMouseCapture() upon the destruction of |mouse_capture_|. See + // https://crbug.com/622201. Also we do this before setting the delegate to + // nil, because this may lead to callbacks to bridge which rely on a valid // delegate. ReleaseCapture(); @@ -660,8 +675,8 @@ void BridgedNativeWidget::OnWindowWillClose() { DCHECK(!show_animation_); [window_ setDelegate:nil]; - native_widget_mac_->WindowDestroyed(); - // Note: |this| is deleted here. + host_->OnWindowHasClosed(); + // Note: |this| and its host will be deleted here. } void BridgedNativeWidget::OnFullscreenTransitionStart( @@ -674,12 +689,7 @@ void BridgedNativeWidget::OnFullscreenTransitionStart( target_fullscreen_state_ = target_fullscreen_state; in_fullscreen_transition_ = true; - // If going into fullscreen, store an answer for GetRestoredBounds(). - if (target_fullscreen_state) - bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]); - - // Notify that fullscreen state changed. - native_widget_mac_->OnWindowFullscreenStateChange(); + host_->OnWindowFullscreenTransitionStart(target_fullscreen_state); } void BridgedNativeWidget::OnFullscreenTransitionComplete( @@ -687,8 +697,7 @@ void BridgedNativeWidget::OnFullscreenTransitionComplete( in_fullscreen_transition_ = false; if (target_fullscreen_state_ == actual_fullscreen_state) { - // Ensure constraints are re-applied when completing a transition. - OnSizeConstraintsChanged(); + host_->OnWindowFullscreenTransitionComplete(actual_fullscreen_state); return; } @@ -720,7 +729,7 @@ void BridgedNativeWidget::ToggleDesiredFullscreenState(bool async) { // of relying on AppKit to do it, and not worry that OnVisibilityChanged() // won't be called for externally triggered fullscreen requests. if (!window_visible_) - SetVisibilityState(SHOW_INACTIVE); + SetVisibilityState(WindowVisibilityState::kShowInactive); // Enable fullscreen collection behavior because: // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored, @@ -738,37 +747,23 @@ void BridgedNativeWidget::ToggleDesiredFullscreenState(bool async) { withObject:nil afterDelay:0]; } else { + // Suppress synchronous CA transactions during AppKit fullscreen transition + // since there is no need for updates during such transition. + // Re-layout and re-paint will be done after the transtion. See + // https://crbug.com/875707 for potiential problems if we don't suppress. + // |ca_transaction_sync_suppressed_| will be reset to false when the next + // frame comes in. + ca_transaction_sync_suppressed_ = true; [window_ toggleFullScreen:nil]; } } void BridgedNativeWidget::OnSizeChanged() { - const gfx::Rect new_bounds = native_widget_mac_->GetWindowBoundsInScreen(); - if (new_bounds.origin() != last_window_frame_origin_) { - native_widget_mac_->GetWidget()->OnNativeWidgetMove(); - last_window_frame_origin_ = new_bounds.origin(); - } - - // Note we can't use new_bounds.size(), since it includes the titlebar for the - // purposes of detecting a window move. - gfx::Size new_size = GetClientAreaSize(); - native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size); - if (layer()) { - UpdateLayerProperties(); - if ([window_ inLiveResize]) - MaybeWaitForFrame(new_size); - } + UpdateWindowGeometry(); } void BridgedNativeWidget::OnPositionChanged() { - // When a window grows vertically, the AppKit origin changes, but as far as - // tookit-views is concerned, the window hasn't moved. Suppress these. - const gfx::Rect new_bounds = native_widget_mac_->GetWindowBoundsInScreen(); - if (new_bounds.origin() == last_window_frame_origin_) - return; - - last_window_frame_origin_ = new_bounds.origin(); - native_widget_mac_->GetWidget()->OnNativeWidgetMove(); + UpdateWindowGeometry(); } void BridgedNativeWidget::OnVisibilityChanged() { @@ -799,26 +794,13 @@ void BridgedNativeWidget::OnVisibilityChanged() { [parent_->GetNSWindow() removeChildWindow:window_]; } - // TODO(tapted): Investigate whether we want this for Mac. This is what Aura - // does, and it is what tests expect. However, because layer drawing is - // asynchronous (and things like deminiaturize in AppKit are not), it can - // result in the compositor producing a blank frame during the time that the - // layer is not visible. Avoid this by locking the compositor (preventing any - // new frames) in UpdateLayerVisibility whenever the layer is hidden. - if (layer()) { - UpdateLayerVisibility(); - layer()->SchedulePaint(gfx::Rect(GetClientAreaSize())); - - // For translucent windows which are made visible, recalculate shadow when - // the frame from the compositor arrives. - if (![window_ isOpaque]) - invalidate_shadow_on_frame_swap_ = window_visible_; - } + // Showing a translucent window after hiding it should trigger shadow + // invalidation. + if (window_visible && ![window_ isOpaque]) + invalidate_shadow_on_frame_swap_ = true; NotifyVisibilityChangeDown(); - - native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged( - window_visible_); + host_->OnVisibilityChanged(window_visible_); // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking // for an "empty" draw, disable auto-display while hidden. For example, this @@ -832,45 +814,28 @@ void BridgedNativeWidget::OnSystemControlTintChanged() { } void BridgedNativeWidget::OnBackingPropertiesChanged() { - if (layer()) - UpdateLayerProperties(); + UpdateWindowDisplay(); } void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) { - Widget* widget = native_widget_mac()->GetWidget(); - if (!widget->OnNativeWidgetActivationChanged(is_key)) - return; - // The contentView is the BridgedContentView hosting the views::RootView. The - // focus manager will already know if a native subview has focus. - if ([window_ contentView] == [window_ firstResponder]) { - if (is_key) { - widget->OnNativeFocus(); - // Explicitly set the keyboard accessibility state on regaining key - // window status. - [bridged_view_ updateFullKeyboardAccess]; - widget->GetFocusManager()->RestoreFocusedView(); - } else { - widget->OnNativeBlur(); - widget->GetFocusManager()->StoreFocusedView(true); - } - } + host_->OnWindowKeyStatusChanged( + is_key, [window_ contentView] == [window_ firstResponder], + [NSApp isFullKeyboardAccessEnabled]); } -void BridgedNativeWidget::OnSizeConstraintsChanged() { +void BridgedNativeWidget::SetSizeConstraints(const gfx::Size& min_size, + const gfx::Size& max_size, + bool is_resizable, + bool is_maximizable) { // Don't modify the size constraints or fullscreen collection behavior while // in fullscreen or during a transition. OnFullscreenTransitionComplete will // reset these after leaving fullscreen. if (target_fullscreen_state_ || in_fullscreen_transition_) return; - Widget* widget = native_widget_mac()->GetWidget(); - gfx::Size min_size = widget->GetMinimumSize(); - gfx::Size max_size = widget->GetMaximumSize(); - bool is_resizable = widget->widget_delegate()->CanResize(); bool shows_resize_controls = is_resizable && (min_size.IsEmpty() || min_size != max_size); - bool shows_fullscreen_controls = - is_resizable && widget->widget_delegate()->CanMaximize(); + bool shows_fullscreen_controls = is_resizable && is_maximizable; gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size, shows_resize_controls, @@ -881,51 +846,16 @@ void BridgedNativeWidget::OnShowAnimationComplete() { show_animation_.reset(); } -ui::InputMethod* BridgedNativeWidget::GetInputMethod() { - if (!input_method_) { - input_method_ = ui::CreateInputMethod(this, gfx::kNullAcceleratedWidget); - // For now, use always-focused mode on Mac for the input method. - // TODO(tapted): Move this to OnWindowKeyStatusChangedTo() and balance. - input_method_->OnFocus(); - } - return input_method_.get(); -} - -gfx::Rect BridgedNativeWidget::GetRestoredBounds() const { - if (target_fullscreen_state_ || in_fullscreen_transition_) - return bounds_before_fullscreen_; - - return gfx::ScreenRectFromNSRect([window_ frame]); -} - -void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type, - bool translucent) { - DCHECK(bridged_view_); - DCHECK(!layer()); - - CreateCompositor(); - DCHECK(compositor_); - - SetLayer(std::make_unique<ui::Layer>(layer_type)); - // Note, except for controls, this will set the layer to be hidden, since it - // is only called during Init(). - UpdateLayerVisibility(); - layer()->set_delegate(this); - - InitCompositor(); - - // Transparent window support. - layer()->GetCompositor()->SetBackgroundColor(translucent ? SK_ColorTRANSPARENT - : SK_ColorWHITE); - layer()->SetFillsBoundsOpaquely(!translucent); +void BridgedNativeWidget::InitCompositorView() { + AddCompositorSuperview(); - // Use the regular window background for window modal sheets. The layer() will + // Use the regular window background for window modal sheets. The layer will // still paint over most of it, but the native -[NSApp beginSheet:] animation // blocks the UI thread, so there's no way to invalidate the shadow to match // the composited layer. This assumes the native window shape is a good match // for the composited NonClientFrameView, which should be the case since the // native shape is what's most appropriate for displaying sheets on Mac. - if (translucent && !native_widget_mac_->IsWindowModalSheet()) { + if (is_translucent_window_ && !IsWindowModalSheet()) { [window_ setOpaque:NO]; [window_ setBackgroundColor:[NSColor clearColor]]; @@ -939,7 +869,10 @@ void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type, DCHECK(!ca_transaction_sync_suppressed_); } - UpdateLayerProperties(); + // Send the initial window geometry and screen properties. Any future changes + // will be forwarded. + UpdateWindowDisplay(); + UpdateWindowGeometry(); } void BridgedNativeWidget::SetAssociationForView(const views::View* view, @@ -981,16 +914,12 @@ void BridgedNativeWidget::ReparentNativeView(NSView* native_view, BridgedNativeWidget* parent_bridge = NativeWidgetMac::GetBridgeForNativeWindow([new_parent window]); if (native_view == bridged_view_.get() && parent_bridge != parent_) { - if (parent_) - parent_->RemoveChildWindow(this); - - if (parent_bridge) { - parent_ = parent_bridge; - parent_bridge->child_windows_.push_back(this); - } else { - parent_ = new WidgetOwnerNSWindowAdapter(this, new_parent); - } + SetParent(new_parent); + // TODO(ccameron): This is likely not correct, as the window for |this| + // should only be added as a child window if it is visible. + if (!window_visible_) + NOTIMPLEMENTED(); [[new_parent window] addChildWindow:window_ ordered:NSWindowAbove]; } @@ -1022,15 +951,40 @@ bool BridgedNativeWidget::ShouldRunCustomAnimationFor( constexpr int kSupported = Widget::ANIMATE_SHOW | Widget::ANIMATE_HIDE | Widget::ANIMATE_NONE; DCHECK_EQ(0, transitions_to_animate_ & ~kSupported); + if (!(transitions_to_animate_ & transition)) + return false; + + // Custom animations are only used for tab-modals. + bool widget_is_modal = false; + host_->GetWidgetIsModal(&widget_is_modal); + if (!widget_is_modal) + return false; + + // Note this also checks the native animation property. Clearing that will + // also disable custom animations to ensure that the views::Widget API + // behaves consistently. + if ([window_ animationBehavior] == NSWindowAnimationBehaviorNone) + return false; + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableModalAnimations)) { + return false; + } + + return true; +} + +NSWindow* BridgedNativeWidget::ns_window() { + return window_.get(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidget, ui::CATransactionObserver - // Custom animations are only used for tab-modals. Note this also checks the - // native animation property. Clearing that will also disable custom - // animations to ensure that the views::Widget API behaves consistently. - return (transitions_to_animate_ & transition) && - native_widget_mac_->GetWidget()->IsModal() && - [window_ animationBehavior] != NSWindowAnimationBehaviorNone && - !base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableModalAnimations); +void BridgedNativeWidget::OnDisplayMetricsChanged( + const display::Display& display, + uint32_t metrics) { + UpdateWindowDisplay(); } //////////////////////////////////////////////////////////////////////////////// @@ -1041,9 +995,9 @@ bool BridgedNativeWidget::ShouldWaitInPreCommit() { return false; if (ca_transaction_sync_suppressed_) return false; - if (!compositor_) + if (!compositor_superview_) return false; - return !compositor_->widget()->HasFrameOfSize(GetClientAreaSize()); + return content_dip_size_ != compositor_frame_dip_size_; } base::TimeDelta BridgedNativeWidget::PreCommitTimeout() { @@ -1051,19 +1005,6 @@ base::TimeDelta BridgedNativeWidget::PreCommitTimeout() { } //////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, internal::InputMethodDelegate: - -ui::EventDispatchDetails BridgedNativeWidget::DispatchKeyEventPostIME( - ui::KeyEvent* key) { - DCHECK(focus_manager_); - if (!focus_manager_->OnKeyEvent(*key)) - key->StopPropagation(); - else - native_widget_mac_->GetWidget()->OnKeyEvent(key); - return ui::EventDispatchDetails(); -} - -//////////////////////////////////////////////////////////////////////////////// // BridgedNativeWidget, CocoaMouseCaptureDelegate: void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) { @@ -1071,7 +1012,7 @@ void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) { } void BridgedNativeWidget::OnMouseCaptureLost() { - native_widget_mac_->GetWidget()->OnMouseCaptureLost(); + host_->OnMouseCaptureActiveChanged(false); } NSWindow* BridgedNativeWidget::GetWindow() const { @@ -1079,54 +1020,55 @@ NSWindow* BridgedNativeWidget::GetWindow() const { } //////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, FocusChangeListener: - -void BridgedNativeWidget::OnWillChangeFocus(View* focused_before, - View* focused_now) { -} - -void BridgedNativeWidget::OnDidChangeFocus(View* focused_before, - View* focused_now) { - ui::InputMethod* input_method = - native_widget_mac_->GetWidget()->GetInputMethod(); - if (input_method) { - ui::TextInputClient* input_client = input_method->GetTextInputClient(); - // Sanity check: When focus moves away from the widget (i.e. |focused_now| - // is nil), then the textInputClient will be cleared. - DCHECK(!!focused_now || !input_client); - [bridged_view_ setTextInputClient:input_client]; - } +// TODO(ccameron): Update class names to: +// BridgedNativeWidgetImpl, BridgedNativeWidget: + +void BridgedNativeWidget::SetVisibleOnAllSpaces(bool always_visible) { + gfx::SetNSWindowVisibleOnAllWorkspaces(window_, always_visible); } -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, LayerDelegate: +void BridgedNativeWidget::SetFullscreen(bool fullscreen) { + if (fullscreen == target_fullscreen_state_) + return; + ToggleDesiredFullscreenState(); +} -void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) { - native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context); +void BridgedNativeWidget::SetMiniaturized(bool miniaturized) { + if (miniaturized) { + // Calling performMiniaturize: will momentarily highlight the button, but + // AppKit will reject it if there is no miniaturize button. + if ([window_ styleMask] & NSMiniaturizableWindowMask) + [window_ performMiniaturize:nil]; + else + [window_ miniaturize:nil]; + } else { + [window_ deminiaturize:nil]; + } } -void BridgedNativeWidget::OnDeviceScaleFactorChanged( - float old_device_scale_factor, - float new_device_scale_factor) { - native_widget_mac_->GetWidget()->DeviceScaleFactorChanged( - old_device_scale_factor, new_device_scale_factor); +void BridgedNativeWidget::SetOpacity(float opacity) { + [window_ setAlphaValue:opacity]; } -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, AcceleratedWidgetMac: +void BridgedNativeWidget::SetContentAspectRatio( + const gfx::SizeF& aspect_ratio) { + [window_ setContentAspectRatio:NSMakeSize(aspect_ratio.width(), + aspect_ratio.height())]; +} -void BridgedNativeWidget::AcceleratedWidgetCALayerParamsUpdated() { +void BridgedNativeWidget::SetCALayerParams( + const gfx::CALayerParams& ca_layer_params) { // Ignore frames arriving "late" for an old size. A frame at the new size // should arrive soon. - if (!compositor_->widget()->HasFrameOfSize(GetClientAreaSize())) + gfx::Size frame_dip_size = gfx::ConvertSizeToDIP(ca_layer_params.scale_factor, + ca_layer_params.pixel_size); + if (content_dip_size_ != frame_dip_size) return; + compositor_frame_dip_size_ = frame_dip_size; // Update the DisplayCALayerTree with the most recent CALayerParams, to make // the content display on-screen. - const gfx::CALayerParams* ca_layer_params = - compositor_->widget()->GetCALayerParams(); - if (ca_layer_params) - display_ca_layer_tree_->UpdateCALayerTree(*ca_layer_params); + display_ca_layer_tree_->UpdateCALayerTree(ca_layer_params); if (ca_transaction_sync_suppressed_) ca_transaction_sync_suppressed_ = false; @@ -1137,6 +1079,27 @@ void BridgedNativeWidget::AcceleratedWidgetCALayerParamsUpdated() { } } +void BridgedNativeWidget::MakeFirstResponder() { + [window_ makeFirstResponder:bridged_view_]; +} + +void BridgedNativeWidget::SetWindowTitle(const base::string16& title) { + NSString* new_title = base::SysUTF16ToNSString(title); + [window_ setTitle:new_title]; +} + +void BridgedNativeWidget::ClearTouchBar() { + if (@available(macOS 10.12.2, *)) { + if ([bridged_view_ respondsToSelector:@selector(setTouchBar:)]) + [bridged_view_ setTouchBar:nil]; + } +} + +void BridgedNativeWidget::SetTextInputClient( + ui::TextInputClient* text_input_client) { + [bridged_view_ setTextInputClient:text_input_client]; +} + //////////////////////////////////////////////////////////////////////////////// // BridgedNativeWidget, BridgedNativeWidgetOwner: @@ -1166,18 +1129,6 @@ void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) { } //////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, DialogObserver: - -void BridgedNativeWidget::OnDialogModelChanged() { - // Note it's only necessary to clear the TouchBar. If the OS needs it again, - // a new one will be created. - if (@available(macOS 10.12.2, *)) { - if ([bridged_view_ respondsToSelector:@selector(setTouchBar:)]) - [bridged_view_ setTouchBar:nil]; - } -} - -//////////////////////////////////////////////////////////////////////////////// // BridgedNativeWidget, private: void BridgedNativeWidget::RemoveOrDestroyChildren() { @@ -1241,59 +1192,6 @@ void BridgedNativeWidget::NotifyVisibilityChangeDown() { CountBridgedWindows([window_ childWindows])); } -gfx::Size BridgedNativeWidget::GetClientAreaSize() const { - NSRect content_rect = [window_ contentRectForFrameRect:[window_ frame]]; - return gfx::Size(NSWidth(content_rect), NSHeight(content_rect)); -} - -void BridgedNativeWidget::CreateCompositor() { - DCHECK(!compositor_); - DCHECK(ViewsDelegate::GetInstance()); - - ui::ContextFactory* context_factory = - ViewsDelegate::GetInstance()->GetContextFactory(); - DCHECK(context_factory); - ui::ContextFactoryPrivate* context_factory_private = - ViewsDelegate::GetInstance()->GetContextFactoryPrivate(); - - AddCompositorSuperview(); - - compositor_ = ui::RecyclableCompositorMacFactory::Get()->CreateCompositor( - context_factory, context_factory_private); - compositor_->widget()->SetNSView(this); -} - -void BridgedNativeWidget::InitCompositor() { - TRACE_EVENT0("ui", "BridgedNativeWidget::InitCompositor"); - DCHECK(layer()); - float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_); - gfx::Size size_in_dip = GetClientAreaSize(); - compositor_->UpdateSurface(ConvertSizeToPixel(scale_factor, size_in_dip), - scale_factor); - compositor_->compositor()->SetRootLayer(layer()); - compositor_->Unsuspend(); -} - -void BridgedNativeWidget::DestroyCompositor() { - if (layer()) { - // LayerOwner supports a change in ownership, e.g., to animate a closing - // window, but that won't work as expected for the root layer in - // BridgedNativeWidget. - DCHECK_EQ(this, layer()->owner()); - layer()->CompleteAllAnimations(); - layer()->SuppressPaint(); - layer()->set_delegate(nullptr); - } - DestroyLayer(); - - if (!compositor_) - return; - compositor_->widget()->ResetNSView(); - compositor_->compositor()->SetRootLayer(nullptr); - ui::RecyclableCompositorMacFactory::Get()->RecycleCompositor( - std::move(compositor_)); -} - void BridgedNativeWidget::AddCompositorSuperview() { DCHECK(!compositor_superview_); compositor_superview_.reset( @@ -1323,46 +1221,31 @@ void BridgedNativeWidget::AddCompositorSuperview() { [bridged_view_ addSubview:compositor_superview_]; } -void BridgedNativeWidget::UpdateLayerProperties() { - DCHECK(layer()); - DCHECK(compositor_superview_); - float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_); - gfx::Size size_in_dip = GetClientAreaSize(); - gfx::Size size_in_pixel = ConvertSizeToPixel(scale_factor, size_in_dip); +void BridgedNativeWidget::UpdateWindowGeometry() { + gfx::Rect window_in_screen = gfx::ScreenRectFromNSRect([window_ frame]); + gfx::Rect content_in_screen = gfx::ScreenRectFromNSRect( + [window_ contentRectForFrameRect:[window_ frame]]); + bool content_resized = content_dip_size_ != content_in_screen.size(); + content_dip_size_ = content_in_screen.size(); - if (!ca_transaction_sync_suppressed_) - ui::CATransactionCoordinator::Get().Synchronize(); + host_->OnWindowGeometryChanged(window_in_screen, content_in_screen); - layer()->SetBounds(gfx::Rect(size_in_dip)); - compositor_->UpdateSurface(size_in_pixel, scale_factor); + if (content_resized && !ca_transaction_sync_suppressed_) + ui::CATransactionCoordinator::Get().Synchronize(); // For a translucent window, the shadow calculation needs to be carried out // after the frame from the compositor arrives. - if (![window_ isOpaque]) + if (content_resized && ![window_ isOpaque]) invalidate_shadow_on_frame_swap_ = true; } -void BridgedNativeWidget::MaybeWaitForFrame(const gfx::Size& size_in_dip) { - return; // TODO(https://crbug.com/682825): Delete this during cleanup. - if (!layer()->IsDrawn() || compositor_->widget()->HasFrameOfSize(size_in_dip)) - return; +void BridgedNativeWidget::UpdateWindowDisplay() { + host_->OnWindowDisplayChanged( + display::Screen::GetScreen()->GetDisplayNearestWindow(window_)); +} - const int kPaintMsgTimeoutMS = 50; - const base::TimeTicks start_time = base::TimeTicks::Now(); - const base::TimeTicks timeout_time = - start_time + base::TimeDelta::FromMilliseconds(kPaintMsgTimeoutMS); - - ui::WindowResizeHelperMac* resize_helper = ui::WindowResizeHelperMac::Get(); - for (base::TimeTicks now = start_time; now < timeout_time; - now = base::TimeTicks::Now()) { - if (!resize_helper->WaitForSingleTaskToRun(timeout_time - now)) - return; // Timeout. - - // Since the UI thread is blocked, the size shouldn't change. - DCHECK(size_in_dip == GetClientAreaSize()); - if (compositor_->widget()->HasFrameOfSize(size_in_dip)) - return; // Frame arrived. - } +bool BridgedNativeWidget::IsWindowModalSheet() const { + return parent_ && modal_type_ == ui::MODAL_TYPE_WINDOW; } void BridgedNativeWidget::ShowAsModalSheet() { @@ -1370,9 +1253,7 @@ void BridgedNativeWidget::ShowAsModalSheet() { // So that it doesn't animate a fully transparent window, first wait for a // frame. The first step is to pretend that the window is already visible. window_visible_ = true; - UpdateLayerVisibility(); - native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(true); - MaybeWaitForFrame(GetClientAreaSize()); + host_->OnVisibilityChanged(window_visible_); NSWindow* parent_window = parent_->GetNSWindow(); DCHECK(parent_window); @@ -1399,12 +1280,4 @@ NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const { return properties; } -void BridgedNativeWidget::UpdateLayerVisibility() { - layer()->SetVisible(window_visible_); - if (window_visible_) - compositor_->Unsuspend(); - else - compositor_->Suspend(); -} - } // namespace views diff --git a/chromium/ui/views/cocoa/bridged_native_widget_host.h b/chromium/ui/views/cocoa/bridged_native_widget_host.h new file mode 100644 index 00000000000..9fa2c68091c --- /dev/null +++ b/chromium/ui/views/cocoa/bridged_native_widget_host.h @@ -0,0 +1,152 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_H_ +#define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_H_ + +#include "ui/base/ui_base_types.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/decorated_text.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/size.h" +#include "ui/views/views_export.h" + +@class NSView; + +namespace views { + +// The interface through which the app shim (BridgedNativeWidgetImpl) +// communicates with the browser process (BridgedNativeWidgetHostImpl). +class VIEWS_EXPORT BridgedNativeWidgetHost { + public: + virtual ~BridgedNativeWidgetHost() = default; + + // Retrieve the NSView for accessibility for this widget. + // TODO(ccameron): This interface cannot be implemented over IPC. A scheme + // for implementing accessibility across processes needs to be designed and + // implemented. + virtual NSView* GetNativeViewAccessible() = 0; + + // Update the views::Widget, ui::Compositor and ui::Layer's visibility. + virtual void OnVisibilityChanged(bool visible) = 0; + + // Resize the underlying views::View to |new_size|. Note that this will not + // necessarily match the content bounds from OnWindowGeometryChanged. + virtual void SetViewSize(const gfx::Size& new_size) = 0; + + // Indicate if full keyboard accessibility is needed and updates focus if + // needed. + virtual void SetKeyboardAccessible(bool enabled) = 0; + + // Indicate if the NSView is the first responder. + virtual void SetIsFirstResponder(bool is_first_responder) = 0; + + // Indicate if mouse capture is active. + virtual void OnMouseCaptureActiveChanged(bool capture_is_active) = 0; + + // Handle events. Note that whether or not the event is actually handled is + // not returned. + virtual void OnScrollEvent(const ui::ScrollEvent& const_event) = 0; + virtual void OnMouseEvent(const ui::MouseEvent& const_event) = 0; + virtual void OnGestureEvent(const ui::GestureEvent& const_event) = 0; + + // Synchronously dispatch a key event and return in |event_handled| whether + // or not the event was handled. + virtual void DispatchKeyEvent(const ui::KeyEvent& const_event, + bool* event_handled) = 0; + + // Synchronously dispatch a key event to the current menu controller (if any) + // exists. Return in |event_swallowed| whether or not the event was swallowed + // (that is, if the menu's dispatch returned POST_DISPATCH_NONE). Return in + // in |event_handled| whether or not the event was handled (that is, if the + // event in the caller's frame should be marked as handled). + virtual void DispatchKeyEventToMenuController(const ui::KeyEvent& const_event, + bool* event_swallowed, + bool* event_handled) = 0; + + // Synchronously return in |has_menu_controller| whether or not a menu + // controller exists for this widget. + virtual void GetHasMenuController(bool* has_menu_controller) = 0; + + // Synchronously query if |location_in_content| is a draggable background. + virtual void GetIsDraggableBackgroundAt(const gfx::Point& location_in_content, + bool* is_draggable_background) = 0; + + // Synchronously query the tooltip text for |location_in_content|. + virtual void GetTooltipTextAt(const gfx::Point& location_in_content, + base::string16* new_tooltip_text) = 0; + + // Synchronously query the quicklook text at |location_in_content|. Return in + // |found_word| whether or not a word was found. + virtual void GetWordAt(const gfx::Point& location_in_content, + bool* found_word, + gfx::DecoratedText* decorated_word, + gfx::Point* baseline_point) = 0; + + // Synchronously query the value of IsModal for this widget and store it in + // |*widget_is_modal|. + virtual void GetWidgetIsModal(bool* widget_is_modal) = 0; + + // Synchronously return in |is_textual| whether or not the focused view + // contains text that can be selected and copied. + virtual void GetIsFocusedViewTextual(bool* is_textual) = 0; + + // Called whenever the NSWindow's size or position changes. + virtual void OnWindowGeometryChanged( + const gfx::Rect& window_bounds_in_screen_dips, + const gfx::Rect& content_bounds_in_screen_dips) = 0; + + // Called when the window begins transitioning to or from being fullscreen. + virtual void OnWindowFullscreenTransitionStart( + bool target_fullscreen_state) = 0; + + // Called when the window has completed its transition to or from being + // fullscreen. Note that if there are multiple consecutive transitions + // (because a new transition was initiated before the previous one completed) + // then this will only be called when all transitions have competed. + virtual void OnWindowFullscreenTransitionComplete(bool is_fullscreen) = 0; + + // Called when the window is miniaturized or deminiaturized. + virtual void OnWindowMiniaturizedChanged(bool miniaturized) = 0; + + // Called when the current display or the properties of the current display + // change. + virtual void OnWindowDisplayChanged(const display::Display& display) = 0; + + // Called before the NSWindow is closed and destroyed. + virtual void OnWindowWillClose() = 0; + + // Called after the NSWindow has been closed and destroyed. + virtual void OnWindowHasClosed() = 0; + + // Called when the NSWindow becomes key or resigns from being key. Additional + // state required for the transition include whether or not the content NSView + // is the first responder for the NSWindow in |is_content_first_responder| and + // whether or not the NSApp's full keyboard access is enabled in + // |full_keyboard_access_enabled|. + virtual void OnWindowKeyStatusChanged(bool is_key, + bool is_content_first_responder, + bool full_keyboard_access_enabled) = 0; + + // Accept or cancel the current dialog window (depending on the value of + // |button|), if a current dialog exists. + virtual void DoDialogButtonAction(ui::DialogButton button) = 0; + + // Synchronously determine if the specified button exists in the current + // dialog (if any), along with its label, whether or not it is enabled, and + // whether or not it is the default button.. + virtual void GetDialogButtonInfo(ui::DialogButton button, + bool* button_exists, + base::string16* title, + bool* is_button_enabled, + bool* is_button_default) = 0; + + // Synchronously return in |buttons_exist| whether or not any buttons exist + // for the current dialog. + virtual void GetDoDialogButtonsExist(bool* buttons_exist) = 0; +}; + +} // namespace views + +#endif // UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_H_ diff --git a/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h new file mode 100644 index 00000000000..561f3553ca6 --- /dev/null +++ b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h @@ -0,0 +1,230 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_IMPL_H_ +#define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_IMPL_H_ + +#include <memory> + +#include "base/macros.h" +#include "ui/accelerated_widget_mac/accelerated_widget_mac.h" +#include "ui/accelerated_widget_mac/display_link_mac.h" +#include "ui/base/ime/input_method_delegate.h" +#include "ui/compositor/layer_owner.h" +#include "ui/views/cocoa/bridged_native_widget_host.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/views_export.h" +#include "ui/views/widget/widget.h" +#include "ui/views/window/dialog_observer.h" + +namespace views_bridge_mac { +namespace mojom { +class BridgedNativeWidget; +} // namespace mojom +} // namespace views_bridge_mac + +namespace ui { +class RecyclableCompositorMac; +} // namespace ui + +namespace views { + +class BridgedNativeWidget; +class NativeWidgetMac; + +// The portion of NativeWidgetMac that lives in the browser process. This +// communicates to the BridgedNativeWidget, which interacts with the Cocoa +// APIs, and which may live in an app shim process. +class VIEWS_EXPORT BridgedNativeWidgetHostImpl + : public BridgedNativeWidgetHost, + public DialogObserver, + public FocusChangeListener, + public ui::internal::InputMethodDelegate, + public ui::LayerDelegate, + public ui::LayerOwner, + public ui::AcceleratedWidgetMacNSView { + public: + // Creates one side of the bridge. |parent| must not be NULL. + explicit BridgedNativeWidgetHostImpl(NativeWidgetMac* parent); + ~BridgedNativeWidgetHostImpl() override; + + // Provide direct access to the BridgedNativeWidget that this is hosting. + // TODO(ccameron): Remove all accesses to this member, and replace them + // with methods that may be sent across processes. + BridgedNativeWidget* bridge_impl() const { return bridge_impl_.get(); } + views_bridge_mac::mojom::BridgedNativeWidget* bridge() const; + + void InitWindow(const Widget::InitParams& params); + + // Changes the bounds of the window and the hosted layer if present. The + // origin is a location in screen coordinates except for "child" windows, + // which are positioned relative to their parent. SetBounds() considers a + // "child" window to be one initialized with InitParams specifying all of: + // a |parent| NSWindow, the |child| attribute, and a |type| that + // views::GetAuraWindowTypeForWidgetType does not consider a "popup" type. + void SetBounds(const gfx::Rect& bounds); + + // Tell the window to transition to being fullscreen or not-fullscreen. + void SetFullscreen(bool fullscreen); + + // The ultimate fullscreen state that is being targeted (irrespective of any + // active transitions). + bool target_fullscreen_state() const { return target_fullscreen_state_; } + + // Set the root view (set during initialization and un-set during teardown). + void SetRootView(views::View* root_view); + + // Initialize the ui::Compositor and ui::Layer. + void CreateCompositor(const Widget::InitParams& params); + + // Sets or clears the focus manager to use for tracking focused views. + // This does NOT take ownership of |focus_manager|. + void SetFocusManager(FocusManager* focus_manager); + + // Set the window's title, returning true if the title has changed. + bool SetWindowTitle(const base::string16& title); + + // Called when the owning Widget's Init method has completed. + void OnWidgetInitDone(); + + // See widget.h for documentation. + ui::InputMethod* GetInputMethod(); + + // Geometry of the window, in DIPs. + const gfx::Rect& GetWindowBoundsInScreen() const { + DCHECK(has_received_window_geometry_); + return window_bounds_in_screen_; + } + + // Geometry of the content area of the window, in DIPs. Note that this is not + // necessarily the same as the views::View's size. + const gfx::Rect& GetContentBoundsInScreen() const { + DCHECK(has_received_window_geometry_); + return content_bounds_in_screen_; + } + + // The display that the window is currently on (or best guess thereof). + const display::Display& GetCurrentDisplay() const { return display_; } + + // The restored bounds will be derived from the current NSWindow frame unless + // fullscreen or transitioning between fullscreen states. + gfx::Rect GetRestoredBounds() const; + + bool IsVisible() const { return is_visible_; } + bool IsMiniaturized() const { return is_miniaturized_; } + bool IsWindowKey() const { return is_window_key_; } + bool IsMouseCaptureActive() const { return is_mouse_capture_active_; } + + private: + gfx::Vector2d GetBoundsOffsetForParent() const; + void UpdateCompositorProperties(); + void DestroyCompositor(); + + // views::BridgedNativeWidgetHost: + NSView* GetNativeViewAccessible() override; + void OnVisibilityChanged(bool visible) override; + void SetViewSize(const gfx::Size& new_size) override; + void SetKeyboardAccessible(bool enabled) override; + void SetIsFirstResponder(bool is_first_responder) override; + void OnMouseCaptureActiveChanged(bool capture_is_active) override; + void OnScrollEvent(const ui::ScrollEvent& const_event) override; + void OnMouseEvent(const ui::MouseEvent& const_event) override; + void OnGestureEvent(const ui::GestureEvent& const_event) override; + void DispatchKeyEvent(const ui::KeyEvent& const_event, + bool* event_handled) override; + void DispatchKeyEventToMenuController(const ui::KeyEvent& const_event, + bool* event_swallowed, + bool* event_handled) override; + void GetHasMenuController(bool* has_menu_controller) override; + void GetIsDraggableBackgroundAt(const gfx::Point& location_in_content, + bool* is_draggable_background) override; + void GetTooltipTextAt(const gfx::Point& location_in_content, + base::string16* new_tooltip_text) override; + void GetWordAt(const gfx::Point& location_in_content, + bool* found_word, + gfx::DecoratedText* decorated_word, + gfx::Point* baseline_point) override; + void GetWidgetIsModal(bool* widget_is_modal) override; + void GetIsFocusedViewTextual(bool* is_textual) override; + void OnWindowGeometryChanged( + const gfx::Rect& window_bounds_in_screen_dips, + const gfx::Rect& content_bounds_in_screen_dips) override; + void OnWindowFullscreenTransitionStart(bool target_fullscreen_state) override; + void OnWindowFullscreenTransitionComplete( + bool target_fullscreen_state) override; + void OnWindowMiniaturizedChanged(bool miniaturized) override; + void OnWindowDisplayChanged(const display::Display& display) override; + void OnWindowWillClose() override; + void OnWindowHasClosed() override; + void OnWindowKeyStatusChanged(bool is_key, + bool is_content_first_responder, + bool full_keyboard_access_enabled) override; + void DoDialogButtonAction(ui::DialogButton button) override; + void GetDialogButtonInfo(ui::DialogButton type, + bool* button_exists, + base::string16* button_label, + bool* is_button_enabled, + bool* is_button_default) override; + void GetDoDialogButtonsExist(bool* buttons_exist) override; + + // DialogObserver: + void OnDialogModelChanged() override; + + // FocusChangeListener: + void OnWillChangeFocus(View* focused_before, View* focused_now) override; + void OnDidChangeFocus(View* focused_before, View* focused_now) override; + + // ui::internal::InputMethodDelegate: + ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override; + + // ui::LayerDelegate: + void OnPaintLayer(const ui::PaintContext& context) override; + void OnDeviceScaleFactorChanged(float old_device_scale_factor, + float new_device_scale_factor) override; + + // ui::AcceleratedWidgetMacNSView: + void AcceleratedWidgetCALayerParamsUpdated() override; + + views::NativeWidgetMac* const native_widget_mac_; // Weak. Owns |this_|. + + Widget::InitParams::Type widget_type_ = Widget::InitParams::TYPE_WINDOW; + + views::View* root_view_ = nullptr; // Weak. Owned by |native_widget_mac_|. + + // TODO(ccameron): Rather than instantiate a BridgedNativeWidget here, + // we will instantiate a mojo BridgedNativeWidget interface to a Cocoa + // instance that may be in another process. + std::unique_ptr<BridgedNativeWidget> bridge_impl_; + + std::unique_ptr<ui::InputMethod> input_method_; + FocusManager* focus_manager_ = nullptr; // Weak. Owned by our Widget. + + base::string16 window_title_; + + // The display that the window is currently on. + display::Display display_; + + // Display link for getting vsync info for |display_|. + scoped_refptr<ui::DisplayLinkMac> display_link_; + + // The geometry of the window and its contents view, in screen coordinates. + bool has_received_window_geometry_ = false; + gfx::Rect window_bounds_in_screen_; + gfx::Rect content_bounds_in_screen_; + bool is_visible_ = false; + bool target_fullscreen_state_ = false; + bool in_fullscreen_transition_ = false; + bool is_miniaturized_ = false; + bool is_window_key_ = false; + bool is_mouse_capture_active_ = false; + gfx::Rect window_bounds_before_fullscreen_; + + std::unique_ptr<ui::RecyclableCompositorMac> compositor_; + + DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetHostImpl); +}; + +} // namespace views + +#endif // UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_IMPL_H_ diff --git a/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm new file mode 100644 index 00000000000..53e6a582b6c --- /dev/null +++ b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm @@ -0,0 +1,651 @@ +// 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/cocoa/bridged_native_widget_host_impl.h" + +#include "ui/base/hit_test.h" +#include "ui/base/ime/input_method.h" +#include "ui/base/ime/input_method_factory.h" +#include "ui/base/models/dialog_model.h" +#include "ui/compositor/recyclable_compositor_mac.h" +#include "ui/display/screen.h" +#include "ui/gfx/geometry/dip_util.h" +#include "ui/views/cocoa/bridged_native_widget.h" +#include "ui/views/controls/menu/menu_config.h" +#include "ui/views/controls/menu/menu_controller.h" +#include "ui/views/views_delegate.h" +#include "ui/views/widget/native_widget_mac.h" +#include "ui/views/widget/widget_aura_utils.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/dialog_client_view.h" +#include "ui/views/window/dialog_delegate.h" +#include "ui/views/word_lookup_client.h" + +using views_bridge_mac::mojom::BridgedNativeWidgetInitParams; +using views_bridge_mac::mojom::WindowVisibilityState; + +namespace views { + +namespace { + +// Returns true if bounds passed to window in SetBounds should be treated as +// though they are in screen coordinates. +bool PositionWindowInScreenCoordinates(Widget* widget, + Widget::InitParams::Type type) { + // Replicate the logic in desktop_aura/desktop_screen_position_client.cc. + if (GetAuraWindowTypeForWidgetType(type) == aura::client::WINDOW_TYPE_POPUP) + return true; + + return widget && widget->is_top_level(); +} + +} // namespace + +BridgedNativeWidgetHostImpl::BridgedNativeWidgetHostImpl( + NativeWidgetMac* parent) + : native_widget_mac_(parent), + bridge_impl_(new BridgedNativeWidget(this, parent)) {} + +BridgedNativeWidgetHostImpl::~BridgedNativeWidgetHostImpl() { + // Destroy the bridge first to prevent any calls back into this during + // destruction. + // TODO(ccameron): When all communication from |bridge_| to this goes through + // the BridgedNativeWidgetHost, this can be replaced with closing that pipe. + bridge_impl_.reset(); + SetFocusManager(nullptr); + DestroyCompositor(); +} + +views_bridge_mac::mojom::BridgedNativeWidget* +BridgedNativeWidgetHostImpl::bridge() const { + return bridge_impl_.get(); +} + +void BridgedNativeWidgetHostImpl::InitWindow(const Widget::InitParams& params) { + // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are + // native on Mac, so nothing should ever want one in Widget form. + DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP); + widget_type_ = params.type; + + bridge_impl_->SetParent(params.parent); + + // Initialize the window. + { + auto bridge_params = BridgedNativeWidgetInitParams::New(); + bridge_params->modal_type = + native_widget_mac_->GetWidget()->widget_delegate()->GetModalType(); + bridge_params->is_translucent = + params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW; + + // OSX likes to put shadows on most things. However, frameless windows (with + // styleMask = NSBorderlessWindowMask) default to no shadow. So change that. + // SHADOW_TYPE_DROP is used for Menus, which get the same shadow style on + // Mac. + switch (params.shadow_type) { + case Widget::InitParams::SHADOW_TYPE_NONE: + bridge_params->has_window_server_shadow = false; + break; + case Widget::InitParams::SHADOW_TYPE_DEFAULT: + // Controls should get views shadows instead of native shadows. + bridge_params->has_window_server_shadow = + params.type != Widget::InitParams::TYPE_CONTROL; + break; + case Widget::InitParams::SHADOW_TYPE_DROP: + bridge_params->has_window_server_shadow = true; + break; + } // No default case, to pick up new types. + + // Include "regular" windows without the standard frame in the window cycle. + // These use NSBorderlessWindowMask so do not get it by default. + bridge_params->force_into_collection_cycle = + widget_type_ == Widget::InitParams::TYPE_WINDOW && + params.remove_standard_frame; + + bridge()->InitWindow(std::move(bridge_params)); + } + + // Set a meaningful initial bounds. Note that except for frameless widgets + // with no WidgetDelegate, the bounds will be set again by Widget after + // initializing the non-client view. In the former case, if bounds were not + // set at all, the creator of the Widget is expected to call SetBounds() + // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized + // (i.e. 1x1) window appearing. + bridge()->SetInitialBounds(params.bounds, + native_widget_mac_->GetWidget()->GetMinimumSize(), + GetBoundsOffsetForParent()); + + // Widgets for UI controls (usually layered above web contents) start visible. + if (widget_type_ == Widget::InitParams::TYPE_CONTROL) + bridge()->SetVisibilityState(WindowVisibilityState::kShowInactive); +} + +void BridgedNativeWidgetHostImpl::SetBounds(const gfx::Rect& bounds) { + gfx::Rect adjusted_bounds = bounds; + adjusted_bounds.Offset(GetBoundsOffsetForParent()); + bridge()->SetBounds(adjusted_bounds, + native_widget_mac_->GetWidget()->GetMinimumSize()); +} + +gfx::Vector2d BridgedNativeWidgetHostImpl::GetBoundsOffsetForParent() const { + gfx::Vector2d offset; + Widget* widget = native_widget_mac_->GetWidget(); + BridgedNativeWidgetOwner* parent = bridge_impl_->parent(); + if (parent && !PositionWindowInScreenCoordinates(widget, widget_type_)) + offset = parent->GetChildWindowOffset(); + return offset; +} + +void BridgedNativeWidgetHostImpl::SetFullscreen(bool fullscreen) { + // Note that when the NSWindow begins a fullscreen transition, the value of + // |target_fullscreen_state_| updates via OnWindowFullscreenTransitionStart. + // The update here is necessary for the case where we are currently in + // transition (and therefore OnWindowFullscreenTransitionStart will not be + // called until the current transition completes). + target_fullscreen_state_ = fullscreen; + bridge()->SetFullscreen(target_fullscreen_state_); +} + +void BridgedNativeWidgetHostImpl::SetRootView(views::View* root_view) { + root_view_ = root_view; +} + +void BridgedNativeWidgetHostImpl::CreateCompositor( + const Widget::InitParams& params) { + DCHECK(!compositor_); + DCHECK(!layer()); + DCHECK(ViewsDelegate::GetInstance()); + + // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit(). + DCHECK_NE(Widget::InitParams::INFER_OPACITY, params.opacity); + bool translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW; + + // Create the layer. + SetLayer(std::make_unique<ui::Layer>(params.layer_type)); + layer()->set_delegate(this); + layer()->SetFillsBoundsOpaquely(!translucent); + + // Create the compositor and attach the layer to it. + ui::ContextFactory* context_factory = + ViewsDelegate::GetInstance()->GetContextFactory(); + DCHECK(context_factory); + ui::ContextFactoryPrivate* context_factory_private = + ViewsDelegate::GetInstance()->GetContextFactoryPrivate(); + compositor_ = ui::RecyclableCompositorMacFactory::Get()->CreateCompositor( + context_factory, context_factory_private); + compositor_->widget()->SetNSView(this); + compositor_->compositor()->SetBackgroundColor( + translucent ? SK_ColorTRANSPARENT : SK_ColorWHITE); + compositor_->compositor()->SetRootLayer(layer()); + + // The compositor is locked (prevented from producing frames) until the widget + // is made visible. + UpdateCompositorProperties(); + layer()->SetVisible(is_visible_); + if (is_visible_) + compositor_->Unsuspend(); + + bridge()->InitCompositorView(); +} + +void BridgedNativeWidgetHostImpl::UpdateCompositorProperties() { + if (!compositor_) + return; + gfx::Size surface_size_in_dip = content_bounds_in_screen_.size(); + layer()->SetBounds(gfx::Rect(surface_size_in_dip)); + compositor_->UpdateSurface( + ConvertSizeToPixel(display_.device_scale_factor(), surface_size_in_dip), + display_.device_scale_factor()); +} + +void BridgedNativeWidgetHostImpl::DestroyCompositor() { + if (layer()) { + // LayerOwner supports a change in ownership, e.g., to animate a closing + // window, but that won't work as expected for the root layer in + // BridgedNativeWidget. + DCHECK_EQ(this, layer()->owner()); + layer()->CompleteAllAnimations(); + layer()->SuppressPaint(); + layer()->set_delegate(nullptr); + } + DestroyLayer(); + if (!compositor_) + return; + compositor_->widget()->ResetNSView(); + compositor_->compositor()->SetRootLayer(nullptr); + ui::RecyclableCompositorMacFactory::Get()->RecycleCompositor( + std::move(compositor_)); +} + +void BridgedNativeWidgetHostImpl::SetFocusManager(FocusManager* focus_manager) { + if (focus_manager_ == focus_manager) + return; + + if (focus_manager_) { + // Only the destructor can replace the focus manager (and it passes null). + DCHECK(!focus_manager); + if (View* old_focus = focus_manager_->GetFocusedView()) + OnDidChangeFocus(old_focus, nullptr); + focus_manager_->RemoveFocusChangeListener(this); + focus_manager_ = nullptr; + return; + } + + focus_manager_ = focus_manager; + focus_manager_->AddFocusChangeListener(this); + if (View* new_focus = focus_manager_->GetFocusedView()) + OnDidChangeFocus(nullptr, new_focus); +} + +bool BridgedNativeWidgetHostImpl::SetWindowTitle(const base::string16& title) { + if (window_title_ == title) + return false; + window_title_ = title; + bridge()->SetWindowTitle(window_title_); + return true; +} + +void BridgedNativeWidgetHostImpl::OnWidgetInitDone() { + Widget* widget = native_widget_mac_->GetWidget(); + if (DialogDelegate* dialog = widget->widget_delegate()->AsDialogDelegate()) + dialog->AddObserver(this); +} + +ui::InputMethod* BridgedNativeWidgetHostImpl::GetInputMethod() { + if (!input_method_) { + input_method_ = ui::CreateInputMethod(this, gfx::kNullAcceleratedWidget); + // For now, use always-focused mode on Mac for the input method. + // TODO(tapted): Move this to OnWindowKeyStatusChangedTo() and balance. + input_method_->OnFocus(); + } + return input_method_.get(); +} + +gfx::Rect BridgedNativeWidgetHostImpl::GetRestoredBounds() const { + if (target_fullscreen_state_ || in_fullscreen_transition_) + return window_bounds_before_fullscreen_; + return window_bounds_in_screen_; +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, views::BridgedNativeWidgetHost: + +NSView* BridgedNativeWidgetHostImpl::GetNativeViewAccessible() { + return root_view_ ? root_view_->GetNativeViewAccessible() : nil; +} + +void BridgedNativeWidgetHostImpl::OnVisibilityChanged(bool window_visible) { + is_visible_ = window_visible; + if (compositor_) { + layer()->SetVisible(window_visible); + if (window_visible) { + compositor_->Unsuspend(); + layer()->SchedulePaint(layer()->bounds()); + } else { + compositor_->Suspend(); + } + } + native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged( + window_visible); +} + +void BridgedNativeWidgetHostImpl::OnScrollEvent( + const ui::ScrollEvent& const_event) { + ui::ScrollEvent event = const_event; + root_view_->GetWidget()->OnScrollEvent(&event); +} + +void BridgedNativeWidgetHostImpl::OnMouseEvent( + const ui::MouseEvent& const_event) { + ui::MouseEvent event = const_event; + root_view_->GetWidget()->OnMouseEvent(&event); +} + +void BridgedNativeWidgetHostImpl::OnGestureEvent( + const ui::GestureEvent& const_event) { + ui::GestureEvent event = const_event; + root_view_->GetWidget()->OnGestureEvent(&event); +} + +void BridgedNativeWidgetHostImpl::DispatchKeyEvent( + const ui::KeyEvent& const_event, + bool* event_handled) { + ui::KeyEvent event = const_event; + ignore_result( + root_view_->GetWidget()->GetInputMethod()->DispatchKeyEvent(&event)); + *event_handled = event.handled(); +} + +void BridgedNativeWidgetHostImpl::DispatchKeyEventToMenuController( + const ui::KeyEvent& const_event, + bool* event_swallowed, + bool* event_handled) { + ui::KeyEvent event = const_event; + MenuController* menu_controller = MenuController::GetActiveInstance(); + if (menu_controller && root_view_ && + menu_controller->owner() == root_view_->GetWidget()) { + *event_swallowed = menu_controller->OnWillDispatchKeyEvent(&event) == + ui::POST_DISPATCH_NONE; + } else { + *event_swallowed = false; + } + *event_handled = event.handled(); +} + +void BridgedNativeWidgetHostImpl::GetHasMenuController( + bool* has_menu_controller) { + MenuController* menu_controller = MenuController::GetActiveInstance(); + *has_menu_controller = menu_controller && root_view_ && + menu_controller->owner() == root_view_->GetWidget(); +} + +void BridgedNativeWidgetHostImpl::SetViewSize(const gfx::Size& new_size) { + root_view_->SetSize(new_size); +} + +void BridgedNativeWidgetHostImpl::SetKeyboardAccessible(bool enabled) { + views::FocusManager* focus_manager = + root_view_->GetWidget()->GetFocusManager(); + if (focus_manager) + focus_manager->SetKeyboardAccessible(enabled); +} + +void BridgedNativeWidgetHostImpl::SetIsFirstResponder(bool is_first_responder) { + if (is_first_responder) + root_view_->GetWidget()->GetFocusManager()->RestoreFocusedView(); + else + root_view_->GetWidget()->GetFocusManager()->StoreFocusedView(true); +} + +void BridgedNativeWidgetHostImpl::OnMouseCaptureActiveChanged(bool is_active) { + DCHECK_NE(is_mouse_capture_active_, is_active); + is_mouse_capture_active_ = is_active; + if (!is_mouse_capture_active_) + native_widget_mac_->GetWidget()->OnMouseCaptureLost(); +} + +void BridgedNativeWidgetHostImpl::GetIsDraggableBackgroundAt( + const gfx::Point& location_in_content, + bool* is_draggable_background) { + int component = + root_view_->GetWidget()->GetNonClientComponent(location_in_content); + *is_draggable_background = component == HTCAPTION; +} + +void BridgedNativeWidgetHostImpl::GetTooltipTextAt( + const gfx::Point& location_in_content, + base::string16* new_tooltip_text) { + views::View* view = + root_view_->GetTooltipHandlerForPoint(location_in_content); + if (view) { + gfx::Point view_point = location_in_content; + views::View::ConvertPointToScreen(root_view_, &view_point); + views::View::ConvertPointFromScreen(view, &view_point); + if (!view->GetTooltipText(view_point, new_tooltip_text)) + DCHECK(new_tooltip_text->empty()); + } +} + +void BridgedNativeWidgetHostImpl::GetWordAt( + const gfx::Point& location_in_content, + bool* found_word, + gfx::DecoratedText* decorated_word, + gfx::Point* baseline_point) { + *found_word = false; + + views::View* target = + root_view_->GetEventHandlerForPoint(location_in_content); + if (!target) + return; + + views::WordLookupClient* word_lookup_client = target->GetWordLookupClient(); + if (!word_lookup_client) + return; + + gfx::Point location_in_target = location_in_content; + views::View::ConvertPointToTarget(root_view_, target, &location_in_target); + if (!word_lookup_client->GetWordLookupDataAtPoint( + location_in_target, decorated_word, baseline_point)) { + return; + } + + // Convert |baselinePoint| to the coordinate system of |root_view_|. + views::View::ConvertPointToTarget(target, root_view_, baseline_point); + *found_word = true; +} + +void BridgedNativeWidgetHostImpl::GetWidgetIsModal(bool* widget_is_modal) { + *widget_is_modal = native_widget_mac_->GetWidget()->IsModal(); +} + +void BridgedNativeWidgetHostImpl::GetIsFocusedViewTextual(bool* is_textual) { + views::FocusManager* focus_manager = + root_view_ ? root_view_->GetWidget()->GetFocusManager() : nullptr; + *is_textual = focus_manager && focus_manager->GetFocusedView() && + focus_manager->GetFocusedView()->GetClassName() == + views::Label::kViewClassName; +} + +void BridgedNativeWidgetHostImpl::OnWindowGeometryChanged( + const gfx::Rect& new_window_bounds_in_screen, + const gfx::Rect& new_content_bounds_in_screen) { + has_received_window_geometry_ = true; + + bool window_has_moved = + new_window_bounds_in_screen.origin() != window_bounds_in_screen_.origin(); + bool content_has_resized = + new_content_bounds_in_screen.size() != content_bounds_in_screen_.size(); + + window_bounds_in_screen_ = new_window_bounds_in_screen; + content_bounds_in_screen_ = new_content_bounds_in_screen; + + // When a window grows vertically, the AppKit origin changes, but as far as + // tookit-views is concerned, the window hasn't moved. Suppress these. + if (window_has_moved) + native_widget_mac_->GetWidget()->OnNativeWidgetMove(); + + // Note we can't use new_window_bounds_in_screen.size(), since it includes the + // titlebar for the purposes of detecting a window move. + if (content_has_resized) + native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged( + content_bounds_in_screen_.size()); + + // Update the compositor surface and layer size. + UpdateCompositorProperties(); +} + +void BridgedNativeWidgetHostImpl::OnWindowFullscreenTransitionStart( + bool target_fullscreen_state) { + target_fullscreen_state_ = target_fullscreen_state; + in_fullscreen_transition_ = true; + + // If going into fullscreen, store an answer for GetRestoredBounds(). + if (target_fullscreen_state) + window_bounds_before_fullscreen_ = window_bounds_in_screen_; + + // Notify that fullscreen state changed. + native_widget_mac_->OnWindowFullscreenStateChange(); +} + +void BridgedNativeWidgetHostImpl::OnWindowFullscreenTransitionComplete( + bool actual_fullscreen_state) { + in_fullscreen_transition_ = false; + + // Ensure constraints are re-applied when completing a transition. + native_widget_mac_->OnSizeConstraintsChanged(); +} + +void BridgedNativeWidgetHostImpl::OnWindowMiniaturizedChanged( + bool miniaturized) { + is_miniaturized_ = miniaturized; +} + +void BridgedNativeWidgetHostImpl::OnWindowDisplayChanged( + const display::Display& new_display) { + bool scale_factor_changed = + display_.device_scale_factor() != new_display.device_scale_factor(); + bool display_id_changed = display_.id() != new_display.id(); + display_ = new_display; + if (scale_factor_changed && compositor_ && has_received_window_geometry_) { + compositor_->UpdateSurface( + ConvertSizeToPixel(display_.device_scale_factor(), + content_bounds_in_screen_.size()), + display_.device_scale_factor()); + } + if (display_id_changed) { + display_link_ = ui::DisplayLinkMac::GetForDisplay(display_.id()); + if (!display_link_) { + // Note that on some headless systems, the display link will fail to be + // created, so this should not be a fatal error. + LOG(ERROR) << "Failed to create display link."; + } + } +} + +void BridgedNativeWidgetHostImpl::OnWindowWillClose() { + Widget* widget = native_widget_mac_->GetWidget(); + if (DialogDelegate* dialog = widget->widget_delegate()->AsDialogDelegate()) + dialog->RemoveObserver(this); + native_widget_mac_->WindowDestroying(); +} + +void BridgedNativeWidgetHostImpl::OnWindowHasClosed() { + native_widget_mac_->WindowDestroyed(); +} + +void BridgedNativeWidgetHostImpl::OnWindowKeyStatusChanged( + bool is_key, + bool is_content_first_responder, + bool full_keyboard_access_enabled) { + is_window_key_ = is_key; + Widget* widget = native_widget_mac_->GetWidget(); + if (!widget->OnNativeWidgetActivationChanged(is_key)) + return; + // The contentView is the BridgedContentView hosting the views::RootView. The + // focus manager will already know if a native subview has focus. + if (is_content_first_responder) { + if (is_key) { + widget->OnNativeFocus(); + // Explicitly set the keyboard accessibility state on regaining key + // window status. + SetKeyboardAccessible(full_keyboard_access_enabled); + widget->GetFocusManager()->RestoreFocusedView(); + } else { + widget->OnNativeBlur(); + widget->GetFocusManager()->StoreFocusedView(true); + } + } +} + +void BridgedNativeWidgetHostImpl::DoDialogButtonAction( + ui::DialogButton button) { + views::DialogDelegate* dialog = + root_view_->GetWidget()->widget_delegate()->AsDialogDelegate(); + DCHECK(dialog); + views::DialogClientView* client = dialog->GetDialogClientView(); + if (button == ui::DIALOG_BUTTON_OK) { + client->AcceptWindow(); + } else { + DCHECK_EQ(button, ui::DIALOG_BUTTON_CANCEL); + client->CancelWindow(); + } +} + +void BridgedNativeWidgetHostImpl::GetDialogButtonInfo( + ui::DialogButton button, + bool* button_exists, + base::string16* button_label, + bool* is_button_enabled, + bool* is_button_default) { + *button_exists = false; + ui::DialogModel* model = + root_view_->GetWidget()->widget_delegate()->AsDialogDelegate(); + if (!model || !(model->GetDialogButtons() & button)) + return; + *button_exists = true; + *button_label = model->GetDialogButtonLabel(button); + *is_button_enabled = model->IsDialogButtonEnabled(button); + *is_button_default = button == model->GetDefaultDialogButton(); +} + +void BridgedNativeWidgetHostImpl::GetDoDialogButtonsExist(bool* buttons_exist) { + ui::DialogModel* model = + root_view_->GetWidget()->widget_delegate()->AsDialogDelegate(); + *buttons_exist = model && model->GetDialogButtons(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, DialogObserver: + +void BridgedNativeWidgetHostImpl::OnDialogModelChanged() { + // Note it's only necessary to clear the TouchBar. If the OS needs it again, + // a new one will be created. + bridge()->ClearTouchBar(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, FocusChangeListener: + +void BridgedNativeWidgetHostImpl::OnWillChangeFocus(View* focused_before, + View* focused_now) {} + +void BridgedNativeWidgetHostImpl::OnDidChangeFocus(View* focused_before, + View* focused_now) { + ui::InputMethod* input_method = + native_widget_mac_->GetWidget()->GetInputMethod(); + if (input_method) { + ui::TextInputClient* input_client = input_method->GetTextInputClient(); + // Sanity check: When focus moves away from the widget (i.e. |focused_now| + // is nil), then the textInputClient will be cleared. + DCHECK(!!focused_now || !input_client); + bridge_impl_->SetTextInputClient(input_client); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidget, internal::InputMethodDelegate: + +ui::EventDispatchDetails BridgedNativeWidgetHostImpl::DispatchKeyEventPostIME( + ui::KeyEvent* key) { + DCHECK(focus_manager_); + if (!focus_manager_->OnKeyEvent(*key)) + key->StopPropagation(); + else + native_widget_mac_->GetWidget()->OnKeyEvent(key); + return ui::EventDispatchDetails(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, LayerDelegate: + +void BridgedNativeWidgetHostImpl::OnPaintLayer( + const ui::PaintContext& context) { + native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context); +} + +void BridgedNativeWidgetHostImpl::OnDeviceScaleFactorChanged( + float old_device_scale_factor, + float new_device_scale_factor) { + native_widget_mac_->GetWidget()->DeviceScaleFactorChanged( + old_device_scale_factor, new_device_scale_factor); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, AcceleratedWidgetMac: + +void BridgedNativeWidgetHostImpl::AcceleratedWidgetCALayerParamsUpdated() { + const gfx::CALayerParams* ca_layer_params = + compositor_->widget()->GetCALayerParams(); + if (ca_layer_params) + bridge()->SetCALayerParams(*ca_layer_params); + + // Take this opportunity to update the VSync parameters, if needed. + if (display_link_) { + base::TimeTicks timebase; + base::TimeDelta interval; + if (display_link_->GetVSyncParameters(&timebase, &interval)) + compositor_->compositor()->SetDisplayVSyncParameters(timebase, interval); + } +} + +} // namespace views diff --git a/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm b/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm index f02e1b94c08..53c32814537 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm @@ -15,6 +15,7 @@ #include "ui/base/test/ui_controls.h" #import "ui/base/test/windowed_nsnotification_observer.h" #import "ui/events/test/cocoa_test_event_utils.h" +#include "ui/views/cocoa/bridged_native_widget_host_impl.h" #include "ui/views/test/views_interactive_ui_test_base.h" #include "ui/views/test/widget_test.h" #include "ui/views/widget/native_widget_mac.h" @@ -256,7 +257,7 @@ class HitTestNativeWidgetMac : public NativeWidgetMac { HitTestNativeWidgetMac(internal::NativeWidgetDelegate* delegate, NativeFrameView* native_frame_view) : NativeWidgetMac(delegate), native_frame_view_(native_frame_view) { - NativeWidgetMac::bridge_.reset(new BridgedNativeWidget(this)); + bridge_host_ = std::make_unique<BridgedNativeWidgetHostImpl>(this); } // internal::NativeWidgetPrivate: diff --git a/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm b/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm index f25287d5bb5..8f3abc65650 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm @@ -26,6 +26,7 @@ #include "ui/events/test/cocoa_test_event_utils.h" #import "ui/gfx/mac/coordinate_conversion.h" #import "ui/views/cocoa/bridged_content_view.h" +#import "ui/views/cocoa/bridged_native_widget_host_impl.h" #import "ui/views/cocoa/native_widget_mac_nswindow.h" #import "ui/views/cocoa/views_nswindow_delegate.h" #include "ui/views/controls/textfield/textfield.h" @@ -211,12 +212,15 @@ NSTextInputContext* g_fake_current_input_context = nullptr; } // namespace -// Class to hook [NSView interpretKeyEvents:] to simulate it interacting with an -// IME window. -@interface InterpretKeyEventsDonorForNSView : NSView +// Subclass of BridgedContentView with an override of interpretKeyEvents:. Note +// the size of the class must match BridgedContentView since the method table +// is swapped out at runtime. This is basically a mock, but mocks are banned +// under ui/views. Method swizzling causes these tests to flake when +// parallelized in the same process. +@interface InterpretKeyEventMockedBridgedContentView : BridgedContentView @end -@implementation InterpretKeyEventsDonorForNSView +@implementation InterpretKeyEventMockedBridgedContentView - (void)interpretKeyEvents:(NSArray<NSEvent*>*)eventArray { ASSERT_TRUE(g_fake_interpret_key_events); @@ -251,15 +255,18 @@ NSTextInputContext* g_fake_current_input_context = nullptr; // NSWindow's behavior when attempting to toggle fullscreen state again, when // the last attempt failed but Cocoa has not yet sent // windowDidFailToEnterFullScreen:. -@interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow { +@interface BridgedNativeWidgetTestWindow : NativeWidgetMacNSWindow { @private + BOOL ignoreToggleFullScreen_; int ignoredToggleFullScreenCount_; } +@property(assign, nonatomic) BOOL ignoreToggleFullScreen; @property(readonly, nonatomic) int ignoredToggleFullScreenCount; @end -@implementation BridgedNativeWidgetTestFullScreenWindow +@implementation BridgedNativeWidgetTestWindow +@synthesize ignoreToggleFullScreen = ignoreToggleFullScreen_; @synthesize ignoredToggleFullScreenCount = ignoredToggleFullScreenCount_; - (void)performSelector:(SEL)aSelector @@ -268,14 +275,17 @@ NSTextInputContext* g_fake_current_input_context = nullptr; // This is used in simulations without a message loop. Don't start a message // loop since that would expose the tests to system notifications and // potential flakes. Instead, just pretend the message loop is flushed here. - if (aSelector == @selector(toggleFullScreen:)) + if (ignoreToggleFullScreen_ && aSelector == @selector(toggleFullScreen:)) [self toggleFullScreen:anArgument]; else [super performSelector:aSelector withObject:anArgument afterDelay:delay]; } - (void)toggleFullScreen:(id)sender { - ++ignoredToggleFullScreenCount_; + if (ignoreToggleFullScreen_) + ++ignoredToggleFullScreenCount_; + else + [super toggleFullScreen:sender]; } @end @@ -288,20 +298,28 @@ class MockNativeWidgetMac : public NativeWidgetMac { public: explicit MockNativeWidgetMac(internal::NativeWidgetDelegate* delegate) : NativeWidgetMac(delegate) {} - - // Expose a reference, so that it can be reset() independently. - std::unique_ptr<BridgedNativeWidget>& bridge() { return bridge_; } + using NativeWidgetMac::bridge; + using NativeWidgetMac::bridge_host_for_testing; // internal::NativeWidgetPrivate: void InitNativeWidget(const Widget::InitParams& params) override { ownership_ = params.ownership; + base::scoped_nsobject<NativeWidgetMacNSWindow> window( + [[BridgedNativeWidgetTestWindow alloc] + initWithContentRect:ui::kWindowSizeDeterminedLater + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]); + bridge()->SetWindow(window); + bridge_host_for_testing()->InitWindow(params); + // Usually the bridge gets initialized here. It is skipped to run extra // checks in tests, and so that a second window isn't created. delegate()->OnNativeWidgetCreated(true); // To allow events to dispatch to a view, it needs a way to get focus. - bridge_->SetFocusManager(GetWidget()->GetFocusManager()); + bridge_host_for_testing()->SetFocusManager(GetWidget()->GetFocusManager()); } void ReorderNativeViews() override { @@ -324,8 +342,9 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { explicit BridgedNativeWidgetTestBase(SkipInitialization tag) : native_widget_mac_(nullptr) {} - std::unique_ptr<BridgedNativeWidget>& bridge() { - return native_widget_mac_->bridge(); + BridgedNativeWidget* bridge() { return native_widget_mac_->bridge(); } + BridgedNativeWidgetHostImpl* bridge_host() { + return native_widget_mac_->bridge_host_for_testing(); } // Overridden from testing::Test: @@ -357,10 +376,18 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { } void TearDown() override { + // ui::CocoaTest::TearDown will wait until all NSWindows are destroyed, so + // be sure to destroy the widget (which will destroy its NSWindow) + // beforehand. + widget_.reset(); ui::test::MaterialDesignControllerTestAPI::Uninitialize(); ui::CocoaTest::TearDown(); } + NSWindow* bridge_window() const { + return native_widget_mac_->bridge()->ns_window(); + } + protected: std::unique_ptr<Widget> widget_; MockNativeWidgetMac* native_widget_mac_; // Weak. Owned by |widget_|. @@ -464,6 +491,23 @@ class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase, DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest); }; +// Class that counts occurrences of a VKEY_RETURN accelerator, marking them +// processed. +class EnterAcceleratorView : public View { + public: + EnterAcceleratorView() { AddAccelerator({ui::VKEY_RETURN, 0}); } + int count() const { return count_; } + + // View: + bool AcceleratorPressed(const ui::Accelerator& accelerator) override { + ++count_; + return true; + } + + private: + int count_ = 0; +}; + BridgedNativeWidgetTest::BridgedNativeWidgetTest() : scoped_task_environment_( base::test::ScopedTaskEnvironment::MainThreadType::UI) {} @@ -565,30 +609,27 @@ void BridgedNativeWidgetTest::SetUp() { BridgedNativeWidgetTestBase::SetUp(); view_.reset(new views::internal::RootView(widget_.get())); - base::scoped_nsobject<NSWindow> window([test_window() retain]); - - // BridgedNativeWidget expects to be initialized with a hidden (deferred) - // window. - [window orderOut:nil]; - EXPECT_FALSE([window delegate]); - bridge()->Init(window, init_params_); + base::scoped_nsobject<NSWindow> window([bridge_window() retain]); // The delegate should exist before setting the root view. EXPECT_TRUE([window delegate]); - bridge()->SetRootView(view_.get()); + bridge_host()->SetRootView(view_.get()); + bridge()->CreateContentView(view_->bounds()); ns_view_ = bridge()->ns_view(); // Pretend it has been shown via NativeWidgetMac::Show(). [window orderFront:nil]; - [test_window() makePretendKeyWindowAndSetFirstResponder:bridge()->ns_view()]; + [window makeFirstResponder:bridge()->ns_view()]; } void BridgedNativeWidgetTest::TearDown() { // Clear kill buffer so that no state persists between tests. TextfieldModel::ClearKillBuffer(); - if (bridge()) - bridge()->SetRootView(nullptr); + if (bridge_host()) { + bridge_host()->SetRootView(nullptr); + bridge()->DestroyContentView(); + } view_.reset(); BridgedNativeWidgetTestBase::TearDown(); } @@ -740,12 +781,13 @@ void BridgedNativeWidgetTest::TestEditingCommands(NSArray* selectors) { // what TEST_VIEW usually does. TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewAddRemove) { base::scoped_nsobject<BridgedContentView> view([bridge()->ns_view() retain]); - EXPECT_NSEQ([test_window() contentView], view); - EXPECT_NSEQ(test_window(), [view window]); + base::scoped_nsobject<NSWindow> window([bridge_window() retain]); + EXPECT_NSEQ([window contentView], view); + EXPECT_NSEQ(window, [view window]); // The superview of a contentView is an NSNextStepFrame. EXPECT_TRUE([view superview]); - EXPECT_TRUE([view hostedView]); + EXPECT_TRUE([view bridge]); // Ensure the tracking area to propagate mouseMoved: events to the RootView is // installed. @@ -753,13 +795,13 @@ TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewAddRemove) { // Closing the window should tear down the C++ bridge, remove references to // any C++ objects in the ObjectiveC object, and remove it from the hierarchy. - [test_window() close]; - EXPECT_FALSE([view hostedView]); + [window close]; + EXPECT_FALSE([view bridge]); EXPECT_FALSE([view superview]); EXPECT_FALSE([view window]); EXPECT_EQ(0u, [[view trackingAreas] count]); - EXPECT_FALSE([test_window() contentView]); - EXPECT_FALSE([test_window() delegate]); + EXPECT_FALSE([window contentView]); + EXPECT_FALSE([window delegate]); } TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewDisplay) { @@ -771,8 +813,8 @@ TEST_F(BridgedNativeWidgetTest, ViewSizeTracksWindow) { const int kTestNewWidth = 400; const int kTestNewHeight = 300; - // |test_window()| is borderless, so these should align. - NSSize window_size = [test_window() frame].size; + // |bridge_window()| is borderless, so these should align. + NSSize window_size = [bridge_window() frame].size; EXPECT_EQ(view_->width(), static_cast<int>(window_size.width)); EXPECT_EQ(view_->height(), static_cast<int>(window_size.height)); @@ -780,14 +822,14 @@ TEST_F(BridgedNativeWidgetTest, ViewSizeTracksWindow) { EXPECT_NE(kTestNewWidth, view_->width()); EXPECT_NE(kTestNewHeight, view_->height()); - [test_window() setFrame:NSMakeRect(0, 0, kTestNewWidth, kTestNewHeight) - display:NO]; + [bridge_window() setFrame:NSMakeRect(0, 0, kTestNewWidth, kTestNewHeight) + display:NO]; EXPECT_EQ(kTestNewWidth, view_->width()); EXPECT_EQ(kTestNewHeight, view_->height()); } TEST_F(BridgedNativeWidgetTest, GetInputMethodShouldNotReturnNull) { - EXPECT_TRUE(bridge()->GetInputMethod()); + EXPECT_TRUE(bridge_host()->GetInputMethod()); } // A simpler test harness for testing initialization flows. @@ -797,13 +839,7 @@ class BridgedNativeWidgetInitTest : public BridgedNativeWidgetTestBase { : BridgedNativeWidgetTestBase(SkipInitialization()) {} // Prepares a new |window_| and |widget_| for a call to PerformInit(). - void CreateNewWidgetToInit(NSUInteger style_mask) { - window_.reset( - [[NSWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater - styleMask:style_mask - backing:NSBackingStoreBuffered - defer:NO]); - [window_ setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. + void CreateNewWidgetToInit() { widget_.reset(new Widget); native_widget_mac_ = new MockNativeWidgetMac(widget_.get()); init_params_.native_widget = native_widget_mac_; @@ -811,12 +847,8 @@ class BridgedNativeWidgetInitTest : public BridgedNativeWidgetTestBase { void PerformInit() { widget_->Init(init_params_); - bridge()->Init(window_, init_params_); } - protected: - base::scoped_nsobject<NSWindow> window_; - private: DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetInitTest); }; @@ -841,34 +873,32 @@ TEST_F(BridgedNativeWidgetInitTest, ShadowType) { EXPECT_EQ(Widget::InitParams::OPAQUE_WINDOW, init_params_.opacity); EXPECT_EQ(Widget::InitParams::SHADOW_TYPE_DEFAULT, init_params_.shadow_type); - CreateNewWidgetToInit(NSBorderlessWindowMask); - EXPECT_FALSE([window_ hasShadow]); // Default for NSBorderlessWindowMask. + CreateNewWidgetToInit(); + EXPECT_FALSE( + [bridge_window() hasShadow]); // Default for NSBorderlessWindowMask. PerformInit(); // Borderless is 0, so isn't really a mask. Check that nothing is set. - EXPECT_EQ(NSBorderlessWindowMask, [window_ styleMask]); - EXPECT_TRUE([window_ hasShadow]); // SHADOW_TYPE_DEFAULT means a shadow. + EXPECT_EQ(NSBorderlessWindowMask, [bridge_window() styleMask]); + EXPECT_TRUE( + [bridge_window() hasShadow]); // SHADOW_TYPE_DEFAULT means a shadow. - CreateNewWidgetToInit(NSBorderlessWindowMask); + CreateNewWidgetToInit(); init_params_.shadow_type = Widget::InitParams::SHADOW_TYPE_NONE; PerformInit(); - EXPECT_FALSE([window_ hasShadow]); // Preserves lack of shadow. + EXPECT_FALSE([bridge_window() hasShadow]); // Preserves lack of shadow. // Default for Widget::InitParams::TYPE_WINDOW. - NSUInteger kBorderedMask = - NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | - NSResizableWindowMask | NSTexturedBackgroundWindowMask; - CreateNewWidgetToInit(kBorderedMask); - EXPECT_TRUE([window_ hasShadow]); // Default for non-borderless. + CreateNewWidgetToInit(); PerformInit(); - EXPECT_FALSE([window_ hasShadow]); // SHADOW_TYPE_NONE removes shadow. + EXPECT_FALSE( + [bridge_window() hasShadow]); // SHADOW_TYPE_NONE removes shadow. init_params_.shadow_type = Widget::InitParams::SHADOW_TYPE_DEFAULT; - CreateNewWidgetToInit(kBorderedMask); + CreateNewWidgetToInit(); PerformInit(); - EXPECT_TRUE([window_ hasShadow]); // Preserves shadow. + EXPECT_TRUE([bridge_window() hasShadow]); // Preserves shadow. - window_.reset(); widget_.reset(); } @@ -1072,6 +1102,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_AccentedCharacter) { widget_->GetNativeWindow(), true, ui::VKEY_A, 0)); [ns_view_ insertText:@"a" replacementRange:EmptyRange()]; [dummy_text_view_ insertText:@"a" replacementRange:EmptyRange()]; + SetKeyDownEvent(nil); EXPECT_EQ_3(NO, [dummy_text_view_ hasMarkedText], [ns_view_ hasMarkedText]); EXPECT_NSEQ_3(@"abca", GetExpectedText(), GetActualText()); @@ -1086,6 +1117,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_AccentedCharacter) { [dummy_text_view_ setMarkedText:@"à" selectedRange:NSMakeRange(0, 1) replacementRange:NSMakeRange(3, 1)]; + SetKeyDownEvent(nil); EXPECT_EQ_3(YES, [dummy_text_view_ hasMarkedText], [ns_view_ hasMarkedText]); EXPECT_EQ_RANGE_3(NSMakeRange(3, 1), [dummy_text_view_ markedRange], [ns_view_ markedRange]); @@ -1098,6 +1130,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_AccentedCharacter) { widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0)); [ns_view_ insertText:@"à" replacementRange:EmptyRange()]; [dummy_text_view_ insertText:@"à" replacementRange:EmptyRange()]; + SetKeyDownEvent(nil); EXPECT_EQ_3(NO, [dummy_text_view_ hasMarkedText], [ns_view_ hasMarkedText]); EXPECT_EQ_RANGE_3(NSMakeRange(4, 0), GetExpectedSelectionRange(), GetActualSelectionRange()); @@ -1397,9 +1430,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_SimulatePhoneticIme) { Textfield* textfield = InstallTextField(""); EXPECT_TRUE([ns_view_ textInputClient]); - base::mac::ScopedObjCClassSwizzler interpret_key_events_swizzler( - [NSView class], [InterpretKeyEventsDonorForNSView class], - @selector(interpretKeyEvents:)); + object_setClass(ns_view_, [InterpretKeyEventMockedBridgedContentView class]); // Sequence of calls (and corresponding keyDown events) obtained via tracing // with 2-Set Korean IME and pressing q, o, then Enter on the keyboard. @@ -1465,6 +1496,136 @@ TEST_F(BridgedNativeWidgetTest, TextInput_SimulatePhoneticIme) { g_fake_interpret_key_events = nullptr; } +// Simulate 'a', Enter in Hiragana. This should just insert "あ", suppressing +// accelerators. +TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorEnterComposition) { + Textfield* textfield = InstallTextField(""); + EXPECT_TRUE([ns_view_ textInputClient]); + + EnterAcceleratorView* enter_view = new EnterAcceleratorView(); + textfield->parent()->AddChildView(enter_view); + + // Sequence of calls (and corresponding keyDown events) obtained via tracing + // with Hiragana IME and pressing 'a', then Enter on the keyboard. + NSEvent* a_in_ime = cocoa_test_event_utils::KeyEventWithKeyCode( + 0, [@"a" characterAtIndex:0], NSKeyDown, 0); + InterpretKeyEventsCallback handle_a_in_ime = base::BindRepeating([](id view) { + // TODO(crbug/612675): |text| should be an NSAttributedString. + [view setMarkedText:@"あ" + selectedRange:NSMakeRange(1, 0) + replacementRange:NSMakeRange(NSNotFound, 0)]; + }); + + NSEvent* return_event = cocoa_test_event_utils::SynthesizeKeyEvent( + widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0); + InterpretKeyEventsCallback handle_return_in_ime = + base::BindRepeating([](id view) { + [view insertText:@"あ" replacementRange:NSMakeRange(NSNotFound, 0)]; + }); + + EXPECT_EQ(base::UTF8ToUTF16(""), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + object_setClass(ns_view_, [InterpretKeyEventMockedBridgedContentView class]); + g_fake_interpret_key_events = &handle_a_in_ime; + [ns_view_ keyDown:a_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + g_fake_interpret_key_events = &handle_return_in_ime; + [ns_view_ keyDown:return_event]; + EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); // Not seen as an accelerator. + + // IME Window is dismissed here and there is no marked text, so remove the + // swizzler. + object_setClass(ns_view_, [BridgedContentView class]); + + [ns_view_ keyDown:return_event]; // Sanity check: send Enter again. + EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); // No change. + EXPECT_EQ(1, enter_view->count()); // Now we see the accelerator. +} + +// Simulate 'a', Tab, Enter, Enter in Hiragana. This should just insert "a", +// suppressing accelerators. +TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorTabEnterComposition) { + Textfield* textfield = InstallTextField(""); + EXPECT_TRUE([ns_view_ textInputClient]); + + EnterAcceleratorView* enter_view = new EnterAcceleratorView(); + textfield->parent()->AddChildView(enter_view); + + // Sequence of calls (and corresponding keyDown events) obtained via tracing + // with Hiragana IME and pressing 'a', Tab, then Enter on the keyboard. + NSEvent* a_in_ime = cocoa_test_event_utils::KeyEventWithKeyCode( + 0, [@"a" characterAtIndex:0], NSKeyDown, 0); + InterpretKeyEventsCallback handle_a_in_ime = base::BindRepeating([](id view) { + // TODO(crbug/612675): |text| should have an underline. + [view setMarkedText:@"あ" + selectedRange:NSMakeRange(1, 0) + replacementRange:NSMakeRange(NSNotFound, 0)]; + }); + + NSEvent* tab_in_ime = cocoa_test_event_utils::SynthesizeKeyEvent( + widget_->GetNativeWindow(), true, ui::VKEY_TAB, 0); + InterpretKeyEventsCallback handle_tab_in_ime = + base::BindRepeating([](id view) { + // TODO(crbug/612675): |text| should be an NSAttributedString (now with + // a different underline color). + [view setMarkedText:@"a" + selectedRange:NSMakeRange(0, 1) + replacementRange:NSMakeRange(NSNotFound, 0)]; + }); + + NSEvent* return_event = cocoa_test_event_utils::SynthesizeKeyEvent( + widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0); + InterpretKeyEventsCallback handle_first_return_in_ime = + base::BindRepeating([](id view) { + // Do *nothing*. Enter does not confirm nor change the composition, it + // just dismisses the IME window, leaving the text marked. + }); + InterpretKeyEventsCallback handle_second_return_in_ime = + base::BindRepeating([](id view) { + // The second return will confirm the composition. + [view insertText:@"a" replacementRange:NSMakeRange(NSNotFound, 0)]; + }); + + EXPECT_EQ(base::UTF8ToUTF16(""), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + object_setClass(ns_view_, [InterpretKeyEventMockedBridgedContentView class]); + g_fake_interpret_key_events = &handle_a_in_ime; + [ns_view_ keyDown:a_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + g_fake_interpret_key_events = &handle_tab_in_ime; + [ns_view_ keyDown:tab_in_ime]; + // Tab will switch to a Romanji (Latin) character. + EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + g_fake_interpret_key_events = &handle_first_return_in_ime; + [ns_view_ keyDown:return_event]; + // Enter just dismisses the IME window. The composition is still active. + EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); // Not seen as an accelerator. + + g_fake_interpret_key_events = &handle_second_return_in_ime; + [ns_view_ keyDown:return_event]; + // Enter now confirms the composition (unmarks text). Note there is still no + // IME window visible but, since there is marked text, IME is still active. + EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); // Not seen as an accelerator. + + // No marked text, no IME window. We could remove the swizzler here, but + // that is equivalent to the "do nothing" case, so set than handler again. + g_fake_interpret_key_events = &handle_first_return_in_ime; + [ns_view_ keyDown:return_event]; // Send Enter a _third_ time. + EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); // No change. + EXPECT_EQ(1, enter_view->count()); // Now we see the accelerator. +} + // Test a codepath that could hypothetically cause [NSApp updateWindows] to be // called recursively due to IME dismissal during teardown triggering a focus // change. Twice. @@ -1472,9 +1633,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_RecursiveUpdateWindows) { Textfield* textfield = InstallTextField(""); EXPECT_TRUE([ns_view_ textInputClient]); - base::mac::ScopedObjCClassSwizzler interpret_key_events_swizzler( - [NSView class], [InterpretKeyEventsDonorForNSView class], - @selector(interpretKeyEvents:)); + object_setClass(ns_view_, [InterpretKeyEventMockedBridgedContentView class]); base::mac::ScopedObjCClassSwizzler update_windows_swizzler( [NSApplication class], [UpdateWindowsDonorForNSApp class], @selector(updateWindows)); @@ -1580,18 +1739,10 @@ typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause // the fullscreen transition to fail. TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { - base::scoped_nsobject<NSWindow> owned_window( - [[BridgedNativeWidgetTestFullScreenWindow alloc] - initWithContentRect:NSMakeRect(50, 50, 400, 300) - styleMask:NSBorderlessWindowMask - backing:NSBackingStoreBuffered - defer:NO]); - [owned_window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. - bridge()->Init(owned_window, init_params_); // Transfers ownership. - - BridgedNativeWidgetTestFullScreenWindow* window = - base::mac::ObjCCastStrict<BridgedNativeWidgetTestFullScreenWindow>( + BridgedNativeWidgetTestWindow* window = + base::mac::ObjCCastStrict<BridgedNativeWidgetTestWindow>( widget_->GetNativeWindow()); + [window setIgnoreToggleFullScreen:YES]; widget_->Show(); NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; diff --git a/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm b/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm index dd5edea74a8..9f3559de2ef 100644 --- a/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm +++ b/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm @@ -14,6 +14,7 @@ #import "ui/base/clipboard/clipboard_util_mac.h" #include "ui/gfx/image/image_unittest_util.h" #import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views/cocoa/bridged_native_widget_host_impl.h" #include "ui/views/test/widget_test.h" #include "ui/views/view.h" #include "ui/views/widget/native_widget_mac.h" @@ -187,6 +188,8 @@ class DragDropClientMacTest : public WidgetTest { bridge_ = NativeWidgetMac::GetBridgeForNativeWindow(widget_->GetNativeWindow()); + bridge_host_ = NativeWidgetMac::GetBridgeHostImplForNativeWindow( + widget_->GetNativeWindow()); widget_->Show(); target_ = new DragDropView(); @@ -205,6 +208,7 @@ class DragDropClientMacTest : public WidgetTest { protected: Widget* widget_ = nullptr; BridgedNativeWidget* bridge_ = nullptr; + BridgedNativeWidgetHostImpl* bridge_host_ = nullptr; DragDropView* target_ = nullptr; base::scoped_nsobject<MockDraggingInfo> dragging_info_; @@ -236,7 +240,7 @@ TEST_F(DragDropClientMacTest, ReleaseCapture) { // since the runloop will exit before the system has any opportunity to // capture anything. bridge_->AcquireCapture(); - EXPECT_TRUE(bridge_->HasCapture()); + EXPECT_TRUE(bridge_host_->IsMouseCaptureActive()); // Create the drop data OSExchangeData data; @@ -262,7 +266,7 @@ TEST_F(DragDropClientMacTest, ReleaseCapture) { target_, data, 0, ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE); // The capture should be released. - EXPECT_FALSE(bridge_->HasCapture()); + EXPECT_FALSE(bridge_host_->IsMouseCaptureActive()); } // Tests if the drag and drop target rejects the dropped data with the diff --git a/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm b/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm index ea244d97722..3e5775bc184 100644 --- a/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm +++ b/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm @@ -7,6 +7,7 @@ #include "base/mac/foundation_util.h" #import "base/mac/sdk_forward_declarations.h" #import "ui/base/cocoa/user_interface_item_command_handler.h" +#import "ui/base/cocoa/window_size_constants.h" #import "ui/views/cocoa/bridged_native_widget.h" #import "ui/views/cocoa/views_nswindow_delegate.h" #import "ui/views/cocoa/window_touch_bar_delegate.h" @@ -88,7 +89,8 @@ styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation { - if ((self = [super initWithContentRect:contentRect + DCHECK(NSEqualRects(contentRect, ui::kWindowSizeDeterminedLater)); + if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater styleMask:windowStyle backing:bufferingType defer:deferCreation])) { diff --git a/chromium/ui/views/cocoa/views_nswindow_delegate.mm b/chromium/ui/views/cocoa/views_nswindow_delegate.mm index 9472e5e4043..bce74956126 100644 --- a/chromium/ui/views/cocoa/views_nswindow_delegate.mm +++ b/chromium/ui/views/cocoa/views_nswindow_delegate.mm @@ -9,6 +9,7 @@ #include "base/threading/thread_task_runner_handle.h" #import "ui/views/cocoa/bridged_content_view.h" #import "ui/views/cocoa/bridged_native_widget.h" +#include "ui/views/cocoa/bridged_native_widget_host.h" #include "ui/views/widget/native_widget_mac.h" @implementation ViewsNSWindowDelegate @@ -155,10 +156,12 @@ } - (void)windowDidMiniaturize:(NSNotification*)notification { + parent_->host()->OnWindowMiniaturizedChanged(true); parent_->OnVisibilityChanged(); } - (void)windowDidDeminiaturize:(NSNotification*)notification { + parent_->host()->OnWindowMiniaturizedChanged(false); parent_->OnVisibilityChanged(); } diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc index f73a3102e63..9656e5dc4a3 100644 --- a/chromium/ui/views/controls/button/button.cc +++ b/chromium/ui/views/controls/button/button.cc @@ -476,6 +476,7 @@ Button::Button(ButtonListener* listener) SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); SetProperty(kIsButtonProperty, true); hover_animation_.SetSlideDuration(kHoverFadeDurationMs); + SetInstallFocusRingOnFocus(PlatformStyle::kPreferFocusRings); } Button::KeyClickAction Button::GetKeyClickActionForEvent( diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc index 5dc51b40272..91e398ebfe3 100644 --- a/chromium/ui/views/controls/button/checkbox.cc +++ b/chromium/ui/views/controls/button/checkbox.cc @@ -9,7 +9,6 @@ #include <utility> #include "ui/accessibility/ax_node_data.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_palette.h" @@ -33,72 +32,23 @@ namespace views { // static const char Checkbox::kViewClassName[] = "Checkbox"; -Checkbox::Checkbox(const base::string16& label, - ButtonListener* listener, - bool force_md) - : LabelButton(listener, label), - checked_(false), - label_ax_id_(0), - use_md_(force_md || - ui::MaterialDesignController::IsSecondaryUiMaterial()) { +Checkbox::Checkbox(const base::string16& label, ButtonListener* listener) + : LabelButton(listener, label), checked_(false), label_ax_id_(0) { SetHorizontalAlignment(gfx::ALIGN_LEFT); SetFocusForPlatform(); SetFocusPainter(nullptr); - if (UseMd()) { - set_request_focus_on_press(false); - SetInkDropMode(InkDropMode::ON); - set_has_ink_drop_action_on_click(true); - focus_ring_ = FocusRing::Install(this); - } else { - std::unique_ptr<LabelButtonBorder> button_border(new LabelButtonBorder()); - // Inset the trailing side by a couple pixels for the focus border. - button_border->set_insets(gfx::Insets(0, 0, 0, 2)); - SetBorder(std::move(button_border)); - set_request_focus_on_press(true); - - ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - - // Unchecked/Unfocused images. - SetCustomImage(false, false, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_CHECKBOX)); - SetCustomImage(false, false, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_HOVER)); - SetCustomImage(false, false, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_PRESSED)); - SetCustomImage(false, false, STATE_DISABLED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_DISABLED)); - - // Checked/Unfocused images. - SetCustomImage(true, false, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_CHECKED)); - SetCustomImage(true, false, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_CHECKED_HOVER)); - SetCustomImage(true, false, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_CHECKED_PRESSED)); - SetCustomImage(true, false, STATE_DISABLED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_CHECKED_DISABLED)); - - // Unchecked/Focused images. - SetCustomImage(false, true, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_FOCUSED)); - SetCustomImage(false, true, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_FOCUSED_HOVER)); - SetCustomImage(false, true, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_FOCUSED_PRESSED)); - - // Checked/Focused images. - SetCustomImage(true, true, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_FOCUSED_CHECKED)); - SetCustomImage(true, true, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_FOCUSED_CHECKED_HOVER)); - SetCustomImage(true, true, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_CHECKBOX_FOCUSED_CHECKED_PRESSED)); - } + set_request_focus_on_press(false); + SetInkDropMode(InkDropMode::ON); + set_has_ink_drop_action_on_click(true); // Limit the checkbox height to match the legacy appearance. const gfx::Size preferred_size(LabelButton::CalculatePreferredSize()); SetMinSize(gfx::Size(0, preferred_size.height() + 4)); + + // Checkboxes always have a focus ring, even when the platform otherwise + // doesn't generally use them for buttons. + SetInstallFocusRingOnFocus(true); } Checkbox::~Checkbox() { @@ -125,12 +75,6 @@ void Checkbox::SetAssociatedLabel(View* labelling_view) { node_data.GetString16Attribute(ax::mojom::StringAttribute::kName)); } -// TODO(tetsui): Remove this method and |use_md_| when MD for secondary UI -// becomes default and IsSecondaryUiMaterial() is tautology. -bool Checkbox::UseMd() const { - return use_md_; -} - const char* Checkbox::GetClassName() const { return kViewClassName; } @@ -155,22 +99,9 @@ void Checkbox::GetAccessibleNodeData(ui::AXNodeData* node_data) { } } -void Checkbox::OnFocus() { - LabelButton::OnFocus(); - if (!UseMd()) - UpdateImage(); -} - -void Checkbox::OnBlur() { - LabelButton::OnBlur(); - if (!UseMd()) - UpdateImage(); -} - void Checkbox::OnNativeThemeChanged(const ui::NativeTheme* theme) { LabelButton::OnNativeThemeChanged(theme); - if (UseMd()) - UpdateImage(); + UpdateImage(); } std::unique_ptr<InkDrop> Checkbox::CreateInkDrop() { @@ -194,19 +125,10 @@ SkColor Checkbox::GetInkDropBaseColor() const { } gfx::ImageSkia Checkbox::GetImage(ButtonState for_state) const { - if (UseMd()) { - const int checked = checked_ ? IconState::CHECKED : 0; - const int enabled = for_state != STATE_DISABLED ? IconState::ENABLED : 0; - return gfx::CreateVectorIcon(GetVectorIcon(), 16, - GetIconImageColor(checked | enabled)); - } - - const size_t checked_index = checked_ ? 1 : 0; - const size_t focused_index = HasFocus() ? 1 : 0; - if (for_state != STATE_NORMAL && - images_[checked_index][focused_index][for_state].isNull()) - return images_[checked_index][focused_index][STATE_NORMAL]; - return images_[checked_index][focused_index][for_state]; + const int checked = checked_ ? IconState::CHECKED : 0; + const int enabled = for_state != STATE_DISABLED ? IconState::ENABLED : 0; + return gfx::CreateVectorIcon(GetVectorIcon(), 16, + GetIconImageColor(checked | enabled)); } std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const { @@ -219,8 +141,8 @@ std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const { void Checkbox::Layout() { LabelButton::Layout(); - if (focus_ring_ && !image()->bounds().IsEmpty()) - focus_ring_->SetPath(GetFocusRingPath()); + if (focus_ring() && !image()->bounds().IsEmpty()) + focus_ring()->SetPath(GetFocusRingPath()); } SkPath Checkbox::GetFocusRingPath() const { @@ -231,22 +153,11 @@ SkPath Checkbox::GetFocusRingPath() const { return path; } -void Checkbox::SetCustomImage(bool checked, - bool focused, - ButtonState for_state, - const gfx::ImageSkia& image) { - const size_t checked_index = checked ? 1 : 0; - const size_t focused_index = focused ? 1 : 0; - images_[checked_index][focused_index][for_state] = image; - UpdateImage(); -} - const gfx::VectorIcon& Checkbox::GetVectorIcon() const { return checked() ? kCheckboxActiveIcon : kCheckboxNormalIcon; } SkColor Checkbox::GetIconImageColor(int icon_state) const { - DCHECK(UseMd()); const SkColor active_color = (icon_state & IconState::CHECKED) ? GetNativeTheme()->GetSystemColor( diff --git a/chromium/ui/views/controls/button/checkbox.h b/chromium/ui/views/controls/button/checkbox.h index 6fb4c93b61a..d2b6f12e698 100644 --- a/chromium/ui/views/controls/button/checkbox.h +++ b/chromium/ui/views/controls/button/checkbox.h @@ -28,8 +28,7 @@ class VIEWS_EXPORT Checkbox : public LabelButton { // |force_md| forces MD even when --secondary-ui-md flag is not set. explicit Checkbox(const base::string16& label, - ButtonListener* listener = nullptr, - bool force_md = false); + ButtonListener* listener = nullptr); ~Checkbox() override; // Sets/Gets whether or not the checkbox is checked. @@ -48,14 +47,8 @@ class VIEWS_EXPORT Checkbox : public LabelButton { void GetAccessibleNodeData(ui::AXNodeData* node_data) override; protected: - // Returns whether MD is enabled. Returns true if |force_md| in the - // constructor or --secondary-ui-md flag is set. - bool UseMd() const; - // LabelButton: const char* GetClassName() const override; - void OnFocus() override; - void OnBlur() override; void OnNativeThemeChanged(const ui::NativeTheme* theme) override; std::unique_ptr<InkDrop> CreateInkDrop() override; std::unique_ptr<InkDropRipple> CreateInkDropRipple() const override; @@ -64,13 +57,6 @@ class VIEWS_EXPORT Checkbox : public LabelButton { std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const override; void Layout() override; - // Set the image shown for each button state depending on whether it is - // [checked] or [focused]. - void SetCustomImage(bool checked, - bool focused, - ButtonState for_state, - const gfx::ImageSkia& image); - // Gets the vector icon to use based on the current state of |checked_|. virtual const gfx::VectorIcon& GetVectorIcon() const; @@ -95,17 +81,9 @@ class VIEWS_EXPORT Checkbox : public LabelButton { // True if the checkbox is checked. bool checked_; - // The images for each button node_data. - gfx::ImageSkia images_[2][2][STATE_COUNT]; - // The unique id for the associated label's accessible object. int32_t label_ax_id_; - // The focus ring to use for this Checkbox. - std::unique_ptr<FocusRing> focus_ring_; - - bool use_md_; - DISALLOW_COPY_AND_ASSIGN(Checkbox); }; diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc index e3c0f6e9dc2..4e6011dc24e 100644 --- a/chromium/ui/views/controls/button/md_text_button.cc +++ b/chromium/ui/views/controls/button/md_text_button.cc @@ -27,47 +27,20 @@ namespace views { -namespace { - -bool UseMaterialSecondaryButtons() { -#if defined(OS_MACOSX) - return true; -#else - return ui::MaterialDesignController::IsSecondaryUiMaterial(); -#endif // defined(OS_MACOSX) -} - -LabelButton* CreateButton(ButtonListener* listener, - const base::string16& text, - bool md) { - if (md) - return MdTextButton::Create(listener, text, style::CONTEXT_BUTTON_MD); - - LabelButton* button = new LabelButton(listener, text, style::CONTEXT_BUTTON); - button->SetStyleDeprecated(Button::STYLE_BUTTON); - return button; -} - -} // namespace - // static LabelButton* MdTextButton::CreateSecondaryUiButton(ButtonListener* listener, const base::string16& text) { - return CreateButton(listener, text, UseMaterialSecondaryButtons()); + return MdTextButton::Create(listener, text, style::CONTEXT_BUTTON_MD); } // static LabelButton* MdTextButton::CreateSecondaryUiBlueButton( ButtonListener* listener, const base::string16& text) { - if (UseMaterialSecondaryButtons()) { - MdTextButton* md_button = - MdTextButton::Create(listener, text, style::CONTEXT_BUTTON_MD); - md_button->SetProminent(true); - return md_button; - } - - return new BlueButton(listener, text); + MdTextButton* md_button = + MdTextButton::Create(listener, text, style::CONTEXT_BUTTON_MD); + md_button->SetProminent(true); + return md_button; } // static @@ -106,7 +79,11 @@ void MdTextButton::OnPaintBackground(gfx::Canvas* canvas) { if (hover_animation().is_animating() || state() == STATE_HOVERED) { const int kHoverAlpha = is_prominent_ ? 0x0c : 0x05; SkScalar alpha = hover_animation().CurrentValueBetween(0, kHoverAlpha); - canvas->FillRect(GetLocalBounds(), SkColorSetA(SK_ColorBLACK, alpha)); + cc::PaintFlags flags; + flags.setColor(SkColorSetA(SK_ColorBLACK, alpha)); + flags.setStyle(cc::PaintFlags::kFill_Style); + flags.setAntiAlias(true); + canvas->DrawRoundRect(gfx::RectF(GetLocalBounds()), corner_radius_, flags); } } @@ -303,25 +280,10 @@ void MdTextButton::UpdateColors() { // Harmony and non-Harmony colors. stroke_alpha = 0x43; } else { - // These alpha values will take the enabled button colors, 5a5a5a @ 1.0 - // alpha for non-Harmony, 757575 @ 1.0 alpha for Harmony and turn it into - // an effective b2b2b2 @ 1.0 alpha or 000000 @ 0.3 for the stroke_color. - stroke_alpha = UseMaterialSecondaryButtons() ? 0x8f : 0x77; -#if defined(OS_MACOSX) - // Without full secondary UI MD support, the text color is solid black, - // and so the border is too dark on Mac. On Retina it looks OK, so - // heuristically determine the scale factor as well. - if (!ui::MaterialDesignController::IsSecondaryUiMaterial()) { - // The Compositor may only be set when attached to a Widget. But, since - // that also determines the theme, UpdateColors() will always be called - // after attaching to a Widget. - // TODO(tapted): Move this into SolidRoundRectPainter if we like this - // logic for Harmony. - auto* compositor = layer()->GetCompositor(); - if (compositor && compositor->device_scale_factor() == 1) - stroke_alpha = 0x4d; // Chosen to match full secondary UI MD (0.3). - } -#endif + // These alpha values will take the enabled button colors, 757575 @ 1.0 + // alpha turn it into an effective b2b2b2 @ 1.0 alpha or 000000 @ 0.3 for + // the stroke_color. + stroke_alpha = 0x8f; } stroke_color = SkColorSetA(text_color, stroke_alpha); } diff --git a/chromium/ui/views/controls/button/radio_button.cc b/chromium/ui/views/controls/button/radio_button.cc index b9dbdbe82dc..9adf4949221 100644 --- a/chromium/ui/views/controls/button/radio_button.cc +++ b/chromium/ui/views/controls/button/radio_button.cc @@ -19,51 +19,9 @@ namespace views { // static const char RadioButton::kViewClassName[] = "RadioButton"; -RadioButton::RadioButton(const base::string16& label, - int group_id, - bool force_md) - : Checkbox(label, nullptr, force_md) { +RadioButton::RadioButton(const base::string16& label, int group_id) + : Checkbox(label, nullptr) { SetGroup(group_id); - - if (!UseMd()) { - set_request_focus_on_press(true); - ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - // Unchecked/Unfocused images. - SetCustomImage(false, false, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_RADIO)); - SetCustomImage(false, false, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_RADIO_HOVER)); - SetCustomImage(false, false, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_RADIO_PRESSED)); - SetCustomImage(false, false, STATE_DISABLED, - *rb.GetImageSkiaNamed(IDR_RADIO_DISABLED)); - - // Checked/Unfocused images. - SetCustomImage(true, false, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED)); - SetCustomImage(true, false, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED_HOVER)); - SetCustomImage(true, false, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED_PRESSED)); - SetCustomImage(true, false, STATE_DISABLED, - *rb.GetImageSkiaNamed(IDR_RADIO_CHECKED_DISABLED)); - - // Unchecked/Focused images. - SetCustomImage(false, true, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED)); - SetCustomImage(false, true, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_HOVER)); - SetCustomImage(false, true, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_PRESSED)); - - // Checked/Focused images. - SetCustomImage(true, true, STATE_NORMAL, - *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_CHECKED)); - SetCustomImage(true, true, STATE_HOVERED, - *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_CHECKED_HOVER)); - SetCustomImage(true, true, STATE_PRESSED, - *rb.GetImageSkiaNamed(IDR_RADIO_FOCUSED_CHECKED_PRESSED)); - } } RadioButton::~RadioButton() { diff --git a/chromium/ui/views/controls/button/radio_button.h b/chromium/ui/views/controls/button/radio_button.h index 02cf91dcf8d..2810468881f 100644 --- a/chromium/ui/views/controls/button/radio_button.h +++ b/chromium/ui/views/controls/button/radio_button.h @@ -19,8 +19,7 @@ class VIEWS_EXPORT RadioButton : public Checkbox { // The button's class name. static const char kViewClassName[]; - // |force_md| forces MD even when --secondary-ui-md flag is not set. - RadioButton(const base::string16& label, int group_id, bool force_md = false); + RadioButton(const base::string16& label, int group_id); ~RadioButton() override; // Overridden from View: diff --git a/chromium/ui/views/controls/button/radio_button_unittest.cc b/chromium/ui/views/controls/button/radio_button_unittest.cc index 19485e6c773..390809e36ee 100644 --- a/chromium/ui/views/controls/button/radio_button_unittest.cc +++ b/chromium/ui/views/controls/button/radio_button_unittest.cc @@ -6,7 +6,6 @@ #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/events/base_event_utils.h" #include "ui/views/test/views_test_base.h" @@ -113,26 +112,18 @@ TEST_F(RadioButtonTest, FocusOnClick) { EXPECT_TRUE(button2->checked()); auto* focus_manager = button_container().GetFocusManager(); - // Focus behavior is different pre/post-Harmony. - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { - // No focus on click. - EXPECT_EQ(nullptr, focus_manager->GetFocusedView()); - - ui::KeyEvent pressed_tab(ui::ET_KEY_PRESSED, ui::VKEY_TAB, ui::EF_NONE); - focus_manager->OnKeyEvent(pressed_tab); - EXPECT_EQ(button2, focus_manager->GetFocusedView()); - - button1->OnMousePressed(event); - button1->OnMouseReleased(event); - // Button 1 gets focus on click because button 2 already had it. - EXPECT_TRUE(button1->checked()); - EXPECT_EQ(button1, focus_manager->GetFocusedView()); - } else if (button2->request_focus_on_press()) { - EXPECT_EQ(button2, focus_manager->GetFocusedView()); - } else { - // On Mac, buttons never (and can not be made to) request focus on clicks. - EXPECT_EQ(nullptr, focus_manager->GetFocusedView()); - } + // No focus on click. + EXPECT_EQ(nullptr, focus_manager->GetFocusedView()); + + ui::KeyEvent pressed_tab(ui::ET_KEY_PRESSED, ui::VKEY_TAB, ui::EF_NONE); + focus_manager->OnKeyEvent(pressed_tab); + EXPECT_EQ(button2, focus_manager->GetFocusedView()); + + button1->OnMousePressed(event); + button1->OnMouseReleased(event); + // Button 1 gets focus on click because button 2 already had it. + EXPECT_TRUE(button1->checked()); + EXPECT_EQ(button1, focus_manager->GetFocusedView()); } } // namespace views diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc index 2cb22fea6dc..f900b99d012 100644 --- a/chromium/ui/views/controls/combobox/combobox.cc +++ b/chromium/ui/views/controls/combobox/combobox.cc @@ -15,7 +15,6 @@ #include "ui/accessibility/ax_node_data.h" #include "ui/base/default_style.h" #include "ui/base/ime/input_method.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/base/models/combobox_model_observer.h" #include "ui/base/resource/resource_bundle.h" #include "ui/events/event.h" @@ -93,10 +92,6 @@ const int kFocusedPressedMenuButtonImages[] = #undef MENU_IMAGE_GRID -bool UseMd() { - return ui::MaterialDesignController::IsSecondaryUiMaterial(); -} - SkColor GetTextColorForEnableState(const Combobox& combobox, bool enabled) { return style::GetColor( combobox, style::CONTEXT_TEXTFIELD, @@ -130,10 +125,8 @@ class TransparentButton : public Button { SetFocusBehavior(FocusBehavior::NEVER); set_notify_action(PlatformStyle::kMenuNotifyActivationAction); - if (UseMd()) { - SetInkDropMode(InkDropMode::ON); - set_has_ink_drop_action_on_click(true); - } + SetInkDropMode(InkDropMode::ON); + set_has_ink_drop_action_on_click(true); } ~TransparentButton() override {} @@ -455,16 +448,10 @@ Combobox::Combobox(ui::ComboboxModel* model, Style style) // A layer is applied to make sure that canvas bounds are snapped to pixel // boundaries (for the sake of drawing the arrow). - if (UseMd()) { - SetPaintToLayer(); - layer()->SetFillsBoundsOpaquely(false); - } else { - arrow_image_ = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( - IDR_MENU_DROPARROW); - } + SetPaintToLayer(); + layer()->SetFillsBoundsOpaquely(false); - if (UseMd()) - focus_ring_ = FocusRing::Install(this); + focus_ring_ = FocusRing::Install(this); } Combobox::~Combobox() { @@ -567,9 +554,6 @@ void Combobox::Layout() { } void Combobox::OnNativeThemeChanged(const ui::NativeTheme* theme) { - if (!UseMd()) - return; - SetBackground( CreateBackgroundFromPainter(Painter::CreateSolidRoundRectPainter( theme->GetSystemColor( @@ -795,9 +779,6 @@ void Combobox::ButtonPressed(Button* sender, const ui::Event& event) { if (!enabled()) return; - if (!UseMd()) - RequestFocus(); - if (sender == text_button_) { OnPerformAction(); } else { @@ -865,7 +846,7 @@ void Combobox::PaintText(gfx::Canvas* canvas) { PositionArrowWithinContainer(arrow_bounds, ArrowSize(), style_); AdjustBoundsForRTLUI(&arrow_bounds); - if (UseMd()) { + { // Since this is a core piece of UI and vector icons don't handle fractional // scale factors particularly well, manually draw an arrow and make sure it // looks good at all scale factors. @@ -890,8 +871,6 @@ void Combobox::PaintText(gfx::Canvas* canvas) { flags.setColor(arrow_color); flags.setAntiAlias(true); canvas->DrawPath(path, flags); - } else { - canvas->DrawImageInt(arrow_image_, arrow_bounds.x(), arrow_bounds.y()); } } @@ -1005,7 +984,7 @@ void Combobox::OnPerformAction() { } gfx::Size Combobox::ArrowSize() const { - return UseMd() ? gfx::Size(8, 4) : arrow_image_.size(); + return gfx::Size(8, 4); } gfx::Size Combobox::GetContentSize() const { @@ -1031,11 +1010,9 @@ PrefixSelector* Combobox::GetPrefixSelector() { } int Combobox::GetArrowContainerWidth() const { - constexpr int kMdPaddingWidth = 8; - constexpr int kNormalPaddingWidth = 7; - int arrow_pad = UseMd() ? kMdPaddingWidth : kNormalPaddingWidth; + constexpr int kPaddingWidth = 8; int padding = style_ == STYLE_NORMAL - ? arrow_pad * 2 + ? kPaddingWidth * 2 : kActionLeftPadding + kActionRightPadding; return ArrowSize().width() + padding; } diff --git a/chromium/ui/views/controls/focusable_border.cc b/chromium/ui/views/controls/focusable_border.cc index 3b60677ecb0..800736a15d0 100644 --- a/chromium/ui/views/controls/focusable_border.cc +++ b/chromium/ui/views/controls/focusable_border.cc @@ -6,7 +6,6 @@ #include "cc/paint/paint_flags.h" #include "third_party/skia/include/core/SkPath.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_palette.h" #include "ui/gfx/color_utils.h" @@ -43,10 +42,7 @@ void FocusableBorder::Paint(const View& view, gfx::Canvas* canvas) { gfx::ScopedCanvas scoped(canvas); float dsf = canvas->UndoDeviceScaleFactor(); - const int stroke_width_px = - ui::MaterialDesignController::IsSecondaryUiMaterial() - ? 1 - : gfx::ToFlooredInt(dsf); + const int stroke_width_px = 1; flags.setStrokeWidth(SkIntToScalar(stroke_width_px)); // Scale the rect and snap to pixel boundaries. @@ -54,14 +50,10 @@ void FocusableBorder::Paint(const View& view, gfx::Canvas* canvas) { rect.Inset(gfx::InsetsF(stroke_width_px / 2.0f)); SkPath path; - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { flags.setAntiAlias(true); float corner_radius_px = kCornerRadiusDp * dsf; path.addRoundRect(gfx::RectFToSkRect(rect), corner_radius_px, corner_radius_px); - } else { - path.addRect(gfx::RectFToSkRect(rect), SkPath::kCW_Direction); - } canvas->DrawPath(path, flags); } @@ -85,18 +77,11 @@ void FocusableBorder::SetInsets(int vertical, int horizontal) { SkColor FocusableBorder::GetCurrentColor(const View& view) const { ui::NativeTheme::ColorId color_id = ui::NativeTheme::kColorId_UnfocusedBorderColor; - if (override_color_id_) { + if (override_color_id_) color_id = *override_color_id_; - } else if (view.HasFocus() && - !ui::MaterialDesignController::IsSecondaryUiMaterial()) { - // Note with --secondary-ui-md there is a FocusRing indicator, so the border - // retains its unfocused color. - color_id = ui::NativeTheme::kColorId_FocusedBorderColor; - } SkColor color = view.GetNativeTheme()->GetSystemColor(color_id); - if (ui::MaterialDesignController::IsSecondaryUiMaterial() && - !view.enabled()) { + if (!view.enabled()) { return color_utils::BlendTowardOppositeLuma(color, gfx::kDisabledControlAlpha); } diff --git a/chromium/ui/views/controls/label.h b/chromium/ui/views/controls/label.h index 1d35afeda78..9c78b30ab3a 100644 --- a/chromium/ui/views/controls/label.h +++ b/chromium/ui/views/controls/label.h @@ -258,7 +258,7 @@ class VIEWS_EXPORT Label : public View, FRIEND_TEST_ALL_PREFIXES(LabelTest, MultilineSupportedRenderText); FRIEND_TEST_ALL_PREFIXES(LabelTest, TextChangeWithoutLayout); FRIEND_TEST_ALL_PREFIXES(LabelTest, EmptyLabel); - FRIEND_TEST_ALL_PREFIXES(MDLabelTest, FocusBounds); + FRIEND_TEST_ALL_PREFIXES(LabelTest, FocusBounds); FRIEND_TEST_ALL_PREFIXES(LabelTest, MultiLineSizingWithElide); friend class LabelSelectionTest; diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc index e5eafe1bd53..d87acf7c42d 100644 --- a/chromium/ui/views/controls/label_unittest.cc +++ b/chromium/ui/views/controls/label_unittest.cc @@ -9,13 +9,11 @@ #include "base/command_line.h" #include "base/i18n/rtl.h" #include "base/strings/utf_string_conversions.h" -#include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/l10n/l10n_util.h" -#include "ui/base/ui_base_features.h" #include "ui/base/ui_base_switches.h" #include "ui/compositor/canvas_painter.h" #include "ui/events/base_event_utils.h" @@ -97,13 +95,6 @@ base::string16 GetClipboardText(ui::ClipboardType clipboard_type) { return clipboard_text; } -enum class SecondaryUiMode { NON_MD, MD }; - -std::string SecondaryUiModeToString( - const ::testing::TestParamInfo<SecondaryUiMode>& info) { - return info.param == SecondaryUiMode::MD ? "MD" : "NonMD"; -} - // Makes an RTL string by mapping 0..6 to [א,ב,ג,ד,ה,ו,ז]. base::string16 ToRTL(const char* ascii) { base::string16 rtl; @@ -269,31 +260,6 @@ class LabelSelectionTest : public LabelTest { DISALLOW_COPY_AND_ASSIGN(LabelSelectionTest); }; -// LabelTest harness that runs both with and without secondary UI set to MD. -class MDLabelTest : public LabelTest, - public ::testing::WithParamInterface<SecondaryUiMode> { - public: - MDLabelTest() {} - - // LabelTest: - void SetUp() override { - if (GetParam() == SecondaryUiMode::MD) { - scoped_feature_list_.InitAndEnableFeature(features::kSecondaryUiMd); - } else { - // Force Refresh UI to be off, since that mode implies MD secondary UI. - base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( - switches::kTopChromeMD, switches::kTopChromeMDMaterial); - scoped_feature_list_.InitAndDisableFeature(features::kSecondaryUiMd); - } - LabelTest::SetUp(); - } - - private: - base::test::ScopedFeatureList scoped_feature_list_; - - DISALLOW_COPY_AND_ASSIGN(MDLabelTest); -}; - // Crashes on Linux only. http://crbug.com/612406 #if defined(OS_LINUX) #define MAYBE_FontPropertySymbol DISABLED_FontPropertySymbol @@ -933,7 +899,7 @@ TEST_F(LabelTest, NoSchedulePaintInOnPaint) { EXPECT_EQ(count, label.schedule_paint_count()); // Unchanged. } -TEST_P(MDLabelTest, FocusBounds) { +TEST_F(LabelTest, FocusBounds) { label()->SetText(ASCIIToUTF16("Example")); Link concrete_link(ASCIIToUTF16("Example")); Label* link = &concrete_link; // Allow LabelTest to call methods as friend. @@ -959,16 +925,10 @@ TEST_P(MDLabelTest, FocusBounds) { gfx::Size normal_link_size = link->GetPreferredSize(); link->SetFocusBehavior(View::FocusBehavior::ALWAYS); gfx::Size focusable_link_size = link->GetPreferredSize(); - if (GetParam() == SecondaryUiMode::MD) { - // Everything should match under MD since underlines indicates focus. - EXPECT_EQ(normal_label_size, normal_link_size); - EXPECT_EQ(normal_link_size, focusable_link_size); - } else { - // Otherwise, links get bigger in order to paint the focus rectangle. - EXPECT_NE(normal_link_size, focusable_link_size); - EXPECT_GT(focusable_link_size.width(), normal_link_size.width()); - EXPECT_GT(focusable_link_size.height(), normal_link_size.height()); - } + + // Everything should match since underlines indicates focus. + EXPECT_EQ(normal_label_size, normal_link_size); + EXPECT_EQ(normal_link_size, focusable_link_size); // Requesting focus doesn't change the preferred size since that would mess up // layout. @@ -1405,10 +1365,4 @@ TEST_F(LabelSelectionTest, ContextMenuContents) { EXPECT_FALSE(IsMenuCommandEnabled(IDS_APP_SELECT_ALL)); } -INSTANTIATE_TEST_CASE_P(, - MDLabelTest, - ::testing::Values(SecondaryUiMode::MD, - SecondaryUiMode::NON_MD), - &SecondaryUiModeToString); - } // namespace views diff --git a/chromium/ui/views/controls/link.cc b/chromium/ui/views/controls/link.cc index f4e605aaedb..e47a0c89edb 100644 --- a/chromium/ui/views/controls/link.cc +++ b/chromium/ui/views/controls/link.cc @@ -10,7 +10,6 @@ #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/cursor/cursor.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/events/event.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/canvas.h" @@ -39,9 +38,7 @@ Link::~Link() { // static Link::FocusStyle Link::GetDefaultFocusStyle() { - return ui::MaterialDesignController::IsSecondaryUiMaterial() - ? FocusStyle::UNDERLINE - : FocusStyle::RING; + return FocusStyle::UNDERLINE; } Link::FocusStyle Link::GetFocusStyle() const { diff --git a/chromium/ui/views/controls/menu/menu_cocoa_watcher_mac.h b/chromium/ui/views/controls/menu/menu_cocoa_watcher_mac.h new file mode 100644 index 00000000000..96e55221033 --- /dev/null +++ b/chromium/ui/views/controls/menu/menu_cocoa_watcher_mac.h @@ -0,0 +1,36 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_VIEWS_CONTROLS_MENU_MENU_COCOA_WATCHER_MAC_H_ +#define UI_VIEWS_CONTROLS_MENU_MENU_COCOA_WATCHER_MAC_H_ + +#include <objc/objc.h> + +#include "base/callback.h" +#include "base/macros.h" +#include "ui/views/views_export.h" + +namespace views { + +// This class executes a callback when a native menu begins tracking. With +// native menus, each one automatically closes when a new one begins tracking. +// This allows Views menus to tie into this behavior. +class VIEWS_EXPORT MenuCocoaWatcherMac { + public: + explicit MenuCocoaWatcherMac(base::OnceClosure callback); + ~MenuCocoaWatcherMac(); + + private: + // The closure to call when the notification comes in. + base::OnceClosure callback_; + + // The token representing the notification observer. + id observer_token_; + + DISALLOW_COPY_AND_ASSIGN(MenuCocoaWatcherMac); +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_MENU_MENU_COCOA_WATCHER_MAC_H_ diff --git a/chromium/ui/views/controls/menu/menu_cocoa_watcher_mac.mm b/chromium/ui/views/controls/menu/menu_cocoa_watcher_mac.mm new file mode 100644 index 00000000000..da563e6a4cd --- /dev/null +++ b/chromium/ui/views/controls/menu/menu_cocoa_watcher_mac.mm @@ -0,0 +1,28 @@ +// 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/menu/menu_cocoa_watcher_mac.h" + +#import <Cocoa/Cocoa.h> + +#import <utility> + +namespace views { + +MenuCocoaWatcherMac::MenuCocoaWatcherMac(base::OnceClosure callback) + : callback_(std::move(callback)) { + observer_token_ = [[NSNotificationCenter defaultCenter] + addObserverForName:NSMenuDidBeginTrackingNotification + object:[NSApp mainMenu] + queue:nil + usingBlock:^(NSNotification* notification) { + std::move(this->callback_).Run(); + }]; +} + +MenuCocoaWatcherMac::~MenuCocoaWatcherMac() { + [[NSNotificationCenter defaultCenter] removeObserver:observer_token_]; +} + +} // namespace views diff --git a/chromium/ui/views/controls/menu/menu_config.cc b/chromium/ui/views/controls/menu/menu_config.cc index db532134db5..7ea5c0104bc 100644 --- a/chromium/ui/views/controls/menu/menu_config.cc +++ b/chromium/ui/views/controls/menu/menu_config.cc @@ -68,7 +68,8 @@ MenuConfig::MenuConfig() vertical_touchable_menu_item_padding(8), padded_separator_left_margin(64), arrow_key_selection_wraps(true), - show_context_menu_accelerators(true) { + show_context_menu_accelerators(true), + all_menus_use_prefix_selection(false) { Init(); } diff --git a/chromium/ui/views/controls/menu/menu_config.h b/chromium/ui/views/controls/menu/menu_config.h index df02a976c59..885aa4ceb9e 100644 --- a/chromium/ui/views/controls/menu/menu_config.h +++ b/chromium/ui/views/controls/menu/menu_config.h @@ -38,7 +38,9 @@ struct VIEWS_EXPORT MenuConfig { // Color for the arrow to scroll bookmarks. SkColor arrow_color; - // Menu border sizes. + // Menu border sizes. The vertical border size does not apply to menus with + // rounded corners - those menus always use the corner radius as the vertical + // border size. int menu_vertical_border_size; int menu_horizontal_border_size; @@ -195,6 +197,9 @@ struct VIEWS_EXPORT MenuConfig { // Whether to show accelerators in context menus. bool show_context_menu_accelerators; + // Whether all types of menus use prefix selection for items. + bool all_menus_use_prefix_selection; + private: // Configures a MenuConfig as appropriate for the current platform. void Init(); diff --git a/chromium/ui/views/controls/menu/menu_config_mac.mm b/chromium/ui/views/controls/menu/menu_config_mac.mm index 90939262b2e..607ed754986 100644 --- a/chromium/ui/views/controls/menu/menu_config_mac.mm +++ b/chromium/ui/views/controls/menu/menu_config_mac.mm @@ -7,7 +7,6 @@ #import <AppKit/AppKit.h> #include "base/mac/mac_util.h" -#include "ui/base/material_design/material_design_controller.h" namespace { @@ -46,8 +45,8 @@ void MenuConfig::Init() { arrow_key_selection_wraps = false; use_mnemonics = false; show_context_menu_accelerators = false; - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) - InitMaterialMenuConfig(this); + all_menus_use_prefix_selection = true; + InitMaterialMenuConfig(this); } } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc index beecb7f2717..d6806061652 100644 --- a/chromium/ui/views/controls/menu/menu_controller.cc +++ b/chromium/ui/views/controls/menu/menu_controller.cc @@ -31,6 +31,7 @@ #include "ui/views/controls/menu/menu_controller_delegate.h" #include "ui/views/controls/menu/menu_host_root_view.h" #include "ui/views/controls/menu/menu_item_view.h" +#include "ui/views/controls/menu/menu_pre_target_handler.h" #include "ui/views/controls/menu/menu_scroll_view_container.h" #include "ui/views/controls/menu/submenu_view.h" #include "ui/views/drag_utils.h" @@ -55,7 +56,6 @@ #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/aura/window_tree_host.h" -#include "ui/views/controls/menu/menu_pre_target_handler.h" #endif using base::TimeDelta; @@ -461,13 +461,16 @@ void MenuController::Run(Widget* parent, if (owner_) owner_->AddObserver(this); -#if defined(USE_AURA) // Only create a MenuPreTargetHandler for non-nested menus. Nested menus // will use the existing one. - menu_pre_target_handler_.reset(new MenuPreTargetHandler(this, owner_)); -#endif + menu_pre_target_handler_ = MenuPreTargetHandler::Create(this, owner_); } +#if defined(OS_MACOSX) + menu_cocoa_watcher_ = std::make_unique<MenuCocoaWatcherMac>( + base::BindOnce(&MenuController::CancelAll, base::Unretained(this))); +#endif + // Reset current state. pending_state_ = State(); state_ = State(); @@ -582,7 +585,13 @@ bool MenuController::OnMousePressed(SubmenuView* source, // Empty menu items are always handled by the menu controller. if (!view || view->id() != MenuItemView::kEmptyMenuItemViewID) { + base::WeakPtr<MenuController> this_ref = AsWeakPtr(); bool processed = forward_to_root->ProcessMousePressed(event_for_root); + // This object may be destroyed as a result of a mouse press event (some + // item may close the menu). + if (!this_ref) + return true; + // If the event was processed, the root view becomes our current mouse // handler... if (processed && !current_mouse_event_target_) { @@ -863,6 +872,10 @@ void MenuController::OnGestureEvent(SubmenuView* source, } void MenuController::OnTouchEvent(SubmenuView* source, ui::TouchEvent* event) { + // Bail if owner wants the current active gesture sequence. + if (owner_ && send_gesture_events_to_owner()) + return; + if (event->type() == ui::ET_TOUCH_PRESSED) { MenuPart part = GetMenuPart(source, event->location()); if (part.type == MenuPart::NONE) { @@ -1371,6 +1384,15 @@ void MenuController::OnKeyDown(ui::KeyboardCode key_code) { #if defined(OS_MACOSX) case ui::VKEY_SPACE: #endif + // An odd special case: if a prefix selection is in flight, space should + // add to that selection rather than activating the menu. This is + // important for allowing the user to select between items that have the + // same first word. + if (key_code == ui::VKEY_SPACE && + MenuConfig::instance().all_menus_use_prefix_selection && + ShouldContinuePrefixSelection()) { + break; + } if (pending_state_.item) { if (pending_state_.item->HasSubmenu()) { if (key_code == ui::VKEY_F4 && @@ -1400,6 +1422,7 @@ void MenuController::OnKeyDown(ui::KeyboardCode key_code) { CloseSubmenu(); break; +#if !defined(OS_MACOSX) case ui::VKEY_APPS: { Button* hot_view = GetFirstHotTrackedView(pending_state_.item); if (hot_view) { @@ -1419,6 +1442,7 @@ void MenuController::OnKeyDown(ui::KeyboardCode key_code) { } break; } +#endif #if defined(OS_WIN) // On Windows, pressing Alt and F10 keys should hide the menu to match the @@ -2046,7 +2070,10 @@ gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, const int right_of_parent = item_loc.x() + item->width() - submenu_horizontal_inset; - menu_bounds.set_y(item_loc.y() - menu_config.menu_vertical_border_size); + int border_size = menu_config.CornerRadiusForMenu(this); + if (!border_size) + border_size = menu_config.menu_vertical_border_size; + menu_bounds.set_y(item_loc.y() - border_size); // Assume the menu can be placed in the preferred location. menu_bounds.set_x(create_on_right ? right_of_parent : left_of_parent); @@ -2111,7 +2138,14 @@ gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, item->actual_menu_position() == MenuItemView::POSITION_ABOVE_BOUNDS) { // Menu has been drawn below/above the anchor bounds, make sure it fits // on the screen in its current location. + const int drawn_width = menu_bounds.width(); menu_bounds.Intersect(monitor_bounds); + + // Do not allow the menu to get narrower. This handles the case where the + // menu would have drawn off-screen, but the effective anchor was shifted + // at the end of this function. Preserve the width, so it is shifted + // again. + menu_bounds.set_width(drawn_width); } else if (menu_bounds.bottom() <= monitor_bounds.bottom()) { // Menu fits below anchor bounds. item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS); @@ -2140,6 +2174,7 @@ gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, } } + // Ensure the menu is not displayed off screen. menu_bounds.set_x( base::ClampToRange(menu_bounds.x(), monitor_bounds.x(), monitor_bounds.right() - menu_bounds.width())); @@ -2161,7 +2196,7 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, SubmenuView* submenu = item->GetSubmenu(); DCHECK(submenu); - gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); + gfx::Size menu_size = submenu->GetScrollViewContainer()->GetPreferredSize(); int x = 0; int y = 0; const MenuConfig& menu_config = MenuConfig::instance(); @@ -2171,119 +2206,117 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, BubbleBorder::GetBorderAndShadowInsets( menu_config.touchable_menu_shadow_elevation); + const gfx::Rect& monitor_bounds = state_.monitor_bounds; + if (!item->GetParentMenuItem()) { // This is a top-level menu, position it relative to the anchor bounds. - const gfx::Rect& owner_bounds = pending_state_.initial_bounds; + const gfx::Rect& anchor_bounds = pending_state_.initial_bounds; // First the size gets reduced to the possible space. - if (!state_.monitor_bounds.IsEmpty()) { - int max_width = state_.monitor_bounds.width(); - int max_height = state_.monitor_bounds.height(); + if (!monitor_bounds.IsEmpty()) { + int max_width = monitor_bounds.width(); + int max_height = monitor_bounds.height(); // In case of bubbles, the maximum width is limited by the space // between the display corner and the target area + the tip size. if (state_.anchor == MENU_ANCHOR_BUBBLE_LEFT) { - max_width = owner_bounds.x() - state_.monitor_bounds.x() + - kBubbleTipSizeLeftRight; + max_width = + anchor_bounds.x() - monitor_bounds.x() + kBubbleTipSizeLeftRight; } else if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) { - max_width = state_.monitor_bounds.right() - owner_bounds.right() + + max_width = monitor_bounds.right() - anchor_bounds.right() + kBubbleTipSizeLeftRight; } else if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) { - max_height = owner_bounds.y() - state_.monitor_bounds.y() + - kBubbleTipSizeTopBottom; + max_height = + anchor_bounds.y() - monitor_bounds.y() + kBubbleTipSizeTopBottom; } else if (state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { - max_height = state_.monitor_bounds.bottom() - owner_bounds.bottom() + + max_height = monitor_bounds.bottom() - anchor_bounds.bottom() + kBubbleTipSizeTopBottom; } // The menu should always have a non-empty available area. DCHECK_GE(max_width, kBubbleTipSizeLeftRight); DCHECK_GE(max_height, kBubbleTipSizeTopBottom); - pref.set_width(std::min(pref.width(), max_width)); - pref.set_height(std::min(pref.height(), max_height)); + menu_size.SetToMin(gfx::Size(max_width, max_height)); } // Respect the delegate's maximum width. - pref.set_width( - std::min(pref.width(), item->GetDelegate()->GetMaxWidthForMenu(item))); + menu_size.set_width(std::min( + menu_size.width(), item->GetDelegate()->GetMaxWidthForMenu(item))); if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE || state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) - y = owner_bounds.y() - pref.height() + kBubbleTipSizeTopBottom; + y = anchor_bounds.y() - menu_size.height() + kBubbleTipSizeTopBottom; else - y = owner_bounds.bottom() - kBubbleTipSizeTopBottom; + y = anchor_bounds.bottom() - kBubbleTipSizeTopBottom; - x = owner_bounds.CenterPoint().x() - pref.width() / 2; + x = anchor_bounds.CenterPoint().x() - menu_size.width() / 2; int x_old = x; - if (x < state_.monitor_bounds.x()) - x = state_.monitor_bounds.x(); - else if (x + pref.width() > state_.monitor_bounds.right()) - x = state_.monitor_bounds.right() - pref.width(); - submenu->GetScrollViewContainer()->SetBubbleArrowOffset(pref.width() / 2 - - x + x_old); + x = base::ClampToRange(x, monitor_bounds.x(), + monitor_bounds.right() - menu_size.width()); + submenu->GetScrollViewContainer()->SetBubbleArrowOffset( + menu_size.width() / 2 - x + x_old); + } else if (state_.anchor == MENU_ANCHOR_BUBBLE_LEFT || + state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) { + if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) + x = anchor_bounds.right() - kBubbleTipSizeLeftRight; + else + x = anchor_bounds.x() - menu_size.width() + kBubbleTipSizeLeftRight; + + y = anchor_bounds.CenterPoint().y() - menu_size.height() / 2; + int y_old = y; + y = base::ClampToRange(y, monitor_bounds.y(), + monitor_bounds.bottom() - menu_size.height()); + submenu->GetScrollViewContainer()->SetBubbleArrowOffset( + menu_size.height() / 2 - y + y_old); } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE) { // Align the left edges of the menu and anchor, and the bottom of the menu // with the top of the anchor. - x = owner_bounds.origin().x() - border_and_shadow_insets.left(); - y = owner_bounds.origin().y() - pref.height() + + x = anchor_bounds.x() - border_and_shadow_insets.left(); + y = anchor_bounds.y() - menu_size.height() + border_and_shadow_insets.bottom() - menu_config.touchable_anchor_offset; // Align the right of the container with the right of the anchor. - if (x + pref.width() > state_.monitor_bounds.width()) { - x = owner_bounds.right() - pref.width() + + if (x + menu_size.width() > monitor_bounds.width()) { + x = anchor_bounds.right() - menu_size.width() + border_and_shadow_insets.right(); } // Align the top of the menu with the bottom of the anchor. - if (y < state_.monitor_bounds.y()) { - y = owner_bounds.bottom() - border_and_shadow_insets.top() + + if (y < monitor_bounds.y()) { + y = anchor_bounds.bottom() - border_and_shadow_insets.top() + menu_config.touchable_anchor_offset; } } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT) { // Align the right of the menu with the left of the anchor, and the top of // the menu with the top of the anchor. - x = owner_bounds.origin().x() - pref.width() + + x = anchor_bounds.x() - menu_size.width() + border_and_shadow_insets.right() - menu_config.touchable_anchor_offset; - y = owner_bounds.origin().y() - border_and_shadow_insets.top(); + y = anchor_bounds.y() - border_and_shadow_insets.top(); // Align the left of the menu with the right of the anchor. - if (x < state_.monitor_bounds.x()) { - x = owner_bounds.right() - border_and_shadow_insets.left() + + if (x < monitor_bounds.x()) { + x = anchor_bounds.right() - border_and_shadow_insets.left() + menu_config.touchable_anchor_offset; } // Align the bottom of the menu to the bottom of the anchor. - if (y + pref.height() > state_.monitor_bounds.height()) { - y = owner_bounds.bottom() - pref.height() + + if (y + menu_size.height() > monitor_bounds.height()) { + y = anchor_bounds.bottom() - menu_size.height() + border_and_shadow_insets.bottom(); } } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_RIGHT) { // Align the left of the menu with the right of the anchor, and the top of // the menu with the top of the anchor. - x = owner_bounds.right() - border_and_shadow_insets.left() + + x = anchor_bounds.right() - border_and_shadow_insets.left() + menu_config.touchable_anchor_offset; - y = owner_bounds.origin().y() - border_and_shadow_insets.top(); - if (x + pref.width() > state_.monitor_bounds.width()) { + y = anchor_bounds.y() - border_and_shadow_insets.top(); + if (x + menu_size.width() > monitor_bounds.width()) { // Align the right of the menu with the left of the anchor. - x = owner_bounds.origin().x() - pref.width() + + x = anchor_bounds.x() - menu_size.width() + border_and_shadow_insets.right() - menu_config.touchable_anchor_offset; } - if (y + pref.height() > state_.monitor_bounds.height()) { + if (y + menu_size.height() > monitor_bounds.height()) { // Align the bottom of the menu with the bottom of the anchor. - y = owner_bounds.bottom() - pref.height() + + y = anchor_bounds.bottom() - menu_size.height() + border_and_shadow_insets.bottom(); } - } else { - if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) - x = owner_bounds.right() - kBubbleTipSizeLeftRight; - else - x = owner_bounds.x() - pref.width() + kBubbleTipSizeLeftRight; - - y = owner_bounds.CenterPoint().y() - pref.height() / 2; - int y_old = y; - if (y < state_.monitor_bounds.y()) - y = state_.monitor_bounds.y(); - else if (y + pref.height() > state_.monitor_bounds.bottom()) - y = state_.monitor_bounds.bottom() - pref.height(); - submenu->GetScrollViewContainer()->SetBubbleArrowOffset( - pref.height() / 2 - y + y_old); } } else { if (!use_touchable_layout_) { @@ -2296,14 +2329,12 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, // If the layout is RTL, then a 'leading' menu is positioned to the left of // the parent menu item and not to the right. const bool layout_is_rtl = base::i18n::IsRTL(); - const bool create_on_the_right = (prefer_leading && !layout_is_rtl) || - (!prefer_leading && layout_is_rtl); - if (create_on_the_right) { + const bool create_on_right = prefer_leading != layout_is_rtl; + if (create_on_right) { x = item_bounds.right() - border_and_shadow_insets.left(); - if (state_.monitor_bounds.width() != 0 && - (x + menu_config.touchable_menu_width - - border_and_shadow_insets.right() > - state_.monitor_bounds.right())) { + if (monitor_bounds.width() != 0 && (x + menu_config.touchable_menu_width - + border_and_shadow_insets.right() > + monitor_bounds.right())) { *is_leading = prefer_leading; x = item_bounds.x() - menu_config.touchable_menu_width - border_and_shadow_insets.right(); @@ -2311,7 +2342,7 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, } else { x = item_bounds.x() - menu_config.touchable_menu_width - border_and_shadow_insets.right(); - if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) { + if (monitor_bounds.width() != 0 && x < monitor_bounds.x()) { *is_leading = !prefer_leading; x = item_bounds.x() + menu_config.touchable_menu_width - border_and_shadow_insets.left(); @@ -2319,15 +2350,12 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, } y = item_bounds.y() - border_and_shadow_insets.top() - menu_config.vertical_touchable_menu_item_padding; - if (y + pref.height() - border_and_shadow_insets.bottom() > - state_.monitor_bounds.bottom()) { - y = state_.monitor_bounds.bottom() - pref.height() + - border_and_shadow_insets.top(); - } - if (y < state_.monitor_bounds.y()) - y = state_.monitor_bounds.y() - border_and_shadow_insets.top(); + y = base::ClampToRange(y, + monitor_bounds.y() - border_and_shadow_insets.top(), + monitor_bounds.bottom() - menu_size.height() + + border_and_shadow_insets.top()); } - return gfx::Rect(x, y, pref.width(), pref.height()); + return gfx::Rect(x, y, menu_size.width(), menu_size.height()); } // static @@ -2522,7 +2550,7 @@ void MenuController::SelectByChar(base::char16 character) { return; } - if (is_combobox_) { + if (is_combobox_ || MenuConfig::instance().all_menus_use_prefix_selection) { item->GetSubmenu()->GetPrefixSelector()->InsertText(char_array); } else { // If no mnemonics found, look at first character of titles. @@ -2860,6 +2888,13 @@ void MenuController::SetHotTrackedButton(Button* hot_button) { } } +bool MenuController::ShouldContinuePrefixSelection() const { + MenuItemView* item = pending_state_.item; + if (!item->SubmenuIsShowing()) + item = item->GetParentMenuItem(); + return item->GetSubmenu()->GetPrefixSelector()->ShouldContinueSelection(); +} + bool MenuController::CanProcessInputEvents() const { #if defined(OS_MACOSX) return !menu_closure_animation_; diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h index 1fcfaa99d46..588612b1edc 100644 --- a/chromium/ui/views/controls/menu/menu_controller.h +++ b/chromium/ui/views/controls/menu/menu_controller.h @@ -28,6 +28,7 @@ #if defined(OS_MACOSX) #include "ui/views/controls/menu/menu_closure_animation_mac.h" +#include "ui/views/controls/menu/menu_cocoa_watcher_mac.h" #endif namespace ui { @@ -38,15 +39,12 @@ namespace views { class MenuButton; class MenuHostRootView; class MenuItemView; +class MenuPreTargetHandler; class MouseEvent; class SubmenuView; class View; class ViewTracker; -#if defined(USE_AURA) -class MenuPreTargetHandler; -#endif - namespace internal { class MenuControllerDelegate; class MenuRunnerImpl; @@ -582,6 +580,12 @@ class VIEWS_EXPORT MenuController // Updates the current |hot_button_| and its hot tracked state. void SetHotTrackedButton(Button* hot_button); + // Returns whether typing a new character will continue the existing prefix + // selection. If this returns false, typing a new character will start a new + // prefix selection, and some characters (such as Space) will be treated as + // commands instead of parts of the prefix. + bool ShouldContinuePrefixSelection() const; + // The active instance. static MenuController* active_instance_; @@ -727,11 +731,10 @@ class VIEWS_EXPORT MenuController #if defined(OS_MACOSX) std::unique_ptr<MenuClosureAnimationMac> menu_closure_animation_; + std::unique_ptr<MenuCocoaWatcherMac> menu_cocoa_watcher_; #endif -#if defined(USE_AURA) std::unique_ptr<MenuPreTargetHandler> menu_pre_target_handler_; -#endif DISALLOW_COPY_AND_ASSIGN(MenuController); }; diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc index c8052ea2734..57c22228ae9 100644 --- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc @@ -16,7 +16,6 @@ #include "ui/events/event_constants.h" #include "ui/events/event_handler.h" #include "ui/events/event_utils.h" -#include "ui/events/null_event_targeter.h" #include "ui/events/test/event_generator.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" @@ -35,6 +34,7 @@ #if defined(USE_AURA) #include "ui/aura/client/drag_drop_client.h" #include "ui/aura/client/drag_drop_client_observer.h" +#include "ui/aura/null_window_targeter.h" #include "ui/aura/scoped_window_targeter.h" #include "ui/aura/window.h" #include "ui/views/controls/menu/menu_pre_target_handler.h" @@ -240,6 +240,27 @@ void DestructingTestViewsDelegate::ReleaseRef() { release_ref_callback_.Run(); } +// View which cancels the menu it belongs to on mouse press. +class CancelMenuOnMousePressView : public View { + public: + explicit CancelMenuOnMousePressView(MenuController* controller) + : controller_(controller) {} + + // View: + bool OnMousePressed(const ui::MouseEvent& event) override { + controller_->CancelAll(); + return true; + } + + // This is needed to prevent the view from being "squashed" to zero height + // when the menu which owns it is shown. In such state the logic which + // determines if the menu contains the mouse press location doesn't work. + gfx::Size CalculatePreferredSize() const override { return size(); } + + private: + MenuController* controller_; +}; + } // namespace class TestMenuItemViewShown : public MenuItemView { @@ -257,6 +278,9 @@ class TestMenuItemViewShown : public MenuItemView { void SetActualMenuPosition(MenuItemView::MenuPosition position) { set_actual_menu_position(position); } + MenuItemView::MenuPosition ActualMenuPosition() { + return actual_menu_position(); + } private: DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown); @@ -627,11 +651,11 @@ class MenuControllerTest : public ViewsTestBase { // event dispatcher. TEST_F(MenuControllerTest, EventTargeter) { { - // With the |ui::NullEventTargeter| instantiated and assigned we expect + // With the aura::NullWindowTargeter instantiated and assigned we expect // the menu to not handle the key event. aura::ScopedWindowTargeter scoped_targeter( owner()->GetNativeWindow()->GetRootWindow(), - std::unique_ptr<ui::EventTargeter>(new ui::NullEventTargeter)); + std::make_unique<aura::NullWindowTargeter>()); PressKey(ui::VKEY_ESCAPE); EXPECT_EQ(MenuController::EXIT_NONE, menu_exit_type()); } @@ -1068,8 +1092,7 @@ TEST_F(MenuControllerTest, AsynchronousPerformDrop) { SetDropMenuItem(target, MenuDelegate::DropPosition::DROP_AFTER); ui::OSExchangeData drop_data; - gfx::Rect bounds(target->bounds()); - gfx::Point location(bounds.x(), bounds.y()); + gfx::PointF location(target->origin()); ui::DropTargetEvent target_event(drop_data, location, location, ui::DragDropTypes::DRAG_MOVE); controller->OnPerformDrop(source, target_event); @@ -1168,6 +1191,8 @@ TEST_F(MenuControllerTest, DoubleAsynchronousNested) { EXPECT_EQ(1, nested_delegate->on_menu_closed_called()); } +// Tests that setting send_gesture_events_to_owner flag forwards gesture events +// to owner and the forwarding stops when the current gesture sequence ends. TEST_F(MenuControllerTest, PreserveGestureForOwner) { MenuController* controller = menu_controller(); MenuItemView* item = menu_item(); @@ -1204,6 +1229,40 @@ TEST_F(MenuControllerTest, PreserveGestureForOwner) { EXPECT_EQ(CountOwnerOnGestureEvent(), 2); } +// Tests that touch outside menu does not closes the menu when forwarding +// gesture events to owner. +TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { + MenuController* controller = menu_controller(); + + // Owner wants the gesture events. + controller->set_send_gesture_events_to_owner(true); + + // Show a sub menu and touch outside of it. + MenuItemView* item = menu_item(); + SubmenuView* sub_menu = item->GetSubmenu(); + sub_menu->ShowAt(owner(), item->bounds(), false); + gfx::Point location(sub_menu->bounds().bottom_right()); + location.Offset(1, 1); + ui::TouchEvent touch_event( + ui::ET_TOUCH_PRESSED, location, ui::EventTimeForNow(), + ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); + controller->OnTouchEvent(sub_menu, &touch_event); + + // Menu should still be visible. + EXPECT_TRUE(IsShowing()); + + // The current gesture sequence ends. + ui::GestureEvent gesture_end_event( + location.x(), location.y(), 0, ui::EventTimeForNow(), + ui::GestureEventDetails(ui::ET_GESTURE_END)); + controller->OnGestureEvent(sub_menu, &gesture_end_event); + + // Touch outside again and menu should be closed. + controller->OnTouchEvent(sub_menu, &touch_event); + EXPECT_FALSE(IsShowing()); + EXPECT_EQ(MenuController::EXIT_ALL, controller->exit_type()); +} + // Tests that a nested menu does not crash when trying to repost events that // occur outside of the bounds of the menu. Instead a proper shutdown should // occur. @@ -1548,6 +1607,37 @@ TEST_F(MenuControllerTest, CalculateMenuBoundsMonitorFitTest) { EXPECT_EQ(expected, CalculateMenuBounds(options)); } +// Test that a menu that was originally drawn below the anchor does not get +// squished or move above the anchor when it grows vertically and horizontally +// beyond the monitor bounds. +TEST_F(MenuControllerTest, GrowingMenuMovesLaterallyNotVertically) { + MenuBoundsOptions options; + options.monitor_bounds = gfx::Rect(0, 0, 100, 100); + // The anchor should be near the bottom right side of the screen. + options.anchor_bounds = gfx::Rect(80, 70, 15, 10); + // The menu should fit the available space, below the anchor. + options.menu_size = gfx::Size(20, 20); + + // Ensure the menu is initially drawn below the bounds, and the MenuPosition + // is set to POSITION_BELOW_BOUNDS; + const gfx::Rect first_drawn_expected(80, 80, 20, 20); + EXPECT_EQ(first_drawn_expected, CalculateMenuBounds(options)); + EXPECT_EQ(MenuItemView::MenuPosition::POSITION_BELOW_BOUNDS, + menu_item()->ActualMenuPosition()); + + options.menu_position = MenuItemView::MenuPosition::POSITION_BELOW_BOUNDS; + + // The menu bounds are larger than the remaining space on the monitor. This + // simulates the case where the menu has been grown vertically and + // horizontally to where it would no longer fit on the screen. + options.menu_size = gfx::Size(50, 50); + + // The menu bounds should move left to show the wider menu, and grow to fill + // the remaining vertical space without moving upwards. + const gfx::Rect final_expected(50, 80, 50, 20); + EXPECT_EQ(final_expected, CalculateMenuBounds(options)); +} + #if defined(USE_AURA) // This tests that mouse moved events from the initial position of the mouse // when the menu was shown don't select the menu item at the mouse position. @@ -1814,5 +1904,35 @@ TEST_F(MenuControllerTest, DragFromViewIntoMenuAndExit) { #endif // defined(USE_AURA) +// Tests that having the MenuController deleted during OnMousePressed does not +// cause a crash. ASAN bots should not detect use-after-free in MenuController. +TEST_F(MenuControllerTest, NoUseAfterFreeWhenMenuCanceledOnMousePress) { + MenuController* controller = menu_controller(); + DestroyMenuControllerOnMenuClosed(menu_controller_delegate()); + + // Creating own MenuItem for a minimal test environment. + auto item = std::make_unique<TestMenuItemViewNotShown>(menu_delegate()); + item->SetController(controller); + item->SetBounds(0, 0, 50, 50); + + SubmenuView* sub_menu = item->CreateSubmenu(); + auto* canceling_view = new CancelMenuOnMousePressView(controller); + sub_menu->AddChildView(canceling_view); + canceling_view->SetBoundsRect(item->bounds()); + + controller->Run(owner(), nullptr, item.get(), item->bounds(), + MENU_ANCHOR_TOPLEFT, false, false); + sub_menu->ShowAt(owner(), item->bounds(), true); + + // Simulate a mouse press in the middle of the |closing_widget|. + gfx::Point location(canceling_view->bounds().CenterPoint()); + ui::MouseEvent event(ui::ET_MOUSE_PRESSED, location, location, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0); + EXPECT_TRUE(controller->OnMousePressed(sub_menu, event)); + + // Close to remove observers before test TearDown. + sub_menu->Close(); +} + } // namespace test } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_delegate.cc b/chromium/ui/views/controls/menu/menu_delegate.cc index 14660727afd..56c3c51ca94 100644 --- a/chromium/ui/views/controls/menu/menu_delegate.cc +++ b/chromium/ui/views/controls/menu/menu_delegate.cc @@ -19,13 +19,7 @@ base::string16 MenuDelegate::GetLabel(int id) const { return base::string16(); } -const gfx::FontList* MenuDelegate::GetLabelFontList(int id) const { - return NULL; -} - -bool MenuDelegate::GetShouldUseNormalForegroundColor(int command_id) const { - return false; -} +void MenuDelegate::GetLabelStyle(int id, LabelStyle* style) const {} base::string16 MenuDelegate::GetTooltipText(int id, const gfx::Point& screen_loc) const { diff --git a/chromium/ui/views/controls/menu/menu_delegate.h b/chromium/ui/views/controls/menu/menu_delegate.h index 4dea63f9f28..706605182f9 100644 --- a/chromium/ui/views/controls/menu/menu_delegate.h +++ b/chromium/ui/views/controls/menu/menu_delegate.h @@ -10,9 +10,11 @@ #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" #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/ui_base_types.h" +#include "ui/gfx/font_list.h" #include "ui/views/controls/menu/menu_runner.h" #include "ui/views/controls/menu/menu_types.h" #include "ui/views/views_export.h" @@ -59,6 +61,12 @@ class VIEWS_EXPORT MenuDelegate { DROP_ON }; + // Used when indicating the style for a given label. + struct LabelStyle { + gfx::FontList font_list; + SkColor foreground; + }; + virtual ~MenuDelegate(); // Whether or not an item should be shown as checked. This is invoked for @@ -69,12 +77,9 @@ class VIEWS_EXPORT MenuDelegate { // added with an empty label. virtual base::string16 GetLabel(int id) const; - // The font for the menu item label. - virtual const gfx::FontList* GetLabelFontList(int id) const; - - // Whether this item should be displayed with the normal text color, even if - // it's disabled. - virtual bool GetShouldUseNormalForegroundColor(int command_id) const; + // The style for the label with the given |id|. Implementations may update any + // parts of |style| or leave it unmodified. + virtual void GetLabelStyle(int id, LabelStyle* style) const; // The tooltip shown for the menu item. This is invoked when the user // hovers over the item, and no tooltip text has been set for that item. diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc index f14a1c06685..104ae4c6da5 100644 --- a/chromium/ui/views/controls/menu/menu_host.cc +++ b/chromium/ui/views/controls/menu/menu_host.cc @@ -84,7 +84,7 @@ void TransferGesture(Widget* source, Widget* target) { #if defined(OS_MACOSX) NOTIMPLEMENTED(); #else // !defined(OS_MACOSX) - ui::GestureRecognizer::Get()->TransferEventsTo( + source->GetGestureRecognizer()->TransferEventsTo( source->GetNativeView(), target->GetNativeView(), ui::GestureRecognizer::ShouldCancelTouches::DontCancel); #endif // defined(OS_MACOSX) @@ -126,6 +126,11 @@ void MenuHost::InitMenuHost(Widget* parent, Widget::InitParams::OPAQUE_WINDOW; params.parent = parent ? parent->GetNativeView() : NULL; params.bounds = bounds; + // If MenuHost has no parent widget, it needs to be marked + // Activatable, so that calling Show in ShowMenuHost will + // get keyboard focus. + if (parent == nullptr) + params.activatable = Widget::InitParams::ACTIVATABLE_YES; #if defined(OS_WIN) // On Windows use the software compositor to ensure that we don't block // the UI thread blocking issue during command buffer creation. We can @@ -166,13 +171,17 @@ void MenuHost::ShowMenuHost(bool do_capture) { // gesture events instead of being dropped. internal::TransferGesture(owner_, this); } else { - ui::GestureRecognizer::Get()->CancelActiveTouchesExcept(nullptr); + GetGestureRecognizer()->CancelActiveTouchesExcept(nullptr); } #if defined(MACOSX) // Cancel existing touches, so we don't miss some touch release/cancel // events due to the menu taking capture. - ui::GestureRecognizer::Get()->CancelActiveTouchesExcept(nullptr); + GetGestureRecognizer()->CancelActiveTouchesExcept(nullptr); #endif // defined (OS_MACOSX) + // If MenuHost has no parent widget, it needs to call Show to get focus, + // so that it will get keyboard events. + if (owner_ == nullptr) + Show(); native_widget_private()->SetCapture(); } } diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc index 4ede4cb5fa0..6c007a1030c 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.cc +++ b/chromium/ui/views/controls/menu/menu_item_view.cc @@ -849,17 +849,20 @@ int MenuItemView::GetDrawStringFlags() { return flags; } -const gfx::FontList& MenuItemView::GetFontList() const { - const MenuDelegate* delegate = GetDelegate(); - if (delegate) { - const gfx::FontList* font_list = delegate->GetLabelFontList(GetCommand()); - if (font_list) - return *font_list; +void MenuItemView::GetLabelStyle(MenuDelegate::LabelStyle* style) const { + // Start with the default font: + style->font_list = MenuConfig::instance().font_list; + + // Replace it with the touchable font in touchable menus: + if (GetMenuController() && GetMenuController()->use_touchable_layout()) { + style->font_list = + style::GetFont(style::CONTEXT_TOUCH_MENU, style::STYLE_PRIMARY); } - if (GetMenuController() && GetMenuController()->use_touchable_layout()) - return style::GetFont(style::CONTEXT_TOUCH_MENU, style::STYLE_PRIMARY); - return MenuConfig::instance().font_list; + // Then let the delegate replace any part of |style|. + const MenuDelegate* delegate = GetDelegate(); + if (delegate) + delegate->GetLabelStyle(GetCommand(), style); } void MenuItemView::AddEmptyMenus() { @@ -908,8 +911,6 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { render_selection = *forced_visual_selection_; MenuDelegate *delegate = GetDelegate(); - bool emphasized = - delegate && delegate->GetShouldUseNormalForegroundColor(GetCommand()); // Render the background. As MenuScrollViewContainer draws the background, we // only need the background when we want it to look different, as when we're // selected. @@ -939,8 +940,11 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { const int available_height = height() - top_margin - bottom_margin; // Calculate some colors. - SkColor fg_color = GetTextColor(false, render_selection, emphasized); - SkColor icon_color = color_utils::DeriveDefaultIconColor(fg_color); + MenuDelegate::LabelStyle style; + style.foreground = GetTextColor(false, render_selection); + GetLabelStyle(&style); + + SkColor icon_color = color_utils::DeriveDefaultIconColor(style.foreground); if (GetMenuController() && GetMenuController()->use_touchable_layout()) icon_color = config.touchable_icon_color; @@ -953,7 +957,6 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { } // Render the foreground. - const gfx::FontList& font_list = GetFontList(); int accel_width = parent_menu_item_->GetSubmenu()->max_minor_text_width(); int label_start = GetLabelStartForThisItem(); @@ -968,25 +971,26 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { int flags = GetDrawStringFlags(); if (mode == PB_FOR_DRAG) flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING; - canvas->DrawStringRectWithFlags(title(), font_list, fg_color, text_bounds, - flags); + canvas->DrawStringRectWithFlags(title(), style.font_list, style.foreground, + text_bounds, flags); if (!subtitle_.empty()) { canvas->DrawStringRectWithFlags( - subtitle_, font_list, + subtitle_, style.font_list, GetNativeTheme()->GetSystemColor( ui::NativeTheme::kColorId_MenuItemMinorTextColor), - text_bounds + gfx::Vector2d(0, font_list.GetHeight()), flags); + text_bounds + gfx::Vector2d(0, style.font_list.GetHeight()), flags); } - PaintMinorIconAndText(canvas, - GetTextColor(true, render_selection, emphasized)); + PaintMinorIconAndText(canvas, style); // Set the submenu indicator (arrow) image and color. if (HasSubmenu()) submenu_arrow_image_view_->SetImage(GetSubmenuArrowImage(icon_color)); } -void MenuItemView::PaintMinorIconAndText(gfx::Canvas* canvas, SkColor color) { +void MenuItemView::PaintMinorIconAndText( + gfx::Canvas* canvas, + const MenuDelegate::LabelStyle& style) { base::string16 minor_text = GetMinorText(); const gfx::VectorIcon* minor_icon = GetMinorIcon(); if (minor_text.empty() && !minor_icon) @@ -1007,8 +1011,8 @@ void MenuItemView::PaintMinorIconAndText(gfx::Canvas* canvas, SkColor color) { auto render_text = gfx::RenderText::CreateHarfBuzzInstance(); if (!minor_text.empty()) { render_text->SetText(minor_text); - render_text->SetFontList(GetFontList()); - render_text->SetColor(color); + render_text->SetFontList(style.font_list); + render_text->SetColor(style.foreground); render_text->SetDisplayRect(minor_text_bounds); render_text->SetHorizontalAlignment(base::i18n::IsRTL() ? gfx::ALIGN_LEFT : gfx::ALIGN_RIGHT); @@ -1016,7 +1020,7 @@ void MenuItemView::PaintMinorIconAndText(gfx::Canvas* canvas, SkColor color) { } if (minor_icon) { - gfx::ImageSkia image = CreateVectorIcon(*minor_icon, color); + gfx::ImageSkia image = CreateVectorIcon(*minor_icon, style.foreground); int image_x = GetMirroredRect(minor_text_bounds).right() - render_text->GetContentWidth() - @@ -1030,9 +1034,7 @@ void MenuItemView::PaintMinorIconAndText(gfx::Canvas* canvas, SkColor color) { } } -SkColor MenuItemView::GetTextColor(bool minor, - bool render_selection, - bool emphasized) const { +SkColor MenuItemView::GetTextColor(bool minor, bool render_selection) const { ui::NativeTheme::ColorId color_id = minor ? ui::NativeTheme::kColorId_MenuItemMinorTextColor : ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor; @@ -1040,8 +1042,7 @@ SkColor MenuItemView::GetTextColor(bool minor, if (render_selection) color_id = ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor; } else { - if (!emphasized) - color_id = ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor; + color_id = ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor; } if (GetMenuController() && GetMenuController()->use_touchable_layout()) @@ -1138,7 +1139,8 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { return dimensions; } - const gfx::FontList& font_list = GetFontList(); + MenuDelegate::LabelStyle style; + GetLabelStyle(&style); base::string16 minor_text = GetMinorText(); dimensions.height = child_size.height(); @@ -1170,23 +1172,23 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { int label_start = GetLabelStartForThisItem(); // Determine the length of the label text. - int string_width = gfx::GetStringWidth(title_, font_list); + int string_width = gfx::GetStringWidth(title_, style.font_list); if (!subtitle_.empty()) { - string_width = std::max(string_width, - gfx::GetStringWidth(subtitle_, font_list)); + string_width = + std::max(string_width, gfx::GetStringWidth(subtitle_, style.font_list)); } 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, font_list); + minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, style.font_list); // Determine the height to use. - dimensions.height = - std::max(dimensions.height, - (subtitle_.empty() ? 0 : font_list.GetHeight()) + - font_list.GetHeight() + GetBottomMargin() + GetTopMargin()); + dimensions.height = std::max( + dimensions.height, (subtitle_.empty() ? 0 : style.font_list.GetHeight()) + + style.font_list.GetHeight() + GetBottomMargin() + + GetTopMargin()); dimensions.height = std::max(dimensions.height, MenuConfig::instance().item_min_height); diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h index 929f3f87204..160a0069256 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.h +++ b/chromium/ui/views/controls/menu/menu_item_view.h @@ -28,7 +28,6 @@ #endif namespace gfx { -class FontList; struct VectorIcon; } @@ -406,8 +405,8 @@ class VIEWS_EXPORT MenuItemView : public View { // Returns the flags passed to DrawStringRect. int GetDrawStringFlags(); - // Returns the font list to use for menu text. - const gfx::FontList& GetFontList() const; + // Returns the style for the menu text. + void GetLabelStyle(MenuDelegate::LabelStyle* style) const; // If this menu item has no children a child is added showing it has no // children. Otherwise AddEmtpyMenus is recursively invoked on child menu @@ -426,7 +425,8 @@ class VIEWS_EXPORT MenuItemView : public View { void PaintButton(gfx::Canvas* canvas, PaintButtonMode mode); // Paints the right-side icon and text. - void PaintMinorIconAndText(gfx::Canvas* canvas, SkColor color); + void PaintMinorIconAndText(gfx::Canvas* canvas, + const MenuDelegate::LabelStyle& style); // Destroys the window used to display this menu and recursively destroys // the windows used to display all descendants. @@ -441,9 +441,7 @@ class VIEWS_EXPORT MenuItemView : public View { // Returns the text color for the current state. |minor| specifies if the // minor text or the normal text is desired. - SkColor GetTextColor(bool minor, - bool render_selection, - bool emphasized) const; + SkColor GetTextColor(bool minor, bool render_selection) const; // Calculates and returns the MenuItemDimensions. MenuItemDimensions CalculateDimensions() const; diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc index e9cfe1c466c..cbccee16e4c 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter.cc +++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc @@ -172,17 +172,19 @@ base::string16 MenuModelAdapter::GetLabel(int id) const { return base::string16(); } -const gfx::FontList* MenuModelAdapter::GetLabelFontList(int id) const { +void MenuModelAdapter::GetLabelStyle(int id, LabelStyle* style) const { ui::MenuModel* model = menu_model_; int index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) { const gfx::FontList* font_list = model->GetLabelFontListAt(index); - if (font_list) - return font_list; + if (font_list) { + style->font_list = *font_list; + return; + } } // This line may be reached for the empty menu item. - return MenuDelegate::GetLabelFontList(id); + return MenuDelegate::GetLabelStyle(id, style); } bool MenuModelAdapter::IsCommandEnabled(int id) const { @@ -269,6 +271,10 @@ void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) { const int item_count = model->GetItemCount(); for (int i = 0; i < item_count; ++i) { MenuItemView* item = AppendMenuItem(menu, model, i); + if (item) { + item->SetEnabled(model->IsEnabledAt(i)); + item->SetVisible(model->IsVisibleAt(i)); + } if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU || model->GetTypeAt(i) == ui::MenuModel::TYPE_ACTIONABLE_SUBMENU) { diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.h b/chromium/ui/views/controls/menu/menu_model_adapter.h index 0ac493c3c6a..e52edfe5edd 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter.h +++ b/chromium/ui/views/controls/menu/menu_model_adapter.h @@ -71,7 +71,7 @@ class VIEWS_EXPORT MenuModelAdapter : public MenuDelegate { bool IsTriggerableEvent(MenuItemView* source, const ui::Event& e) override; bool GetAccelerator(int id, ui::Accelerator* accelerator) const override; base::string16 GetLabel(int id) const override; - const gfx::FontList* GetLabelFontList(int id) const override; + void GetLabelStyle(int id, LabelStyle* style) const override; bool IsCommandEnabled(int id) const override; bool IsCommandVisible(int id) const override; bool IsItemChecked(int id) const override; 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 df3bb3ef251..00da8e3ebcc 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc @@ -4,6 +4,10 @@ #include "ui/views/controls/menu/menu_model_adapter.h" +#include <memory> +#include <string> +#include <vector> + #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "ui/base/models/menu_model.h" @@ -70,9 +74,9 @@ class MenuModelBase : public ui::MenuModel { return NULL; } - bool IsEnabledAt(int index) const override { return true; } + bool IsEnabledAt(int index) const override { return items_[index].enabled; } - bool IsVisibleAt(int index) const override { return true; } + bool IsVisibleAt(int index) const override { return items_[index].visible; } MenuModel* GetSubmenuModelAt(int index) const override { return items_[index].submenu; @@ -99,12 +103,26 @@ class MenuModelBase : public ui::MenuModel { ui::MenuModel* item_submenu) : type(item_type), label(base::ASCIIToUTF16(item_label)), - submenu(item_submenu) { - } + submenu(item_submenu), + enabled(true), + visible(true) {} + + Item(ItemType item_type, + const std::string& item_label, + ui::MenuModel* item_submenu, + bool enabled, + bool visible) + : type(item_type), + label(base::ASCIIToUTF16(item_label)), + submenu(item_submenu), + enabled(enabled), + visible(visible) {} ItemType type; base::string16 label; ui::MenuModel* submenu; + bool enabled; + bool visible; }; const Item& GetItemDefinition(int index) { @@ -130,7 +148,7 @@ class MenuModelBase : public ui::MenuModel { class SubmenuModel : public MenuModelBase { public: SubmenuModel() : MenuModelBase(kSubmenuIdBase) { - items_.push_back(Item(TYPE_COMMAND, "submenu item 0", NULL)); + items_.push_back(Item(TYPE_COMMAND, "submenu item 0", NULL, false, true)); items_.push_back(Item(TYPE_COMMAND, "submenu item 1", NULL)); } @@ -158,7 +176,7 @@ class RootModel : public MenuModelBase { submenu_model_ = std::make_unique<SubmenuModel>(); actionable_submenu_model_ = std::make_unique<ActionableSubmenuModel>(); - items_.push_back(Item(TYPE_COMMAND, "command 0", NULL)); + items_.push_back(Item(TYPE_COMMAND, "command 0", NULL, false, false)); items_.push_back(Item(TYPE_CHECK, "check 1", NULL)); items_.push_back(Item(TYPE_SEPARATOR, "", NULL)); items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get())); @@ -225,6 +243,12 @@ void CheckSubmenu(const RootModel& model, break; } + // Check enabled state. + EXPECT_EQ(model_item.enabled, item->enabled()); + + // Check visibility. + EXPECT_EQ(model_item.visible, item->visible()); + // Check activation. static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id); EXPECT_EQ(i, submodel->last_activation()); @@ -290,6 +314,12 @@ TEST_F(MenuModelAdapterTest, BasicTest) { break; } + // Check enabled state. + EXPECT_EQ(model_item.enabled, item->enabled()); + + // Check visibility. + EXPECT_EQ(model_item.visible, item->visible()); + // Check activation. static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id); EXPECT_EQ(i, model.last_activation()); diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler.h b/chromium/ui/views/controls/menu/menu_pre_target_handler.h index 4ff27ddcea8..8a02e960543 100644 --- a/chromium/ui/views/controls/menu/menu_pre_target_handler.h +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler.h @@ -1,56 +1,39 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. +// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef UI_VIEWS_CONTROLS_MENU_PRE_TARGET_HANDLER_H_ -#define UI_VIEWS_CONTROLS_MENU_PRE_TARGET_HANDLER_H_ +#ifndef UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_H_ +#define UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_H_ -#include "ui/aura/window_observer.h" -#include "ui/events/event_handler.h" -#include "ui/views/views_export.h" -#include "ui/wm/public/activation_change_observer.h" +#include <memory> -namespace aura { -class Window; -} // namespace aura +#include "base/macros.h" namespace views { class MenuController; class Widget; -// MenuPreTargetHandler is used to observe activation changes, cancel events, -// and root window destruction, to shutdown the menu. -// -// Additionally handles key events to provide accelerator support to menus. -class VIEWS_EXPORT MenuPreTargetHandler : public wm::ActivationChangeObserver, - public aura::WindowObserver, - public ui::EventHandler { +// A MenuPreTargetHandler is responsible for intercepting events destined for +// another widget (the menu's owning widget) and letting the menu's controller +// try dispatching them first. +class MenuPreTargetHandler { public: - MenuPreTargetHandler(MenuController* controller, Widget* owner); - ~MenuPreTargetHandler() override; + virtual ~MenuPreTargetHandler() = default; - // aura::client:ActivationChangeObserver: - void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason, - aura::Window* gained_active, - aura::Window* lost_active) override; + // There are separate implementations of this method for Aura platforms and + // for Mac. + static std::unique_ptr<MenuPreTargetHandler> Create( + MenuController* controller, + Widget* owner); - // aura::WindowObserver: - void OnWindowDestroying(aura::Window* window) override; - - // ui::EventHandler: - void OnCancelMode(ui::CancelModeEvent* event) override; - void OnKeyEvent(ui::KeyEvent* event) override; + protected: + MenuPreTargetHandler() = default; private: - void Cleanup(); - - MenuController* controller_; - aura::Window* root_; - DISALLOW_COPY_AND_ASSIGN(MenuPreTargetHandler); }; } // namespace views -#endif // UI_VIEWS_CONTROLS_MENU_PRE_TARGET_HANDLER_H_ +#endif // UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_H_ diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler.cc b/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.cc index 9f9dd9448a4..45b071661bb 100644 --- a/chromium/ui/views/controls/menu/menu_pre_target_handler.cc +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/controls/menu/menu_pre_target_handler.h" +#include "ui/views/controls/menu/menu_pre_target_handler_aura.h" #include "ui/aura/env.h" #include "ui/aura/window.h" @@ -20,10 +20,10 @@ aura::Window* GetOwnerRootWindow(views::Widget* owner) { } // namespace -MenuPreTargetHandler::MenuPreTargetHandler(MenuController* controller, - Widget* owner) +MenuPreTargetHandlerAura::MenuPreTargetHandlerAura(MenuController* controller, + Widget* owner) : controller_(controller), root_(GetOwnerRootWindow(owner)) { - aura::Env::GetInstanceDontCreate()->AddPreTargetHandler( + aura::Env::GetInstance()->AddPreTargetHandler( this, ui::EventTarget::Priority::kSystem); if (root_) { wm::GetActivationClient(root_)->AddObserver(this); @@ -31,12 +31,12 @@ MenuPreTargetHandler::MenuPreTargetHandler(MenuController* controller, } } -MenuPreTargetHandler::~MenuPreTargetHandler() { - aura::Env::GetInstanceDontCreate()->RemovePreTargetHandler(this); +MenuPreTargetHandlerAura::~MenuPreTargetHandlerAura() { + aura::Env::GetInstance()->RemovePreTargetHandler(this); Cleanup(); } -void MenuPreTargetHandler::OnWindowActivated( +void MenuPreTargetHandlerAura::OnWindowActivated( wm::ActivationChangeObserver::ActivationReason reason, aura::Window* gained_active, aura::Window* lost_active) { @@ -44,19 +44,19 @@ void MenuPreTargetHandler::OnWindowActivated( controller_->CancelAll(); } -void MenuPreTargetHandler::OnWindowDestroying(aura::Window* window) { +void MenuPreTargetHandlerAura::OnWindowDestroying(aura::Window* window) { Cleanup(); } -void MenuPreTargetHandler::OnCancelMode(ui::CancelModeEvent* event) { +void MenuPreTargetHandlerAura::OnCancelMode(ui::CancelModeEvent* event) { controller_->CancelAll(); } -void MenuPreTargetHandler::OnKeyEvent(ui::KeyEvent* event) { +void MenuPreTargetHandlerAura::OnKeyEvent(ui::KeyEvent* event) { controller_->OnWillDispatchKeyEvent(event); } -void MenuPreTargetHandler::Cleanup() { +void MenuPreTargetHandlerAura::Cleanup() { if (!root_) return; // The ActivationClient may have been destroyed by the time we get here. @@ -67,4 +67,11 @@ void MenuPreTargetHandler::Cleanup() { root_ = nullptr; } +// static +std::unique_ptr<MenuPreTargetHandler> MenuPreTargetHandler::Create( + MenuController* controller, + Widget* owner) { + return std::make_unique<MenuPreTargetHandlerAura>(controller, owner); +} + } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.h b/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.h new file mode 100644 index 00000000000..13fc79cbde2 --- /dev/null +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.h @@ -0,0 +1,59 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_AURA_H_ +#define UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_AURA_H_ + +#include "ui/aura/window_observer.h" +#include "ui/events/event_handler.h" +#include "ui/views/controls/menu/menu_pre_target_handler.h" +#include "ui/views/views_export.h" +#include "ui/wm/public/activation_change_observer.h" + +namespace aura { +class Window; +} // namespace aura + +namespace views { + +class MenuController; +class Widget; + +// MenuPreTargetHandlerAura is used to observe activation changes, cancel +// events, and root window destruction, to shutdown the menu. +// +// Additionally handles key events to provide accelerator support to menus. +class VIEWS_EXPORT MenuPreTargetHandlerAura + : public wm::ActivationChangeObserver, + public aura::WindowObserver, + public ui::EventHandler, + public MenuPreTargetHandler { + public: + MenuPreTargetHandlerAura(MenuController* controller, Widget* owner); + ~MenuPreTargetHandlerAura() override; + + // aura::client:ActivationChangeObserver: + void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason, + aura::Window* gained_active, + aura::Window* lost_active) override; + + // aura::WindowObserver: + void OnWindowDestroying(aura::Window* window) override; + + // ui::EventHandler: + void OnCancelMode(ui::CancelModeEvent* event) override; + void OnKeyEvent(ui::KeyEvent* event) override; + + private: + void Cleanup(); + + MenuController* controller_; + aura::Window* root_; + + DISALLOW_COPY_AND_ASSIGN(MenuPreTargetHandlerAura); +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_AURA_H_ diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h b/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h new file mode 100644 index 00000000000..d2250979d3d --- /dev/null +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h @@ -0,0 +1,32 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_MAC_H_ +#define UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_MAC_H_ + +#include "ui/events/event_handler.h" +#include "ui/views/controls/menu/menu_pre_target_handler.h" +#include "ui/views/event_monitor_mac.h" + +namespace views { + +class MenuPreTargetHandlerMac : public MenuPreTargetHandler, + public ui::EventHandler { + public: + MenuPreTargetHandlerMac(MenuController* controller, Widget* widget); + ~MenuPreTargetHandlerMac() override; + + // ui::EventHandler: + void OnKeyEvent(ui::KeyEvent* event) override; + + private: + MenuController* controller_; // Weak. Owns |this|. + std::unique_ptr<EventMonitorMac> event_monitor_; + + DISALLOW_COPY_AND_ASSIGN(MenuPreTargetHandlerMac); +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_MENU_MENU_PRE_TARGET_HANDLER_MAC_H_ diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.mm b/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.mm new file mode 100644 index 00000000000..81ce0dafa79 --- /dev/null +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.mm @@ -0,0 +1,34 @@ +// 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/menu/menu_pre_target_handler_mac.h" + +#include "ui/views/controls/menu/menu_controller.h" +#include "ui/views/widget/widget.h" + +namespace views { + +MenuPreTargetHandlerMac::MenuPreTargetHandlerMac(MenuController* controller, + Widget* widget) + : controller_(controller), + event_monitor_( + std::make_unique<EventMonitorMac>(this, widget->GetNativeWindow())) {} + +MenuPreTargetHandlerMac::~MenuPreTargetHandlerMac() {} + +void MenuPreTargetHandlerMac::OnKeyEvent(ui::KeyEvent* event) { + if (controller_->OnWillDispatchKeyEvent(event) != + ui::POST_DISPATCH_PERFORM_DEFAULT) { + event->SetHandled(); + } +} + +// static +std::unique_ptr<MenuPreTargetHandler> MenuPreTargetHandler::Create( + MenuController* controller, + Widget* widget) { + return std::make_unique<MenuPreTargetHandlerMac>(controller, widget); +} + +} // namespace views diff --git a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm index ae0c8bafd05..2cc6a8c1a7c 100644 --- a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm +++ b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm @@ -45,7 +45,7 @@ class TestModel : public ui::SimpleMenuModel { bool IsCommandIdEnabled(int command_id) const override { return true; } void ExecuteCommand(int command_id, int event_flags) override {} - void MenuWillShow(SimpleMenuModel* source) override { + void OnMenuWillShow(SimpleMenuModel* source) override { if (!model_->menu_open_callback_.is_null()) std::move(model_->menu_open_callback_).Run(); } diff --git a/chromium/ui/views/controls/menu/menu_runner_unittest.cc b/chromium/ui/views/controls/menu/menu_runner_unittest.cc index 704f242106d..bb3961c9af0 100644 --- a/chromium/ui/views/controls/menu/menu_runner_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_runner_unittest.cc @@ -10,8 +10,10 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/simple_test_tick_clock.h" #include "build/build_config.h" #include "ui/base/ui_base_types.h" +#include "ui/events/keycodes/dom/dom_code.h" #include "ui/events/test/event_generator.h" #include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_delegate.h" @@ -80,6 +82,11 @@ class MenuRunnerTest : public ViewsTestBase { // ViewsTestBase: void TearDown() override; + bool IsItemSelected(int command_id) { + MenuItemView* item = menu_item_view()->GetMenuItemByID(command_id); + return item ? item->IsSelected() : false; + } + private: // Owned by menu_runner_. MenuItemView* menu_item_view_ = nullptr; @@ -167,6 +174,11 @@ TEST_F(MenuRunnerTest, LatinMnemonic) { if (IsMus()) return; + // Menus that use prefix selection don't support mnemonics - the input is + // always part of the prefix. + if (MenuConfig::instance().all_menus_use_prefix_selection) + return; + views::test::DisableMenuClosureAnimations(); InitMenuRunner(0); MenuRunner* runner = menu_runner(); @@ -193,6 +205,11 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) { if (IsMus()) return; + // Menus that use prefix selection don't support mnemonics - the input is + // always part of the prefix. + if (MenuConfig::instance().all_menus_use_prefix_selection) + return; + views::test::DisableMenuClosureAnimations(); InitMenuRunner(0); MenuRunner* runner = menu_runner(); @@ -201,7 +218,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) { EXPECT_TRUE(runner->IsRunning()); ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow()); - ui::KeyEvent key_press(0x062f, ui::VKEY_N, 0); + ui::KeyEvent key_press(0x062f, ui::VKEY_N, ui::DomCode::NONE, 0); generator.Dispatch(&key_press); views::test::WaitForMenuClosureAnimation(); EXPECT_FALSE(runner->IsRunning()); @@ -212,6 +229,85 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) { } #endif // !defined(OS_WIN) +TEST_F(MenuRunnerTest, PrefixSelect) { + if (!MenuConfig::instance().all_menus_use_prefix_selection) + return; + + base::SimpleTestTickClock clock; + + // This test has a menu with three items: + // { 1, "One" } + // { 2, "\x062f\x0648" } + // { 3, "One Two" } + // It progressively prefix searches for "One " (note the space) and ensures + // that the right item is found. + + views::test::DisableMenuClosureAnimations(); + InitMenuRunner(0); + menu_item_view()->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("One Two")); + + MenuRunner* runner = menu_runner(); + runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_NONE); + EXPECT_TRUE(runner->IsRunning()); + + menu_item_view() + ->GetSubmenu() + ->GetPrefixSelector() + ->set_tick_clock_for_testing(&clock); + + ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow()); + generator.PressKey(ui::VKEY_O, 0); + EXPECT_TRUE(IsItemSelected(1)); + generator.PressKey(ui::VKEY_N, 0); + generator.PressKey(ui::VKEY_E, 0); + EXPECT_TRUE(IsItemSelected(1)); + + generator.PressKey(ui::VKEY_SPACE, 0); + EXPECT_TRUE(IsItemSelected(3)); + + // Wait out the PrefixSelector's timeout. + clock.Advance(base::TimeDelta::FromSeconds(10)); + + // Send Space to activate the selected menu item. + generator.PressKey(ui::VKEY_SPACE, 0); + views::test::WaitForMenuClosureAnimation(); + EXPECT_FALSE(runner->IsRunning()); + TestMenuDelegate* delegate = menu_delegate(); + EXPECT_EQ(3, delegate->execute_command_id()); + EXPECT_EQ(1, delegate->on_menu_closed_called()); + EXPECT_NE(nullptr, delegate->on_menu_closed_menu()); +} + +// This test is Mac-specific: Mac is the only platform where VKEY_SPACE +// activates menu items. +#if defined(OS_MACOSX) +TEST_F(MenuRunnerTest, SpaceActivatesItem) { + if (!MenuConfig::instance().all_menus_use_prefix_selection) + return; + + views::test::DisableMenuClosureAnimations(); + InitMenuRunner(0); + + MenuRunner* runner = menu_runner(); + runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_NONE); + EXPECT_TRUE(runner->IsRunning()); + + ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow()); + generator.PressKey(ui::VKEY_DOWN, 0); + EXPECT_TRUE(IsItemSelected(1)); + generator.PressKey(ui::VKEY_SPACE, 0); + views::test::WaitForMenuClosureAnimation(); + + EXPECT_FALSE(runner->IsRunning()); + TestMenuDelegate* delegate = menu_delegate(); + EXPECT_EQ(1, delegate->execute_command_id()); + EXPECT_EQ(1, delegate->on_menu_closed_called()); + EXPECT_NE(nullptr, delegate->on_menu_closed_menu()); +} +#endif // OS_MACOSX + // Tests that attempting to nest a menu within a drag-and-drop menu does not // cause a crash. Instead the drag and drop action should be canceled, and the // new menu should be openned. diff --git a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc index 16a1bef6715..ecbd5d773b9 100644 --- a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc +++ b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc @@ -286,7 +286,9 @@ void MenuScrollViewContainer::CreateDefaultBorder() { ? kBorderPaddingDueToRoundedCorners : 0; - const int vertical_inset = menu_config.menu_vertical_border_size + padding; + const int vertical_inset = + (corner_radius ? corner_radius : menu_config.menu_vertical_border_size) + + padding; const int horizontal_inset = menu_config.menu_horizontal_border_size + padding; diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc index cdb36b6df6c..c9ad4e4f5b9 100644 --- a/chromium/ui/views/controls/menu/submenu_view.cc +++ b/chromium/ui/views/controls/menu/submenu_view.cc @@ -374,7 +374,8 @@ void SubmenuView::SetSelectedRow(int row) { } base::string16 SubmenuView::GetTextForRow(int row) { - return GetMenuItemAt(row)->title(); + return MenuItemView::GetAccessibleNameForMenuItem(GetMenuItemAt(row)->title(), + base::string16()); } bool SubmenuView::IsShowing() { diff --git a/chromium/ui/views/controls/native/native_view_host_aura.cc b/chromium/ui/views/controls/native/native_view_host_aura.cc index 6afc8fbf9c3..4bd67c7a87d 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.cc +++ b/chromium/ui/views/controls/native/native_view_host_aura.cc @@ -69,34 +69,24 @@ class NativeViewHostAura::ClippingWindowDelegate : public aura::WindowDelegate { aura::Window* native_view_; }; -NativeViewHostAura::NativeViewHostAura(NativeViewHost* host) - : host_(host), - clipping_window_delegate_(new ClippingWindowDelegate()), - clipping_window_(clipping_window_delegate_.get()) { - // Set the type so descendant views (including popups) get positioned - // appropriately. - clipping_window_.SetType(aura::client::WINDOW_TYPE_CONTROL); - clipping_window_.Init(ui::LAYER_NOT_DRAWN); - clipping_window_.set_owned_by_parent(false); - clipping_window_.SetName("NativeViewHostAuraClip"); - clipping_window_.layer()->SetMasksToBounds(true); - clipping_window_.SetProperty(views::kHostViewKey, static_cast<View*>(host_)); -} +NativeViewHostAura::NativeViewHostAura(NativeViewHost* host) : host_(host) {} NativeViewHostAura::~NativeViewHostAura() { if (host_->native_view()) { host_->native_view()->RemoveObserver(this); host_->native_view()->ClearProperty(views::kHostViewKey); host_->native_view()->ClearProperty(aura::client::kHostWindowKey); - clipping_window_.ClearProperty(views::kHostViewKey); - if (host_->native_view()->parent() == &clipping_window_) - clipping_window_.RemoveChild(host_->native_view()); + clipping_window_->ClearProperty(views::kHostViewKey); + if (host_->native_view()->parent() == clipping_window_.get()) + clipping_window_->RemoveChild(host_->native_view()); } } //////////////////////////////////////////////////////////////////////////////// // NativeViewHostAura, NativeViewHostWrapper implementation: void NativeViewHostAura::AttachNativeView() { + if (!clipping_window_) + CreateClippingWindow(); clipping_window_delegate_->set_native_view(host_->native_view()); host_->native_view()->AddObserver(this); host_->native_view()->SetProperty(views::kHostViewKey, @@ -202,18 +192,17 @@ void NativeViewHostAura::ShowWidget(int x, } } - clipping_window_.SetBounds(clip_rect_ ? *clip_rect_ - : gfx::Rect(x, y, w, h)); - gfx::Point clip_offset = clipping_window_.bounds().origin(); + clipping_window_->SetBounds(clip_rect_ ? *clip_rect_ : gfx::Rect(x, y, w, h)); + gfx::Point clip_offset = clipping_window_->bounds().origin(); host_->native_view()->SetBounds( gfx::Rect(x - clip_offset.x(), y - clip_offset.y(), native_w, native_h)); host_->native_view()->Show(); - clipping_window_.Show(); + clipping_window_->Show(); } void NativeViewHostAura::HideWidget() { host_->native_view()->Hide(); - clipping_window_.Hide(); + clipping_window_->Hide(); } void NativeViewHostAura::SetFocus() { @@ -265,35 +254,48 @@ NativeViewHostWrapper* NativeViewHostWrapper::CreateWrapper( return new NativeViewHostAura(host); } +void NativeViewHostAura::CreateClippingWindow() { + clipping_window_delegate_ = std::make_unique<ClippingWindowDelegate>(); + // Use WINDOW_TYPE_CONTROLLER type so descendant views (including popups) get + // positioned appropriately. + clipping_window_ = std::make_unique<aura::Window>( + clipping_window_delegate_.get(), aura::client::WINDOW_TYPE_CONTROL, + host_->native_view()->env()); + clipping_window_->Init(ui::LAYER_NOT_DRAWN); + clipping_window_->set_owned_by_parent(false); + clipping_window_->SetName("NativeViewHostAuraClip"); + clipping_window_->layer()->SetMasksToBounds(true); + clipping_window_->SetProperty(views::kHostViewKey, static_cast<View*>(host_)); +} + void NativeViewHostAura::AddClippingWindow() { RemoveClippingWindow(); host_->native_view()->SetProperty(aura::client::kHostWindowKey, host_->GetWidget()->GetNativeView()); - Widget::ReparentNativeView(host_->native_view(), - &clipping_window_); + Widget::ReparentNativeView(host_->native_view(), clipping_window_.get()); if (host_->GetWidget()->GetNativeView()) { - Widget::ReparentNativeView(&clipping_window_, + Widget::ReparentNativeView(clipping_window_.get(), host_->GetWidget()->GetNativeView()); } } void NativeViewHostAura::RemoveClippingWindow() { - clipping_window_.Hide(); + clipping_window_->Hide(); if (host_->native_view()) host_->native_view()->ClearProperty(aura::client::kHostWindowKey); - if (host_->native_view()->parent() == &clipping_window_) { + if (host_->native_view()->parent() == clipping_window_.get()) { if (host_->GetWidget() && host_->GetWidget()->GetNativeView()) { Widget::ReparentNativeView(host_->native_view(), host_->GetWidget()->GetNativeView()); } else { - clipping_window_.RemoveChild(host_->native_view()); + clipping_window_->RemoveChild(host_->native_view()); } - host_->native_view()->SetBounds(clipping_window_.bounds()); + host_->native_view()->SetBounds(clipping_window_->bounds()); } - if (clipping_window_.parent()) - clipping_window_.parent()->RemoveChild(&clipping_window_); + if (clipping_window_->parent()) + clipping_window_->parent()->RemoveChild(clipping_window_.get()); } void NativeViewHostAura::InstallMask() { diff --git a/chromium/ui/views/controls/native/native_view_host_aura.h b/chromium/ui/views/controls/native/native_view_host_aura.h index 0ea78965b8f..a6072997977 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.h +++ b/chromium/ui/views/controls/native/native_view_host_aura.h @@ -7,13 +7,16 @@ #include "base/compiler_specific.h" #include "base/macros.h" -#include "ui/aura/window.h" #include "ui/aura/window_observer.h" #include "ui/compositor/layer_owner.h" #include "ui/gfx/transform.h" #include "ui/views/controls/native/native_view_host_wrapper.h" #include "ui/views/views_export.h" +namespace aura { +class Window; +} + namespace views { class NativeViewHost; @@ -53,6 +56,8 @@ class NativeViewHostAura : public NativeViewHostWrapper, const gfx::Rect& new_bounds, ui::PropertyChangeReason reason) override; + void CreateClippingWindow(); + // Reparents the native view with the clipping window existing between it and // its old parent, so that the fast resize path works. void AddClippingWindow(); @@ -75,7 +80,7 @@ class NativeViewHostAura : public NativeViewHostWrapper, // Window that exists between the native view and the parent that allows for // clipping to occur. This is positioned in the coordinate space of // host_->GetWidget(). - aura::Window clipping_window_; + std::unique_ptr<aura::Window> clipping_window_; std::unique_ptr<gfx::Rect> clip_rect_; // This mask exists for the sake of SetCornerRadius(). diff --git a/chromium/ui/views/controls/native/native_view_host_aura_unittest.cc b/chromium/ui/views/controls/native/native_view_host_aura_unittest.cc index 349d1a1b8c5..4a7e05f70b8 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura_unittest.cc +++ b/chromium/ui/views/controls/native/native_view_host_aura_unittest.cc @@ -97,7 +97,9 @@ class NativeViewHostAuraTest : public test::NativeViewHostTestBase { return child_.get(); } - aura::Window* clipping_window() { return &(native_host()->clipping_window_); } + aura::Window* clipping_window() { + return native_host()->clipping_window_.get(); + } void CreateHost() { CreateTopLevel(); diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc index 9844d192d78..98aab8c61c7 100644 --- a/chromium/ui/views/controls/prefix_selector.cc +++ b/chromium/ui/views/controls/prefix_selector.cc @@ -5,6 +5,7 @@ #include "ui/views/controls/prefix_selector.h" #include "base/i18n/case_conversion.h" +#include "base/time/default_tick_clock.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_input_type.h" #include "ui/gfx/range/range.h" @@ -21,8 +22,9 @@ const int64_t kTimeBeforeClearingMS = 1000; } // namespace PrefixSelector::PrefixSelector(PrefixDelegate* delegate, View* host_view) - : prefix_delegate_(delegate), host_view_(host_view) { -} + : prefix_delegate_(delegate), + host_view_(host_view), + tick_clock_(base::DefaultTickClock::GetInstance()) {} PrefixSelector::~PrefixSelector() { } @@ -31,6 +33,11 @@ void PrefixSelector::OnViewBlur() { ClearText(); } +bool PrefixSelector::ShouldContinueSelection() const { + const base::TimeTicks now(tick_clock_->NowTicks()); + return ((now - time_of_last_key_).InMilliseconds() < kTimeBeforeClearingMS); +} + void PrefixSelector::SetCompositionText( const ui::CompositionText& composition) { } @@ -174,15 +181,14 @@ void PrefixSelector::OnTextInput(const base::string16& text) { // while search after the current row, otherwise search starting from the // current row. int row = std::max(0, prefix_delegate_->GetSelectedRow()); - const base::TimeTicks now(base::TimeTicks::Now()); - if ((now - time_of_last_key_).InMilliseconds() < kTimeBeforeClearingMS) { + if (ShouldContinueSelection()) { current_text_ += text; } else { current_text_ = text; if (prefix_delegate_->GetSelectedRow() >= 0) row = (row + 1) % row_count; } - time_of_last_key_ = now; + time_of_last_key_ = tick_clock_->NowTicks(); const int start_row = row; const base::string16 lower_text(base::i18n::ToLower(current_text_)); diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h index ba2005191b3..801c9112b73 100644 --- a/chromium/ui/views/controls/prefix_selector.h +++ b/chromium/ui/views/controls/prefix_selector.h @@ -14,6 +14,10 @@ #include "ui/base/ime/text_input_client.h" #include "ui/views/views_export.h" +namespace base { +class TickClock; +} + namespace views { class PrefixDelegate; @@ -29,6 +33,10 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { // Invoked from the view when it loses focus. void OnViewBlur(); + // Returns whether a key typed now would continue the existing search or start + // a new search. + bool ShouldContinueSelection() const; + // ui::TextInputClient: void SetCompositionText(const ui::CompositionText& composition) override; void ConfirmCompositionText() override; @@ -63,6 +71,10 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { ukm::SourceId GetClientSourceForMetrics() const override; bool ShouldDoLearning() override; + void set_tick_clock_for_testing(const base::TickClock* clock) { + tick_clock_ = clock; + } + private: // Invoked when text is typed. Tries to change the selection appropriately. void OnTextInput(const base::string16& text); @@ -82,6 +94,10 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { base::string16 current_text_; + // TickClock used for getting the time of the current keystroke, used for + // continuing or restarting selections. + const base::TickClock* tick_clock_; + DISALLOW_COPY_AND_ASSIGN(PrefixSelector); }; diff --git a/chromium/ui/views/controls/scroll_view.cc b/chromium/ui/views/controls/scroll_view.cc index e58ef7d3cdc..9f7028a505c 100644 --- a/chromium/ui/views/controls/scroll_view.cc +++ b/chromium/ui/views/controls/scroll_view.cc @@ -7,7 +7,6 @@ #include "base/feature_list.h" #include "base/logging.h" #include "base/macros.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/base/ui_base_features.h" #include "ui/compositor/overscroll/scroll_input_handler.h" #include "ui/events/event.h" @@ -215,13 +214,11 @@ ScrollView::ScrollView() } UpdateBackground(); - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { - focus_ring_ = FocusRing::Install(this); - focus_ring_->SetHasFocusPredicate([](View* view) -> bool { - auto* v = static_cast<ScrollView*>(view); - return v->draw_focus_indicator_; - }); - } + focus_ring_ = FocusRing::Install(this); + focus_ring_->SetHasFocusPredicate([](View* view) -> bool { + auto* v = static_cast<ScrollView*>(view); + return v->draw_focus_indicator_; + }); } ScrollView::~ScrollView() { @@ -335,10 +332,7 @@ void ScrollView::SetHasFocusIndicator(bool has_focus_indicator) { return; draw_focus_indicator_ = has_focus_indicator; - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) focus_ring_->SchedulePaint(); - else - UpdateBorder(); SchedulePaint(); } diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc index 4da3917fefb..4166c97c0bc 100644 --- a/chromium/ui/views/controls/scroll_view_unittest.cc +++ b/chromium/ui/views/controls/scroll_view_unittest.cc @@ -321,7 +321,6 @@ class WidgetScrollViewTest : public test::WidgetTest, void OnCompositingStarted(ui::Compositor* compositor, base::TimeTicks start_time) override {} void OnCompositingEnded(ui::Compositor* compositor) override {} - void OnCompositingLockStateChanged(ui::Compositor* compositor) override {} void OnCompositingChildResizing(ui::Compositor* compositor) override {} void OnCompositingShuttingDown(ui::Compositor* compositor) override {} @@ -1654,8 +1653,9 @@ TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) { EXPECT_TRUE(compositor); // But setting on the impl side should fail since the layer isn't committed. - int layer_id = container->layer()->cc_layer_for_testing()->id(); - EXPECT_FALSE(compositor->ScrollLayerTo(layer_id, gfx::ScrollOffset(0, 0))); + cc::ElementId element_id = + container->layer()->cc_layer_for_testing()->element_id(); + EXPECT_FALSE(compositor->ScrollLayerTo(element_id, gfx::ScrollOffset(0, 0))); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset()); WaitForCommit(); @@ -1663,13 +1663,13 @@ TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) { // Upon commit, the impl side should report the same value too. gfx::ScrollOffset impl_offset; - EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset)); + EXPECT_TRUE(compositor->GetScrollOffsetForLayer(element_id, &impl_offset)); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset); // Now impl-side scrolling should work, and also update the ScrollView. offset.set_y(kDefaultHeight * 3); EXPECT_TRUE( - compositor->ScrollLayerTo(layer_id, gfx::ScrollOffset(0, offset.y()))); + compositor->ScrollLayerTo(element_id, gfx::ScrollOffset(0, offset.y()))); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset()); // Scroll via ScrollView API. Should be reflected on the impl side. @@ -1677,7 +1677,7 @@ TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) { scroll_view->contents()->ScrollRectToVisible(offset); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset()); - EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset)); + EXPECT_TRUE(compositor->GetScrollOffsetForLayer(element_id, &impl_offset)); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset); // Test horizontal scrolling. @@ -1686,7 +1686,7 @@ TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) { EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), test_api.CurrentOffset()); - EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset)); + EXPECT_TRUE(compositor->GetScrollOffsetForLayer(element_id, &impl_offset)); EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), impl_offset); } diff --git a/chromium/ui/views/controls/separator.cc b/chromium/ui/views/controls/separator.cc index 8be91e790d2..d6a43ea8249 100644 --- a/chromium/ui/views/controls/separator.cc +++ b/chromium/ui/views/controls/separator.cc @@ -50,10 +50,15 @@ void Separator::OnPaint(gfx::Canvas* canvas) { : GetNativeTheme()->GetSystemColor( ui::NativeTheme::kColorId_SeparatorColor); - // The separator fills its bounds, but avoid filling partial pixels. float dsf = canvas->UndoDeviceScaleFactor(); - gfx::RectF contents = gfx::ScaleRect(gfx::RectF(GetContentsBounds()), dsf); - canvas->FillRect(gfx::ToEnclosedRect(contents), color); + + // The separator fills its bounds, but avoid filling partial pixels. + gfx::Rect aligned = gfx::ScaleToEnclosedRect(GetContentsBounds(), dsf, dsf); + + // At least 1 pixel should be drawn to make the separator visible. + aligned.set_width(std::max(1, aligned.width())); + aligned.set_height(std::max(1, aligned.height())); + canvas->FillRect(aligned, color); View::OnPaint(canvas); } diff --git a/chromium/ui/views/controls/separator_unittest.cc b/chromium/ui/views/controls/separator_unittest.cc new file mode 100644 index 00000000000..44296c040d2 --- /dev/null +++ b/chromium/ui/views/controls/separator_unittest.cc @@ -0,0 +1,56 @@ +// 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/separator.h" + +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/image_unittest_util.h" +#include "ui/views/test/views_test_base.h" + +namespace views { + +// Base test fixture for Separator tests. +class SeparatorTest : public views::ViewsTestBase { + public: + SeparatorTest() = default; + ~SeparatorTest() override = default; + + protected: + void ExpectDrawAtLeastOnePixel(float image_scale); + + Separator separator_; + + private: + DISALLOW_COPY_AND_ASSIGN(SeparatorTest); +}; + +void SeparatorTest::ExpectDrawAtLeastOnePixel(float image_scale) { + const gfx::Size kTestImageSize = gfx::Size(24, 24); + const SkColor kBackgroundColor = SK_ColorRED; + gfx::Canvas init(kTestImageSize, image_scale, true); + gfx::Canvas canvas(kTestImageSize, image_scale, true); + init.DrawColor(kBackgroundColor); + canvas.DrawColor(kBackgroundColor); + ASSERT_TRUE(gfx::test::AreBitmapsEqual(canvas.GetBitmap(), init.GetBitmap())); + separator_.OnPaint(&canvas); + + // At least 1 pixel should be changed. + EXPECT_FALSE( + gfx::test::AreBitmapsEqual(canvas.GetBitmap(), init.GetBitmap())); +} + +TEST_F(SeparatorTest, ImageScaleBelowOne) { + // Vertical line with 1[dp] thickness by default. + separator_.SetPreferredHeight(8); + ExpectDrawAtLeastOnePixel(0.4); +} + +TEST_F(SeparatorTest, ImageScaleBelowOne_HorizontalLine) { + const int kThickness = 1; + // Use Separator as a horizontal line with 1[dp] thickness. + separator_.SetBounds(4, 5, 8, kThickness); + ExpectDrawAtLeastOnePixel(0.4); +} + +} // namespace views diff --git a/chromium/ui/views/controls/styled_label.cc b/chromium/ui/views/controls/styled_label.cc index 1f2d6fbcd4e..2c12e6e77ef 100644 --- a/chromium/ui/views/controls/styled_label.cc +++ b/chromium/ui/views/controls/styled_label.cc @@ -82,6 +82,16 @@ int HorizontalAdjustment(int used_width, } // namespace +// StyledLabel::TestApi ------------------------------------------------ + +StyledLabel::TestApi::TestApi(StyledLabel* view) : view_(view) {} + +StyledLabel::TestApi::~TestApi() = default; + +const std::map<View*, gfx::Range>& StyledLabel::TestApi::link_targets() { + return view_->link_targets_; +} + // StyledLabel::RangeStyleInfo ------------------------------------------------ StyledLabel::RangeStyleInfo::RangeStyleInfo() = default; diff --git a/chromium/ui/views/controls/styled_label.h b/chromium/ui/views/controls/styled_label.h index ae5bac5511a..43544ef691b 100644 --- a/chromium/ui/views/controls/styled_label.h +++ b/chromium/ui/views/controls/styled_label.h @@ -37,6 +37,20 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { // Internal class name. static const char kViewClassName[]; + // TestApi is used for tests to get internal implementation details. + class VIEWS_EXPORT TestApi { + public: + explicit TestApi(StyledLabel* view); + ~TestApi(); + + const std::map<View*, gfx::Range>& link_targets(); + + private: + StyledLabel* const view_; + + DISALLOW_COPY_AND_ASSIGN(TestApi); + }; + // Parameters that define label style for a styled label's text range. struct VIEWS_EXPORT RangeStyleInfo { RangeStyleInfo(); diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc index a71c62d657a..e79fadddb32 100644 --- a/chromium/ui/views/controls/styled_label_unittest.cc +++ b/chromium/ui/views/controls/styled_label_unittest.cc @@ -9,15 +9,14 @@ #include <memory> #include <string> +#include "base/command_line.h" #include "base/i18n/base_i18n_switches.h" #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "base/test/icu_test_util.h" -#include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkColor.h" -#include "ui/base/ui_base_features.h" #include "ui/base/ui_base_switches.h" #include "ui/gfx/font_list.h" #include "ui/views/border.h" @@ -32,15 +31,6 @@ using base::ASCIIToUTF16; namespace views { -namespace { - -enum class SecondaryUiMode { NON_MD, MD }; - -std::string SecondaryUiModeToString( - const ::testing::TestParamInfo<SecondaryUiMode>& info) { - return info.param == SecondaryUiMode::MD ? "MD" : "NonMD"; -} -} // namespace class StyledLabelTest : public ViewsTestBase, public StyledLabelListener { public: @@ -70,39 +60,6 @@ class StyledLabelTest : public ViewsTestBase, public StyledLabelListener { DISALLOW_COPY_AND_ASSIGN(StyledLabelTest); }; -// StyledLabelTest harness that runs both with and without secondary UI set to -// MD. -class MDStyledLabelTest - : public StyledLabelTest, - public ::testing::WithParamInterface<SecondaryUiMode> { - public: - MDStyledLabelTest() {} - - // StyledLabelTest: - void SetUp() override { - if (GetParam() == SecondaryUiMode::NON_MD) { - // Force Refresh UI to be off, since that mode implies MD secondary UI. - // Must be done before ViewsTestBase::SetUp(). - base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( - switches::kTopChromeMD, switches::kTopChromeMDMaterial); - } - // This works while StyledLabelTest has no SetUp() of its own. Otherwise the - // mode should be set after ViewsTestBase::SetUp(), but before the rest of - // StyledLabelTest::SetUp(), so that StyledLabelTest::SetUp() obeys the MD - // setting. - StyledLabelTest::SetUp(); - if (GetParam() == SecondaryUiMode::MD) - scoped_feature_list_.InitAndEnableFeature(features::kSecondaryUiMd); - else - scoped_feature_list_.InitAndDisableFeature(features::kSecondaryUiMd); - } - - private: - base::test::ScopedFeatureList scoped_feature_list_; - - DISALLOW_COPY_AND_ASSIGN(MDStyledLabelTest); -}; - TEST_F(StyledLabelTest, NoWrapping) { const std::string text("This is a test block of text"); InitStyledLabel(text); @@ -255,7 +212,7 @@ TEST_F(StyledLabelTest, WrapLongWords) { static_cast<Label*>(styled()->child_at(1))->text()); } -TEST_P(MDStyledLabelTest, CreateLinks) { +TEST_F(StyledLabelTest, CreateLinks) { const std::string text("This is a test block of text."); InitStyledLabel(text); @@ -272,14 +229,9 @@ TEST_P(MDStyledLabelTest, CreateLinks) { styled()->AddStyleRange(gfx::Range(12, 13), StyledLabel::RangeStyleInfo::CreateForLink()); - if (GetParam() == SecondaryUiMode::MD) { - // Insets shouldn't change under MD when links are added, since the links - // indicate focus by adding an underline instead. - EXPECT_TRUE(styled()->GetInsets().IsEmpty()); - } else { - // Now there should be a focus border because there are non-empty Links. - EXPECT_FALSE(styled()->GetInsets().IsEmpty()); - } + // Insets shouldn't change when links are added, since the links indicate + // focus by adding an underline instead. + EXPECT_TRUE(styled()->GetInsets().IsEmpty()); // Verify layout creates the right number of children. styled()->SetBounds(0, 0, 1000, 1000); @@ -287,7 +239,7 @@ TEST_P(MDStyledLabelTest, CreateLinks) { EXPECT_EQ(7, styled()->child_count()); } -TEST_P(MDStyledLabelTest, DontBreakLinks) { +TEST_F(StyledLabelTest, DontBreakLinks) { const std::string text("This is a test block of text, "); const std::string link_text("and this should be a link"); InitStyledLabel(text + link_text); @@ -305,16 +257,9 @@ TEST_P(MDStyledLabelTest, DontBreakLinks) { styled()->Layout(); ASSERT_EQ(2, styled()->child_count()); - if (GetParam() == SecondaryUiMode::MD) { - // No additional insets should be added under MD. - EXPECT_EQ(0, styled()->child_at(0)->x()); - } else { - // The label has no focus border while, when non-MD, the link (and thus - // overall styled label) does, so the label should be inset by the width of - // the focus border. - EXPECT_EQ(Link::kFocusBorderPadding, styled()->child_at(0)->x()); - } - // The Link shouldn't be offset (it grows in size under non-MD instead). + // No additional insets should be added. + EXPECT_EQ(0, styled()->child_at(0)->x()); + // The Link shouldn't be offset. EXPECT_EQ(0, styled()->child_at(1)->x()); } @@ -488,7 +433,7 @@ TEST_F(StyledLabelTest, Color) { widget->CloseNow(); } -TEST_P(MDStyledLabelTest, StyledRangeWithTooltip) { +TEST_F(StyledLabelTest, StyledRangeWithTooltip) { const std::string text("This is a test block of text, "); const std::string tooltip_text("this should have a tooltip,"); const std::string normal_text(" this should not have a tooltip, "); @@ -522,17 +467,9 @@ TEST_P(MDStyledLabelTest, StyledRangeWithTooltip) { ASSERT_EQ(5, styled()->child_count()); - if (GetParam() == SecondaryUiMode::MD) { - // In MD, the labels shouldn't be offset to cater for focus rings. - EXPECT_EQ(0, styled()->child_at(0)->x()); - EXPECT_EQ(0, styled()->child_at(2)->x()); - } else { - // The labels have no focus border while the link (and thus overall styled - // label) does, so the labels should be inset by the width of the focus - // border. - EXPECT_EQ(Link::kFocusBorderPadding, styled()->child_at(0)->x()); - EXPECT_EQ(Link::kFocusBorderPadding, styled()->child_at(2)->x()); - } + // The labels shouldn't be offset to cater for focus rings. + EXPECT_EQ(0, styled()->child_at(0)->x()); + EXPECT_EQ(0, styled()->child_at(2)->x()); EXPECT_EQ(styled()->child_at(0)->bounds().right(), styled()->child_at(1)->x()); @@ -776,7 +713,7 @@ TEST_F(StyledLabelTest, AlignmentInRTL) { styled()->child_at(0)->bounds().x()); } -TEST_P(MDStyledLabelTest, ViewsCenteredWithLinkAndCustomView) { +TEST_F(StyledLabelTest, ViewsCenteredWithLinkAndCustomView) { const std::string text("This is a test block of text, "); const std::string link_text("and this should be a link"); const std::string custom_view_text("And this is a custom view"); @@ -810,10 +747,4 @@ TEST_P(MDStyledLabelTest, ViewsCenteredWithLinkAndCustomView) { styled()->child_at(2)->bounds().y()); } -INSTANTIATE_TEST_CASE_P(, - MDStyledLabelTest, - ::testing::Values(SecondaryUiMode::MD, - SecondaryUiMode::NON_MD), - &SecondaryUiModeToString); - } // namespace views diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc index 4cd621b05c5..04e572c25d0 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc @@ -12,7 +12,6 @@ #include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/default_style.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/animation/animation_delegate.h" @@ -667,9 +666,7 @@ TabbedPane::TabbedPane(TabbedPane::Orientation orientation, : listener_(NULL), contents_(new View()) { DCHECK(orientation != TabbedPane::Orientation::kHorizontal || style != TabbedPane::TabStripStyle::kHighlight); - tab_strip_ = ui::MaterialDesignController::IsSecondaryUiMaterial() - ? new MdTabStrip(orientation, style) - : new TabStrip(orientation, style); + tab_strip_ = new MdTabStrip(orientation, style); AddChildView(tab_strip_); AddChildView(contents_); } @@ -695,11 +692,7 @@ void TabbedPane::AddTabAtIndex(int index, DCHECK(index >= 0 && index <= GetTabCount()); contents->SetVisible(false); - tab_strip_->AddChildViewAt( - ui::MaterialDesignController::IsSecondaryUiMaterial() - ? new MdTab(this, title, contents) - : new Tab(this, title, contents), - index); + tab_strip_->AddChildViewAt(new MdTab(this, title, contents), index); contents_->AddChildViewAt(contents, index); if (!GetSelectedTab()) SelectTabAt(index); diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc index c9a96b43788..752c642e2da 100644 --- a/chromium/ui/views/controls/textfield/textfield.cc +++ b/chromium/ui/views/controls/textfield/textfield.cc @@ -21,7 +21,6 @@ #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_edit_commands.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_switches.h" #include "ui/base/ui_base_switches_util.h" @@ -93,9 +92,6 @@ const gfx::SelectionBehavior kMoveParagraphSelectionBehavior = gfx::SELECTION_RETAIN; #endif -// Default placeholder text color. -const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY; - // Get the default command for a given key |event|. ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) { if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode()) @@ -279,7 +275,7 @@ Textfield::Textfield() selection_controller_(this), drag_start_display_offset_(0), touch_handles_hidden_due_to_scroll_(false), - use_focus_ring_(ui::MaterialDesignController::IsSecondaryUiMaterial()), + use_focus_ring_(true), weak_ptr_factory_(this) { set_context_menu_controller(this); set_drag_controller(this); @@ -666,7 +662,9 @@ bool Textfield::OnMousePressed(const ui::MouseEvent& event) { (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton())) { if (!had_focus) RequestFocusWithPointer(ui::EventPointerType::POINTER_TYPE_MOUSE); +#if !defined(OS_WIN) ShowVirtualKeyboardIfEnabled(); +#endif } #if defined(OS_LINUX) && !defined(OS_CHROMEOS) @@ -743,10 +741,16 @@ bool Textfield::OnKeyReleased(const ui::KeyEvent& event) { } void Textfield::OnGestureEvent(ui::GestureEvent* event) { + bool show_virtual_keyboard = true; +#if defined(OS_WIN) + show_virtual_keyboard = event->details().primary_pointer_type() == + ui::EventPointerType::POINTER_TYPE_TOUCH; +#endif switch (event->type()) { case ui::ET_GESTURE_TAP_DOWN: RequestFocusWithPointer(event->details().primary_pointer_type()); - ShowVirtualKeyboardIfEnabled(); + if (show_virtual_keyboard) + ShowVirtualKeyboardIfEnabled(); event->SetHandled(); break; case ui::ET_GESTURE_TAP: @@ -2083,13 +2087,9 @@ void Textfield::UpdateSelectionClipboard() { void Textfield::UpdateBackgroundColor() { const SkColor color = GetBackgroundColor(); - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { SetBackground( CreateBackgroundFromPainter(Painter::CreateSolidRoundRectPainter( color, FocusableBorder::kCornerRadiusDp))); - } else { - SetBackground(CreateSolidBackground(color)); - } // Disable subpixel rendering when the background color is not opaque because // it draws incorrect colors around the glyphs in that case. // See crbug.com/115198 @@ -2165,10 +2165,7 @@ void Textfield::PaintTextAndCursor(gfx::Canvas* canvas) { GetPlaceholderText(), placeholder_font_list_.has_value() ? placeholder_font_list_.value() : GetFontList(), - placeholder_text_color_.value_or( - ui::MaterialDesignController::IsSecondaryUiMaterial() - ? SkColorSetA(GetTextColor(), 0x83) - : kDefaultPlaceholderTextColor), + placeholder_text_color_.value_or(SkColorSetA(GetTextColor(), 0x83)), render_text->display_rect(), placeholder_text_draw_flags); } diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc index 6ccde824c4d..f8a8292c501 100644 --- a/chromium/ui/views/controls/textfield/textfield_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc @@ -39,6 +39,7 @@ #include "ui/events/event.h" #include "ui/events/event_processor.h" #include "ui/events/event_utils.h" +#include "ui/events/keycodes/dom/dom_code.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/events/test/event_generator.h" #include "ui/events/test/keyboard_layout.h" @@ -565,7 +566,7 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController { // keyboard. So they are dispatched directly to the input method. But on // Mac, key events don't pass through InputMethod. Hence they are // dispatched regularly. - ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, flags); + ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, ui::DomCode::NONE, flags); #if defined(OS_MACOSX) event_generator_->Dispatch(&event); #else @@ -574,6 +575,15 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController { } } + // Send a key to trigger MockInputMethod::DispatchKeyEvent(). Note the + // specific VKEY isn't used (MockInputMethod will mock a ui::VKEY_PROCESSKEY + // whenever it has a test composition). However, on Mac, it can't be a letter + // (e.g. VKEY_A) since all native character events on Mac are unicode events + // and don't have a meaningful ui::KeyEvent that would trigger + // DispatchKeyEvent(). It also can't be VKEY_ENTER, since those key events may + // need to be suppressed when interacting with real system IME. + void DispatchMockInputMethodKeyEvent() { SendKeyEvent(ui::VKEY_INSERT); } + // Sends a platform-specific move (and select) to the logical start of line. // Eg. this should move (and select) to the right end of line for RTL text. void SendHomeEvent(bool shift) { @@ -1708,7 +1718,7 @@ TEST_F(TextfieldTest, DragAndDrop_AcceptDrop) { EXPECT_EQ(ui::OSExchangeData::STRING, formats); EXPECT_TRUE(format_types.empty()); EXPECT_TRUE(textfield_->CanDrop(data)); - gfx::Point drop_point(GetCursorPositionX(6), 0); + gfx::PointF drop_point(GetCursorPositionX(6), 0); ui::DropTargetEvent drop(data, drop_point, drop_point, ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE); EXPECT_EQ(ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_MOVE, @@ -1802,7 +1812,7 @@ TEST_F(TextfieldTest, DragAndDrop_ToTheRight) { EXPECT_TRUE(format_types.empty()); // Drop "ello" after "w". - const gfx::Point kDropPoint(GetCursorPositionX(7), cursor_y); + const gfx::PointF kDropPoint(GetCursorPositionX(7), cursor_y); EXPECT_TRUE(textfield_->CanDrop(data)); ui::DropTargetEvent drop_a(data, kDropPoint, kDropPoint, operations); EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a)); @@ -1854,7 +1864,7 @@ TEST_F(TextfieldTest, DragAndDrop_ToTheLeft) { // Drop " worl" after "h". EXPECT_TRUE(textfield_->CanDrop(data)); - gfx::Point drop_point(GetCursorPositionX(1), cursor_y); + gfx::PointF drop_point(GetCursorPositionX(1), cursor_y); ui::DropTargetEvent drop_a(data, drop_point, drop_point, operations); EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop_a)); EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnPerformDrop(drop_a)); @@ -1890,7 +1900,7 @@ TEST_F(TextfieldTest, DragAndDrop_Canceled) { textfield_->WriteDragDataForView(nullptr, point, &data); EXPECT_TRUE(textfield_->CanDrop(data)); // Drag the text over somewhere valid, outside the current selection. - gfx::Point drop_point(GetCursorPositionX(2), cursor_y); + gfx::PointF drop_point(GetCursorPositionX(2), cursor_y); ui::DropTargetEvent drop(data, drop_point, drop_point, ui::DragDropTypes::DRAG_MOVE); EXPECT_EQ(ui::DragDropTypes::DRAG_MOVE, textfield_->OnDragUpdated(drop)); @@ -2000,14 +2010,7 @@ TEST_F(TextfieldTest, TextInputClientTest) { textfield_->clear(); on_before_user_action_ = on_after_user_action_ = 0; - - // Send a key to trigger MockInputMethod::DispatchKeyEvent(). Note the - // specific VKEY isn't used (MockInputMethod will mock a ui::VKEY_PROCESSKEY - // whenever it has a test composition). However, on Mac, it can't be a letter - // (e.g. VKEY_A) since all native character events on Mac are unicode events - // and don't have a meaningful ui::KeyEvent that would trigger - // DispatchKeyEvent(). - SendKeyEvent(ui::VKEY_RETURN); + DispatchMockInputMethodKeyEvent(); EXPECT_TRUE(textfield_->key_received()); EXPECT_FALSE(textfield_->key_handled()); @@ -2021,7 +2024,7 @@ TEST_F(TextfieldTest, TextInputClientTest) { input_method_->SetResultTextForNextKey(UTF8ToUTF16("123")); on_before_user_action_ = on_after_user_action_ = 0; textfield_->clear(); - SendKeyEvent(ui::VKEY_RETURN); + DispatchMockInputMethodKeyEvent(); EXPECT_TRUE(textfield_->key_received()); EXPECT_FALSE(textfield_->key_handled()); EXPECT_FALSE(client->HasCompositionText()); @@ -2033,7 +2036,7 @@ TEST_F(TextfieldTest, TextInputClientTest) { input_method_->Clear(); input_method_->SetCompositionTextForNextKey(composition); textfield_->clear(); - SendKeyEvent(ui::VKEY_RETURN); + DispatchMockInputMethodKeyEvent(); EXPECT_TRUE(client->HasCompositionText()); EXPECT_STR_EQ("0123321456789", textfield_->text()); diff --git a/chromium/ui/views/controls/webview/web_contents_set_background_color.cc b/chromium/ui/views/controls/webview/web_contents_set_background_color.cc index e5de0e42b34..cfdf3e6b6b1 100644 --- a/chromium/ui/views/controls/webview/web_contents_set_background_color.cc +++ b/chromium/ui/views/controls/webview/web_contents_set_background_color.cc @@ -9,8 +9,6 @@ #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" -DEFINE_WEB_CONTENTS_USER_DATA_KEY(views::WebContentsSetBackgroundColor); - namespace views { // static diff --git a/chromium/ui/views/corewm/tooltip_aura.cc b/chromium/ui/views/corewm/tooltip_aura.cc index 80237a0abcf..c36fdd45c43 100644 --- a/chromium/ui/views/corewm/tooltip_aura.cc +++ b/chromium/ui/views/corewm/tooltip_aura.cc @@ -42,7 +42,8 @@ bool CanUseTranslucentTooltipWidget() { } // Creates a widget of type TYPE_TOOLTIP -views::Widget* CreateTooltipWidget(aura::Window* tooltip_window) { +views::Widget* CreateTooltipWidget(aura::Window* tooltip_window, + const gfx::Rect& bounds) { views::Widget* widget = new views::Widget; views::Widget::InitParams params; // For aura, since we set the type to TYPE_TOOLTIP, the widget will get @@ -52,6 +53,7 @@ views::Widget* CreateTooltipWidget(aura::Window* tooltip_window) { DCHECK(params.context); params.keep_on_top = true; params.accept_events = false; + params.bounds = bounds; if (CanUseTranslucentTooltipWidget()) params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE; @@ -166,8 +168,8 @@ gfx::RenderText* TooltipAura::GetRenderTextForTest() { return tooltip_view_->render_text_for_test(); } -void TooltipAura::SetTooltipBounds(const gfx::Point& mouse_pos, - const gfx::Size& tooltip_size) { +gfx::Rect TooltipAura::GetTooltipBounds(const gfx::Point& mouse_pos, + const gfx::Size& tooltip_size) { gfx::Rect tooltip_rect(mouse_pos, tooltip_size); tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY); display::Screen* screen = display::Screen::GetScreen(); @@ -186,7 +188,7 @@ void TooltipAura::SetTooltipBounds(const gfx::Point& mouse_pos, tooltip_rect.set_y(mouse_pos.y() - tooltip_size.height()); tooltip_rect.AdjustToFit(display_bounds); - widget_->SetBounds(tooltip_rect); + return tooltip_rect; } void TooltipAura::DestroyWidget() { @@ -210,14 +212,17 @@ void TooltipAura::SetText(aura::Window* window, tooltip_view_->SetMaxWidth(GetMaxWidth(location)); tooltip_view_->SetText(tooltip_text); + const gfx::Rect adjusted_bounds = + GetTooltipBounds(location, tooltip_view_->GetPreferredSize()); + if (!widget_) { - widget_ = CreateTooltipWidget(tooltip_window_); + widget_ = CreateTooltipWidget(tooltip_window_, adjusted_bounds); widget_->SetContentsView(tooltip_view_.get()); widget_->AddObserver(this); + } else { + widget_->SetBounds(adjusted_bounds); } - SetTooltipBounds(location, tooltip_view_->GetPreferredSize()); - ui::NativeTheme* native_theme = widget_->GetNativeTheme(); tooltip_view_->SetBackgroundColor(native_theme->GetSystemColor( ui::NativeTheme::kColorId_TooltipBackground)); diff --git a/chromium/ui/views/corewm/tooltip_aura.h b/chromium/ui/views/corewm/tooltip_aura.h index 4540c3638ab..02b81ccd257 100644 --- a/chromium/ui/views/corewm/tooltip_aura.h +++ b/chromium/ui/views/corewm/tooltip_aura.h @@ -38,9 +38,9 @@ class VIEWS_EXPORT TooltipAura : public Tooltip, public WidgetObserver { gfx::RenderText* GetRenderTextForTest(); // Adjusts the bounds given by the arguments to fit inside the desktop - // and applies the adjusted bounds to the label_. - void SetTooltipBounds(const gfx::Point& mouse_pos, - const gfx::Size& tooltip_size); + // and returns the adjusted bounds. + gfx::Rect GetTooltipBounds(const gfx::Point& mouse_pos, + const gfx::Size& tooltip_size); // Destroys |widget_|. void DestroyWidget(); diff --git a/chromium/ui/views/corewm/tooltip_controller_unittest.cc b/chromium/ui/views/corewm/tooltip_controller_unittest.cc index 357e7a3809a..1212a6f38b1 100644 --- a/chromium/ui/views/corewm/tooltip_controller_unittest.cc +++ b/chromium/ui/views/corewm/tooltip_controller_unittest.cc @@ -85,20 +85,18 @@ class TooltipControllerTest : public ViewsTestBase { void SetUp() override { ViewsTestBase::SetUp(); - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - aura::Window* root_window = GetContext(); - new wm::DefaultActivationClient(root_window); + if (root_window) + new wm::DefaultActivationClient(root_window); #if defined(OS_CHROMEOS) - tooltip_aura_ = new views::corewm::TooltipAura(); - controller_.reset(new TooltipController( - std::unique_ptr<views::corewm::Tooltip>(tooltip_aura_))); - root_window->AddPreTargetHandler(controller_.get()); - SetTooltipClient(root_window, controller_.get()); + if (root_window) { + tooltip_aura_ = new views::corewm::TooltipAura(); + controller_.reset(new TooltipController( + std::unique_ptr<views::corewm::Tooltip>(tooltip_aura_))); + root_window->AddPreTargetHandler(controller_.get()); + SetTooltipClient(root_window, controller_.get()); + } #endif widget_.reset(CreateWidget(root_window)); widget_->SetContentsView(new View); @@ -111,17 +109,17 @@ class TooltipControllerTest : public ViewsTestBase { } void TearDown() override { - if (!IsMus()) { #if defined(OS_CHROMEOS) - aura::Window* root_window = GetContext(); + aura::Window* root_window = GetContext(); + if (root_window) { root_window->RemovePreTargetHandler(controller_.get()); wm::SetTooltipClient(root_window, NULL); controller_.reset(); -#endif - generator_.reset(); - helper_.reset(); - widget_.reset(); } +#endif + generator_.reset(); + helper_.reset(); + widget_.reset(); ViewsTestBase::TearDown(); } @@ -178,11 +176,6 @@ class TooltipControllerTest : public ViewsTestBase { }; TEST_F(TooltipControllerTest, ViewTooltip) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -206,11 +199,6 @@ TEST_F(TooltipControllerTest, ViewTooltip) { } TEST_F(TooltipControllerTest, HideEmptyTooltip) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -225,11 +213,6 @@ TEST_F(TooltipControllerTest, HideEmptyTooltip) { } TEST_F(TooltipControllerTest, DontShowTooltipOnTouch) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(nullptr, helper_->GetTooltipWindow()); @@ -253,8 +236,8 @@ TEST_F(TooltipControllerTest, DontShowTooltipOnTouch) { #if defined(OS_CHROMEOS) // crbug.com/664370. TEST_F(TooltipControllerTest, MaxWidth) { - // TODO: these tests use GetContext(). That should go away for mus client. - // http://crbug.com/663781. + // This test relies on TooltipAura being created, which does not happen in + // this test with mus (it happens in DesktopNativeWidgetAura). if (IsMus()) return; @@ -276,11 +259,6 @@ TEST_F(TooltipControllerTest, MaxWidth) { #endif TEST_F(TooltipControllerTest, TooltipsInMultipleViews) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -314,11 +292,6 @@ TEST_F(TooltipControllerTest, TooltipsInMultipleViews) { } TEST_F(TooltipControllerTest, EnableOrDisableTooltips) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -341,11 +314,6 @@ TEST_F(TooltipControllerTest, EnableOrDisableTooltips) { // Verifies tooltip isn't shown if tooltip text consists entirely of whitespace. TEST_F(TooltipControllerTest, DontShowEmptyTooltips) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16(" ")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -355,11 +323,6 @@ TEST_F(TooltipControllerTest, DontShowEmptyTooltips) { } TEST_F(TooltipControllerTest, TooltipHidesOnKeyPressAndStaysHiddenUntilChange) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -404,11 +367,6 @@ TEST_F(TooltipControllerTest, TooltipHidesOnKeyPressAndStaysHiddenUntilChange) { } TEST_F(TooltipControllerTest, TooltipHidesOnTimeoutAndStaysHiddenUntilChange) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1")); EXPECT_EQ(base::string16(), helper_->GetTooltipText()); EXPECT_EQ(NULL, helper_->GetTooltipWindow()); @@ -454,11 +412,6 @@ TEST_F(TooltipControllerTest, TooltipHidesOnTimeoutAndStaysHiddenUntilChange) { // Verifies a mouse exit event hides the tooltips. TEST_F(TooltipControllerTest, HideOnExit) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); generator_->MoveMouseToCenterOf(GetWindow()); base::string16 expected_tooltip = ASCIIToUTF16("Tooltip Text"); @@ -472,11 +425,6 @@ TEST_F(TooltipControllerTest, HideOnExit) { } TEST_F(TooltipControllerTest, ReshowOnClickAfterEnterExit) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - // Owned by |view_|. TooltipTestView* v1 = new TooltipTestView; TooltipTestView* v2 = new TooltipTestView; @@ -532,11 +480,6 @@ class TooltipControllerCaptureTest : public TooltipControllerTest { void SetUp() override { TooltipControllerTest::SetUp(); - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - aura::client::SetScreenPositionClient(GetRootWindow(), &screen_position_client_); } @@ -557,11 +500,6 @@ class TooltipControllerCaptureTest : public TooltipControllerTest { // Verifies when capture is released the TooltipController resets state. // Flaky on all builders. http://crbug.com/388268 TEST_F(TooltipControllerCaptureTest, DISABLED_CloseOnCaptureLost) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. - if (IsMus()) - return; - view_->GetWidget()->SetCapture(view_); RunPendingMessages(); view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); @@ -587,9 +525,8 @@ TEST_F(TooltipControllerCaptureTest, DISABLED_CloseOnCaptureLost) { #endif // Verifies the correct window is found for tooltips when there is a capture. TEST_F(TooltipControllerCaptureTest, MAYBE_Capture) { - // Currently, capture in one test affects capture in other tests. - // TODO: these tests use GetContext(). That should go away for mus client. - // http://crbug.com/663781. + // This test doesn't make sense with mus as it creates two widgets and + // expects to move the mouse between them. if (IsMus()) return; @@ -708,6 +645,11 @@ class TooltipControllerTest2 : public aura::test::AuraTestBase { }; TEST_F(TooltipControllerTest2, VerifyLeadingTrailingWhitespaceStripped) { + // This test does not have a real connection to mus (because it's using + // AuraTestBase, not ViewsTestBase), so it can't use EventGenerator. + if (ViewsTestBase::IsMus()) + return; + aura::test::TestWindowDelegate test_delegate; std::unique_ptr<aura::Window> window( CreateNormalWindow(100, root_window(), &test_delegate)); @@ -721,6 +663,11 @@ TEST_F(TooltipControllerTest2, VerifyLeadingTrailingWhitespaceStripped) { // Verifies that tooltip is hidden and tooltip window closed upon cancel mode. TEST_F(TooltipControllerTest2, CloseOnCancelMode) { + // This test does not have a real connection to mus (because it's using + // AuraTestBase, not ViewsTestBase), so it can't use EventGenerator. + if (ViewsTestBase::IsMus()) + return; + aura::test::TestWindowDelegate test_delegate; std::unique_ptr<aura::Window> window( CreateNormalWindow(100, root_window(), &test_delegate)); @@ -743,14 +690,14 @@ TEST_F(TooltipControllerTest2, CloseOnCancelMode) { // Use for tests that need both views and a TestTooltip. class TooltipControllerTest3 : public ViewsTestBase { public: - TooltipControllerTest3() : test_tooltip_(new TestTooltip) {} - ~TooltipControllerTest3() override {} + TooltipControllerTest3() = default; + ~TooltipControllerTest3() override = default; void SetUp() override { ViewsTestBase::SetUp(); - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. + // This test assumes a hierarchy like that of Ash, which doesn't make sense + // with mus. if (IsMus()) return; @@ -764,8 +711,9 @@ class TooltipControllerTest3 : public ViewsTestBase { view_->SetBoundsRect(widget_->GetContentsView()->GetLocalBounds()); generator_.reset(new ui::test::EventGenerator(GetRootWindow())); - controller_.reset(new TooltipController( - std::unique_ptr<views::corewm::Tooltip>(test_tooltip_))); + auto tooltip = std::make_unique<TestTooltip>(); + test_tooltip_ = tooltip.get(); + controller_ = std::make_unique<TooltipController>(std::move(tooltip)); GetRootWindow()->RemovePreTargetHandler(static_cast<TooltipController*>( wm::GetTooltipClient(widget_->GetNativeWindow()->GetRootWindow()))); GetRootWindow()->AddPreTargetHandler(controller_.get()); @@ -790,7 +738,7 @@ class TooltipControllerTest3 : public ViewsTestBase { protected: // Owned by |controller_|. - TestTooltip* test_tooltip_; + TestTooltip* test_tooltip_ = nullptr; std::unique_ptr<TooltipControllerTestHelper> helper_; std::unique_ptr<ui::test::EventGenerator> generator_; std::unique_ptr<views::Widget> widget_; @@ -809,8 +757,8 @@ class TooltipControllerTest3 : public ViewsTestBase { }; TEST_F(TooltipControllerTest3, TooltipPositionChangesOnTwoViewsWithSameLabel) { - // TODO: these tests use GetContext(). That should go away for aura-mus - // client. http://crbug.com/663781. + // See comment in TooltipControllerTest3::SetUp() for why this does nothing in + // mus. if (IsMus()) return; diff --git a/chromium/ui/views/event_monitor.h b/chromium/ui/views/event_monitor.h index 2867ecb14bc..d866e432a83 100644 --- a/chromium/ui/views/event_monitor.h +++ b/chromium/ui/views/event_monitor.h @@ -25,9 +25,11 @@ class VIEWS_EXPORT EventMonitor { // Create an instance for monitoring application events. // Events will be forwarded to |event_handler| before they are dispatched to - // the application. + // the application. |context| is used to determine where to observer events + // from. |context| may be destroyed before the EventMonitor. static std::unique_ptr<EventMonitor> CreateApplicationMonitor( - ui::EventHandler* event_handler); + ui::EventHandler* event_handler, + gfx::NativeWindow context); // Create an instance for monitoring events on a specific window. // Events will be forwarded to |event_handler| before they are dispatched to @@ -39,7 +41,7 @@ class VIEWS_EXPORT EventMonitor { // Returns the last mouse location seen in a mouse event in screen // coordinates. - static gfx::Point GetLastMouseLocation(); + virtual gfx::Point GetLastMouseLocation() = 0; }; } // namespace views diff --git a/chromium/ui/views/event_monitor_aura.cc b/chromium/ui/views/event_monitor_aura.cc index 6c52620b157..5afde17f3dc 100644 --- a/chromium/ui/views/event_monitor_aura.cc +++ b/chromium/ui/views/event_monitor_aura.cc @@ -14,26 +14,25 @@ namespace views { // static std::unique_ptr<EventMonitor> EventMonitor::CreateApplicationMonitor( - ui::EventHandler* event_handler) { - return base::WrapUnique( - new EventMonitorAura(event_handler, aura::Env::GetInstance())); + ui::EventHandler* event_handler, + gfx::NativeWindow context) { + aura::Env* env = context->env(); + return std::make_unique<EventMonitorAura>(env, event_handler, env); } // static std::unique_ptr<EventMonitor> EventMonitor::CreateWindowMonitor( ui::EventHandler* event_handler, gfx::NativeWindow target_window) { - return base::WrapUnique(new EventMonitorAura(event_handler, target_window)); -} - -// static -gfx::Point EventMonitor::GetLastMouseLocation() { - return aura::Env::GetInstance()->last_mouse_location(); + return std::make_unique<EventMonitorAura>(target_window->env(), event_handler, + target_window); } -EventMonitorAura::EventMonitorAura(ui::EventHandler* event_handler, +EventMonitorAura::EventMonitorAura(aura::Env* env, + ui::EventHandler* event_handler, ui::EventTarget* event_target) - : event_handler_(event_handler), event_target_(event_target) { + : env_(env), event_handler_(event_handler), event_target_(event_target) { + DCHECK(env_); DCHECK(event_handler_); DCHECK(event_target_); event_target_->AddPreTargetHandler(event_handler_); @@ -43,4 +42,8 @@ EventMonitorAura::~EventMonitorAura() { event_target_->RemovePreTargetHandler(event_handler_); } +gfx::Point EventMonitorAura::GetLastMouseLocation() { + return env_->last_mouse_location(); +} + } // namespace views diff --git a/chromium/ui/views/event_monitor_aura.h b/chromium/ui/views/event_monitor_aura.h index 64f1c48c890..68c633b655a 100644 --- a/chromium/ui/views/event_monitor_aura.h +++ b/chromium/ui/views/event_monitor_aura.h @@ -8,6 +8,10 @@ #include "base/macros.h" #include "ui/views/event_monitor.h" +namespace aura { +class Env; +} + namespace ui { class EventTarget; } @@ -16,11 +20,16 @@ namespace views { class EventMonitorAura : public EventMonitor { public: - EventMonitorAura(ui::EventHandler* event_handler, + EventMonitorAura(aura::Env* env, + ui::EventHandler* event_handler, ui::EventTarget* event_target); ~EventMonitorAura() override; + // EventMonitor: + gfx::Point GetLastMouseLocation() override; + private: + aura::Env* env_; // Weak. ui::EventHandler* event_handler_; // Weak. Owned by our owner. ui::EventTarget* event_target_; // Weak. diff --git a/chromium/ui/views/event_monitor_mac.h b/chromium/ui/views/event_monitor_mac.h index 3d8fa11b9e5..5cc56a5fef1 100644 --- a/chromium/ui/views/event_monitor_mac.h +++ b/chromium/ui/views/event_monitor_mac.h @@ -18,6 +18,9 @@ class EventMonitorMac : public EventMonitor { gfx::NativeWindow target_window); ~EventMonitorMac() override; + // EventMonitor: + gfx::Point GetLastMouseLocation() override; + private: id monitor_; ui::WeakPtrNSObjectFactory<EventMonitorMac> factory_; diff --git a/chromium/ui/views/event_monitor_mac.mm b/chromium/ui/views/event_monitor_mac.mm index 85113473e98..46562a63972 100644 --- a/chromium/ui/views/event_monitor_mac.mm +++ b/chromium/ui/views/event_monitor_mac.mm @@ -17,7 +17,9 @@ namespace views { // static std::unique_ptr<EventMonitor> EventMonitor::CreateApplicationMonitor( - ui::EventHandler* event_handler) { + ui::EventHandler* event_handler, + gfx::NativeWindow context) { + // |context| is not needed on Mac. return base::WrapUnique(new EventMonitorMac(event_handler, nullptr)); } @@ -28,11 +30,6 @@ std::unique_ptr<EventMonitor> EventMonitor::CreateWindowMonitor( return base::WrapUnique(new EventMonitorMac(event_handler, target_window)); } -// static -gfx::Point EventMonitor::GetLastMouseLocation() { - return display::Screen::GetScreen()->GetCursorScreenPoint(); -} - EventMonitorMac::EventMonitorMac(ui::EventHandler* event_handler, gfx::NativeWindow target_window) : factory_(this) { @@ -48,8 +45,13 @@ EventMonitorMac::EventMonitorMac(ui::EventHandler* event_handler, if (!target_window || [event window] == target_window) { std::unique_ptr<ui::Event> ui_event = ui::EventFromNative(event); - if (ui_event) + if (ui_event) { event_handler->OnEvent(ui_event.get()); + // If an event is handled, swallow it by returning nil so the event + // never proceeds to the normal event handling machinery. + if (ui_event->handled()) + return nil; + } } return event; }; @@ -62,4 +64,8 @@ EventMonitorMac::~EventMonitorMac() { [NSEvent removeMonitor:monitor_]; } +gfx::Point EventMonitorMac::GetLastMouseLocation() { + return display::Screen::GetScreen()->GetCursorScreenPoint(); +} + } // namespace views diff --git a/chromium/ui/views/event_monitor_unittest.cc b/chromium/ui/views/event_monitor_unittest.cc index fe1f5e0e544..178ef70b56b 100644 --- a/chromium/ui/views/event_monitor_unittest.cc +++ b/chromium/ui/views/event_monitor_unittest.cc @@ -46,8 +46,8 @@ class EventMonitorTest : public WidgetTest { }; TEST_F(EventMonitorTest, ShouldReceiveAppEventsWhileInstalled) { - std::unique_ptr<EventMonitor> monitor( - EventMonitor::CreateApplicationMonitor(&handler_)); + std::unique_ptr<EventMonitor> monitor(EventMonitor::CreateApplicationMonitor( + &handler_, widget_->GetNativeWindow())); generator_->ClickLeftButton(); EXPECT_EQ(2, handler_.num_mouse_events()); @@ -84,8 +84,8 @@ TEST_F(EventMonitorTest, ShouldNotReceiveEventsFromOtherWindow) { namespace { class DeleteOtherOnEventHandler : public ui::EventHandler { public: - DeleteOtherOnEventHandler() { - monitor_ = EventMonitor::CreateApplicationMonitor(this); + explicit DeleteOtherOnEventHandler(gfx::NativeWindow context) { + monitor_ = EventMonitor::CreateApplicationMonitor(this, context); } bool DidDelete() const { return !handler_to_delete_; } @@ -111,16 +111,20 @@ class DeleteOtherOnEventHandler : public ui::EventHandler { // Ensure correct behavior when an event monitor is removed while iterating // over the OS-controlled observer list. TEST_F(EventMonitorTest, TwoMonitors) { - auto deleter = std::make_unique<DeleteOtherOnEventHandler>(); - deleter->set_monitor_to_delete(std::make_unique<DeleteOtherOnEventHandler>()); + auto deleter = + std::make_unique<DeleteOtherOnEventHandler>(widget_->GetNativeWindow()); + deleter->set_monitor_to_delete( + std::make_unique<DeleteOtherOnEventHandler>(widget_->GetNativeWindow())); EXPECT_FALSE(deleter->DidDelete()); generator_->PressLeftButton(); EXPECT_TRUE(deleter->DidDelete()); // Now try setting up observers in the alternate order. - auto deletee = std::make_unique<DeleteOtherOnEventHandler>(); - deleter = std::make_unique<DeleteOtherOnEventHandler>(); + auto deletee = + std::make_unique<DeleteOtherOnEventHandler>(widget_->GetNativeWindow()); + deleter = + std::make_unique<DeleteOtherOnEventHandler>(widget_->GetNativeWindow()); deleter->set_monitor_to_delete(std::move(deletee)); EXPECT_FALSE(deleter->DidDelete()); diff --git a/chromium/ui/views/examples/bubble_example.cc b/chromium/ui/views/examples/bubble_example.cc index 026a1032636..a93f6c402a5 100644 --- a/chromium/ui/views/examples/bubble_example.cc +++ b/chromium/ui/views/examples/bubble_example.cc @@ -7,7 +7,7 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "ui/gfx/geometry/insets.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/label.h" #include "ui/views/layout/box_layout.h" @@ -89,8 +89,6 @@ void BubbleExample::CreateExampleView(View* container) { container->AddChildView(small_shadow_); no_assets_ = new LabelButton(this, ASCIIToUTF16("No Assets")); container->AddChildView(no_assets_); - align_to_edge_ = new LabelButton(this, ASCIIToUTF16("Align To Edge")); - container->AddChildView(align_to_edge_); persistent_ = new LabelButton(this, ASCIIToUTF16("Persistent")); container->AddChildView(persistent_); } @@ -123,8 +121,6 @@ void BubbleExample::ButtonPressed(Button* sender, const ui::Event& event) { bubble->set_close_on_deactivate(false); BubbleDialogDelegateView::CreateBubble(bubble); - if (sender == align_to_edge_) - bubble->SetAlignment(BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); bubble->GetWidget()->Show(); } diff --git a/chromium/ui/views/examples/bubble_example.h b/chromium/ui/views/examples/bubble_example.h index 91e514db089..6d5717f07aa 100644 --- a/chromium/ui/views/examples/bubble_example.h +++ b/chromium/ui/views/examples/bubble_example.h @@ -31,7 +31,6 @@ class VIEWS_EXAMPLES_EXPORT BubbleExample : public ExampleBase, Button* big_shadow_; Button* small_shadow_; Button* no_assets_; - Button* align_to_edge_; Button* persistent_; DISALLOW_COPY_AND_ASSIGN(BubbleExample); diff --git a/chromium/ui/views/examples/button_sticker_sheet.cc b/chromium/ui/views/examples/button_sticker_sheet.cc index f1d0099ec9e..1de3940d1af 100644 --- a/chromium/ui/views/examples/button_sticker_sheet.cc +++ b/chromium/ui/views/examples/button_sticker_sheet.cc @@ -5,7 +5,6 @@ #include "ui/views/examples/button_sticker_sheet.h" #include "base/strings/utf_string_conversions.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/button/md_text_button.h" #include "ui/views/layout/grid_layout.h" @@ -91,13 +90,6 @@ ButtonStickerSheet::~ButtonStickerSheet() {} void ButtonStickerSheet::CreateExampleView(View* container) { GridLayout* layout = MakeStretchyGridLayout(container, 3); - if (!ui::MaterialDesignController::IsSecondaryUiMaterial()) { - const char* kNeedsMdWarning = - "This will look wrong without --secondary-ui-md."; - layout->StartRow(0, 0); - layout->AddView(MakePlainLabel(kNeedsMdWarning), 3, 1); - } - // The title row has an empty row label. AddLabelledRowToGridLayout( layout, std::string(), diff --git a/chromium/ui/views/examples/dialog_example.cc b/chromium/ui/views/examples/dialog_example.cc index e10f2fadd5b..7b5ce6b74a5 100644 --- a/chromium/ui/views/examples/dialog_example.cc +++ b/chromium/ui/views/examples/dialog_example.cc @@ -6,7 +6,7 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/checkbox.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/button/md_text_button.h" diff --git a/chromium/ui/views/features.gni b/chromium/ui/views/features.gni index e5222f85d37..16b8660ad2e 100644 --- a/chromium/ui/views/features.gni +++ b/chromium/ui/views/features.gni @@ -2,12 +2,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/linux/gtk/gtk.gni") import("//build/config/ui.gni") declare_args() { # Whether we should draw the minimize, maximize/restore, and close - # buttons using the system theme. Only used on Linux with GTK3. - enable_native_window_nav_buttons = - use_aura && !use_ozone && is_desktop_linux && use_gtk3 + # buttons using the system theme. Only used on Linux. + enable_native_window_nav_buttons = use_aura && !use_ozone && is_desktop_linux } diff --git a/chromium/ui/views/focus/focus_manager.h b/chromium/ui/views/focus/focus_manager.h index c5fb037312d..d36ac497473 100644 --- a/chromium/ui/views/focus/focus_manager.h +++ b/chromium/ui/views/focus/focus_manager.h @@ -359,7 +359,8 @@ class VIEWS_EXPORT FocusManager : public ViewObserver { FocusChangeReason focus_change_reason_ = kReasonDirectFocusChange; // The list of registered FocusChange listeners. - base::ObserverList<FocusChangeListener, true> focus_change_listeners_; + base::ObserverList<FocusChangeListener, true>::Unchecked + focus_change_listeners_; // This is true if full keyboard accessibility is needed. This causes // IsAccessibilityFocusable() to be checked rather than IsFocusable(). This diff --git a/chromium/ui/views/focus/focus_manager_unittest.cc b/chromium/ui/views/focus/focus_manager_unittest.cc index 5e7c7a6778e..ed29e91704b 100644 --- a/chromium/ui/views/focus/focus_manager_unittest.cc +++ b/chromium/ui/views/focus/focus_manager_unittest.cc @@ -17,7 +17,7 @@ #include "ui/base/accelerators/test_accelerator_target.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/views/accessible_pane_view.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/focus/focus_manager_delegate.h" #include "ui/views/focus/focus_manager_factory.h" #include "ui/views/focus/widget_focus_manager.h" diff --git a/chromium/ui/views/focus/focus_search.cc b/chromium/ui/views/focus/focus_search.cc index 4c33f0b266e..c0e33176e20 100644 --- a/chromium/ui/views/focus/focus_search.cc +++ b/chromium/ui/views/focus/focus_search.cc @@ -5,7 +5,7 @@ #include "ui/views/focus/focus_search.h" #include "base/logging.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/view.h" #include "ui/views/view_properties.h" diff --git a/chromium/ui/views/focus/widget_focus_manager.h b/chromium/ui/views/focus/widget_focus_manager.h index deaaab447ce..068a4825b46 100644 --- a/chromium/ui/views/focus/widget_focus_manager.h +++ b/chromium/ui/views/focus/widget_focus_manager.h @@ -55,7 +55,8 @@ class VIEWS_EXPORT WidgetFocusManager { WidgetFocusManager(); ~WidgetFocusManager(); - base::ObserverList<WidgetFocusChangeListener> focus_change_listeners_; + base::ObserverList<WidgetFocusChangeListener>::Unchecked + focus_change_listeners_; bool enabled_; diff --git a/chromium/ui/views/layout/box_layout.cc b/chromium/ui/views/layout/box_layout.cc index d0d5624bf24..f9c1b6dc485 100644 --- a/chromium/ui/views/layout/box_layout.cc +++ b/chromium/ui/views/layout/box_layout.cc @@ -165,19 +165,19 @@ void BoxLayout::Layout(View* host) { total_main_axis_size -= between_child_spacing_; // Free space can be negative indicating that the views want to overflow. int main_free_space = MainAxisSize(child_area) - total_main_axis_size; + int main_position = MainAxisPosition(child_area); { - int position = MainAxisPosition(child_area); int size = MainAxisSize(child_area); if (!flex_sum) { switch (main_axis_alignment_) { case MAIN_AXIS_ALIGNMENT_START: break; case MAIN_AXIS_ALIGNMENT_CENTER: - position += main_free_space / 2; + main_position += main_free_space / 2; size = total_main_axis_size; break; case MAIN_AXIS_ALIGNMENT_END: - position += main_free_space; + main_position += main_free_space; size = total_main_axis_size; break; default: @@ -186,12 +186,11 @@ void BoxLayout::Layout(View* host) { } } gfx::Rect new_child_area(child_area); - SetMainAxisPosition(position, &new_child_area); + SetMainAxisPosition(main_position, &new_child_area); SetMainAxisSize(size, &new_child_area); child_area.Intersect(new_child_area); } - int main_position = MainAxisPosition(child_area); int total_padding = 0; int current_flex = 0; for (int i = 0; i < host->child_count(); ++i) { diff --git a/chromium/ui/views/layout/box_layout.h b/chromium/ui/views/layout/box_layout.h index 315ac561859..653d810eb9e 100644 --- a/chromium/ui/views/layout/box_layout.h +++ b/chromium/ui/views/layout/box_layout.h @@ -33,9 +33,10 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager { kVertical, }; - // This specifies where along the main axis the children should be laid out. - // e.g. a horizontal layout of MAIN_AXIS_ALIGNMENT_END will result in the - // child views being right-aligned. + // This specifies that the start/center/end of the collective child views is + // aligned with the start/center/end of the host view. e.g. a horizontal + // layout of MAIN_AXIS_ALIGNMENT_END will result in the child views being + // right-aligned. enum MainAxisAlignment { MAIN_AXIS_ALIGNMENT_START, MAIN_AXIS_ALIGNMENT_CENTER, diff --git a/chromium/ui/views/layout/box_layout_unittest.cc b/chromium/ui/views/layout/box_layout_unittest.cc index 676109da968..b500e89c2ff 100644 --- a/chromium/ui/views/layout/box_layout_unittest.cc +++ b/chromium/ui/views/layout/box_layout_unittest.cc @@ -102,29 +102,30 @@ TEST_F(BoxLayoutTest, Overflow) { host_->AddChildView(v1); View* v2 = new StaticSizedView(gfx::Size(10, 20)); host_->AddChildView(v2); - host_->SetBounds(0, 0, 10, 10); + host_->SetBounds(0, 0, 15, 10); // Overflows by positioning views at the start and truncating anything that // doesn't fit. host_->Layout(); - EXPECT_EQ(gfx::Rect(0, 0, 10, 10), v1->bounds()); + EXPECT_EQ(gfx::Rect(0, 0, 15, 10), v1->bounds()); EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds()); - // All values of main axis alignment should overflow in the same manner. + // Clipping of children should occur at the opposite end(s) to the main axis + // alignment position. layout->set_main_axis_alignment(BoxLayout::MAIN_AXIS_ALIGNMENT_START); host_->Layout(); - EXPECT_EQ(gfx::Rect(0, 0, 10, 10), v1->bounds()); + EXPECT_EQ(gfx::Rect(0, 0, 15, 10), v1->bounds()); EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds()); layout->set_main_axis_alignment(BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); host_->Layout(); - EXPECT_EQ(gfx::Rect(0, 0, 10, 10), v1->bounds()); - EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds()); + EXPECT_EQ(gfx::Rect(0, 0, 13, 10), v1->bounds()); + EXPECT_EQ(gfx::Rect(13, 0, 2, 10), v2->bounds()); layout->set_main_axis_alignment(BoxLayout::MAIN_AXIS_ALIGNMENT_END); host_->Layout(); - EXPECT_EQ(gfx::Rect(0, 0, 10, 10), v1->bounds()); - EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds()); + EXPECT_EQ(gfx::Rect(0, 0, 5, 10), v1->bounds()); + EXPECT_EQ(gfx::Rect(5, 0, 10, 10), v2->bounds()); } TEST_F(BoxLayoutTest, NoSpace) { diff --git a/chromium/ui/views/layout/layout_provider.cc b/chromium/ui/views/layout/layout_provider.cc index caba8a5228e..310d695c175 100644 --- a/chromium/ui/views/layout/layout_provider.cc +++ b/chromium/ui/views/layout/layout_provider.cc @@ -166,8 +166,9 @@ int LayoutProvider::GetShadowElevationMetric( return 3; } -gfx::ShadowValues LayoutProvider::MakeShadowValues(int elevation) const { - return gfx::ShadowValue::MakeMdShadowValues(elevation); +gfx::ShadowValues LayoutProvider::MakeShadowValues(int elevation, + SkColor color) const { + return gfx::ShadowValue::MakeMdShadowValues(elevation, color); } } // namespace views diff --git a/chromium/ui/views/layout/layout_provider.h b/chromium/ui/views/layout/layout_provider.h index cf92ab718c9..c7f194643de 100644 --- a/chromium/ui/views/layout/layout_provider.h +++ b/chromium/ui/views/layout/layout_provider.h @@ -175,7 +175,8 @@ class VIEWS_EXPORT LayoutProvider { // Creates shadows for the given elevation. Use GetShadowElevationMetric for // the appropriate elevation. - virtual gfx::ShadowValues MakeShadowValues(int elevation) const; + virtual gfx::ShadowValues MakeShadowValues(int elevation, + SkColor color) const; private: DefaultTypographyProvider typography_provider_; diff --git a/chromium/ui/views/linux_ui/linux_ui.h b/chromium/ui/views/linux_ui/linux_ui.h index c1f14d5c65a..86a3c0a78cb 100644 --- a/chromium/ui/views/linux_ui/linux_ui.h +++ b/chromium/ui/views/linux_ui/linux_ui.h @@ -57,11 +57,6 @@ class NavButtonProvider; // Adapter class with targets to render like different toolkits. Set by any // project that wants to do linux desktop native rendering. -// -// TODO(erg): We're hardcoding GTK2, when we'll need to have backends for (at -// minimum) GTK2 and GTK3. LinuxUI::instance() should actually be a very -// complex method that pokes around with dlopen against a libuigtk2.so, a -// liuigtk3.so, etc. class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory, public gfx::LinuxFontDelegate, public ui::ShellDialogLinux, diff --git a/chromium/ui/views/mouse_watcher.cc b/chromium/ui/views/mouse_watcher.cc index e0c1a3bb44b..5a91062a11b 100644 --- a/chromium/ui/views/mouse_watcher.cc +++ b/chromium/ui/views/mouse_watcher.cc @@ -26,11 +26,10 @@ const int kNotifyListenerTimeMs = 300; class MouseWatcher::Observer : public ui::EventHandler { public: - explicit Observer(MouseWatcher* mouse_watcher) + Observer(MouseWatcher* mouse_watcher, gfx::NativeWindow window) : mouse_watcher_(mouse_watcher), - event_monitor_(EventMonitor::CreateApplicationMonitor(this)), - notify_listener_factory_(this) { - } + event_monitor_(EventMonitor::CreateApplicationMonitor(this, window)), + notify_listener_factory_(this) {} // ui::EventHandler implementation: void OnMouseEvent(ui::MouseEvent* event) override { @@ -57,7 +56,7 @@ class MouseWatcher::Observer : public ui::EventHandler { void HandleMouseEvent(MouseWatcherHost::MouseEventType event_type) { // It's safe to use last_mouse_location() here as this function is invoked // during event dispatching. - if (!host()->Contains(EventMonitor::GetLastMouseLocation(), event_type)) { + if (!host()->Contains(event_monitor_->GetLastMouseLocation(), event_type)) { if (event_type == MouseWatcherHost::MOUSE_PRESS) { NotifyListener(); } else if (!notify_listener_factory_.HasWeakPtrs()) { @@ -109,9 +108,9 @@ MouseWatcher::MouseWatcher(std::unique_ptr<MouseWatcherHost> host, MouseWatcher::~MouseWatcher() { } -void MouseWatcher::Start() { +void MouseWatcher::Start(gfx::NativeWindow window) { if (!is_observing()) - observer_ = std::make_unique<Observer>(this); + observer_ = std::make_unique<Observer>(this, window); } void MouseWatcher::Stop() { diff --git a/chromium/ui/views/mouse_watcher.h b/chromium/ui/views/mouse_watcher.h index c9edf322030..d27dc0928e9 100644 --- a/chromium/ui/views/mouse_watcher.h +++ b/chromium/ui/views/mouse_watcher.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "base/time/time.h" #include "ui/gfx/geometry/insets.h" +#include "ui/gfx/native_widget_types.h" #include "ui/views/views_export.h" namespace gfx { @@ -65,8 +66,10 @@ class VIEWS_EXPORT MouseWatcher { // Starts watching mouse movements. When the mouse moves outside the bounds of // the host the listener is notified. |Start| may be invoked any number of // times. If the mouse moves outside the bounds of the host the listener is - // notified and watcher stops watching events. - void Start(); + // notified and watcher stops watching events. |window| must be a window in + // the hierarchy related to the host. |window| is used to setup initial state, + // and may be deleted before MouseWatcher. + void Start(gfx::NativeWindow window); // Stops watching mouse events. void Stop(); diff --git a/chromium/ui/views/mus/BUILD.gn b/chromium/ui/views/mus/BUILD.gn index 0c9092d362b..883a69b8bbb 100644 --- a/chromium/ui/views/mus/BUILD.gn +++ b/chromium/ui/views/mus/BUILD.gn @@ -45,7 +45,7 @@ jumbo_component("mus") { public_deps = [ ":resources", - "//services/ui/public/cpp", + "//services/ws/public/cpp", "//ui/aura", ] deps = [ @@ -59,9 +59,9 @@ jumbo_component("mus") { "//services/catalog/public/mojom:constants", "//services/service_manager/public/cpp", "//services/service_manager/public/mojom", - "//services/ui/public/cpp", - "//services/ui/public/cpp/input_devices", - "//services/ui/public/interfaces", + "//services/ws/public/cpp", + "//services/ws/public/cpp/input_devices", + "//services/ws/public/mojom", "//skia", "//third_party/icu", "//ui/accessibility", @@ -126,7 +126,7 @@ jumbo_static_library("test_support") { "//services/catalog:lib", "//services/service_manager/background:lib", "//services/service_manager/public/cpp", - "//services/ui/common:mus_common", + "//services/ws/common", "//testing/gtest", "//ui/aura", "//ui/aura:test_support", @@ -144,7 +144,7 @@ jumbo_static_library("test_support") { data_deps = [ ":views_mus_tests_catalog", - "//services/ui/ime/test_ime_driver", + "//services/ws/ime/test_ime_driver", "//ui/resources:ui_test_pak_data", ] } @@ -171,7 +171,7 @@ test("views_mus_unittests") { "//base/test:test_support", "//cc", "//net", - "//services/ui/public/interfaces", + "//services/ws/public/mojom", "//skia", "//testing/gtest", "//third_party/icu", @@ -202,8 +202,8 @@ test("views_mus_unittests") { data_deps = [ ":views_mus_tests_catalog_copy", - "//services/ui/ime/test_ime_driver", - "//services/ui/test_ws", + "//services/ws/ime/test_ime_driver", + "//services/ws/test_ws", ] if (is_win) { @@ -269,7 +269,7 @@ test("views_mus_interactive_ui_tests") { data_deps = [ ":views_mus_tests_catalog_copy", - "//services/ui/test_ws", + "//services/ws/test_ws", ] if (is_win) { @@ -305,8 +305,8 @@ catalog("views_mus_tests_catalog") { ] standalone_services = [ - "//services/ui/test_ws:manifest", - "//services/ui/ime/test_ime_driver:manifest", + "//services/ws/test_ws:manifest", + "//services/ws/ime/test_ime_driver:manifest", ] } diff --git a/chromium/ui/views/mus/DEPS b/chromium/ui/views/mus/DEPS index 8953b55dfbd..6c5f22db116 100644 --- a/chromium/ui/views/mus/DEPS +++ b/chromium/ui/views/mus/DEPS @@ -7,7 +7,7 @@ include_rules = [ "+mojo/public", "+services/catalog", "+services/service_manager/public", - "+services/ui", + "+services/ws", "+skia", "+ui/aura", "+ui/base", diff --git a/chromium/ui/views/mus/aura_init.cc b/chromium/ui/views/mus/aura_init.cc index 56b6c3c0a1f..c6c4966ef68 100644 --- a/chromium/ui/views/mus/aura_init.cc +++ b/chromium/ui/views/mus/aura_init.cc @@ -46,19 +46,13 @@ std::unique_ptr<AuraInit> AuraInit::Create(const InitParams& params) { bool AuraInit::Init(const InitParams& params) { env_ = aura::Env::CreateInstance(aura::Env::Mode::MUS); - if (params.mode == Mode::AURA_MUS || params.mode == Mode::AURA_MUS2) { - MusClient::InitParams mus_params; - mus_params.connector = params.connector; - mus_params.identity = params.identity; - mus_params.io_task_runner = params.io_task_runner; - mus_params.wtc_config = - params.mode == Mode::AURA_MUS2 - ? aura::WindowTreeClient::Config::kMus2 - : aura::WindowTreeClient::Config::kMashDeprecated; - mus_params.create_wm_state = true; - mus_params.use_accessibility_host = params.use_accessibility_host; - mus_client_ = std::make_unique<MusClient>(mus_params); - } + MusClient::InitParams mus_params; + mus_params.connector = params.connector; + mus_params.identity = params.identity; + mus_params.io_task_runner = params.io_task_runner; + mus_params.create_wm_state = true; + mus_params.use_accessibility_host = params.use_accessibility_host; + mus_client_ = std::make_unique<MusClient>(mus_params); // MaterialDesignController may have initialized already (such as happens // in the utility process). if (!ui::MaterialDesignController::is_mode_initialized()) diff --git a/chromium/ui/views/mus/aura_init.h b/chromium/ui/views/mus/aura_init.h index c821deae031..dad4d1f42c9 100644 --- a/chromium/ui/views/mus/aura_init.h +++ b/chromium/ui/views/mus/aura_init.h @@ -33,20 +33,6 @@ class ViewsDelegate; // |resource_file| is the path to the apk file containing the resources. class VIEWS_MUS_EXPORT AuraInit { public: - // TODO(sky): remove Mode. https://crbug.com/842365. - enum class Mode { - // Indicates AuraInit should target using aura with mus. This is deprecated. - AURA_MUS, - - // Indicates AuraInit should target using aura with mus, for a Window - // Manager client. This is deprecated. - AURA_MUS_WINDOW_MANAGER, - - // Targets ws2. Mode will eventually be removed entirely and this will be - // the default. - AURA_MUS2, - }; - ~AuraInit(); struct VIEWS_MUS_EXPORT InitParams { @@ -59,7 +45,6 @@ class VIEWS_MUS_EXPORT AuraInit { // File for 2x icons. Can be empty. std::string resource_file_200; scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr; - Mode mode = Mode::AURA_MUS; bool register_path_provider = true; // When true the client application will connect to the accessibility host // in the browser to supply AX node trees and handle AX actions (e.g. to @@ -72,7 +57,6 @@ class VIEWS_MUS_EXPORT AuraInit { // unusable state, and calling services should shutdown. static std::unique_ptr<AuraInit> Create(const InitParams& params); - // Only valid if Mode::AURA_MUS was passed to constructor. MusClient* mus_client() { return mus_client_.get(); } private: diff --git a/chromium/ui/views/mus/ax_remote_host.cc b/chromium/ui/views/mus/ax_remote_host.cc index bc1d4edb33e..0affab635ac 100644 --- a/chromium/ui/views/mus/ax_remote_host.cc +++ b/chromium/ui/views/mus/ax_remote_host.cc @@ -11,7 +11,10 @@ #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_event.h" #include "ui/accessibility/platform/ax_unique_id.h" +#include "ui/aura/mus/window_tree_client.h" #include "ui/aura/window.h" +#include "ui/display/display.h" +#include "ui/display/screen.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h" #include "ui/views/mus/ax_tree_source_mus.h" #include "ui/views/mus/mus_client.h" @@ -19,6 +22,9 @@ #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" +using display::Display; +using display::Screen; + namespace views { // For external linkage. @@ -51,7 +57,8 @@ void AXRemoteHost::StartMonitoringWidget(Widget* widget) { // Check if we're already tracking a widget. // TODO(jamescook): Support multiple widgets. if (widget_) - return; + StopMonitoringWidget(); + widget_ = widget; widget_->AddObserver(this); @@ -67,12 +74,17 @@ void AXRemoteHost::StartMonitoringWidget(Widget* widget) { tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper); tree_serializer_ = std::make_unique<AuraAXTreeSerializer>(tree_source_.get()); + // Inform the serializer of the display device scale factor. + UpdateDeviceScaleFactor(); + Screen::GetScreen()->AddObserver(this); + SendEvent(contents_wrapper, ax::mojom::Event::kLoadComplete); } void AXRemoteHost::StopMonitoringWidget() { DCHECK(widget_); DCHECK(widget_->HasObserver(this)); + Screen::GetScreen()->RemoveObserver(this); widget_->RemoveObserver(this); AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); cache->OnRootWindowObjDestroyed(widget_->GetNativeWindow()); @@ -100,20 +112,47 @@ void AXRemoteHost::OnAutomationEnabled(bool enabled) { Disable(); } -void AXRemoteHost::PerformAction(const ui::AXActionData& action) { - // TODO(jamescook): Support ax::mojom::Action::kHitTest. - tree_source_->HandleAccessibleAction(action); +void AXRemoteHost::PerformAction(const ui::AXActionData& action_data) { + if (!enabled_) + return; + + // A hit test requires determining the node to perform the action on first. + if (action_data.action == ax::mojom::Action::kHitTest) { + PerformHitTest(action_data); + return; + } + + tree_source_->HandleAccessibleAction(action_data); +} + +void AXRemoteHost::OnWidgetClosing(Widget* widget) { + // In the typical case, clean up early during widget close. Waiting until + // widget destroy can sometimes lead to crashes due to AX window visibility + // events being triggered during close. https://crbug.com/869608 + DCHECK_EQ(widget_, widget); + StopMonitoringWidget(); } void AXRemoteHost::OnWidgetDestroying(Widget* widget) { + // Widgets can be deleted without being closed, which is common in tests + // and possible in production. DCHECK_EQ(widget_, widget); StopMonitoringWidget(); } +void AXRemoteHost::OnDisplayMetricsChanged(const Display& display, + uint32_t metrics) { + if (metrics & display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR) { + UpdateDeviceScaleFactor(); + SendEvent(tree_source_->GetRoot(), ax::mojom::Event::kLocationChanged); + } +} + void AXRemoteHost::OnChildWindowRemoved(AXAuraObjWrapper* parent) { - if (!enabled_) + if (!enabled_ || !widget_) return; + DCHECK(tree_source_); if (!parent) parent = tree_source_->GetRoot(); @@ -136,24 +175,25 @@ void AXRemoteHost::BindAndSetRemote() { } void AXRemoteHost::Enable() { - // Extensions can send multiple enable events. - if (enabled_) - return; + // Don't early-exit if already enabled. AXRemoteHost can start up in the + // "enabled" state even if ChromeVox is on at the moment the app launches. + // Turning on ChromeVox later will generate another OnAutomationEnabled() + // call and we need to serialize the node tree again. This is similar to + // AutomationManagerAura's behavior. https://crbug.com/876407 enabled_ = true; std::set<aura::Window*> roots = MusClient::Get()->window_tree_client()->GetRoots(); - if (roots.empty()) { - // Client hasn't opened any widgets yet. - return; + for (aura::Window* root : roots) { + Widget* widget = Widget::GetWidgetForNativeWindow(root); + // Typically it's only tests that create Windows (the WindowTreeHost + // backing the root Window) in such a way that there is no associated + // widget. + if (widget) { + // TODO(jamescook): Support multiple roots. + StartMonitoringWidget(widget); + } } - - // TODO(jamescook): Support multiple roots. - aura::Window* root_window = *roots.begin(); - DCHECK(root_window); - Widget* root_widget = Widget::GetWidgetForNativeWindow(root_window); - DCHECK(root_widget); - StartMonitoringWidget(root_widget); } void AXRemoteHost::Disable() { @@ -165,9 +205,13 @@ void AXRemoteHost::Disable() { void AXRemoteHost::SendEvent(AXAuraObjWrapper* aura_obj, ax::mojom::Event event_type) { - if (!enabled_ || !tree_serializer_) + DCHECK(aura_obj); + if (!enabled_ || !widget_) return; + DCHECK(tree_source_); + DCHECK(tree_serializer_); + ui::AXTreeUpdate update; if (!tree_serializer_->SerializeChanges(aura_obj, &update)) { LOG(ERROR) << "Unable to serialize accessibility tree."; @@ -193,4 +237,28 @@ void AXRemoteHost::SendEvent(AXAuraObjWrapper* aura_obj, ax_host_ptr_->HandleAccessibilityEvent(kRemoteAXTreeID, updates, event); } +void AXRemoteHost::PerformHitTest(const ui::AXActionData& action) { + DCHECK(enabled_); + DCHECK_EQ(action.action, ax::mojom::Action::kHitTest); + + // If the widget isn't open yet there's nothing to hit. + if (!widget_) + return; + + views::View* hit_view = + widget_->GetRootView()->GetEventHandlerForPoint(action.target_point); + if (hit_view) + hit_view->NotifyAccessibilityEvent(action.hit_test_event_to_fire, true); +} + +void AXRemoteHost::UpdateDeviceScaleFactor() { + DCHECK(widget_); + DCHECK(tree_source_); + + // Use the scale factor for the widget's window's current display. + Display display = + Screen::GetScreen()->GetDisplayNearestWindow(widget_->GetNativeWindow()); + tree_source_->set_device_scale_factor(display.device_scale_factor()); +} + } // namespace views diff --git a/chromium/ui/views/mus/ax_remote_host.h b/chromium/ui/views/mus/ax_remote_host.h index f213060ab95..7e506159487 100644 --- a/chromium/ui/views/mus/ax_remote_host.h +++ b/chromium/ui/views/mus/ax_remote_host.h @@ -12,6 +12,7 @@ #include "mojo/public/cpp/bindings/binding.h" #include "ui/accessibility/ax_tree_serializer.h" #include "ui/accessibility/mojom/ax_host.mojom.h" +#include "ui/display/display_observer.h" #include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/mus/mus_export.h" #include "ui/views/widget/widget_observer.h" @@ -37,6 +38,7 @@ class Widget; // (e.g. the keyboard shortcut viewer app). class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, public WidgetObserver, + public display::DisplayObserver, public AXAuraObjCache::Delegate { public: // Well-known tree ID for the remote client. @@ -65,14 +67,20 @@ class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, void PerformAction(const ui::AXActionData& action) override; // WidgetObserver: + void OnWidgetClosing(Widget* widget) override; void OnWidgetDestroying(Widget* widget) override; + // display::DisplayObserver: + void OnDisplayMetricsChanged(const display::Display& display, + uint32_t changed_metrics) override; + // AXAuraObjCache::Delegate: void OnChildWindowRemoved(AXAuraObjWrapper* parent) override; void OnEvent(AXAuraObjWrapper* aura_obj, ax::mojom::Event event_type) override; void FlushForTesting(); + Widget* widget_for_testing() { return widget_; } private: // Registers this object as a remote host for the parent AXHost. @@ -84,6 +92,11 @@ class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, // Sends an event to the host. void SendEvent(AXAuraObjWrapper* aura_obj, ax::mojom::Event event_type); + void PerformHitTest(const ui::AXActionData& action); + + // Updates the display device scale factor used when serializing nodes. + void UpdateDeviceScaleFactor(); + // Accessibility host service in the browser. ax::mojom::AXHostPtr ax_host_ptr_; diff --git a/chromium/ui/views/mus/ax_remote_host_unittest.cc b/chromium/ui/views/mus/ax_remote_host_unittest.cc index cf93bb3d11e..fd187350140 100644 --- a/chromium/ui/views/mus/ax_remote_host_unittest.cc +++ b/chromium/ui/views/mus/ax_remote_host_unittest.cc @@ -5,10 +5,15 @@ #include "ui/views/mus/ax_remote_host.h" #include "base/macros.h" +#include "base/run_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/accessibility/mojom/ax_host.mojom.h" +#include "ui/display/display.h" +#include "ui/gfx/geometry/vector2d_f.h" +#include "ui/gfx/transform.h" #include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/mus/mus_client_test_api.h" +#include "ui/views/mus/screen_mus.h" #include "ui/views/test/views_test_base.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" @@ -29,6 +34,14 @@ class TestAXHostService : public ax::mojom::AXHost { return ptr; } + void ResetCounts() { + add_client_count_ = 0; + event_count_ = 0; + last_tree_id_ = 0; + last_updates_.clear(); + last_event_ = ui::AXEvent(); + } + // ax::mojom::AXHost: void SetRemoteHost(ax::mojom::AXRemoteHostPtr client) override { ++add_client_count_; @@ -40,6 +53,7 @@ class TestAXHostService : public ax::mojom::AXHost { const ui::AXEvent& event) override { ++event_count_; last_tree_id_ = tree_id; + last_updates_ = updates; last_event_ = event; } @@ -48,6 +62,7 @@ class TestAXHostService : public ax::mojom::AXHost { int add_client_count_ = 0; int event_count_ = 0; int last_tree_id_ = 0; + std::vector<ui::AXTreeUpdate> last_updates_; ui::AXEvent last_event_; private: @@ -57,18 +72,31 @@ class TestAXHostService : public ax::mojom::AXHost { // TestView senses accessibility actions. class TestView : public View { public: - TestView() = default; + TestView() { set_owned_by_client(); } ~TestView() override = default; + void ResetCounts() { + action_count_ = 0; + last_action_ = {}; + event_count_ = 0; + last_event_type_ = ax::mojom::Event::kNone; + } + // View: bool HandleAccessibleAction(const ui::AXActionData& action) override { ++action_count_; last_action_ = action; return true; } + void OnAccessibilityEvent(ax::mojom::Event event_type) override { + ++event_count_; + last_event_type_ = event_type; + } int action_count_ = 0; ui::AXActionData last_action_; + int event_count_ = 0; + ax::mojom::Event last_event_type_ = ax::mojom::Event::kNone; private: DISALLOW_COPY_AND_ASSIGN(TestView); @@ -115,6 +143,28 @@ TEST_F(AXRemoteHostTest, AutomationEnabled) { service.last_event_.id); } +// Regression test for https://crbug.com/876407 +TEST_F(AXRemoteHostTest, AutomationEnabledTwice) { + // If ChromeVox has ever been open during the user session then the remote app + // can see automation enabled at startup. + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + std::unique_ptr<Widget> widget = CreateTestWidget(); + remote->FlushForTesting(); + + // Remote host sent load complete. + EXPECT_EQ(ax::mojom::Event::kLoadComplete, service.last_event_.event_type); + service.ResetCounts(); + + // Simulate host service asking to enable again. This happens if ChromeVox is + // re-enabled after the remote app is open. + remote->OnAutomationEnabled(true); + remote->FlushForTesting(); + + // Load complete was sent again after the second enable. + EXPECT_EQ(ax::mojom::Event::kLoadComplete, service.last_event_.event_type); +} + // Views can trigger accessibility events during Widget construction before the // AXRemoteHost starts monitoring the widget. This happens with the material // design focus ring on text fields. Verify we don't crash in this case. @@ -127,6 +177,34 @@ TEST_F(AXRemoteHostTest, SendEventBeforeWidgetCreated) { // No crash. } +// Verifies that the AXRemoteHost stops monitoring widgets that are closed +// asynchronously, like when ash requests close via DesktopWindowTreeHostMus. +// https://crbug.com/869608 +TEST_F(AXRemoteHostTest, AsyncWidgetClose) { + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + remote->FlushForTesting(); + + Widget* widget = new Widget(); // Owned by native widget. + Widget::InitParams params; + params.bounds = gfx::Rect(1, 2, 333, 444); + widget->Init(params); + widget->Show(); + + // AXRemoteHost is tracking the widget. + EXPECT_TRUE(remote->widget_for_testing()); + + // Asynchronously close the widget using Close() instead of CloseNow(). + widget->Close(); + + // AXRemoteHost stops tracking the widget, even though it isn't destroyed yet. + EXPECT_FALSE(remote->widget_for_testing()); + + // Widget finishes closing. + base::RunLoop().RunUntilIdle(); + // No crash. +} + TEST_F(AXRemoteHostTest, CreateWidgetThenEnableAutomation) { TestAXHostService service(false /*automation_enabled*/); AXRemoteHost* remote = CreateRemote(&service); @@ -165,5 +243,60 @@ TEST_F(AXRemoteHostTest, PerformAction) { EXPECT_EQ(ax::mojom::Action::kScrollDown, view.last_action_.action); } +TEST_F(AXRemoteHostTest, PerformHitTest) { + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + + // Create a view to sense the action. + TestView view; + view.SetBounds(0, 0, 100, 100); + std::unique_ptr<Widget> widget = CreateTestWidget(); + widget->GetRootView()->AddChildView(&view); + + // Clear event counts triggered by view creation and bounds set. + view.ResetCounts(); + + // Request a hit test. + ui::AXActionData action; + action.action = ax::mojom::Action::kHitTest; + action.hit_test_event_to_fire = ax::mojom::Event::kHitTestResult; + action.target_point = gfx::Point(5, 5); + remote->PerformAction(action); + + // View sensed a hit. + EXPECT_EQ(1, view.event_count_); + EXPECT_EQ(ax::mojom::Event::kHitTestResult, view.last_event_type_); + view.ResetCounts(); + + // Try to hit a point outside the view. + action.target_point = gfx::Point(101, 101); + remote->PerformAction(action); + + // View wasn't hit. + EXPECT_EQ(0, view.event_count_); + EXPECT_EQ(ax::mojom::Event::kNone, view.last_event_type_); +} + +TEST_F(AXRemoteHostTest, ScaleFactor) { + // Set the primary display to high DPI. + ScreenMus* screen = static_cast<ScreenMus*>(display::Screen::GetScreen()); + display::Display display = screen->GetPrimaryDisplay(); + display.set_device_scale_factor(2.f); + screen->display_list().UpdateDisplay(display); + + // Create a widget. + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + std::unique_ptr<Widget> widget = CreateTestWidget(); + remote->FlushForTesting(); + + // Widget transform is scaled by a factor of 2. + ASSERT_FALSE(service.last_updates_.empty()); + gfx::Transform* transform = service.last_updates_[0].nodes[0].transform.get(); + ASSERT_TRUE(transform); + EXPECT_TRUE(transform->IsScale2d()); + EXPECT_EQ(gfx::Vector2dF(2.f, 2.f), transform->Scale2d()); +} + } // namespace } // namespace views diff --git a/chromium/ui/views/mus/ax_tree_source_mus.cc b/chromium/ui/views/mus/ax_tree_source_mus.cc index 6acef96a413..7f767c443f6 100644 --- a/chromium/ui/views/mus/ax_tree_source_mus.cc +++ b/chromium/ui/views/mus/ax_tree_source_mus.cc @@ -34,7 +34,15 @@ void AXTreeSourceMus::SerializeNode(AXAuraObjWrapper* node, // However, the contents view in the host (browser) already has an offset // from its Widget, so the root should start at (0,0). out_data->location.set_origin(gfx::PointF()); - out_data->transform.reset(); + + // Adjust for display device scale factor. + if (device_scale_factor_ == 1.f) { + // The AX system represents the identity transform with null. + out_data->transform.reset(); + } else { + out_data->transform = std::make_unique<gfx::Transform>(); + out_data->transform->Scale(device_scale_factor_, device_scale_factor_); + } return; } diff --git a/chromium/ui/views/mus/ax_tree_source_mus.h b/chromium/ui/views/mus/ax_tree_source_mus.h index 340f14271d9..6e3681e0773 100644 --- a/chromium/ui/views/mus/ax_tree_source_mus.h +++ b/chromium/ui/views/mus/ax_tree_source_mus.h @@ -23,6 +23,8 @@ class VIEWS_MUS_EXPORT AXTreeSourceMus : public AXTreeSourceViews { explicit AXTreeSourceMus(AXAuraObjWrapper* root); ~AXTreeSourceMus() override; + void set_device_scale_factor(float scale) { device_scale_factor_ = scale; } + // AXTreeSource: bool GetTreeData(ui::AXTreeData* data) const override; AXAuraObjWrapper* GetRoot() const override; @@ -33,6 +35,9 @@ class VIEWS_MUS_EXPORT AXTreeSourceMus : public AXTreeSourceViews { // The top-level object to use for the AX tree. AXAuraObjWrapper* root_; + // The display device scale factor to use while serializing this update. + float device_scale_factor_ = 1.f; + DISALLOW_COPY_AND_ASSIGN(AXTreeSourceMus); }; diff --git a/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc b/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc index d652b04712e..2f7bc9a6299 100644 --- a/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc +++ b/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc @@ -12,6 +12,8 @@ #include "ui/accessibility/ax_tree_data.h" #include "ui/accessibility/platform/ax_unique_id.h" #include "ui/gfx/geometry/rect_f.h" +#include "ui/gfx/geometry/vector2d_f.h" +#include "ui/gfx/transform.h" #include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h" #include "ui/views/controls/label.h" @@ -86,5 +88,23 @@ TEST_F(AXTreeSourceMusTest, Serialize) { EXPECT_EQ(root->GetUniqueId().Get(), node_data.offset_container_id); } +TEST_F(AXTreeSourceMusTest, ScaleFactor) { + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView()); + + // Simulate serializing a widget on a high-dpi display. + AXTreeSourceMus tree(root); + tree.set_device_scale_factor(2.f); + + // Serialize the root. + ui::AXNodeData node_data; + tree.SerializeNode(root, &node_data); + + // Transform is scaled. + ASSERT_TRUE(node_data.transform); + EXPECT_TRUE(node_data.transform->IsScale2d()); + EXPECT_EQ(gfx::Vector2dF(2.f, 2.f), node_data.transform->Scale2d()); +} + } // namespace } // namespace views diff --git a/chromium/ui/views/mus/desktop_window_tree_host_mus.cc b/chromium/ui/views/mus/desktop_window_tree_host_mus.cc index 67be4ae4d87..93d30122d2a 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.cc +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.cc @@ -44,13 +44,16 @@ namespace { // As the window manager renderers the non-client decorations this class does // very little but honor the client area insets from the window manager. -class ClientSideNonClientFrameView : public NonClientFrameView { +class ClientSideNonClientFrameView : public NonClientFrameView, + public aura::WindowObserver { public: explicit ClientSideNonClientFrameView(views::Widget* widget) : widget_(widget) { // Not part of the accessibility node hierarchy because the window frame is // provided by the window manager. GetViewAccessibility().set_is_ignored(true); + + observed_.Add(widget_->GetNativeWindow()->GetRootWindow()); } ~ClientSideNonClientFrameView() override {} @@ -125,7 +128,30 @@ class ClientSideNonClientFrameView : public NonClientFrameView { max_size.height() == 0 ? 0 : converted_size.height()); } + // aura::WindowObserver: + void OnWindowDestroying(aura::Window* window) override { + observed_.Remove(window); + } + + void OnWindowPropertyChanged(aura::Window* window, + const void* key, + intptr_t old) override { + // Do a re-layout on state changes which affect GetBoundsForClientView(). + // The associated bounds change would also cause a re-layout, but there may + // not be a bounds change or it may come from the server before the state is + // updated. + if (key == aura::client::kShowStateKey) { + if (GetBoundsForClientView() != widget_->client_view()->bounds() && + window->GetProperty(aura::client::kShowStateKey) != + ui::SHOW_STATE_MINIMIZED) { + InvalidateLayout(); + widget_->GetRootView()->Layout(); + } + } + } + views::Widget* widget_; + ScopedObserver<aura::Window, aura::WindowObserver> observed_{this}; DISALLOW_COPY_AND_ASSIGN(ClientSideNonClientFrameView); }; @@ -290,8 +316,11 @@ float DesktopWindowTreeHostMus::GetScaleFactor() const { } void DesktopWindowTreeHostMus::SetBoundsInDIP(const gfx::Rect& bounds_in_dip) { - SetBoundsInPixels(gfx::ConvertRectToPixel(GetScaleFactor(), bounds_in_dip), - viz::LocalSurfaceId()); + // Do not use ConvertRectToPixel, enclosing rects cause problems. + const gfx::Rect rect( + gfx::ScaleToFlooredPoint(bounds_in_dip.origin(), GetScaleFactor()), + gfx::ScaleToCeiledSize(bounds_in_dip.size(), GetScaleFactor())); + SetBoundsInPixels(rect, viz::LocalSurfaceId()); } bool DesktopWindowTreeHostMus::ShouldSendClientAreaToServer() const { @@ -361,7 +390,7 @@ void DesktopWindowTreeHostMus::Init(const Widget::InitParams& params) { } if (!params.accept_events) - window()->SetEventTargetingPolicy(ui::mojom::EventTargetingPolicy::NONE); + window()->SetEventTargetingPolicy(ws::mojom::EventTargetingPolicy::NONE); else aura::WindowPortMus::Get(content_window())->SetCanAcceptDrops(true); } @@ -470,17 +499,36 @@ aura::WindowTreeHost* DesktopWindowTreeHostMus::AsWindowTreeHost() { return this; } -void DesktopWindowTreeHostMus::ShowWindowWithState(ui::WindowShowState state) { - if (state == ui::SHOW_STATE_MAXIMIZED || state == ui::SHOW_STATE_FULLSCREEN) - window()->SetProperty(aura::client::kShowStateKey, state); +void DesktopWindowTreeHostMus::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { + native_widget_delegate_->OnNativeWidgetVisibilityChanging(true); + + if (show_state == ui::SHOW_STATE_MAXIMIZED && !restore_bounds.IsEmpty()) { + window()->SetProperty(aura::client::kRestoreBoundsKey, + new gfx::Rect(restore_bounds)); + } + if (show_state == ui::SHOW_STATE_MAXIMIZED || + show_state == ui::SHOW_STATE_FULLSCREEN) { + window()->SetProperty(aura::client::kShowStateKey, show_state); + } + // DesktopWindowTreeHostMus is unique in that it calls window()->Show() here. + // All other implementations call window()->Show() from the constructor. This + // is necessary as window()'s visibility is mirrored in the server, on other + // platforms it's the visibility of the AcceleratedWidget that matters and + // dictates what is actually drawn on screen. window()->Show(); if (compositor()) compositor()->SetVisible(true); + // |content_window_| is the Window that will be focused by way of Activate(). + // Ensure |content_window_| is visible before the call to Activate(), + // otherwise focus goes to window(). + content_window()->Show(); + native_widget_delegate_->OnNativeWidgetVisibilityChanged(true); if (native_widget_delegate_->CanActivate()) { - if (state != ui::SHOW_STATE_INACTIVE) + if (show_state != ui::SHOW_STATE_INACTIVE) Activate(); // SetInitialFocus() should be always be called, even for @@ -490,17 +538,10 @@ void DesktopWindowTreeHostMus::ShowWindowWithState(ui::WindowShowState state) { // should pass SHOW_STATE_INACTIVE to SetInitialFocus() to stop the initial // focused view from getting focused. See crbug.com/515594 for example. native_widget_delegate_->SetInitialFocus( - IsActive() ? state : ui::SHOW_STATE_INACTIVE); + IsActive() ? show_state : ui::SHOW_STATE_INACTIVE); } } -void DesktopWindowTreeHostMus::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - window()->SetProperty(aura::client::kRestoreBoundsKey, - new gfx::Rect(restored_bounds)); - ShowWindowWithState(ui::SHOW_STATE_MAXIMIZED); -} - bool DesktopWindowTreeHostMus::IsVisible() const { // Go through the DesktopNativeWidgetAura::IsVisible() for checking // visibility of the parent as it has additional checks beyond checking the @@ -513,7 +554,7 @@ bool DesktopWindowTreeHostMus::IsVisible() const { } void DesktopWindowTreeHostMus::SetSize(const gfx::Size& size) { - // Use GetBounds() as the origin of window() is always at 0, 0. + // Use GetBoundsInPixels(), as the origin of window() is always at (0, 0). gfx::Rect screen_bounds = gfx::ConvertRectToDIP(GetScaleFactor(), GetBoundsInPixels()); screen_bounds.set_size(size); @@ -562,11 +603,7 @@ void DesktopWindowTreeHostMus::GetWindowPlacement( } gfx::Rect DesktopWindowTreeHostMus::GetWindowBoundsInScreen() const { - gfx::Point display_origin = GetDisplay().bounds().origin(); - gfx::Rect bounds_in_dip = - gfx::ConvertRectToDIP(GetScaleFactor(), GetBoundsInPixels()); - bounds_in_dip.Offset(display_origin.x(), display_origin.y()); - return bounds_in_dip; + return gfx::ConvertRectToDIP(GetScaleFactor(), GetBoundsInPixels()); } gfx::Rect DesktopWindowTreeHostMus::GetClientAreaBoundsInScreen() const { @@ -596,10 +633,7 @@ std::string DesktopWindowTreeHostMus::GetWorkspace() const { } gfx::Rect DesktopWindowTreeHostMus::GetWorkAreaBoundsInScreen() const { - // TODO(sky): GetDisplayNearestWindow() should take a const aura::Window*. - return display::Screen::GetScreen() - ->GetDisplayNearestWindow(const_cast<aura::Window*>(window())) - .work_area(); + return GetDisplay().work_area(); } void DesktopWindowTreeHostMus::SetShape( @@ -647,6 +681,7 @@ bool DesktopWindowTreeHostMus::IsActive() const { void DesktopWindowTreeHostMus::Maximize() { window()->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); } + void DesktopWindowTreeHostMus::Minimize() { window()->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); } @@ -716,10 +751,10 @@ Widget::MoveLoopResult DesktopWindowTreeHostMus::RunMoveLoop( base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); - ui::mojom::MoveLoopSource mus_source = + ws::mojom::MoveLoopSource mus_source = source == Widget::MOVE_LOOP_SOURCE_MOUSE - ? ui::mojom::MoveLoopSource::MOUSE - : ui::mojom::MoveLoopSource::TOUCH; + ? ws::mojom::MoveLoopSource::MOUSE + : ws::mojom::MoveLoopSource::TOUCH; bool success = false; gfx::Point cursor_location = @@ -746,7 +781,10 @@ NonClientFrameView* DesktopWindowTreeHostMus::CreateNonClientFrameView() { if (!ShouldSendClientAreaToServer()) return nullptr; - return new ClientSideNonClientFrameView(native_widget_delegate_->AsWidget()); + auto* frame = + new ClientSideNonClientFrameView(native_widget_delegate_->AsWidget()); + observed_frame_.Add(frame); + return frame; } bool DesktopWindowTreeHostMus::ShouldUseNativeFrame() const { @@ -772,6 +810,7 @@ bool DesktopWindowTreeHostMus::IsFullscreen() const { return window()->GetProperty(aura::client::kShowStateKey) == ui::SHOW_STATE_FULLSCREEN; } + void DesktopWindowTreeHostMus::SetOpacity(float opacity) { WindowTreeHostMus::SetOpacity(opacity); } @@ -801,7 +840,7 @@ bool DesktopWindowTreeHostMus::IsTranslucentWindowOpacitySupported() const { } void DesktopWindowTreeHostMus::SizeConstraintsChanged() { - int32_t behavior = ui::mojom::kResizeBehaviorNone; + int32_t behavior = ws::mojom::kResizeBehaviorNone; Widget* widget = native_widget_delegate_->AsWidget(); if (widget->widget_delegate()) behavior = widget->widget_delegate()->GetResizeBehavior(); @@ -864,11 +903,7 @@ void DesktopWindowTreeHostMus::OnWindowPropertyChanged(aura::Window* window, } void DesktopWindowTreeHostMus::ShowImpl() { - native_widget_delegate_->OnNativeWidgetVisibilityChanging(true); - // Using ui::SHOW_STATE_NORMAL matches that of DesktopWindowTreeHostX11. - ShowWindowWithState(ui::SHOW_STATE_NORMAL); - WindowTreeHostMus::ShowImpl(); - native_widget_delegate_->OnNativeWidgetVisibilityChanged(true); + Show(ui::SHOW_STATE_NORMAL, gfx::Rect()); } void DesktopWindowTreeHostMus::HideImpl() { @@ -884,7 +919,6 @@ void DesktopWindowTreeHostMus::HideImpl() { native_widget_delegate_->OnNativeWidgetVisibilityChanging(false); WindowTreeHostMus::HideImpl(); - window()->Hide(); native_widget_delegate_->OnNativeWidgetVisibilityChanged(false); } @@ -902,13 +936,21 @@ void DesktopWindowTreeHostMus::SetBoundsInPixels( size.SetToMin(max_size_in_pixels); final_bounds_in_pixels.set_size(size); } - const gfx::Rect old_bounds_in_pixels = GetBoundsInPixels(); WindowTreeHostMus::SetBoundsInPixels(final_bounds_in_pixels, local_surface_id); - if (old_bounds_in_pixels.size() != final_bounds_in_pixels.size()) { - SendClientAreaToServer(); - SendHitTestMaskToServer(); - } +} + +void DesktopWindowTreeHostMus::OnViewBoundsChanged(views::View* observed_view) { + DCHECK_EQ( + observed_view, + native_widget_delegate_->AsWidget()->non_client_view()->frame_view()); + + SendClientAreaToServer(); + SendHitTestMaskToServer(); +} + +void DesktopWindowTreeHostMus::OnViewIsDeleting(View* observed_view) { + observed_frame_.Remove(observed_view); } aura::Window* DesktopWindowTreeHostMus::content_window() { diff --git a/chromium/ui/views/mus/desktop_window_tree_host_mus.h b/chromium/ui/views/mus/desktop_window_tree_host_mus.h index 92d4e468695..a93e0c297ed 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.h +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.h @@ -8,11 +8,13 @@ #include <memory> #include <set> +#include "base/scoped_observer.h" #include "base/macros.h" #include "ui/aura/mus/focus_synchronizer_observer.h" #include "ui/aura/mus/window_tree_host_mus.h" #include "ui/aura/window_observer.h" #include "ui/views/mus/mus_client_observer.h" +#include "ui/views/view_observer.h" #include "ui/views/mus/mus_export.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host.h" #include "ui/views/widget/widget.h" @@ -28,7 +30,8 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus public MusClientObserver, public aura::FocusSynchronizerObserver, public aura::WindowObserver, - public aura::WindowTreeHostMus { + public aura::WindowTreeHostMus, + public views::ViewObserver { public: DesktopWindowTreeHostMus( aura::WindowTreeHostMusInitParams init_params, @@ -71,8 +74,8 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus void Close() override; void CloseNow() override; aura::WindowTreeHost* AsWindowTreeHost() override; - void ShowWindowWithState(ui::WindowShowState state) override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; bool IsVisible() const override; void SetSize(const gfx::Size& size) override; void StackAbove(aura::Window* window) override; @@ -144,6 +147,10 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels, const viz::LocalSurfaceId& local_surface_id) override; + // views::ViewObserver: + void OnViewBoundsChanged(views::View* observed_view) override; + void OnViewIsDeleting(View* observed_view) override; + // Accessor for DesktopNativeWidgetAura::content_window(). aura::Window* content_window(); @@ -162,6 +169,8 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus bool auto_update_client_area_ = true; + ScopedObserver<views::View, views::ViewObserver> observed_frame_{this}; + // Used so that Close() isn't immediate. base::WeakPtrFactory<DesktopWindowTreeHostMus> close_widget_factory_; diff --git a/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc index 79aef70d12d..85bfbad2488 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc @@ -22,7 +22,9 @@ #include "ui/events/event.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/mus/mus_client.h" +#include "ui/views/mus/mus_client_test_api.h" #include "ui/views/mus/screen_mus.h" +#include "ui/views/mus/window_manager_frame_values.h" #include "ui/views/test/views_test_base.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" @@ -157,8 +159,7 @@ TEST_F(DesktopWindowTreeHostMusTest, Capture) { ->capture_window()); } -// TODO(http://crbug.com/864614): Fails flakily in mus with ws2. -TEST_F(DesktopWindowTreeHostMusTest, DISABLED_Deactivate) { +TEST_F(DesktopWindowTreeHostMusTest, Deactivate) { std::unique_ptr<Widget> widget1(CreateWidget()); widget1->Show(); @@ -239,6 +240,44 @@ TEST_F(DesktopWindowTreeHostMusTest, ActivateBeforeShow) { ->active_focus_client()); } +// Tests that changes to a widget's show state will cause the client area to be +// updated. +TEST_F(DesktopWindowTreeHostMusTest, ServerShowStateChangeUpdatesClientArea) { + WindowManagerFrameValues test_frame_values; + test_frame_values.normal_insets = {3, 0, 0, 0}; + test_frame_values.maximized_insets = {7, 0, 0, 0}; + WindowManagerFrameValues::SetInstance(test_frame_values); + + std::unique_ptr<Widget> widget(CreateWidget()); + widget->Show(); + + // Simulate state changes from the server. + auto set_widget_state = + [&widget](ui::WindowShowState state) { + widget->GetNativeWindow()->GetRootWindow()->SetProperty( + aura::client::kShowStateKey, state); + }; + + // A restored window respects normal_insets (the client area is inset from the + // root view). + gfx::Rect expected_restored_bounds = widget->GetRootView()->bounds(); + expected_restored_bounds.Inset(test_frame_values.normal_insets); + EXPECT_EQ(expected_restored_bounds, widget->client_view()->bounds()); + + // A fullscreen window has no insets. + EXPECT_FALSE(widget->IsFullscreen()); + set_widget_state(ui::SHOW_STATE_FULLSCREEN); + EXPECT_TRUE(widget->IsFullscreen()); + EXPECT_EQ(widget->GetRootView()->bounds(), widget->client_view()->bounds()); + + // A maximized window respects maximized_insets. + gfx::Rect expected_maximized_bounds = widget->GetRootView()->bounds(); + expected_maximized_bounds.Inset(test_frame_values.maximized_insets); + set_widget_state(ui::SHOW_STATE_MAXIMIZED); + EXPECT_FALSE(widget->IsFullscreen()); + EXPECT_EQ(expected_maximized_bounds, widget->client_view()->bounds()); +} + TEST_F(DesktopWindowTreeHostMusTest, CursorClientDuringTearDown) { std::unique_ptr<Widget> widget(CreateWidget()); widget->Show(); @@ -279,8 +318,7 @@ TEST_F(DesktopWindowTreeHostMusTest, StackAtTopAlreadyOnTop) { waiter.Wait(); } -// TODO(http://crbug.com/864615): Fails consistently in mus with ws2. -TEST_F(DesktopWindowTreeHostMusTest, DISABLED_StackAbove) { +TEST_F(DesktopWindowTreeHostMusTest, StackAbove) { std::unique_ptr<Widget> widget1(CreateWidget(nullptr)); widget1->Show(); @@ -365,7 +403,7 @@ TEST_F(DesktopWindowTreeHostMusTest, CreateFullscreenWidget) { } TEST_F(DesktopWindowTreeHostMusTest, GetWindowBoundsInScreen) { - ScreenMus* screen = MusClient::Get()->screen(); + ScreenMus* screen = MusClientTestApi::screen(); // Add a second display to the right of the primary. const int64_t kSecondDisplayId = 222; @@ -385,11 +423,12 @@ TEST_F(DesktopWindowTreeHostMusTest, GetWindowBoundsInScreen) { Widget widget2; Widget::InitParams params2(Widget::InitParams::TYPE_WINDOW); params2.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - params2.bounds = gfx::Rect(0, 0, 100, 100); + params2.bounds = gfx::Rect(800, 0, 100, 100); widget2.Init(params2); - aura::WindowTreeHostMus::ForWindow(widget2.GetNativeWindow()) - ->set_display_id(kSecondDisplayId); EXPECT_EQ(gfx::Rect(800, 0, 100, 100), widget2.GetWindowBoundsInScreen()); + EXPECT_EQ(kSecondDisplayId, + aura::WindowTreeHostMus::ForWindow(widget2.GetNativeWindow()) + ->display_id()); } // WidgetDelegate implementation that allows setting window-title and whether diff --git a/chromium/ui/views/mus/drag_interactive_uitest.cc b/chromium/ui/views/mus/drag_interactive_uitest.cc index d222948c84c..999ec9e269a 100644 --- a/chromium/ui/views/mus/drag_interactive_uitest.cc +++ b/chromium/ui/views/mus/drag_interactive_uitest.cc @@ -4,9 +4,12 @@ #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" -#include "services/ui/public/interfaces/window_server_test.mojom.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/constants.mojom.h" +#include "services/ws/public/mojom/event_injector.mojom.h" +#include "services/ws/public/mojom/window_server_test.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/aura/mus/in_flight_change.h" +#include "ui/aura/mus/window_tree_client.h" #include "ui/aura/mus/window_tree_host_mus.h" #include "ui/aura/test/mus/change_completion_waiter.h" #include "ui/events/event.h" @@ -117,36 +120,37 @@ void DragTest_Part3(int64_t display_id, quit_closure.Run(); } -void DragTest_Part2(int64_t display_id, +void DragTest_Part2(ws::mojom::EventInjector* event_injector, + int64_t display_id, const base::Closure& quit_closure, bool result) { EXPECT_TRUE(result); if (!result) quit_closure.Run(); - ui::mojom::EventInjector* event_injector = - MusClient::Get()->GetTestingEventInjector(); event_injector->InjectEvent( display_id, CreateMouseUpEvent(30, 30), base::BindOnce(&DragTest_Part3, display_id, quit_closure)); } -void DragTest_Part1(int64_t display_id, +void DragTest_Part1(ws::mojom::EventInjector* event_injector, + int64_t display_id, const base::Closure& quit_closure, bool result) { EXPECT_TRUE(result); if (!result) quit_closure.Run(); - ui::mojom::EventInjector* event_injector = - MusClient::Get()->GetTestingEventInjector(); event_injector->InjectEvent( display_id, CreateMouseMoveEvent(30, 30), - base::BindOnce(&DragTest_Part2, display_id, quit_closure)); + base::BindOnce(&DragTest_Part2, base::Unretained(event_injector), + display_id, quit_closure)); } -// TODO(http://crbug.com/864616): Hangs indefinitely in mus with ws2. -TEST_F(DragTestInteractive, DISABLED_DragTest) { +TEST_F(DragTestInteractive, DragTest) { + ws::mojom::EventInjectorPtr event_injector; + MusClient::Get()->window_tree_client()->connector()->BindInterface( + ws::mojom::kServiceName, &event_injector); Widget* source_widget = CreateTopLevelFramelessPlatformWidget(); View* source_view = new DraggableView; source_widget->SetContentsView(source_view); @@ -176,11 +180,10 @@ TEST_F(DragTestInteractive, DISABLED_DragTest) { { base::RunLoop run_loop; - ui::mojom::EventInjector* event_injector = - MusClient::Get()->GetTestingEventInjector(); event_injector->InjectEvent( display_id, CreateMouseDownEvent(10, 10), - base::BindOnce(&DragTest_Part1, display_id, run_loop.QuitClosure())); + base::BindOnce(&DragTest_Part1, base::Unretained(event_injector.get()), + display_id, run_loop.QuitClosure())); run_loop.Run(); } diff --git a/chromium/ui/views/mus/mus_client.cc b/chromium/ui/views/mus/mus_client.cc index 09eb2fcd5bc..4678479162a 100644 --- a/chromium/ui/views/mus/mus_client.cc +++ b/chromium/ui/views/mus/mus_client.cc @@ -8,18 +8,17 @@ #include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/single_thread_task_runner.h" -#include "base/threading/thread.h" #include "services/service_manager/public/cpp/connector.h" -#include "services/ui/public/cpp/gpu/gpu.h" -#include "services/ui/public/cpp/input_devices/input_device_client.h" -#include "services/ui/public/cpp/property_type_converters.h" -#include "services/ui/public/interfaces/constants.mojom.h" -#include "services/ui/public/interfaces/event_matcher.mojom.h" -#include "services/ui/public/interfaces/window_manager.mojom.h" +#include "services/ws/public/cpp/gpu/gpu.h" +#include "services/ws/public/cpp/input_devices/input_device_client.h" +#include "services/ws/public/cpp/property_type_converters.h" +#include "services/ws/public/mojom/constants.mojom.h" +#include "services/ws/public/mojom/window_manager.mojom.h" #include "ui/aura/env.h" #include "ui/aura/mus/capture_synchronizer.h" #include "ui/aura/mus/mus_context_factory.h" #include "ui/aura/mus/property_converter.h" +#include "ui/aura/mus/window_tree_client.h" #include "ui/aura/mus/window_tree_host_mus.h" #include "ui/aura/mus/window_tree_host_mus_init_params.h" #include "ui/aura/window.h" @@ -41,11 +40,11 @@ #include "ui/base/cursor/ozone/cursor_data_factory_ozone.h" #endif -// Widget::InitParams::Type must match that of ui::mojom::WindowType. +// Widget::InitParams::Type must match that of ws::mojom::WindowType. #define WINDOW_TYPES_MATCH(NAME) \ static_assert( \ static_cast<int32_t>(views::Widget::InitParams::TYPE_##NAME) == \ - static_cast<int32_t>(ui::mojom::WindowType::NAME), \ + static_cast<int32_t>(ws::mojom::WindowType::NAME), \ "Window type constants must match") WINDOW_TYPES_MATCH(WINDOW); @@ -57,7 +56,7 @@ WINDOW_TYPES_MATCH(MENU); WINDOW_TYPES_MATCH(TOOLTIP); WINDOW_TYPES_MATCH(BUBBLE); WINDOW_TYPES_MATCH(DRAG); -// ui::mojom::WindowType::UNKNOWN does not correspond to a value in +// ws::mojom::WindowType::UNKNOWN does not correspond to a value in // Widget::InitParams::Type. namespace views { @@ -85,35 +84,25 @@ MusClient::MusClient(const InitParams& params) : identity_(params.identity) { cursor_factory_ozone_ = std::make_unique<ui::CursorDataFactoryOzone>(); #endif - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = - params.io_task_runner; - if (!io_task_runner) { - io_thread_ = std::make_unique<base::Thread>("IOThread"); - base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); - thread_options.priority = base::ThreadPriority::NORMAL; - CHECK(io_thread_->StartWithOptions(thread_options)); - io_task_runner = io_thread_->task_runner(); - } - property_converter_ = std::make_unique<aura::PropertyConverter>(); property_converter_->RegisterPrimitiveProperty( ::wm::kShadowElevationKey, - ui::mojom::WindowManager::kShadowElevation_Property, + ws::mojom::WindowManager::kShadowElevation_Property, aura::PropertyConverter::CreateAcceptAnyValueCallback()); if (params.create_wm_state) wm_state_ = std::make_unique<wm::WMState>(); service_manager::Connector* connector = params.connector; - if (params.bind_test_ws_interfaces) - connector->BindInterface(ui::mojom::kServiceName, &event_injector_); if (!params.window_tree_client) { - DCHECK(io_task_runner); + // If this process is running in the WindowService, then discardable memory + // should have already been created. + const bool create_discardable_memory = !params.running_in_ws_process; owned_window_tree_client_ = aura::WindowTreeClient::CreateForWindowTreeFactory( - connector, this, true, std::move(io_task_runner), - params.wtc_config); + connector, this, create_discardable_memory, + std::move(params.io_task_runner)); window_tree_client_ = owned_window_tree_client_.get(); aura::Env::GetInstance()->SetWindowTreeClient(window_tree_client_); } else { @@ -123,20 +112,21 @@ MusClient::MusClient(const InitParams& params) : identity_(params.identity) { pointer_watcher_event_router_ = std::make_unique<PointerWatcherEventRouter>(window_tree_client_); - if (connector) { - input_device_client_ = std::make_unique<ui::InputDeviceClient>(); - ui::mojom::InputDeviceServerPtr input_device_server; - connector->BindInterface(ui::mojom::kServiceName, &input_device_server); + if (connector && !params.running_in_ws_process) { + input_device_client_ = std::make_unique<ws::InputDeviceClient>(); + ws::mojom::InputDeviceServerPtr input_device_server; + connector->BindInterface(ws::mojom::kServiceName, &input_device_server); input_device_client_->Connect(std::move(input_device_server)); screen_ = std::make_unique<ScreenMus>(this); - if (params.wtc_config == aura::WindowTreeClient::Config::kMashDeprecated) - screen_->InitDeprecated(connector); - else - window_tree_client_->WaitForDisplays(); + display::Screen::SetScreenInstance(screen_.get()); + + // NOTE: this deadlocks if |running_in_ws_process| is true (because the main + // thread is running the WindowService). + window_tree_client_->WaitForDisplays(); ui::mojom::ClipboardHostPtr clipboard_host_ptr; - connector->BindInterface(ui::mojom::kServiceName, &clipboard_host_ptr); + connector->BindInterface(ws::mojom::kServiceName, &clipboard_host_ptr); ui::Clipboard::SetClipboardForCurrentThread( std::make_unique<ui::ClipboardClient>(std::move(clipboard_host_ptr))); @@ -153,6 +143,10 @@ MusClient::MusClient(const InitParams& params) : identity_(params.identity) { } MusClient::~MusClient() { + // Tear down accessibility before WindowTreeClient to ensure window tree + // cleanup doesn't trigger accessibility events. + ax_remote_host_.reset(); + // ~WindowTreeClient calls back to us (we're its delegate), destroy it while // we are still valid. owned_window_tree_client_.reset(); @@ -167,6 +161,11 @@ MusClient::~MusClient() { ViewsDelegate::DesktopWindowTreeHostFactory()); } + if (screen_) { + display::Screen::SetScreenInstance(nullptr); + screen_.reset(); + } + DCHECK_EQ(instance_, this); instance_ = nullptr; DCHECK(aura::Env::GetInstance()); @@ -175,6 +174,19 @@ MusClient::~MusClient() { // static bool MusClient::ShouldCreateDesktopNativeWidgetAura( const Widget::InitParams& init_params) { + const bool from_window_service = + (init_params.context && + init_params.context->env()->mode() == aura::Env::Mode::LOCAL) || + (init_params.parent && + init_params.parent->env()->mode() == aura::Env::Mode::LOCAL); + // |from_window_service| is true if the aura::Env has a mode of LOCAL. If + // the mode is LOCAL there are two envs, one used by the window service + // (LOCAL), and the other for non-window-service code. Windows created with + // LOCAL should use NativeWidgetAura (which happens if false is returned + // here). + if (from_window_service) + return false; + // TYPE_CONTROL and child widgets require a NativeWidgetAura. return init_params.type != Widget::InitParams::TYPE_CONTROL && !init_params.child; @@ -195,12 +207,12 @@ std::map<std::string, std::vector<uint8_t>> MusClient::ConfigurePropertiesFromParams( const Widget::InitParams& init_params) { using PrimitiveType = aura::PropertyConverter::PrimitiveType; - using WindowManager = ui::mojom::WindowManager; + using WindowManager = ws::mojom::WindowManager; using TransportType = std::vector<uint8_t>; std::map<std::string, TransportType> properties = init_params.mus_properties; - // Widget::InitParams::Type matches ui::mojom::WindowType. + // Widget::InitParams::Type matches ws::mojom::WindowType. properties[WindowManager::kWindowType_InitProperty] = mojo::ConvertTo<TransportType>(static_cast<int32_t>(init_params.type)); @@ -337,11 +349,6 @@ void MusClient::CloseAllWidgets() { } } -ui::mojom::EventInjector* MusClient::GetTestingEventInjector() const { - CHECK(event_injector_); - return event_injector_.get(); -} - std::unique_ptr<DesktopWindowTreeHost> MusClient::CreateDesktopWindowTreeHost( const Widget::InitParams& init_params, internal::NativeWidgetDelegate* delegate, @@ -370,18 +377,21 @@ void MusClient::OnEmbedRootDestroyed( } void MusClient::OnPointerEventObserved(const ui::PointerEvent& event, - int64_t display_id, + const gfx::Point& location_in_screen, aura::Window* target) { - pointer_watcher_event_router_->OnPointerEventObserved(event, display_id, - target); + pointer_watcher_event_router_->OnPointerEventObserved( + event, location_in_screen, target); } void MusClient::OnDisplaysChanged( - std::vector<ui::mojom::WsDisplayPtr> ws_displays, + std::vector<ws::mojom::WsDisplayPtr> ws_displays, int64_t primary_display_id, - int64_t internal_display_id) { - screen_->OnDisplaysChanged(std::move(ws_displays), primary_display_id, - internal_display_id); + int64_t internal_display_id, + int64_t display_id_for_new_windows) { + if (screen_) { + screen_->OnDisplaysChanged(std::move(ws_displays), primary_display_id, + internal_display_id, display_id_for_new_windows); + } } void MusClient::OnWindowManagerFrameValuesChanged() { diff --git a/chromium/ui/views/mus/mus_client.h b/chromium/ui/views/mus/mus_client.h index cb3d6b6cef5..0cd4f65bc06 100644 --- a/chromium/ui/views/mus/mus_client.h +++ b/chromium/ui/views/mus/mus_client.h @@ -13,9 +13,7 @@ #include "base/macros.h" #include "services/service_manager/public/cpp/identity.h" -#include "services/ui/public/interfaces/event_injector.mojom.h" #include "ui/aura/client/capture_client.h" -#include "ui/aura/mus/window_tree_client.h" #include "ui/aura/mus/window_tree_client_delegate.h" #include "ui/views/mus/mus_export.h" #include "ui/views/mus/screen_mus_delegate.h" @@ -29,7 +27,6 @@ class WindowTreeClient; namespace base { class SingleThreadTaskRunner; -class Thread; } namespace service_manager { @@ -38,13 +35,16 @@ class Connector; namespace ui { class CursorDataFactoryOzone; -class InputDeviceClient; } namespace wm { class WMState; } +namespace ws { +class InputDeviceClient; +} + namespace views { class AXRemoteHost; @@ -68,15 +68,13 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, InitParams(); ~InitParams(); - // Production code should provide |connector|, |identity|, |wtc_config| - // and an |io_task_runner| if the process already has one. Test code may - // skip these parameters (e.g. a unit test that does not need to connect - // to the window service does not need to provide a connector). + // Production code should provide |connector|, |identity|, and an + // |io_task_runner| if the process already has one. Test code may skip these + // parameters (e.g. a unit test that does not need to connect to the window + // service does not need to provide a connector). service_manager::Connector* connector = nullptr; service_manager::Identity identity; scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr; - aura::WindowTreeClient::Config wtc_config = - aura::WindowTreeClient::Config::kMashDeprecated; // Create a wm::WMState. Some processes (e.g. the browser) may already // have one. @@ -84,7 +82,6 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // Tests may need to control objects owned by MusClient. bool create_cursor_factory = true; - bool bind_test_ws_interfaces = false; // If provided, MusClient will not create the WindowTreeClient. Not owned. // Must outlive MusClient. @@ -93,6 +90,10 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // Connect to the accessibility host service in the browser (e.g. to support // ChromeVox). bool use_accessibility_host = false; + + // Set to true if the WindowService is running in the same process and on + // the same thread as MusClient. + bool running_in_ws_process = false; }; // Most clients should use AuraInit, which creates a MusClient. @@ -116,6 +117,10 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, static std::map<std::string, std::vector<uint8_t>> ConfigurePropertiesFromParams(const Widget::InitParams& init_params); + aura::PropertyConverter* property_converter() { + return property_converter_.get(); + } + aura::WindowTreeClient* window_tree_client() { return window_tree_client_; } PointerWatcherEventRouter* pointer_watcher_event_router() { @@ -124,9 +129,6 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, AXRemoteHost* ax_remote_host() { return ax_remote_host_.get(); } - // Getter for type safety. Most code can use display::Screen::GetScreen(). - ScreenMus* screen() { return screen_.get(); } - // Creates DesktopNativeWidgetAura with DesktopWindowTreeHostMus. This is // set as the factory function used for creating NativeWidgets when a // NativeWidget has not been explicitly set. @@ -153,10 +155,6 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // Close all widgets this client knows. void CloseAllWidgets(); - // Returns an interface to inject events into the Window Service. Only - // available when created with MusClientTestingState::CREATE_TESTING_STATE. - ui::mojom::EventInjector* GetTestingEventInjector() const; - private: friend class AuraInit; friend class MusClientTestApi; @@ -175,12 +173,13 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, void OnLostConnection(aura::WindowTreeClient* client) override; void OnEmbedRootDestroyed(aura::WindowTreeHostMus* window_tree_host) override; void OnPointerEventObserved(const ui::PointerEvent& event, - int64_t display_id, + const gfx::Point& location_in_screen, aura::Window* target) override; aura::PropertyConverter* GetPropertyConverter() override; - void OnDisplaysChanged(std::vector<ui::mojom::WsDisplayPtr> ws_displays, + void OnDisplaysChanged(std::vector<ws::mojom::WsDisplayPtr> ws_displays, int64_t primary_display_id, - int64_t internal_display_id) override; + int64_t internal_display_id, + int64_t display_id_for_new_windows) override; // ScreenMusDelegate: void OnWindowManagerFrameValuesChanged() override; @@ -190,9 +189,7 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, service_manager::Identity identity_; - std::unique_ptr<base::Thread> io_thread_; - - base::ObserverList<MusClientObserver> observer_list_; + base::ObserverList<MusClientObserver>::Unchecked observer_list_; #if defined(USE_OZONE) std::unique_ptr<ui::CursorDataFactoryOzone> cursor_factory_ozone_; @@ -216,15 +213,13 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, std::unique_ptr<PointerWatcherEventRouter> pointer_watcher_event_router_; // Gives services transparent remote access the InputDeviceManager. - std::unique_ptr<ui::InputDeviceClient> input_device_client_; + std::unique_ptr<ws::InputDeviceClient> input_device_client_; // Forwards accessibility events to extensions in the browser. Can be null for // apps that do not need accessibility support and for the browser itself // under OopAsh. std::unique_ptr<AXRemoteHost> ax_remote_host_; - ui::mojom::EventInjectorPtr event_injector_; - DISALLOW_COPY_AND_ASSIGN(MusClient); }; diff --git a/chromium/ui/views/mus/mus_client_test_api.h b/chromium/ui/views/mus/mus_client_test_api.h index e3052276ce8..bdf20dccb51 100644 --- a/chromium/ui/views/mus/mus_client_test_api.h +++ b/chromium/ui/views/mus/mus_client_test_api.h @@ -9,6 +9,7 @@ #include <utility> #include "base/macros.h" +#include "ui/views/mus/ax_remote_host.h" #include "ui/views/mus/mus_client.h" namespace views { @@ -21,6 +22,8 @@ class MusClientTestApi { MusClient::Get()->ax_remote_host_ = std::move(client); } + static ScreenMus* screen() { return MusClient::Get()->screen_.get(); } + private: DISALLOW_IMPLICIT_CONSTRUCTORS(MusClientTestApi); }; diff --git a/chromium/ui/views/mus/mus_views_delegate.cc b/chromium/ui/views/mus/mus_views_delegate.cc index 927c065ee5f..e383ac00d69 100644 --- a/chromium/ui/views/mus/mus_views_delegate.cc +++ b/chromium/ui/views/mus/mus_views_delegate.cc @@ -15,8 +15,7 @@ MusViewsDelegate::~MusViewsDelegate() = default; void MusViewsDelegate::NotifyAccessibilityEvent(View* view, ax::mojom::Event event_type) { - // Null in AuraInit::Mode::AURA_MUS_WINDOW_MANAGER which is used in mash. - if (MusClient::Get() && MusClient::Get()->ax_remote_host()) + if (MusClient::Get()->ax_remote_host()) MusClient::Get()->ax_remote_host()->HandleEvent(view, event_type); } diff --git a/chromium/ui/views/mus/pointer_watcher_event_router.cc b/chromium/ui/views/mus/pointer_watcher_event_router.cc index beed6b538c9..07a1a7e7b02 100644 --- a/chromium/ui/views/mus/pointer_watcher_event_router.cc +++ b/chromium/ui/views/mus/pointer_watcher_event_router.cc @@ -5,23 +5,20 @@ #include "ui/views/mus/pointer_watcher_event_router.h" #include "ui/aura/client/capture_client.h" +#include "ui/aura/client/screen_position_client.h" #include "ui/aura/mus/window_tree_client.h" #include "ui/aura/window.h" -#include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/events/base_event_utils.h" #include "ui/events/event.h" #include "ui/views/pointer_watcher.h" #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" -using display::Display; -using display::Screen; - namespace views { namespace { bool HasPointerWatcher( - base::ObserverList<views::PointerWatcher, true>* observer_list) { + base::ObserverList<views::PointerWatcher, true>::Unchecked* observer_list) { return observer_list->begin() != observer_list->end(); } @@ -89,7 +86,7 @@ void PointerWatcherEventRouter::RemovePointerWatcher(PointerWatcher* watcher) { void PointerWatcherEventRouter::OnPointerEventObserved( const ui::PointerEvent& event, - int64_t display_id, + const gfx::Point& location_in_screen, aura::Window* target) { Widget* target_widget = nullptr; ui::PointerEvent updated_event(event); @@ -118,13 +115,6 @@ void PointerWatcherEventRouter::OnPointerEventObserved( } } - // Compute screen coordinates via |display_id| because there may not be a - // |target| that can be used to find a ScreenPositionClient. - gfx::Point location_in_screen = event.location(); - Display display; - if (Screen::GetScreen()->GetDisplayWithDisplayId(display_id, &display)) - location_in_screen.Offset(display.bounds().x(), display.bounds().y()); - for (PointerWatcher& observer : move_watchers_) { observer.OnPointerEventObserved( updated_event, location_in_screen, @@ -165,7 +155,8 @@ void PointerWatcherEventRouter::OnCaptureChanged(aura::Window* lost_capture, const ui::MouseEvent mouse_event(ui::ET_MOUSE_CAPTURE_CHANGED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(), 0, 0); const ui::PointerEvent event(mouse_event); - gfx::Point location_in_screen = Screen::GetScreen()->GetCursorScreenPoint(); + gfx::Point location_in_screen = + display::Screen::GetScreen()->GetCursorScreenPoint(); for (PointerWatcher& observer : move_watchers_) observer.OnPointerEventObserved(event, location_in_screen, nullptr); for (PointerWatcher& observer : non_move_watchers_) diff --git a/chromium/ui/views/mus/pointer_watcher_event_router.h b/chromium/ui/views/mus/pointer_watcher_event_router.h index 2c9dd56e081..48cfa96930e 100644 --- a/chromium/ui/views/mus/pointer_watcher_event_router.h +++ b/chromium/ui/views/mus/pointer_watcher_event_router.h @@ -21,6 +21,10 @@ class CaptureClient; } } +namespace gfx { +class Point; +} + namespace ui { class PointerEvent; } @@ -59,7 +63,7 @@ class VIEWS_MUS_EXPORT PointerWatcherEventRouter // Called by WindowTreeClientDelegate to notify PointerWatchers appropriately. void OnPointerEventObserved(const ui::PointerEvent& event, - int64_t display_id, + const gfx::Point& location_in_screen, aura::Window* target); // Called when the |capture_client| has been set or will be unset. @@ -84,8 +88,8 @@ class VIEWS_MUS_EXPORT PointerWatcherEventRouter // destruction. Two sets of observers are maintained, one for observers not // needing moves |non_move_watchers_| and |move_watchers_| for those // observers wanting moves too. - base::ObserverList<views::PointerWatcher, true> non_move_watchers_; - base::ObserverList<views::PointerWatcher, true> move_watchers_; + base::ObserverList<views::PointerWatcher, true>::Unchecked non_move_watchers_; + base::ObserverList<views::PointerWatcher, true>::Unchecked move_watchers_; EventTypes event_types_ = EventTypes::NONE; diff --git a/chromium/ui/views/mus/pointer_watcher_event_router_unittest.cc b/chromium/ui/views/mus/pointer_watcher_event_router_unittest.cc index 821cefdcf38..0f6143e0c43 100644 --- a/chromium/ui/views/mus/pointer_watcher_event_router_unittest.cc +++ b/chromium/ui/views/mus/pointer_watcher_event_router_unittest.cc @@ -7,13 +7,11 @@ #include <memory> #include "base/test/scoped_task_environment.h" -#include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/test/mus/window_tree_client_private.h" #include "ui/events/event.h" #include "ui/views/mus/mus_client.h" -#include "ui/views/mus/screen_mus.h" #include "ui/views/pointer_watcher.h" -#include "ui/views/test/scoped_views_test_helper.h" +#include "ui/views/test/views_test_base.h" namespace views { namespace { @@ -48,15 +46,14 @@ class TestPointerWatcher : public PointerWatcher { } // namespace -class PointerWatcherEventRouterTest : public testing::Test { +class PointerWatcherEventRouterTest : public views::ViewsTestBase { public: PointerWatcherEventRouterTest() = default; ~PointerWatcherEventRouterTest() override = default; - void OnPointerEventObserved(const ui::PointerEvent& event, - int64_t display_id = 0) { + void OnPointerEventObserved(const ui::PointerEvent& event) { MusClient::Get()->pointer_watcher_event_router()->OnPointerEventObserved( - event, display_id, nullptr); + event, event.root_location(), nullptr); } PointerWatcherEventRouter::EventTypes event_types() const { @@ -64,10 +61,6 @@ class PointerWatcherEventRouterTest : public testing::Test { } private: - base::test::ScopedTaskEnvironment scoped_task_environment_{ - base::test::ScopedTaskEnvironment::MainThreadType::UI}; - ScopedViewsTestHelper helper_; - DISALLOW_COPY_AND_ASSIGN(PointerWatcherEventRouterTest); }; @@ -244,36 +237,4 @@ TEST_F(PointerWatcherEventRouterTest, PointerWatcherMove) { EXPECT_FALSE(watcher2.last_event_observed()); } -TEST_F(PointerWatcherEventRouterTest, SecondaryDisplay) { - PointerWatcherEventRouter* pointer_watcher_event_router = - MusClient::Get()->pointer_watcher_event_router(); - TestPointerWatcher watcher; - pointer_watcher_event_router->AddPointerWatcher(&watcher, false); - - ScreenMus* screen = MusClient::Get()->screen(); - const uint64_t kFirstDisplayId = screen->GetPrimaryDisplay().id(); - - // The first display is at 0,0. - ASSERT_TRUE(screen->GetPrimaryDisplay().bounds().origin().IsOrigin()); - - // Add a secondary display to the right of the primary. - const uint64_t kSecondDisplayId = 222; - screen->display_list().AddDisplay( - display::Display(kSecondDisplayId, gfx::Rect(800, 0, 640, 480)), - display::DisplayList::Type::NOT_PRIMARY); - - ui::PointerEvent tap_event( - ui::ET_POINTER_DOWN, gfx::Point(1, 1), gfx::Point(1, 1), ui::EF_NONE, 0, - ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1), - base::TimeTicks()); - - OnPointerEventObserved(tap_event, kFirstDisplayId); - EXPECT_EQ(gfx::Point(1, 1), watcher.last_location_in_screen()); - - OnPointerEventObserved(tap_event, kSecondDisplayId); - EXPECT_EQ(gfx::Point(801, 1), watcher.last_location_in_screen()); - - pointer_watcher_event_router->RemovePointerWatcher(&watcher); -} - } // namespace views diff --git a/chromium/ui/views/mus/remote_view/remote_view_host.cc b/chromium/ui/views/mus/remote_view/remote_view_host.cc index c5f9e577de0..a203d2b14fd 100644 --- a/chromium/ui/views/mus/remote_view/remote_view_host.cc +++ b/chromium/ui/views/mus/remote_view/remote_view_host.cc @@ -22,7 +22,7 @@ void RemoteViewHost::EmbedUsingToken(const base::UnguessableToken& embed_token, int embed_flags, EmbedCallback callback) { // Only works with mus. - DCHECK_EQ(aura::Env::Mode::MUS, aura::Env::GetInstanceDontCreate()->mode()); + DCHECK_EQ(aura::Env::Mode::MUS, aura::Env::GetInstance()->mode()); embed_token_ = embed_token; embed_flags_ = embed_flags; diff --git a/chromium/ui/views/mus/remote_view/remote_view_provider_unittest.cc b/chromium/ui/views/mus/remote_view/remote_view_provider_unittest.cc index f87a3202ebc..f396829094d 100644 --- a/chromium/ui/views/mus/remote_view/remote_view_provider_unittest.cc +++ b/chromium/ui/views/mus/remote_view/remote_view_provider_unittest.cc @@ -14,6 +14,7 @@ #include "ui/aura/test/aura_test_base.h" #include "ui/aura/test/mus/test_window_tree.h" #include "ui/aura/window.h" +#include "ui/aura/window_tracker.h" #include "ui/gfx/geometry/rect.h" #include "ui/views/mus/remote_view/remote_view_provider_test_api.h" @@ -101,7 +102,7 @@ class RemoteViewProviderTest : public aura::test::AuraTestBase { base::BindRepeating([](base::RunLoop* run_loop) { run_loop->Quit(); }, &run_loop)); - const ui::Id embedder_window_id = + const ws::Id embedder_window_id = aura::WindowMus::Get(embedder)->server_id(); window_tree()->RemoveEmbedderWindow(embedder_window_id); run_loop.Run(); @@ -159,11 +160,15 @@ TEST_F(RemoteViewProviderTest, EmbedAgain) { aura::Window* embedder = SimulateEmbed(); ASSERT_TRUE(embedder); + aura::WindowTracker window_tracker; + window_tracker.Add(embedder); SimulateEmbedderClose(embedder); + // SimulateEmbedderClose() should delete |embedder|. + EXPECT_TRUE(window_tracker.windows().empty()); aura::Window* new_embedder = SimulateEmbed(); + // SimulateEmbed() should create a new window. ASSERT_TRUE(new_embedder); - EXPECT_NE(new_embedder, embedder); } } // namespace views diff --git a/chromium/ui/views/mus/screen_mus.cc b/chromium/ui/views/mus/screen_mus.cc index 1238f81aefe..b4c9a2668bd 100644 --- a/chromium/ui/views/mus/screen_mus.cc +++ b/chromium/ui/views/mus/screen_mus.cc @@ -5,8 +5,6 @@ #include "ui/views/mus/screen_mus.h" #include "base/stl_util.h" -#include "services/service_manager/public/cpp/connector.h" -#include "services/ui/public/interfaces/constants.mojom.h" #include "ui/aura/env.h" #include "ui/aura/mus/window_tree_host_mus.h" #include "ui/aura/window.h" @@ -19,9 +17,9 @@ namespace mojo { template <> struct TypeConverter<views::WindowManagerFrameValues, - ui::mojom::FrameDecorationValuesPtr> { + ws::mojom::FrameDecorationValuesPtr> { static views::WindowManagerFrameValues Convert( - const ui::mojom::FrameDecorationValuesPtr& input) { + const ws::mojom::FrameDecorationValuesPtr& input) { views::WindowManagerFrameValues result; result.normal_insets = input->normal_client_area_insets; result.maximized_insets = input->maximized_client_area_insets; @@ -36,46 +34,17 @@ namespace views { using Type = display::DisplayList::Type; -ScreenMus::ScreenMus(ScreenMusDelegate* delegate) - : delegate_(delegate), screen_provider_observer_binding_(this) { +ScreenMus::ScreenMus(ScreenMusDelegate* delegate) : delegate_(delegate) { DCHECK(delegate); - display::Screen::SetScreenInstance(this); } -ScreenMus::~ScreenMus() { - DCHECK_EQ(this, display::Screen::GetScreen()); - display::Screen::SetScreenInstance(nullptr); -} - -void ScreenMus::InitDeprecated(service_manager::Connector* connector) { - connector->BindInterface(ui::mojom::kServiceName, &screen_provider_); - - ui::mojom::ScreenProviderObserverPtr observer; - screen_provider_observer_binding_.Bind(mojo::MakeRequest(&observer)); - screen_provider_->AddObserver(std::move(observer)); - - // We need the set of displays before we can continue. Wait for it. - // - // TODO(rockot): Do something better here. This should not have to block tasks - // from running on the calling thread. http://crbug.com/594852. - bool success = screen_provider_observer_binding_.WaitForIncomingMethodCall(); - - // The WaitForIncomingMethodCall() should have supplied the set of Displays, - // unless mus is going down, in which case encountered_error() is true, or the - // call to WaitForIncomingMethodCall() failed. - if (display_list().displays().empty()) { - DCHECK(screen_provider_.encountered_error() || !success); - // In this case we install a default display and assume the process is - // going to exit shortly so that the real value doesn't matter. - display_list().AddDisplay( - display::Display(0xFFFFFFFF, gfx::Rect(0, 0, 801, 802)), Type::PRIMARY); - } -} +ScreenMus::~ScreenMus() = default; void ScreenMus::OnDisplaysChanged( - std::vector<ui::mojom::WsDisplayPtr> ws_displays, + std::vector<ws::mojom::WsDisplayPtr> ws_displays, int64_t primary_display_id, - int64_t internal_display_id) { + int64_t internal_display_id, + int64_t display_id_for_new_windows) { const bool primary_changed = primary_display_id != GetPrimaryDisplay().id(); int64_t handled_display_id = display::kInvalidDisplayId; const WindowManagerFrameValues initial_frame_values = @@ -88,7 +57,6 @@ void ScreenMus::OnDisplaysChanged( handled_display_id = primary_display_id; for (auto& ws_display_ptr : ws_displays) { if (ws_display_ptr->display.id() == primary_display_id) { - // TODO(sky): Make WindowManagerFrameValues per display. WindowManagerFrameValues frame_values = ws_display_ptr->frame_decoration_values .To<WindowManagerFrameValues>(); @@ -130,6 +98,8 @@ void ScreenMus::OnDisplaysChanged( initial_frame_values != WindowManagerFrameValues::instance()) { delegate_->OnWindowManagerFrameValuesChanged(); } + + SetDisplayForNewWindows(display_id_for_new_windows); } display::Display ScreenMus::GetDisplayNearestWindow( diff --git a/chromium/ui/views/mus/screen_mus.h b/chromium/ui/views/mus/screen_mus.h index ad94f9f1090..36bb1909ced 100644 --- a/chromium/ui/views/mus/screen_mus.h +++ b/chromium/ui/views/mus/screen_mus.h @@ -5,36 +5,29 @@ #ifndef UI_VIEWS_MUS_SCREEN_MUS_H_ #define UI_VIEWS_MUS_SCREEN_MUS_H_ -#include "mojo/public/cpp/bindings/binding.h" -#include "services/ui/public/interfaces/screen_provider.mojom.h" +#include "services/ws/public/mojom/screen_provider_observer.mojom.h" #include "ui/display/screen_base.h" #include "ui/views/mus/mus_export.h" -namespace service_manager { -class Connector; -} - namespace views { class ScreenMusDelegate; -// Screen implementation backed by ui::mojom::ScreenProvider. +// Screen implementation that gets information from +// ws::mojom::ScreenProviderObserver. +// +// NOTE: this is not necessarily installed as the Screen implementation. class VIEWS_MUS_EXPORT ScreenMus : public display::ScreenBase, - public ui::mojom::ScreenProviderObserver { + public ws::mojom::ScreenProviderObserver { public: explicit ScreenMus(ScreenMusDelegate* delegate); ~ScreenMus() override; - // TODO(sky): not used with ws2. Remove. https://crbug.com/842365. - void InitDeprecated(service_manager::Connector* connector); - - // ui::mojom::ScreenProviderObserver: - void OnDisplaysChanged(std::vector<ui::mojom::WsDisplayPtr> ws_displays, + // ws::mojom::ScreenProviderObserver: + void OnDisplaysChanged(std::vector<ws::mojom::WsDisplayPtr> ws_displays, int64_t primary_display_id, - int64_t internal_display_id) override; - - private: - friend class ScreenMusTestApi; + int64_t internal_display_id, + int64_t display_id_for_new_windows) override; // display::Screen: display::Display GetDisplayNearestWindow( @@ -43,10 +36,8 @@ class VIEWS_MUS_EXPORT ScreenMus : public display::ScreenBase, bool IsWindowUnderCursor(gfx::NativeWindow window) override; aura::Window* GetWindowAtScreenPoint(const gfx::Point& point) override; + private: ScreenMusDelegate* delegate_; - ui::mojom::ScreenProviderPtr screen_provider_; - mojo::Binding<ui::mojom::ScreenProviderObserver> - screen_provider_observer_binding_; DISALLOW_COPY_AND_ASSIGN(ScreenMus); }; diff --git a/chromium/ui/views/mus/screen_mus_unittest.cc b/chromium/ui/views/mus/screen_mus_unittest.cc index 6a5e8755414..e27751a1494 100644 --- a/chromium/ui/views/mus/screen_mus_unittest.cc +++ b/chromium/ui/views/mus/screen_mus_unittest.cc @@ -6,46 +6,31 @@ #include "base/command_line.h" #include "base/test/scoped_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" #include "ui/display/display_switches.h" #include "ui/display/screen.h" #include "ui/views/test/scoped_views_test_helper.h" -#include "ui/views/test/views_test_base.h" namespace views { - -class ScreenMusTestApi { - public: - static void CallOnDisplaysChanged( - ScreenMus* screen, - std::vector<ui::mojom::WsDisplayPtr> ws_displays, - int64_t primary_display_id, - int64_t internal_display_id) { - screen->OnDisplaysChanged(std::move(ws_displays), primary_display_id, - internal_display_id); - } - - private: - DISALLOW_IMPLICIT_CONSTRUCTORS(ScreenMusTestApi); -}; - namespace { -std::vector<ui::mojom::WsDisplayPtr> ConvertDisplayToWsDisplays( +std::vector<ws::mojom::WsDisplayPtr> ConvertDisplayToWsDisplays( const std::vector<display::Display>& displays) { - std::vector<ui::mojom::WsDisplayPtr> results; + std::vector<ws::mojom::WsDisplayPtr> results; for (const auto& display : displays) { - ui::mojom::WsDisplayPtr display_ptr = ui::mojom::WsDisplay::New(); + ws::mojom::WsDisplayPtr display_ptr = ws::mojom::WsDisplay::New(); display_ptr->display = display; display_ptr->frame_decoration_values = - ui::mojom::FrameDecorationValues::New(); + ws::mojom::FrameDecorationValues::New(); results.push_back(std::move(display_ptr)); } return results; } -TEST(ScreenMusTest, ConsistentDisplayInHighDPI) { +TEST(ScreenMusScaleFactorTest, ConsistentDisplayInHighDPI) { base::test::ScopedTaskEnvironment task_environment( base::test::ScopedTaskEnvironment::MainThreadType::UI); + // Must be set before |test_helper| is constructed. base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kForceDeviceScaleFactor, "2"); ScopedViewsTestHelper test_helper; @@ -58,10 +43,20 @@ TEST(ScreenMusTest, ConsistentDisplayInHighDPI) { } } -TEST(ScreenMusTest, PrimaryChangedToExisting) { - base::test::ScopedTaskEnvironment task_environment( - base::test::ScopedTaskEnvironment::MainThreadType::UI); - ScopedViewsTestHelper test_helper; +class ScreenMusTest : public testing::Test { + public: + ScreenMusTest() = default; + ~ScreenMusTest() override = default; + + private: + base::test::ScopedTaskEnvironment task_environment_{ + base::test::ScopedTaskEnvironment::MainThreadType::UI}; + ScopedViewsTestHelper test_helper_; + + DISALLOW_COPY_AND_ASSIGN(ScreenMusTest); +}; + +TEST_F(ScreenMusTest, PrimaryChangedToExisting) { ScreenMus* screen = static_cast<ScreenMus*>(display::Screen::GetScreen()); std::vector<display::Display> displays = screen->GetAllDisplays(); ASSERT_FALSE(displays.empty()); @@ -69,17 +64,14 @@ TEST(ScreenMusTest, PrimaryChangedToExisting) { // Convert to a single display with a different primary id. displays.resize(1); displays[0].set_id(displays[0].id() + 1); - ScreenMusTestApi::CallOnDisplaysChanged( - screen, ConvertDisplayToWsDisplays(displays), displays[0].id(), 0); + screen->OnDisplaysChanged(ConvertDisplayToWsDisplays(displays), + displays[0].id(), 0, 0); ASSERT_EQ(1u, screen->GetAllDisplays().size()); EXPECT_EQ(displays[0].id(), screen->GetAllDisplays()[0].id()); EXPECT_EQ(displays[0].id(), screen->GetPrimaryDisplay().id()); } -TEST(ScreenMusTest, AddAndUpdate) { - base::test::ScopedTaskEnvironment task_environment( - base::test::ScopedTaskEnvironment::MainThreadType::UI); - ScopedViewsTestHelper test_helper; +TEST_F(ScreenMusTest, AddAndUpdate) { ScreenMus* screen = static_cast<ScreenMus*>(display::Screen::GetScreen()); std::vector<display::Display> displays = screen->GetAllDisplays(); ASSERT_FALSE(displays.empty()); @@ -91,8 +83,8 @@ TEST(ScreenMusTest, AddAndUpdate) { displays[0].set_bounds(new_bounds); displays.push_back(displays[0]); displays[1].set_id(displays[0].id() + 1); - ScreenMusTestApi::CallOnDisplaysChanged( - screen, ConvertDisplayToWsDisplays(displays), displays[1].id(), 0); + screen->OnDisplaysChanged(ConvertDisplayToWsDisplays(displays), + displays[1].id(), 0, 0); ASSERT_EQ(2u, screen->GetAllDisplays().size()); ASSERT_TRUE(screen->display_list().FindDisplayById(displays[0].id()) != screen->display_list().displays().end()); @@ -105,5 +97,31 @@ TEST(ScreenMusTest, AddAndUpdate) { EXPECT_EQ(displays[1].id(), screen->GetPrimaryDisplay().id()); } +TEST_F(ScreenMusTest, SetDisplayForNewWindows) { + ScreenMus* screen = static_cast<ScreenMus*>(display::Screen::GetScreen()); + + // Set up 2 displays with display 1 as the display for new windows. + constexpr int64_t kDisplayId1 = 111; + constexpr int64_t kDisplayId2 = 222; + std::vector<display::Display> displays = {display::Display(kDisplayId1), + display::Display(kDisplayId2)}; + screen->OnDisplaysChanged(ConvertDisplayToWsDisplays(displays), kDisplayId1, + kDisplayId1, + kDisplayId1 /* display_id_for_new_windows */); + EXPECT_EQ(kDisplayId1, screen->GetDisplayForNewWindows().id()); + + // Set display 2 as the display for new windows. + screen->OnDisplaysChanged(ConvertDisplayToWsDisplays(displays), kDisplayId1, + kDisplayId1, + kDisplayId2 /* display_id_for_new_windows */); + EXPECT_EQ(kDisplayId2, screen->GetDisplayForNewWindows().id()); + + // Set a bad display as the display for new windows. ScreenMus should fall + // back to the primary display. + screen->OnDisplaysChanged(ConvertDisplayToWsDisplays(displays), kDisplayId1, + kDisplayId1, 666 /* display_id_for_new_windows */); + EXPECT_EQ(kDisplayId1, screen->GetDisplayForNewWindows().id()); +} + } // namespace } // namespace views diff --git a/chromium/ui/views/mus/views_mus_test_suite.cc b/chromium/ui/views/mus/views_mus_test_suite.cc index 959f5ad7ede..40dafe4c9db 100644 --- a/chromium/ui/views/mus/views_mus_test_suite.cc +++ b/chromium/ui/views/mus/views_mus_test_suite.cc @@ -22,7 +22,7 @@ #include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/service.h" #include "services/service_manager/public/cpp/service_context.h" -#include "services/ui/common/switches.h" +#include "services/ws/common/switches.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/env.h" #include "ui/aura/mus/window_tree_host_mus.h" @@ -103,8 +103,6 @@ class ServiceManagerConnection { MusClient::InitParams params; params.connector = GetConnector(); params.identity = service_manager_identity_; - params.bind_test_ws_interfaces = true; - params.wtc_config = aura::WindowTreeClient::Config::kMus2; return std::make_unique<MusClient>(params); } @@ -237,7 +235,7 @@ void ViewsMusTestSuite::Initialize() { // Let other services know that we're running in tests. Do this with a // command line flag to avoid making blocking calls to other processes for // setup for tests (e.g. to unlock the screen in the window manager). - EnsureCommandLineSwitch(ui::switches::kUseTestConfig); + EnsureCommandLineSwitch(ws::switches::kUseTestConfig); EnsureCommandLineSwitch(switches::kOverrideUseSoftwareGLForTests); diff --git a/chromium/ui/views/mus/window_manager_constants_converters.cc b/chromium/ui/views/mus/window_manager_constants_converters.cc index 90ee4793112..08d927dc013 100644 --- a/chromium/ui/views/mus/window_manager_constants_converters.cc +++ b/chromium/ui/views/mus/window_manager_constants_converters.cc @@ -7,30 +7,30 @@ namespace mojo { // static -ui::mojom::WindowType -TypeConverter<ui::mojom::WindowType, views::Widget::InitParams::Type>::Convert( +ws::mojom::WindowType +TypeConverter<ws::mojom::WindowType, views::Widget::InitParams::Type>::Convert( views::Widget::InitParams::Type type) { switch (type) { case views::Widget::InitParams::TYPE_WINDOW: - return ui::mojom::WindowType::WINDOW; + return ws::mojom::WindowType::WINDOW; case views::Widget::InitParams::TYPE_PANEL: - return ui::mojom::WindowType::PANEL; + return ws::mojom::WindowType::PANEL; case views::Widget::InitParams::TYPE_WINDOW_FRAMELESS: - return ui::mojom::WindowType::WINDOW_FRAMELESS; + return ws::mojom::WindowType::WINDOW_FRAMELESS; case views::Widget::InitParams::TYPE_CONTROL: - return ui::mojom::WindowType::CONTROL; + return ws::mojom::WindowType::CONTROL; case views::Widget::InitParams::TYPE_POPUP: - return ui::mojom::WindowType::POPUP; + return ws::mojom::WindowType::POPUP; case views::Widget::InitParams::TYPE_MENU: - return ui::mojom::WindowType::MENU; + return ws::mojom::WindowType::MENU; case views::Widget::InitParams::TYPE_TOOLTIP: - return ui::mojom::WindowType::TOOLTIP; + return ws::mojom::WindowType::TOOLTIP; case views::Widget::InitParams::TYPE_BUBBLE: - return ui::mojom::WindowType::BUBBLE; + return ws::mojom::WindowType::BUBBLE; case views::Widget::InitParams::TYPE_DRAG: - return ui::mojom::WindowType::DRAG; + return ws::mojom::WindowType::DRAG; } - return ui::mojom::WindowType::POPUP; + return ws::mojom::WindowType::POPUP; } } // namespace mojo diff --git a/chromium/ui/views/mus/window_manager_constants_converters.h b/chromium/ui/views/mus/window_manager_constants_converters.h index e9493eb6c84..c24deec94e3 100644 --- a/chromium/ui/views/mus/window_manager_constants_converters.h +++ b/chromium/ui/views/mus/window_manager_constants_converters.h @@ -5,7 +5,7 @@ #ifndef UI_VIEWS_MUS_WINDOW_MANAGER_CONSTANTS_CONVERTERS_H_ #define UI_VIEWS_MUS_WINDOW_MANAGER_CONSTANTS_CONVERTERS_H_ -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/views/mus/mus_export.h" #include "ui/views/widget/widget.h" @@ -13,8 +13,8 @@ namespace mojo { template <> struct VIEWS_MUS_EXPORT - TypeConverter<ui::mojom::WindowType, views::Widget::InitParams::Type> { - static ui::mojom::WindowType Convert(views::Widget::InitParams::Type type); + TypeConverter<ws::mojom::WindowType, views::Widget::InitParams::Type> { + static ws::mojom::WindowType Convert(views::Widget::InitParams::Type type); }; } // namespace mojo diff --git a/chromium/ui/views/resources/default_100_percent/bubble_bottom.png b/chromium/ui/views/resources/default_100_percent/bubble_bottom.png Binary files differdeleted file mode 100644 index 03dde937b12..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_bottom_left.png b/chromium/ui/views/resources/default_100_percent/bubble_bottom_left.png Binary files differdeleted file mode 100644 index 2e6b2482f36..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_bottom_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_bottom_right.png b/chromium/ui/views/resources/default_100_percent/bubble_bottom_right.png Binary files differdeleted file mode 100644 index 286449ec616..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_bottom_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_left.png b/chromium/ui/views/resources/default_100_percent/bubble_left.png Binary files differdeleted file mode 100644 index 341df39e3db..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_pointer_bottom.png b/chromium/ui/views/resources/default_100_percent/bubble_pointer_bottom.png Binary files differdeleted file mode 100644 index 4744fed638b..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_pointer_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_pointer_left.png b/chromium/ui/views/resources/default_100_percent/bubble_pointer_left.png Binary files differdeleted file mode 100644 index 60695bd5665..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_pointer_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_pointer_right.png b/chromium/ui/views/resources/default_100_percent/bubble_pointer_right.png Binary files differdeleted file mode 100644 index fd636c5cf7c..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_pointer_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_pointer_top.png b/chromium/ui/views/resources/default_100_percent/bubble_pointer_top.png Binary files differdeleted file mode 100644 index 1f66b21b84b..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_pointer_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_right.png b/chromium/ui/views/resources/default_100_percent/bubble_right.png Binary files differdeleted file mode 100644 index 1323a1cd9d7..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_top.png b/chromium/ui/views/resources/default_100_percent/bubble_top.png Binary files differdeleted file mode 100644 index 50b6817f4f2..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_top_left.png b/chromium/ui/views/resources/default_100_percent/bubble_top_left.png Binary files differdeleted file mode 100644 index 60a2c987a0a..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_top_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/bubble_top_right.png b/chromium/ui/views/resources/default_100_percent/bubble_top_right.png Binary files differdeleted file mode 100644 index 3821309d969..00000000000 --- a/chromium/ui/views/resources/default_100_percent/bubble_top_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/close.png b/chromium/ui/views/resources/default_100_percent/close.png Binary files differindex 75f418dcffd..6d21523a0a4 100644 --- a/chromium/ui/views/resources/default_100_percent/close.png +++ b/chromium/ui/views/resources/default_100_percent/close.png diff --git a/chromium/ui/views/resources/default_100_percent/close_hover.png b/chromium/ui/views/resources/default_100_percent/close_hover.png Binary files differindex 67e605a8618..dcc9c8ac4f1 100644 --- a/chromium/ui/views/resources/default_100_percent/close_hover.png +++ b/chromium/ui/views/resources/default_100_percent/close_hover.png diff --git a/chromium/ui/views/resources/default_100_percent/close_pressed.png b/chromium/ui/views/resources/default_100_percent/close_pressed.png Binary files differindex 5eeb8a70373..502f5e9f4c5 100644 --- a/chromium/ui/views/resources/default_100_percent/close_pressed.png +++ b/chromium/ui/views/resources/default_100_percent/close_pressed.png diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom.png Binary files differdeleted file mode 100644 index e57800c914c..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom_left.png Binary files differdeleted file mode 100644 index 0a51f9af4bf..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom_right.png Binary files differdeleted file mode 100644 index 428b3790da4..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_bottom_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_left.png Binary files differdeleted file mode 100644 index 4c3d9b4a2e2..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_right.png Binary files differdeleted file mode 100644 index 670b8189c98..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top.png Binary files differdeleted file mode 100644 index 059af22c136..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top_left.png Binary files differdeleted file mode 100644 index 43ac459fbe8..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top_right.png Binary files differdeleted file mode 100644 index 8198a7de9eb..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_big_top_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom.png Binary files differdeleted file mode 100644 index 6f5735eda96..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom_left.png Binary files differdeleted file mode 100644 index fbef5a8c4a2..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom_right.png Binary files differdeleted file mode 100644 index 1949fa4a5f5..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_bottom_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_left.png Binary files differdeleted file mode 100644 index 7a77ed6d37b..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_right.png Binary files differdeleted file mode 100644 index 4f5a851a68e..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top.png Binary files differdeleted file mode 100644 index 4ae2fb08875..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top_left.png Binary files differdeleted file mode 100644 index 27933e9d6a4..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top_right.png Binary files differdeleted file mode 100644 index 99d417c1db6..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_small_top_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_bottom.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_bottom.png Binary files differdeleted file mode 100644 index 3fff849fe39..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_left.png Binary files differdeleted file mode 100644 index 3fdd27ee328..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_right.png Binary files differdeleted file mode 100644 index 454ec3401de..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_top.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_top.png Binary files differdeleted file mode 100644 index 4c3e68b274c..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_big_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_bottom.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_bottom.png Binary files differdeleted file mode 100644 index 6a67514b6ae..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_left.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_left.png Binary files differdeleted file mode 100644 index 0ba6a51dd75..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_right.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_right.png Binary files differdeleted file mode 100644 index d5cf1518da3..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_top.png b/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_top.png Binary files differdeleted file mode 100644 index 2c1a022393a..00000000000 --- a/chromium/ui/views/resources/default_100_percent/common/window_bubble_shadow_spike_small_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_100_percent/maximize_hover.png b/chromium/ui/views/resources/default_100_percent/maximize_hover.png Binary files differindex d17a2642be3..4eb77665b0d 100644 --- a/chromium/ui/views/resources/default_100_percent/maximize_hover.png +++ b/chromium/ui/views/resources/default_100_percent/maximize_hover.png diff --git a/chromium/ui/views/resources/default_100_percent/maximize_pressed.png b/chromium/ui/views/resources/default_100_percent/maximize_pressed.png Binary files differindex b5f66ad21db..f130060779e 100644 --- a/chromium/ui/views/resources/default_100_percent/maximize_pressed.png +++ b/chromium/ui/views/resources/default_100_percent/maximize_pressed.png diff --git a/chromium/ui/views/resources/default_100_percent/restore_hover.png b/chromium/ui/views/resources/default_100_percent/restore_hover.png Binary files differindex 88e93a3920c..eda294a64a6 100644 --- a/chromium/ui/views/resources/default_100_percent/restore_hover.png +++ b/chromium/ui/views/resources/default_100_percent/restore_hover.png diff --git a/chromium/ui/views/resources/default_100_percent/restore_pressed.png b/chromium/ui/views/resources/default_100_percent/restore_pressed.png Binary files differindex 88e93a3920c..eda294a64a6 100644 --- a/chromium/ui/views/resources/default_100_percent/restore_pressed.png +++ b/chromium/ui/views/resources/default_100_percent/restore_pressed.png diff --git a/chromium/ui/views/resources/default_200_percent/bubble_bottom.png b/chromium/ui/views/resources/default_200_percent/bubble_bottom.png Binary files differdeleted file mode 100644 index e89a6811968..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_bottom_left.png b/chromium/ui/views/resources/default_200_percent/bubble_bottom_left.png Binary files differdeleted file mode 100644 index 207f7af34cc..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_bottom_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_bottom_right.png b/chromium/ui/views/resources/default_200_percent/bubble_bottom_right.png Binary files differdeleted file mode 100644 index d955a43915a..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_bottom_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_left.png b/chromium/ui/views/resources/default_200_percent/bubble_left.png Binary files differdeleted file mode 100644 index 69844562e02..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_pointer_bottom.png b/chromium/ui/views/resources/default_200_percent/bubble_pointer_bottom.png Binary files differdeleted file mode 100644 index e2ef165f821..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_pointer_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_pointer_left.png b/chromium/ui/views/resources/default_200_percent/bubble_pointer_left.png Binary files differdeleted file mode 100644 index 94f49fb7b0d..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_pointer_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_pointer_right.png b/chromium/ui/views/resources/default_200_percent/bubble_pointer_right.png Binary files differdeleted file mode 100644 index b72e34c3a9b..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_pointer_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_pointer_top.png b/chromium/ui/views/resources/default_200_percent/bubble_pointer_top.png Binary files differdeleted file mode 100644 index 59714dabd4b..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_pointer_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_right.png b/chromium/ui/views/resources/default_200_percent/bubble_right.png Binary files differdeleted file mode 100644 index d82631a0380..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_top.png b/chromium/ui/views/resources/default_200_percent/bubble_top.png Binary files differdeleted file mode 100644 index 004f5655948..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_top_left.png b/chromium/ui/views/resources/default_200_percent/bubble_top_left.png Binary files differdeleted file mode 100644 index bd25b50e94d..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_top_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/bubble_top_right.png b/chromium/ui/views/resources/default_200_percent/bubble_top_right.png Binary files differdeleted file mode 100644 index f5feea6afc9..00000000000 --- a/chromium/ui/views/resources/default_200_percent/bubble_top_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom.png Binary files differdeleted file mode 100644 index bc8b1950e2c..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom_left.png Binary files differdeleted file mode 100644 index f7bef138581..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom_right.png Binary files differdeleted file mode 100644 index c5b1fc2e7a4..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_bottom_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_left.png Binary files differdeleted file mode 100644 index 4a4ce5ff918..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_right.png Binary files differdeleted file mode 100644 index 8a502c80b51..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top.png Binary files differdeleted file mode 100644 index 9a85702fb4b..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top_left.png Binary files differdeleted file mode 100644 index 9046e454fae..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top_right.png Binary files differdeleted file mode 100644 index 82e788dd6b5..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_big_top_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom.png Binary files differdeleted file mode 100644 index 5239c4644d9..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom_left.png Binary files differdeleted file mode 100644 index e958b6ffc8e..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom_right.png Binary files differdeleted file mode 100644 index 21504462080..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_bottom_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_left.png Binary files differdeleted file mode 100644 index 30d93bd269a..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_right.png Binary files differdeleted file mode 100644 index 35999b1e7de..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top.png Binary files differdeleted file mode 100644 index 022e48f44ba..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top_left.png Binary files differdeleted file mode 100644 index 9a8823f5a65..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top_right.png Binary files differdeleted file mode 100644 index fb1d21b61a4..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_small_top_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_bottom.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_bottom.png Binary files differdeleted file mode 100644 index 93461aa0b72..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_left.png Binary files differdeleted file mode 100644 index 286bcb910bb..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_right.png Binary files differdeleted file mode 100644 index ed64369766f..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_top.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_top.png Binary files differdeleted file mode 100644 index 7048347cbb5..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_big_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_bottom.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_bottom.png Binary files differdeleted file mode 100644 index e1008843e94..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_bottom.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_left.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_left.png Binary files differdeleted file mode 100644 index f187da42423..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_left.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_right.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_right.png Binary files differdeleted file mode 100644 index 4bda3e604fb..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_right.png +++ /dev/null diff --git a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_top.png b/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_top.png Binary files differdeleted file mode 100644 index ad5fa498e3b..00000000000 --- a/chromium/ui/views/resources/default_200_percent/common/window_bubble_shadow_spike_small_top.png +++ /dev/null diff --git a/chromium/ui/views/resources/views_resources.grd b/chromium/ui/views/resources/views_resources.grd index 112662e690b..9b6b99641ba 100644 --- a/chromium/ui/views/resources/views_resources.grd +++ b/chromium/ui/views/resources/views_resources.grd @@ -15,18 +15,6 @@ <structure type="chrome_scaled_image" name="IDR_APP_TOP_CENTER" file="app_top_center.png" /> <structure type="chrome_scaled_image" name="IDR_APP_TOP_LEFT" file="app_top_left.png" /> <structure type="chrome_scaled_image" name="IDR_APP_TOP_RIGHT" file="app_top_right.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_B" file="bubble_bottom.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_BL" file="bubble_bottom_left.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_BR" file="bubble_bottom_right.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_B_ARROW" file="bubble_pointer_bottom.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_L" file="bubble_left.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_L_ARROW" file="bubble_pointer_left.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_R" file="bubble_right.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_R_ARROW" file="bubble_pointer_right.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_T" file="bubble_top.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_TL" file="bubble_top_left.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_TR" file="bubble_top_right.png" /> - <structure type="chrome_scaled_image" name="IDR_BUBBLE_T_ARROW" file="bubble_pointer_top.png" /> <structure type="chrome_scaled_image" name="IDR_BUTTON_DISABLED" file="common/button_inactive.png" /> <structure type="chrome_scaled_image" name="IDR_BUTTON_FOCUSED_HOVER" file="common/button_focused_hover.png" /> <structure type="chrome_scaled_image" name="IDR_BUTTON_FOCUSED_NORMAL" file="common/button_focused.png" /> @@ -216,30 +204,6 @@ <structure type="chrome_scaled_image" name="IDR_WINDOW_TOP_CENTER" file="common/window_top_center.png" /> <structure type="chrome_scaled_image" name="IDR_WINDOW_TOP_LEFT_CORNER" file="common/window_top_left_corner.png" /> <structure type="chrome_scaled_image" name="IDR_WINDOW_TOP_RIGHT_CORNER" file="common/window_top_right_corner.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM" file="common/window_bubble_shadow_big_bottom.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_LEFT" file="common/window_bubble_shadow_big_bottom_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_RIGHT" file="common/window_bubble_shadow_big_bottom_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_LEFT" file="common/window_bubble_shadow_big_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_RIGHT" file="common/window_bubble_shadow_big_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP" file="common/window_bubble_shadow_big_top.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_LEFT" file="common/window_bubble_shadow_big_top_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_RIGHT" file="common/window_bubble_shadow_big_top_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_BOTTOM" file="common/window_bubble_shadow_spike_big_bottom.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_LEFT" file="common/window_bubble_shadow_spike_big_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_RIGHT" file="common/window_bubble_shadow_spike_big_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_TOP" file="common/window_bubble_shadow_spike_big_top.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM" file="common/window_bubble_shadow_small_bottom.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT" file="common/window_bubble_shadow_small_bottom_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT" file="common/window_bubble_shadow_small_bottom_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT" file="common/window_bubble_shadow_small_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT" file="common/window_bubble_shadow_small_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP" file="common/window_bubble_shadow_small_top.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT" file="common/window_bubble_shadow_small_top_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT" file="common/window_bubble_shadow_small_top_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_BOTTOM" file="common/window_bubble_shadow_spike_small_bottom.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_LEFT" file="common/window_bubble_shadow_spike_small_left.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_RIGHT" file="common/window_bubble_shadow_spike_small_right.png" /> - <structure type="chrome_scaled_image" name="IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_TOP" file="common/window_bubble_shadow_spike_small_top.png" /> </structures> </release> </grit> diff --git a/chromium/ui/views/selection_controller.cc b/chromium/ui/views/selection_controller.cc index 61d83ff3750..b04a99300b4 100644 --- a/chromium/ui/views/selection_controller.cc +++ b/chromium/ui/views/selection_controller.cc @@ -75,7 +75,8 @@ bool SelectionController::OnMousePressed( initial_focus_state == InitialFocusStateOnMousePress::UNFOCUSED) { SelectAll(); } else if (PlatformStyle::kSelectWordOnRightClick && - !render_text->IsPointInSelection(event.location())) { + !render_text->IsPointInSelection(event.location()) && + IsInsideText(event.location())) { SelectWord(event.location()); } } @@ -223,4 +224,16 @@ void SelectionController::SelectThroughLastDragLocation() { delegate_->OnAfterPointerAction(false, true); } +bool SelectionController::IsInsideText(const gfx::Point& point) { + gfx::RenderText* render_text = GetRenderText(); + std::vector<gfx::Rect> bounds_rects = render_text->GetSubstringBounds( + gfx::Range(0, render_text->text().length())); + + for (const auto& bounds : bounds_rects) + if (bounds.Contains(point)) + return true; + + return false; +} + } // namespace views diff --git a/chromium/ui/views/selection_controller.h b/chromium/ui/views/selection_controller.h index 46ec0607d11..c4382f1bf89 100644 --- a/chromium/ui/views/selection_controller.h +++ b/chromium/ui/views/selection_controller.h @@ -82,6 +82,9 @@ class VIEWS_EXPORT SelectionController { // |last_drag_location_|. Can be called asynchronously, through a timer. void SelectThroughLastDragLocation(); + // Returns whether |point| is inside any substring of the text. + bool IsInsideText(const gfx::Point& point); + // A timer and point used to modify the selection when dragging. base::RepeatingTimer drag_selection_timer_; gfx::Point last_drag_location_; diff --git a/chromium/ui/views/selection_controller_unittest.cc b/chromium/ui/views/selection_controller_unittest.cc new file mode 100644 index 00000000000..e28d5302029 --- /dev/null +++ b/chromium/ui/views/selection_controller_unittest.cc @@ -0,0 +1,189 @@ +// 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/selection_controller.h" +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/render_text.h" +#include "ui/views/metrics.h" +#include "ui/views/selection_controller_delegate.h" +#include "ui/views/style/platform_style.h" + +namespace views { +namespace { + +const gfx::Point CenterLeft(const gfx::Rect& rect) { + return gfx::Point(rect.x(), rect.CenterPoint().y()); +} + +const gfx::Point CenterRight(const gfx::Rect& rect) { + return gfx::Point(rect.right(), rect.CenterPoint().y()); +} + +class TestSelectionControllerDelegate : public SelectionControllerDelegate { + public: + TestSelectionControllerDelegate(gfx::RenderText* render_text) + : render_text_(render_text) {} + ~TestSelectionControllerDelegate() override = default; + + gfx::RenderText* GetRenderTextForSelectionController() override { + return render_text_; + } + + bool IsReadOnly() const override { return true; } + bool SupportsDrag() const override { return true; } + bool HasTextBeingDragged() const override { return false; } + void SetTextBeingDragged(bool value) override {} + int GetViewHeight() const override { + return render_text_->GetStringSize().height(); + } + int GetViewWidth() const override { + return render_text_->GetStringSize().width(); + } + int GetDragSelectionDelay() const override { return 0; } + void OnBeforePointerAction() override {} + void OnAfterPointerAction(bool text_changed, + bool selection_changed) override {} + bool PasteSelectionClipboard() override { return false; } + void UpdateSelectionClipboard() override {} + + private: + gfx::RenderText* render_text_; + + DISALLOW_COPY_AND_ASSIGN(TestSelectionControllerDelegate); +}; + +class SelectionControllerTest : public ::testing::Test { + public: + void SetUp() override { + render_text_ = gfx::RenderText::CreateHarfBuzzInstance(); + delegate_ = + std::make_unique<TestSelectionControllerDelegate>(render_text_.get()); + controller_ = std::make_unique<SelectionController>(delegate_.get()); + } + + SelectionControllerTest() = default; + ~SelectionControllerTest() override = default; + + void SetText(const std::string& text) { + render_text_->SetText(base::ASCIIToUTF16(text)); + } + + std::string GetSelectedText() { + return base::UTF16ToASCII( + render_text_->GetTextFromRange(render_text_->selection())); + } + + void LeftMouseDown(const gfx::Point& location, bool focused = false) { + PressMouseButton(location, ui::EF_LEFT_MOUSE_BUTTON, focused); + } + + void LeftMouseUp() { ReleaseMouseButton(ui::EF_LEFT_MOUSE_BUTTON); } + + void DragMouse(const gfx::Point& location) { + mouse_location_ = location; + controller_->OnMouseDragged(ui::MouseEvent(ui::ET_MOUSE_DRAGGED, location, + location, last_event_time_, + mouse_flags_, 0)); + } + + void RightMouseDown(const gfx::Point& location, bool focused = false) { + PressMouseButton(location, ui::EF_RIGHT_MOUSE_BUTTON, focused); + } + + void RightMouseUp() { ReleaseMouseButton(ui::EF_RIGHT_MOUSE_BUTTON); } + + const gfx::Rect BoundsOfChar(int index) { + return render_text_->GetSubstringBounds(gfx::Range(index, index + 1))[0]; + } + + private: + void PressMouseButton(const gfx::Point& location, int button, bool focused) { + DCHECK(!(mouse_flags_ & button)); + mouse_flags_ |= button; + mouse_location_ = location; + // Ensure that mouse presses are spaced apart by at least the double-click + // interval to avoid triggering a double-click. + last_event_time_ += + base::TimeDelta::FromMilliseconds(views::GetDoubleClickInterval() + 1); + controller_->OnMousePressed( + ui::MouseEvent(ui::ET_MOUSE_PRESSED, location, location, + last_event_time_, mouse_flags_, button), + false, + focused ? SelectionController::FOCUSED + : SelectionController::UNFOCUSED); + } + + void ReleaseMouseButton(int button) { + DCHECK(mouse_flags_ & button); + mouse_flags_ &= ~button; + controller_->OnMouseReleased( + ui::MouseEvent(ui::ET_MOUSE_RELEASED, mouse_location_, mouse_location_, + last_event_time_, mouse_flags_, button)); + } + + std::unique_ptr<gfx::RenderText> render_text_; + std::unique_ptr<TestSelectionControllerDelegate> delegate_; + std::unique_ptr<SelectionController> controller_; + + int mouse_flags_ = 0; + gfx::Point mouse_location_; + base::TimeTicks last_event_time_; + + DISALLOW_COPY_AND_ASSIGN(SelectionControllerTest); +}; + +TEST_F(SelectionControllerTest, ClickAndDragToSelect) { + SetText("abc def"); + EXPECT_EQ("", GetSelectedText()); + + LeftMouseDown(CenterLeft(BoundsOfChar(0))); + DragMouse(CenterRight(BoundsOfChar(0))); + EXPECT_EQ("a", GetSelectedText()); + + DragMouse(CenterRight(BoundsOfChar(2))); + EXPECT_EQ("abc", GetSelectedText()); + + LeftMouseUp(); + EXPECT_EQ("abc", GetSelectedText()); + + LeftMouseDown(CenterRight(BoundsOfChar(3))); + EXPECT_EQ("", GetSelectedText()); + + DragMouse(CenterRight(BoundsOfChar(4))); + EXPECT_EQ("d", GetSelectedText()); +} + +TEST_F(SelectionControllerTest, RightClickWhenUnfocused) { + SetText("abc def"); + + RightMouseDown(CenterRight(BoundsOfChar(0))); + if (PlatformStyle::kSelectAllOnRightClickWhenUnfocused) + EXPECT_EQ("abc def", GetSelectedText()); + else + EXPECT_EQ("", GetSelectedText()); +} + +TEST_F(SelectionControllerTest, RightClickSelectsWord) { + SetText("abc def"); + RightMouseDown(CenterRight(BoundsOfChar(5)), true); + if (PlatformStyle::kSelectWordOnRightClick) + EXPECT_EQ("def", GetSelectedText()); + else + EXPECT_EQ("", GetSelectedText()); +} + +// Regression test for https://crbug.com/856609 +TEST_F(SelectionControllerTest, RightClickPastEndDoesntSelectLastWord) { + SetText("abc def"); + + RightMouseDown(CenterRight(BoundsOfChar(6)), true); + EXPECT_EQ("", GetSelectedText()); +} + +} // namespace +} // namespace views diff --git a/chromium/ui/views/touchui/touch_selection_controller_impl.cc b/chromium/ui/views/touchui/touch_selection_controller_impl.cc index 6e5d1816bff..ae97f1994e4 100644 --- a/chromium/ui/views/touchui/touch_selection_controller_impl.cc +++ b/chromium/ui/views/touchui/touch_selection_controller_impl.cc @@ -9,6 +9,7 @@ #include "base/time/time.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/env.h" +#include "ui/aura/mus/window_port_mus.h" #include "ui/aura/window.h" #include "ui/aura/window_targeter.h" #include "ui/base/resource/resource_bundle.h" @@ -129,10 +130,11 @@ gfx::Image* GetHandleImage(gfx::SelectionBound::Type bound_type) { } // Calculates the bounds of the widget containing the selection handle based -// on the SelectionBound's type and location +// on the SelectionBound's type and location. gfx::Rect GetSelectionWidgetBounds(const gfx::SelectionBound& bound) { gfx::Size image_size = GetHandleImage(bound.type())->Size(); int widget_width = image_size.width() + 2 * kSelectionHandleHorizPadding; + // Extend the widget height to handle touch events below the painted image. int widget_height = bound.GetHeight() + image_size.height() + kSelectionHandleVerticalVisualOffset + kSelectionHandleVertPadding; @@ -200,21 +202,40 @@ gfx::Rect BoundToRect(const gfx::SelectionBound& bound) { bound.edge_bottom_rounded()); } -} // namespace - -namespace views { - -typedef TouchSelectionControllerImpl::EditingHandleView EditingHandleView; - -// A WindowTargeter that shifts the hit-test target down - away from the text -// cursor and expanding the hit-test area just below the visible drag handle. +// A WindowTargeter that insets the top of the touch handle's hit-test region. +// This ensures that the client receives touch events above the painted image. +// The widget extends its height to handle touch events below the painted image. class TouchHandleWindowTargeter : public aura::WindowTargeter { public: - void SetHitTestOffset(int offset) { - SetInsets(gfx::Insets(offset, 0, -offset, 0)); + explicit TouchHandleWindowTargeter(aura::Window* window) : window_(window) {} + ~TouchHandleWindowTargeter() override = default; + + void SetTopInset(int inset) { SetInsets(gfx::Insets(inset, 0, 0, 0)); } + + // aura::WindowTargeter: + void OnSetInsets(const gfx::Insets& last_mouse_extend, + const gfx::Insets& last_touch_extend) override { + // Send the targeter insets to the window service if this is a mus client. + // This helps the window service send events directly to the text window. + // OnSetInsets is generally only called when the insets actually change. + if (window_->env()->mode() == aura::Env::Mode::MUS) { + gfx::Rect mask(window_->bounds().size()); + mask.Inset(touch_extend()); + aura::WindowPortMus::Get(window_->GetRootWindow())->SetHitTestMask(mask); + } } + + private: + aura::Window* window_; + DISALLOW_COPY_AND_ASSIGN(TouchHandleWindowTargeter); }; +} // namespace + +namespace views { + +using EditingHandleView = TouchSelectionControllerImpl::EditingHandleView; + // A View that displays the text selection handle. class TouchSelectionControllerImpl::EditingHandleView : public views::WidgetDelegateView { @@ -230,8 +251,8 @@ class TouchSelectionControllerImpl::EditingHandleView widget_.reset(CreateTouchSelectionPopupWidget(context, this)); aura::Window* window = widget_->GetNativeWindow(); - targeter_ = new TouchHandleWindowTargeter(); - window->SetEventTargeter(std::unique_ptr<ui::EventTargeter>(targeter_)); + targeter_ = new TouchHandleWindowTargeter(window); + window->SetEventTargeter(std::unique_ptr<aura::WindowTargeter>(targeter_)); // We are owned by the TouchSelectionControllerImpl. set_owned_by_client(); @@ -357,8 +378,9 @@ class TouchSelectionControllerImpl::EditingHandleView wm::ConvertPointFromScreen(window, &edge_bottom); selection_bound_.SetEdge(gfx::PointF(edge_top), gfx::PointF(edge_bottom)); } - targeter_->SetHitTestOffset(selection_bound_.GetHeight() + - kSelectionHandleVerticalVisualOffset); + + targeter_->SetTopInset(selection_bound_.GetHeight() + + kSelectionHandleVerticalVisualOffset); } void SetDrawInvisible(bool draw_invisible) { diff --git a/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc b/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc index df2048dfd10..7a99a8f697c 100644 --- a/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc +++ b/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc @@ -18,7 +18,7 @@ #include "ui/gfx/geometry/size.h" #include "ui/gfx/text_utils.h" #include "ui/strings/grit/ui_strings.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/layout/box_layout.h" diff --git a/chromium/ui/views/vector_icons/ic_close.icon b/chromium/ui/views/vector_icons/ic_close.icon index 686da72bcda..f298b77712b 100644 --- a/chromium/ui/views/vector_icons/ic_close.icon +++ b/chromium/ui/views/vector_icons/ic_close.icon @@ -31,3 +31,18 @@ LINE_TO, 17.59f, 19, LINE_TO, 19, 17.59f, LINE_TO, 13.41f, 12, CLOSE + +CANVAS_DIMENSIONS, 20, +MOVE_TO, 16, 5.41f, +LINE_TO, 14.59f, 4, +LINE_TO, 10, 8.59f, +LINE_TO, 5.41f, 4, +LINE_TO, 4, 5.41f, +LINE_TO, 8.59f, 10, +LINE_TO, 4, 14.59f, +LINE_TO, 5.41f, 16, +LINE_TO, 10, 11.41f, +LINE_TO, 14.59f, 16, +LINE_TO, 16, 14.59f, +LINE_TO, 11.41f, 10, +CLOSE diff --git a/chromium/ui/views/vector_icons/menu_drop_arrow.icon b/chromium/ui/views/vector_icons/menu_drop_arrow.icon new file mode 100644 index 00000000000..5f9587aa6ee --- /dev/null +++ b/chromium/ui/views/vector_icons/menu_drop_arrow.icon @@ -0,0 +1,9 @@ +// 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. + +CANVAS_DIMENSIONS, 11, +MOVE_TO, 1, 3, +R_H_LINE_TO, 10, +R_LINE_TO, -5, 6, +CLOSE diff --git a/chromium/ui/views/view.cc b/chromium/ui/views/view.cc index c53f79d424e..d238fad21fd 100644 --- a/chromium/ui/views/view.cc +++ b/chromium/ui/views/view.cc @@ -419,6 +419,10 @@ gfx::Rect View::GetBoundsInScreen() const { return gfx::Rect(origin, size()); } +gfx::Rect View::GetAnchorBoundsInScreen() const { + return GetBoundsInScreen(); +} + gfx::Size View::GetPreferredSize() const { if (preferred_size_) return *preferred_size_; diff --git a/chromium/ui/views/view.h b/chromium/ui/views/view.h index cbc9e1866b4..4248609dd3c 100644 --- a/chromium/ui/views/view.h +++ b/chromium/ui/views/view.h @@ -363,6 +363,11 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, // Return the bounds of the View in screen coordinate system. gfx::Rect GetBoundsInScreen() const; + // Return the bounds that an anchored widget should anchor to. These can be + // different from |GetBoundsInScreen()| when a view is larger than its visible + // size, for instance to provide a larger hittable area. + virtual gfx::Rect GetAnchorBoundsInScreen() const; + // Returns the baseline of this view, or -1 if this view has no baseline. The // return value is relative to the preferred height. virtual int GetBaseline() const; @@ -1827,7 +1832,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, // Observers ------------------------------------------------------------- - base::ObserverList<ViewObserver> observers_; + base::ObserverList<ViewObserver>::Unchecked observers_; DISALLOW_COPY_AND_ASSIGN(View); }; diff --git a/chromium/ui/views/view_properties.cc b/chromium/ui/views/view_properties.cc index d579a16c8cb..e55acc0ca94 100644 --- a/chromium/ui/views/view_properties.cc +++ b/chromium/ui/views/view_properties.cc @@ -4,12 +4,15 @@ #include "ui/views/view_properties.h" +#include "ui/base/hit_test.h" #include "ui/gfx/geometry/insets.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #if !defined(USE_AURA) -// aura_constants.cc also declared the bool ClassProperty type. +// aura_constants.cc also declared the bool and int[32_t] +// ClassProperty type. DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, bool); +DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, int); #endif DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*); @@ -19,6 +22,7 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, namespace views { +DEFINE_UI_CLASS_PROPERTY_KEY(int, kHitTestComponentKey, HTNOWHERE); DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Insets, kMarginsKey, nullptr); DEFINE_UI_CLASS_PROPERTY_KEY(views::BubbleDialogDelegateView*, kAnchoredDialogKey, diff --git a/chromium/ui/views/view_properties.h b/chromium/ui/views/view_properties.h index 4e706ec7a02..2896ee94512 100644 --- a/chromium/ui/views/view_properties.h +++ b/chromium/ui/views/view_properties.h @@ -16,6 +16,10 @@ namespace views { class BubbleDialogDelegateView; +// The hit test component (e.g. HTCLIENT) for a View in a window frame. Defaults +// to HTNOWHERE. +VIEWS_EXPORT extern const ui::ClassProperty<int>* const kHitTestComponentKey; + // A property to store margins around the outer perimeter of the view. Margins // are outside the bounds of the view. This is used by various layout managers // to position views with the proper spacing between them. diff --git a/chromium/ui/views/view_targeter_unittest.cc b/chromium/ui/views/view_targeter_unittest.cc index 1427abd4782..9998b3b0d24 100644 --- a/chromium/ui/views/view_targeter_unittest.cc +++ b/chromium/ui/views/view_targeter_unittest.cc @@ -8,6 +8,7 @@ #include "base/memory/ptr_util.h" #include "ui/events/event_targeter.h" #include "ui/events/event_utils.h" +#include "ui/events/keycodes/dom/dom_code.h" #include "ui/gfx/path.h" #include "ui/views/masked_targeter_delegate.h" #include "ui/views/test/views_test_base.h" @@ -141,7 +142,7 @@ TEST_F(ViewTargeterTest, ViewTargeterForKeyEvents) { static_cast<internal::RootView*>(widget.GetRootView()); ui::EventTargeter* targeter = root_view->targeter(); - ui::KeyEvent key_event('a', ui::VKEY_A, ui::EF_NONE); + ui::KeyEvent key_event('a', ui::VKEY_A, ui::DomCode::NONE, ui::EF_NONE); // The focused view should be the initial target of the event. ui::EventTarget* current_target = targeter->FindTargetForEvent(root_view, diff --git a/chromium/ui/views/views_test_suite.cc b/chromium/ui/views/views_test_suite.cc index bd46fca6a32..e579a7d0503 100644 --- a/chromium/ui/views/views_test_suite.cc +++ b/chromium/ui/views/views_test_suite.cc @@ -45,10 +45,6 @@ void ViewsTestSuite::Initialize() { base::TestSuite::Initialize(); gl::GLSurfaceTestSupport::InitializeOneOff(); -#if defined(OS_MACOSX) - gpu::ImageTransportSurface::SetAllowOSMesaForTesting(true); -#endif - ui::RegisterPathProvider(); base::FilePath ui_test_pak_path; 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 7e79906a819..aceec981589 100644 --- a/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm +++ b/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm @@ -349,9 +349,10 @@ TEST_F(AXNativeWidgetMacTest, ChildrenAttribute) { TEST_F(AXNativeWidgetMacTest, ParentAttribute) { Textfield* child = AddChildTextfield(widget()->GetContentsView()->size()); - // Views with Widget parents will have a NSWindow parent. + // Views with Widget parents will have a NSAccessibilityGroupRole parent. + // See https://crbug.com/875843 for more information. EXPECT_NSEQ( - NSAccessibilityWindowRole, + NSAccessibilityGroupRole, [AXParent() accessibilityAttributeValue:NSAccessibilityRoleAttribute]); // Views with non-Widget parents will have the role of the parent view. @@ -367,7 +368,7 @@ TEST_F(AXNativeWidgetMacTest, ParentAttribute) { // Test an ignored role parent is skipped in favor of the grandparent. parent->set_role(ax::mojom::Role::kIgnored); EXPECT_NSEQ( - NSAccessibilityWindowRole, + NSAccessibilityGroupRole, [AXParent() accessibilityAttributeValue:NSAccessibilityRoleAttribute]); } diff --git a/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc b/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc index c434a9fcfbc..86578b995a2 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc @@ -5,6 +5,7 @@ #include "ui/views/widget/desktop_aura/desktop_capture_client.h" #include "ui/aura/client/capture_client_observer.h" +#include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/aura/window_tracker.h" @@ -58,7 +59,8 @@ void DesktopCaptureClient::SetCapture(aura::Window* new_capture_window) { // committing |capture_window_|. aura::WindowTracker tracker; tracker.Add(new_capture_window); - ui::GestureRecognizer::Get()->CancelActiveTouchesExcept(new_capture_window); + new_capture_window->env()->gesture_recognizer()->CancelActiveTouchesExcept( + new_capture_window); if (!tracker.Contains(new_capture_window)) new_capture_window = nullptr; } diff --git a/chromium/ui/views/widget/desktop_aura/desktop_capture_client.h b/chromium/ui/views/widget/desktop_aura/desktop_capture_client.h index 1f19ec64fa5..8c0811bc2ec 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_capture_client.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_capture_client.h @@ -58,7 +58,7 @@ class VIEWS_EXPORT DesktopCaptureClient : public aura::client::CaptureClient { // Set of DesktopCaptureClients. static CaptureClients* capture_clients_; - base::ObserverList<aura::client::CaptureClientObserver> observers_; + base::ObserverList<aura::client::CaptureClientObserver>::Unchecked observers_; DISALLOW_COPY_AND_ASSIGN(DesktopCaptureClient); }; 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 f1e8c4eff5a..00073d3d3bf 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 @@ -700,8 +700,8 @@ void DesktopDragDropClientAuraX11::OnXdndDrop( xwindow_, target_current_context_->fetched_targets())); ui::DropTargetEvent event(data, - target_window_location_, - target_window_root_location_, + gfx::PointF(target_window_location_), + gfx::PointF(target_window_root_location_), target_current_context_->GetDragOperation()); if (target_current_context_->source_client()) { event.set_flags(target_current_context_->source_client() @@ -1095,8 +1095,8 @@ void DesktopDragDropClientAuraX11::DragTranslate( event->reset(new ui::DropTargetEvent( *(data->get()), - location, - root_location, + gfx::PointF(location), + gfx::PointF(root_location), drag_op)); if (target_current_context_->source_client()) { (*event)->set_flags( 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 3a7edcd000f..99c525f3219 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 @@ -149,8 +149,8 @@ void DesktopDropTargetWin::Translate( aura::Window::ConvertPointToTarget(root_window_, target_window_, &location); event->reset(new ui::DropTargetEvent( *(data->get()), - location, - root_location, + gfx::PointF(location), + gfx::PointF(root_location), ui::DragDropTypes::DropEffectToDragOperation(effect))); (*event)->set_flags(ConvertKeyStateToAuraEventFlags(key_state)); if (target_window_changed) diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc index b482b7781c4..01a429a67e3 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc @@ -8,12 +8,13 @@ #include "base/macros.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/drag_drop_client.h" #include "ui/aura/client/focus_client.h" #include "ui/aura/client/window_parenting_client.h" +#include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/aura/window_observer.h" #include "ui/aura/window_occlusion_tracker.h" @@ -320,8 +321,12 @@ void DesktopNativeWidgetAura::OnHostClosed() { desktop_window_tree_host_ = NULL; content_window_ = NULL; + // |OnNativeWidgetDestroyed| may delete |this| if the object does not own + // itself. + bool should_delete_this = + (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET); native_widget_delegate_->OnNativeWidgetDestroyed(); - if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + if (should_delete_this) delete this; } @@ -746,11 +751,11 @@ void DesktopNativeWidgetAura::CloseNow() { desktop_window_tree_host_->CloseNow(); } -void DesktopNativeWidgetAura::Show() { +void DesktopNativeWidgetAura::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { if (!content_window_) return; - desktop_window_tree_host_->AsWindowTreeHost()->Show(); - content_window_->Show(); + desktop_window_tree_host_->Show(show_state, restore_bounds); } void DesktopNativeWidgetAura::Hide() { @@ -760,23 +765,6 @@ void DesktopNativeWidgetAura::Hide() { content_window_->Hide(); } -void DesktopNativeWidgetAura::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - // IsVisible() should check the same objects here for visibility. - if (!content_window_) - return; - desktop_window_tree_host_->ShowMaximizedWithBounds(restored_bounds); - content_window_->Show(); -} - -void DesktopNativeWidgetAura::ShowWithWindowState(ui::WindowShowState state) { - // IsVisible() should check the same objects here for visibility. - if (!content_window_) - return; - desktop_window_tree_host_->ShowWindowWithState(state); - content_window_->Show(); -} - bool DesktopNativeWidgetAura::IsVisible() const { // The objects checked here should be the same objects changed in // ShowWithWindowState and ShowMaximizedWithBounds. For example, MS Windows @@ -912,6 +900,11 @@ bool DesktopNativeWidgetAura::IsMouseEventsEnabled() const { return cursor_client ? cursor_client->IsMouseEventsEnabled() : true; } +bool DesktopNativeWidgetAura::IsMouseButtonDown() const { + return content_window_ ? content_window_->env()->IsMouseButtonDown() + : aura::Env::GetInstance()->IsMouseButtonDown(); +} + void DesktopNativeWidgetAura::ClearNativeFocus() { desktop_window_tree_host_->ClearNativeFocus(); @@ -977,8 +970,12 @@ bool DesktopNativeWidgetAura::IsTranslucentWindowOpacitySupported() const { desktop_window_tree_host_->IsTranslucentWindowOpacitySupported(); } +ui::GestureRecognizer* DesktopNativeWidgetAura::GetGestureRecognizer() { + return content_window_->env()->gesture_recognizer(); +} + void DesktopNativeWidgetAura::OnSizeConstraintsChanged() { - int32_t behavior = ui::mojom::kResizeBehaviorNone; + int32_t behavior = ws::mojom::kResizeBehaviorNone; if (GetWidget()->widget_delegate()) behavior = GetWidget()->widget_delegate()->GetResizeBehavior(); content_window_->SetProperty(aura::client::kResizeBehaviorKey, behavior); @@ -1113,7 +1110,7 @@ bool DesktopNativeWidgetAura::ShouldActivate() const { } //////////////////////////////////////////////////////////////////////////////// -// DesktopNativeWidgetAura, wmActivationChangeObserver implementation: +// DesktopNativeWidgetAura, wm::ActivationChangeObserver implementation: void DesktopNativeWidgetAura::OnWindowActivated( wm::ActivationChangeObserver::ActivationReason reason, diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h index 40d5b1bfb7e..be95f3ad684 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h @@ -144,10 +144,9 @@ class VIEWS_EXPORT DesktopNativeWidgetAura void SetShape(std::unique_ptr<Widget::ShapeRects> shape) override; void Close() override; void CloseNow() override; - void Show() override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; void Hide() override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; - void ShowWithWindowState(ui::WindowShowState state) override; bool IsVisible() const override; void Activate() override; void Deactivate() override; @@ -174,6 +173,7 @@ class VIEWS_EXPORT DesktopNativeWidgetAura void SchedulePaintInRect(const gfx::Rect& rect) override; void SetCursor(gfx::NativeCursor cursor) override; bool IsMouseEventsEnabled() const override; + bool IsMouseButtonDown() const override; void ClearNativeFocus() override; gfx::Rect GetWorkAreaBoundsInScreen() const override; Widget::MoveLoopResult RunMoveLoop( @@ -186,6 +186,7 @@ class VIEWS_EXPORT DesktopNativeWidgetAura void SetVisibilityAnimationTransition( Widget::VisibilityTransition transition) override; bool IsTranslucentWindowOpacitySupported() const override; + ui::GestureRecognizer* GetGestureRecognizer() override; void OnSizeConstraintsChanged() override; void RepostNativeEvent(gfx::NativeEvent native_event) override; std::string GetName() const override; diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc index 1e7210e7601..9d4d564ee7a 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc @@ -33,6 +33,8 @@ #include "ui/views/window/dialog_delegate.h" #if defined(OS_WIN) +#include <windows.h> + #include "ui/base/view_prop.h" #include "ui/base/win/window_event_target.h" #include "ui/views/win/hwnd_util.h" @@ -122,6 +124,7 @@ TEST_F(DesktopNativeWidgetAuraTest, NativeViewNoActivate) { ->GetFocusedWindow()); } +#if defined(OS_WIN) // Verifies that if the DesktopWindowTreeHost is already shown, the native view // still reports not visible as we haven't shown the content window. TEST_F(DesktopNativeWidgetAuraTest, WidgetNotVisibleOnlyWindowTreeHostShown) { @@ -131,11 +134,11 @@ TEST_F(DesktopNativeWidgetAuraTest, WidgetNotVisibleOnlyWindowTreeHostShown) { init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; init_params.native_widget = new DesktopNativeWidgetAura(&widget); widget.Init(init_params); - DesktopNativeWidgetAura* desktop_native_widget_aura = - static_cast<DesktopNativeWidgetAura*>(widget.native_widget()); - desktop_native_widget_aura->host()->Show(); + ShowWindow(widget.GetNativeView()->GetHost()->GetAcceleratedWidget(), + SW_SHOWNORMAL); EXPECT_FALSE(widget.IsVisible()); } +#endif TEST_F(DesktopNativeWidgetAuraTest, DesktopAuraWindowShowFrameless) { Widget widget; diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc index 546411eaf7e..08eadbeedc7 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc @@ -9,7 +9,7 @@ #include <memory> #include "base/macros.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" @@ -315,7 +315,7 @@ TEST_F(DesktopScreenX11Test, DoubleClickHeaderMaximizes) { aura::Window* window = widget->GetNativeWindow(); window->SetProperty(aura::client::kResizeBehaviorKey, - ui::mojom::kResizeBehaviorCanMaximize); + ws::mojom::kResizeBehaviorCanMaximize); // Cast to superclass as DesktopWindowTreeHostX11 hide IsMaximized DesktopWindowTreeHost* rwh = @@ -341,7 +341,7 @@ TEST_F(DesktopScreenX11Test, DoubleClickTwoDifferentTargetsDoesntMaximizes) { aura::Window* window = widget->GetNativeWindow(); window->SetProperty(aura::client::kResizeBehaviorKey, - ui::mojom::kResizeBehaviorCanMaximize); + ws::mojom::kResizeBehaviorCanMaximize); // Cast to superclass as DesktopWindowTreeHostX11 hide IsMaximized DesktopWindowTreeHost* rwh = @@ -371,7 +371,7 @@ TEST_F(DesktopScreenX11Test, RightClickDuringDoubleClickDoesntMaximize) { aura::Window* window = widget->GetNativeWindow(); window->SetProperty(aura::client::kResizeBehaviorKey, - ui::mojom::kResizeBehaviorCanMaximize); + ws::mojom::kResizeBehaviorCanMaximize); // Cast to superclass as DesktopWindowTreeHostX11 hide IsMaximized DesktopWindowTreeHost* rwh = static_cast<DesktopWindowTreeHost*>( diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h index a6e2eca3abd..8a909991f7d 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h @@ -75,8 +75,24 @@ class VIEWS_EXPORT DesktopWindowTreeHost { virtual aura::WindowTreeHost* AsWindowTreeHost() = 0; - virtual void ShowWindowWithState(ui::WindowShowState show_state) = 0; - virtual void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) = 0; + // There are two distinct ways for DesktopWindowTreeHosts's to be shown: + // 1. This function is called. As this function is specific to + // DesktopWindowTreeHost, it is only called from DesktopNativeWidgetAura. + // 2. Calling Show() directly on the WindowTreeHost associated with this + // DesktopWindowTreeHost. This is very rare. In general, calls go through + // Widget, which ends up in (1). + // + // Implementations must deal with these two code paths. In general, this is + // done by having the WindowTreeHost subclass override ShowImpl() to call this + // function: Show(ui::SHOW_STATE_NORMAL, gfx::Rect()). A subtle + // ramification is the implementation of this function can *not* call + // WindowTreeHost::Show(), and the implementation of this must perform the + // same work as WindowTreeHost::Show(). This means setting the visibility of + // the compositor, window() and DesktopNativeWidgetAura::content_window() + // appropriately. Some subclasses set the visibility of window() in the + // constructor and assume it's always true. + virtual void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) = 0; virtual bool IsVisible() const = 0; diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc index 2081478b174..dcf1d7d40c2 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc @@ -135,6 +135,8 @@ void DesktopWindowTreeHostPlatform::CloseNow() { if (!weak_ref || got_on_closed_) return; + native_widget_delegate_->OnNativeWidgetDestroying(); + got_on_closed_ = true; desktop_native_widget_aura_->OnHostClosed(); } @@ -143,8 +145,11 @@ aura::WindowTreeHost* DesktopWindowTreeHostPlatform::AsWindowTreeHost() { return this; } -void DesktopWindowTreeHostPlatform::ShowWindowWithState( - ui::WindowShowState show_state) { +void DesktopWindowTreeHostPlatform::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { + if (show_state == ui::SHOW_STATE_MAXIMIZED && !restore_bounds.IsEmpty()) + platform_window()->SetRestoredBoundsInPixels(ToPixelRect(restore_bounds)); + if (compositor()) { platform_window()->Show(); compositor()->SetVisible(true); @@ -180,12 +185,8 @@ void DesktopWindowTreeHostPlatform::ShowWindowWithState( native_widget_delegate_->SetInitialFocus( IsActive() ? show_state : ui::SHOW_STATE_INACTIVE); } -} -void DesktopWindowTreeHostPlatform::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - // TODO: support |restored_bounds|. - ShowWindowWithState(ui::SHOW_STATE_MAXIMIZED); + desktop_native_widget_aura_->content_window()->Show(); } bool DesktopWindowTreeHostPlatform::IsVisible() const { @@ -253,8 +254,12 @@ gfx::Rect DesktopWindowTreeHostPlatform::GetClientAreaBoundsInScreen() const { } gfx::Rect DesktopWindowTreeHostPlatform::GetRestoredBounds() const { - NOTIMPLEMENTED_LOG_ONCE(); - return gfx::Rect(0, 0, 640, 840); + gfx::Rect restored_bounds = platform_window()->GetRestoredBoundsInPixels(); + // When window is resized, |restored bounds| is not set and empty. + // If |restored bounds| is empty, it returns the current window size. + gfx::Rect bounds = + !restored_bounds.IsEmpty() ? restored_bounds : GetBoundsInPixels(); + return ToDIPRect(bounds); } std::string DesktopWindowTreeHostPlatform::GetWorkspace() const { @@ -470,10 +475,6 @@ void DesktopWindowTreeHostPlatform::OnCloseRequest() { GetWidget()->Close(); } -void DesktopWindowTreeHostPlatform::OnAcceleratedWidgetDestroying() { - native_widget_delegate_->OnNativeWidgetDestroying(); -} - void DesktopWindowTreeHostPlatform::OnActivationChanged(bool active) { is_active_ = active; aura::WindowTreeHostPlatform::OnActivationChanged(active); @@ -495,6 +496,20 @@ Widget* DesktopWindowTreeHostPlatform::GetWidget() { return native_widget_delegate_->AsWidget(); } +gfx::Rect DesktopWindowTreeHostPlatform::ToDIPRect( + const gfx::Rect& rect_in_pixels) const { + gfx::RectF rect_in_dip = gfx::RectF(rect_in_pixels); + GetRootTransform().TransformRectReverse(&rect_in_dip); + return gfx::ToEnclosingRect(rect_in_dip); +} + +gfx::Rect DesktopWindowTreeHostPlatform::ToPixelRect( + const gfx::Rect& rect_in_dip) const { + gfx::RectF rect_in_pixels = gfx::RectF(rect_in_dip); + GetRootTransform().TransformRect(&rect_in_pixels); + return gfx::ToEnclosingRect(rect_in_pixels); +} + //////////////////////////////////////////////////////////////////////////////// // DesktopWindowTreeHost: diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h index cac4832e63a..b6219dd00a7 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h @@ -34,8 +34,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform void Close() override; void CloseNow() override; aura::WindowTreeHost* AsWindowTreeHost() override; - void ShowWindowWithState(ui::WindowShowState show_state) override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; bool IsVisible() const override; void SetSize(const gfx::Size& size) override; void StackAbove(aura::Window* window) override; @@ -93,7 +93,6 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform void OnClosed() override; void OnWindowStateChanged(ui::PlatformWindowState new_state) override; void OnCloseRequest() override; - void OnAcceleratedWidgetDestroying() override; void OnActivationChanged(bool active) override; private: @@ -101,6 +100,9 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform Widget* GetWidget(); + gfx::Rect ToDIPRect(const gfx::Rect& rect_in_pixels) const; + gfx::Rect ToPixelRect(const gfx::Rect& rect_in_dip) const; + internal::NativeWidgetDelegate* const native_widget_delegate_; DesktopNativeWidgetAura* const desktop_native_widget_aura_; 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 new file mode 100644 index 00000000000..76a55cfb1d8 --- /dev/null +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc @@ -0,0 +1,86 @@ +// 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/widget/desktop_aura/desktop_window_tree_host_platform.h" +#include "base/run_loop.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_observer.h" + +namespace views { + +namespace { + +class WidgetDestroyingObserver : public WidgetObserver { + public: + explicit WidgetDestroyingObserver(Widget* widget) : widget_(widget) { + widget_->AddObserver(this); + } + ~WidgetDestroyingObserver() override { + if (widget_) + widget_->RemoveObserver(this); + } + + // Returns immediately when |widget_| becomes NULL, otherwise a RunLoop is + // used until widget closing event is received. + void Wait() { + if (!on_widget_destroying_) + run_loop_.Run(); + } + + bool widget_destroying() const { return on_widget_destroying_; } + + private: + // views::WidgetObserver override: + void OnWidgetDestroying(Widget* widget) override { + DCHECK_EQ(widget_, widget); + widget_->RemoveObserver(this); + widget_ = nullptr; + on_widget_destroying_ = true; + if (run_loop_.running()) + run_loop_.Quit(); + } + + Widget* widget_; + base::RunLoop run_loop_; + bool on_widget_destroying_ = false; + + DISALLOW_COPY_AND_ASSIGN(WidgetDestroyingObserver); +}; + +std::unique_ptr<Widget> CreateWidgetWithNativeWidget() { + std::unique_ptr<Widget> widget(new Widget); + Widget::InitParams params(Widget::InitParams::TYPE_WINDOW); + params.delegate = nullptr; + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.remove_standard_frame = true; + params.native_widget = new DesktopNativeWidgetAura(widget.get()); + params.bounds = gfx::Rect(100, 100, 100, 100); + widget->Init(params); + return widget; +} + +} // namespace + +class DesktopWindowTreeHostPlatformTest : public ViewsTestBase { + public: + DesktopWindowTreeHostPlatformTest() {} + ~DesktopWindowTreeHostPlatformTest() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostPlatformTest); +}; + +TEST_F(DesktopWindowTreeHostPlatformTest, CallOnNativeWidgetDestroying) { + std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget(); + + WidgetDestroyingObserver observer( + widget->native_widget_private()->GetWidget()); + widget->CloseNow(); + + observer.Wait(); + EXPECT_TRUE(observer.widget_destroying()); +} + +} // 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 9c6b588516f..9ab47c86390 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 @@ -199,20 +199,19 @@ aura::WindowTreeHost* DesktopWindowTreeHostWin::AsWindowTreeHost() { return this; } -void DesktopWindowTreeHostWin::ShowWindowWithState( - ui::WindowShowState show_state) { +void DesktopWindowTreeHostWin::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { if (compositor()) compositor()->SetVisible(true); - message_handler_->ShowWindowWithState(show_state); -} -void DesktopWindowTreeHostWin::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - if (compositor()) - compositor()->SetVisible(true); - gfx::Rect pixel_bounds = - display::win::ScreenWin::DIPToScreenRect(GetHWND(), restored_bounds); - message_handler_->ShowMaximizedWithBounds(pixel_bounds); + gfx::Rect pixel_restore_bounds; + if (show_state == ui::SHOW_STATE_MAXIMIZED) { + pixel_restore_bounds = + display::win::ScreenWin::DIPToScreenRect(GetHWND(), restore_bounds); + } + message_handler_->Show(show_state, pixel_restore_bounds); + + content_window()->Show(); } bool DesktopWindowTreeHostWin::IsVisible() const { @@ -504,7 +503,7 @@ gfx::AcceleratedWidget DesktopWindowTreeHostWin::GetAcceleratedWidget() { } void DesktopWindowTreeHostWin::ShowImpl() { - message_handler_->Show(); + Show(ui::SHOW_STATE_NORMAL, gfx::Rect()); } void DesktopWindowTreeHostWin::HideImpl() { @@ -729,7 +728,8 @@ void DesktopWindowTreeHostWin::GetWindowMask(const gfx::Size& size, } } -bool DesktopWindowTreeHostWin::GetClientAreaInsets(gfx::Insets* insets) const { +bool DesktopWindowTreeHostWin::GetClientAreaInsets(gfx::Insets* insets, + HMONITOR monitor) const { return false; } 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 022de4037ff..364d6df184c 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 @@ -67,8 +67,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin void Close() override; void CloseNow() override; aura::WindowTreeHost* AsWindowTreeHost() override; - void ShowWindowWithState(ui::WindowShowState show_state) override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; bool IsVisible() const override; void SetSize(const gfx::Size& size) override; void StackAbove(aura::Window* window) override; @@ -169,7 +169,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin bool WillProcessWorkAreaChange() const override; int GetNonClientComponent(const gfx::Point& point) const override; void GetWindowMask(const gfx::Size& size, gfx::Path* path) override; - bool GetClientAreaInsets(gfx::Insets* insets) const override; + bool GetClientAreaInsets(gfx::Insets* insets, + HMONITOR monitor) const override; void GetMinMaxSize(gfx::Size* min_size, gfx::Size* max_size) const override; gfx::Size GetRootViewSize() const override; gfx::Size DIPToScreenSize(const gfx::Size& dip_size) const override; 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 22a9914957a..4e04197810d 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 @@ -20,6 +20,7 @@ #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/focus_client.h" +#include "ui/aura/null_window_targeter.h" #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/base/class_property.h" @@ -38,7 +39,6 @@ #include "ui/events/event_utils.h" #include "ui/events/keyboard_hook.h" #include "ui/events/keycodes/dom/dom_code.h" -#include "ui/events/null_event_targeter.h" #include "ui/events/platform/platform_event_source.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/size_conversions.h" @@ -514,16 +514,23 @@ aura::WindowTreeHost* DesktopWindowTreeHostX11::AsWindowTreeHost() { return this; } -void DesktopWindowTreeHostX11::ShowWindowWithState( - ui::WindowShowState show_state) { +void DesktopWindowTreeHostX11::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { if (compositor()) SetVisible(true); - if (!IsVisible() || !window_mapped_in_server_) + + if (!IsVisible()) MapWindow(show_state); switch (show_state) { case ui::SHOW_STATE_MAXIMIZED: Maximize(); + if (!restore_bounds.IsEmpty()) { + // Enforce |restored_bounds_in_pixels_| since calling Maximize() could + // have reset it. + restored_bounds_in_pixels_ = ToPixelRect(restore_bounds); + } + break; case ui::SHOW_STATE_MINIMIZED: Minimize(); @@ -536,18 +543,12 @@ void DesktopWindowTreeHostX11::ShowWindowWithState( } native_widget_delegate_->AsWidget()->SetInitialFocus(show_state); -} -void DesktopWindowTreeHostX11::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - ShowWindowWithState(ui::SHOW_STATE_MAXIMIZED); - // Enforce |restored_bounds_in_pixels_| since calling Maximize() could have - // reset it. - restored_bounds_in_pixels_ = ToPixelRect(restored_bounds); + content_window()->Show(); } bool DesktopWindowTreeHostX11::IsVisible() const { - return window_mapped_in_client_; + return window_mapped_in_client_ && !IsMinimized(); } void DesktopWindowTreeHostX11::SetSize(const gfx::Size& requested_size) { @@ -800,7 +801,7 @@ bool DesktopWindowTreeHostX11::IsActive() const { } void DesktopWindowTreeHostX11::Maximize() { - if (ui::HasWMSpecProperty(window_properties_in_server_, + if (ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"))) { // Unfullscreen the window if it is fullscreen. SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::None); @@ -826,31 +827,34 @@ void DesktopWindowTreeHostX11::Maximize() { SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); if (IsMinimized()) - ShowWindowWithState(ui::SHOW_STATE_NORMAL); + Show(ui::SHOW_STATE_NORMAL, gfx::Rect()); } void DesktopWindowTreeHostX11::Minimize() { ReleaseCapture(); - XIconifyWindow(xdisplay_, xwindow_, 0); + if (window_mapped_in_client_) + XIconifyWindow(xdisplay_, xwindow_, 0); + else + SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::None); } void DesktopWindowTreeHostX11::Restore() { should_maximize_after_map_ = false; SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); - if (IsMinimized()) - ShowWindowWithState(ui::SHOW_STATE_NORMAL); + Show(ui::SHOW_STATE_NORMAL, gfx::Rect()); + SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::None); } bool DesktopWindowTreeHostX11::IsMaximized() const { - return (ui::HasWMSpecProperty(window_properties_in_server_, + return (ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) && - ui::HasWMSpecProperty(window_properties_in_server_, + ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"))); } bool DesktopWindowTreeHostX11::IsMinimized() const { - return ui::HasWMSpecProperty(window_properties_in_server_, + return ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_HIDDEN")); } @@ -1036,7 +1040,7 @@ void DesktopWindowTreeHostX11::SetFullscreen(bool fullscreen) { OnHostMovedInPixels(bounds_in_pixels_.origin()); OnHostResizedInPixels(bounds_in_pixels_.size()); - if (ui::HasWMSpecProperty(window_properties_in_server_, + if (ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN")) == fullscreen) { Relayout(); @@ -1190,11 +1194,11 @@ gfx::AcceleratedWidget DesktopWindowTreeHostX11::GetAcceleratedWidget() { } void DesktopWindowTreeHostX11::ShowImpl() { - ShowWindowWithState(ui::SHOW_STATE_NORMAL); + Show(ui::SHOW_STATE_NORMAL, gfx::Rect()); } void DesktopWindowTreeHostX11::HideImpl() { - if (IsVisible()) { + if (window_mapped_in_client_) { XWithdrawWindow(xdisplay_, xwindow_, 0); window_mapped_in_client_ = false; native_widget_delegate_->OnNativeWidgetVisibilityChanged(false); @@ -1411,7 +1415,7 @@ void DesktopWindowTreeHostX11::InitX11Window( } swa.background_pixel = background_color; - ::Atom window_type; + XAtom window_type; switch (params.type) { case Widget::InitParams::TYPE_MENU: swa.override_redirect = x11::True; @@ -1498,7 +1502,7 @@ void DesktopWindowTreeHostX11::InitX11Window( // TODO(erg): We currently only request window deletion events. We also // should listen for activation events and anything else that GTK+ listens // for, and do something useful. - ::Atom protocols[2]; + XAtom protocols[2]; protocols[0] = gfx::GetAtom("WM_DELETE_WINDOW"); protocols[1] = gfx::GetAtom("_NET_WM_PING"); XSetWMProtocols(xdisplay_, xwindow_, protocols, 2); @@ -1521,25 +1525,25 @@ void DesktopWindowTreeHostX11::InitX11Window( XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(&window_type), 1); - // List of window state properties (_NET_WM_STATE) to set, if any. - std::vector< ::Atom> state_atom_list; + // The changes to |window_properties_| here will be sent to the X server just + // before the window is mapped. // Remove popup windows from taskbar unless overridden. if ((params.type == Widget::InitParams::TYPE_POPUP || params.type == Widget::InitParams::TYPE_BUBBLE) && !params.force_show_in_taskbar) { - state_atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_SKIP_TASKBAR")); + window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_SKIP_TASKBAR")); } // If the window should stay on top of other windows, add the // _NET_WM_STATE_ABOVE property. is_always_on_top_ = params.keep_on_top; if (is_always_on_top_) - state_atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_ABOVE")); + window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_ABOVE")); workspace_ = base::nullopt; if (params.visible_on_all_workspaces) { - state_atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_STICKY")); + window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_STICKY")); ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllDesktops); } else if (!params.workspace.empty()) { int workspace; @@ -1547,18 +1551,6 @@ void DesktopWindowTreeHostX11::InitX11Window( ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", workspace); } - // Setting _NET_WM_STATE by sending a message to the root_window (with - // SetWMSpecState) has no effect here since the window has not yet been - // mapped. So we manually change the state. - if (!state_atom_list.empty()) { - DCHECK(window_properties_in_client_.empty()); - window_properties_in_client_ = state_atom_list; - ui::SetAtomArrayProperty(xwindow_, - "_NET_WM_STATE", - "ATOM", - state_atom_list); - } - if (!params.wm_class_name.empty() || !params.wm_class_class.empty()) { ui::SetWindowClassHint( xdisplay_, xwindow_, params.wm_class_name, params.wm_class_class); @@ -1653,31 +1645,41 @@ gfx::Size DesktopWindowTreeHostX11::AdjustSize( void DesktopWindowTreeHostX11::SetWMSpecState(bool enabled, XAtom state1, XAtom state2) { - if (IsVisible()) + if (window_mapped_in_client_) { ui::SetWMSpecState(xwindow_, enabled, state1, state2); - for (XAtom atom : {state1, state2}) { - if (atom != x11::None) { + } else { + // The updated state will be set when the window is (re)mapped. + base::flat_set<XAtom> new_window_properties = window_properties_; + for (XAtom atom : {state1, state2}) { if (enabled) - window_properties_in_client_.insert(atom); + new_window_properties.insert(atom); else - window_properties_in_client_.erase(atom); + new_window_properties.erase(atom); } + UpdateWindowProperties(new_window_properties); } } void DesktopWindowTreeHostX11::OnWMStateUpdated() { - std::vector< ::Atom> atom_list; - // Ignore the return value of gfx::GetAtomArrayProperty(). Fluxbox removes the - // _NET_WM_STATE property when no _NET_WM_STATE atoms are set. - ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list); + // The EWMH spec requires window managers to remove the _NET_WM_STATE property + // when a window is unmapped. However, Chromium code wants the state to + // persist across a Hide() and Show(). So if the window is currently + // unmapped, leave the state unchanged so it will be restored when the window + // is remapped. + std::vector<XAtom> atom_list; + if (ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list) || + window_mapped_in_client_) { + UpdateWindowProperties( + base::flat_set<XAtom>(std::begin(atom_list), std::end(atom_list))); + } +} +void DesktopWindowTreeHostX11::UpdateWindowProperties( + const base::flat_set<XAtom>& new_window_properties) { bool was_minimized = IsMinimized(); bool was_maximized = IsMaximized(); - window_properties_in_server_.clear(); - std::copy(atom_list.begin(), atom_list.end(), - inserter(window_properties_in_server_, - window_properties_in_server_.begin())); + window_properties_ = new_window_properties; bool is_minimized = IsMinimized(); bool is_maximized = IsMaximized(); @@ -1705,7 +1707,6 @@ void DesktopWindowTreeHostX11::OnWMStateUpdated() { } if (restored_bounds_in_pixels_.IsEmpty()) { - DCHECK(!IsFullscreen()); if (IsMaximized()) { // The request that we become maximized originated from a different // process. |bounds_in_pixels_| already contains our maximized bounds. Do @@ -1726,7 +1727,7 @@ void DesktopWindowTreeHostX11::OnWMStateUpdated() { // do preprocessing before the x window's fullscreen state is toggled. is_always_on_top_ = ui::HasWMSpecProperty( - window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); + window_properties_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); if (was_maximized != is_maximized) OnMaximizedStateChanged(); @@ -1984,11 +1985,16 @@ void DesktopWindowTreeHostX11::MapWindow(ui::WindowShowState show_state) { UpdateMinAndMaxSize(); + if (window_properties_.empty()) { + XDeleteProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_STATE")); + } else { + ui::SetAtomArrayProperty(xwindow_, "_NET_WM_STATE", "ATOM", + std::vector<XAtom>(std::begin(window_properties_), + std::end(window_properties_))); + } + XMapWindow(xdisplay_, xwindow_); window_mapped_in_client_ = true; - - for (XAtom atom : window_properties_in_client_) - ui::SetWMSpecState(xwindow_, true, atom, x11::None); } void DesktopWindowTreeHostX11::SetWindowTransparency() { @@ -2312,7 +2318,7 @@ uint32_t DesktopWindowTreeHostX11::DispatchEvent( break; } case PropertyNotify: { - ::Atom changed_atom = xev->xproperty.atom; + XAtom changed_atom = xev->xproperty.atom; if (changed_atom == gfx::GetAtom("_NET_WM_STATE")) { OnWMStateUpdated(); } else if (changed_atom == gfx::GetAtom("_NET_FRAME_EXTENTS")) { @@ -2361,21 +2367,20 @@ gfx::Rect DesktopWindowTreeHostX11::ToPixelRect( return gfx::ToEnclosingRect(rect_in_pixels); } -std::unique_ptr<base::Closure> +std::unique_ptr<base::OnceClosure> DesktopWindowTreeHostX11::DisableEventListening() { // Allows to open multiple file-pickers. See https://crbug.com/678982 modal_dialog_counter_++; if (modal_dialog_counter_ == 1) { // ScopedWindowTargeter is used to temporarily replace the event-targeter - // with NullEventTargeter to make |dialog| modal. - targeter_for_modal_.reset(new aura::ScopedWindowTargeter( - window(), - std::unique_ptr<ui::EventTargeter>(new ui::NullEventTargeter))); + // with NullWindowEventTargeter to make |dialog| modal. + targeter_for_modal_ = std::make_unique<aura::ScopedWindowTargeter>( + window(), std::make_unique<aura::NullWindowTargeter>()); } - return std::make_unique<base::Closure>( - base::Bind(&DesktopWindowTreeHostX11::EnableEventListening, - weak_factory_.GetWeakPtr())); + return std::make_unique<base::OnceClosure>( + base::BindOnce(&DesktopWindowTreeHostX11::EnableEventListening, + weak_factory_.GetWeakPtr())); } void DesktopWindowTreeHostX11::EnableEventListening() { 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 c5e6df7ce81..cc9d5fb4823 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 @@ -86,7 +86,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 static void CleanUpWindowList(void (*func)(aura::Window* window)); // Disables event listening to make |dialog| modal. - std::unique_ptr<base::Closure> DisableEventListening(); + std::unique_ptr<base::OnceClosure> DisableEventListening(); // Returns a map of KeyboardEvent code to KeyboardEvent key values. base::flat_map<std::string, std::string> GetKeyboardLayoutMap() override; @@ -103,8 +103,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 void Close() override; void CloseNow() override; aura::WindowTreeHost* AsWindowTreeHost() override; - void ShowWindowWithState(ui::WindowShowState show_state) override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; bool IsVisible() const override; void SetSize(const gfx::Size& requested_size) override; void StackAbove(aura::Window* window) override; @@ -208,12 +208,21 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 // If mapped, sends a message to the window manager to enable or disable the // states |state1| and |state2|. Otherwise, the states will be enabled or - // disabled on the next map. + // disabled on the next map. It's the caller's responsibility to make sure + // atoms are set and unset in the appropriate pairs. For example, if a caller + // sets (_NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ), it would + // be invalid to unset the maximized state by making two calls like + // (_NET_WM_STATE_MAXIMIZED_VERT, x11::None), (_NET_WM_STATE_MAXIMIZED_HORZ, + // x11::None). void SetWMSpecState(bool enabled, XAtom state1, XAtom state2); // Called when |xwindow_|'s _NET_WM_STATE property is updated. void OnWMStateUpdated(); + // Updates |window_properties_| with |new_window_properties|. + void UpdateWindowProperties( + const base::flat_set<XAtom>& new_window_properties); + // Called when |xwindow_|'s _NET_FRAME_EXTENTS property is updated. void OnFrameExtentsUpdated(); @@ -343,12 +352,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 // _NET_WM_DESKTOP is unset. base::Optional<int> workspace_; - // The window manager state bits as indicated by the server. May be - // out-of-sync. May include bits set by non-Chrome apps. - base::flat_set<::Atom> window_properties_in_server_; - - // The window manager state bits that Chrome has set. - base::flat_set<::Atom> window_properties_in_client_; + // The window manager state bits. + base::flat_set<XAtom> window_properties_; // Whether |xwindow_| was requested to be fullscreen via SetFullscreen(). bool is_fullscreen_; @@ -381,7 +386,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 DesktopWindowTreeHostX11* window_parent_; std::set<DesktopWindowTreeHostX11*> window_children_; - base::ObserverList<DesktopWindowTreeHostObserverX11> observer_list_; + base::ObserverList<DesktopWindowTreeHostObserverX11>::Unchecked + observer_list_; // The window shape if the window is non-rectangular. gfx::XScopedPtr<_XRegion, gfx::XObjectDeleter<_XRegion, int, XDestroyRegion>> diff --git a/chromium/ui/views/widget/desktop_aura/window_event_filter.cc b/chromium/ui/views/widget/desktop_aura/window_event_filter.cc index e2d9e3e8a6f..530b079289b 100644 --- a/chromium/ui/views/widget/desktop_aura/window_event_filter.cc +++ b/chromium/ui/views/widget/desktop_aura/window_event_filter.cc @@ -4,7 +4,7 @@ #include "ui/views/widget/desktop_aura/window_event_filter.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" @@ -47,7 +47,7 @@ void WindowEventFilter::OnMouseEvent(ui::MouseEvent* event) { OnClickedMaximizeButton(event); } else { if (target->GetProperty(aura::client::kResizeBehaviorKey) & - ui::mojom::kResizeBehaviorCanResize) { + ws::mojom::kResizeBehaviorCanResize) { MaybeDispatchHostWindowDragMovement(component, event); } } @@ -97,7 +97,7 @@ void WindowEventFilter::OnClickedCaption(ui::MouseEvent* event, break; case LinuxUI::WINDOW_FRAME_ACTION_TOGGLE_MAXIMIZE: if (target->GetProperty(aura::client::kResizeBehaviorKey) & - ui::mojom::kResizeBehaviorCanMaximize) + ws::mojom::kResizeBehaviorCanMaximize) ToggleMaximizedState(); event->SetHandled(); break; diff --git a/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.h b/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.h index e8e030f2b4d..bcf36736232 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.h +++ b/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.h @@ -78,7 +78,7 @@ class VIEWS_EXPORT X11DesktopHandler : public ui::PlatformEventDispatcher, // Events selected on x_root_window_. std::unique_ptr<ui::XScopedEventSelector> x_root_window_events_; - base::ObserverList<X11DesktopHandlerObserver> observers_; + base::ObserverList<X11DesktopHandlerObserver>::Unchecked observers_; std::string workspace_; diff --git a/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc b/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc index 3d5ecdc2cfe..bd73d7293f8 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc +++ b/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc @@ -4,7 +4,7 @@ #include "ui/views/widget/desktop_aura/x11_window_event_filter.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" diff --git a/chromium/ui/views/widget/drop_helper.cc b/chromium/ui/views/widget/drop_helper.cc index 540301a61aa..f201f7311bd 100644 --- a/chromium/ui/views/widget/drop_helper.cc +++ b/chromium/ui/views/widget/drop_helper.cc @@ -64,8 +64,8 @@ int DropHelper::OnDrop(const OSExchangeData& data, gfx::Point view_location(root_view_location); View* root_view = drop_view->GetWidget()->GetRootView(); View::ConvertPointToTarget(root_view, drop_view, &view_location); - ui::DropTargetEvent drop_event(data, view_location, view_location, - drag_operation); + ui::DropTargetEvent drop_event(data, gfx::PointF(view_location), + gfx::PointF(view_location), drag_operation); return drop_view->OnPerformDrop(drop_event); } @@ -127,9 +127,8 @@ void DropHelper::NotifyDragEntered(const OSExchangeData& data, gfx::Point target_view_location(root_view_location); View::ConvertPointToTarget(root_view_, target_view_, &target_view_location); - ui::DropTargetEvent enter_event(data, - target_view_location, - target_view_location, + ui::DropTargetEvent enter_event(data, gfx::PointF(target_view_location), + gfx::PointF(target_view_location), drag_operation); target_view_->OnDragEntered(enter_event); } @@ -142,9 +141,8 @@ int DropHelper::NotifyDragOver(const OSExchangeData& data, gfx::Point target_view_location(root_view_location); View::ConvertPointToTarget(root_view_, target_view_, &target_view_location); - ui::DropTargetEvent enter_event(data, - target_view_location, - target_view_location, + ui::DropTargetEvent enter_event(data, gfx::PointF(target_view_location), + gfx::PointF(target_view_location), drag_operation); return target_view_->OnDragUpdated(enter_event); } diff --git a/chromium/ui/views/widget/native_widget.h b/chromium/ui/views/widget/native_widget.h index d01265f81f8..ec1a4ec93cb 100644 --- a/chromium/ui/views/widget/native_widget.h +++ b/chromium/ui/views/widget/native_widget.h @@ -5,9 +5,12 @@ #ifndef UI_VIEWS_WIDGET_NATIVE_WIDGET_H_ #define UI_VIEWS_WIDGET_NATIVE_WIDGET_H_ -#include "ui/views/widget/widget.h" +#include "ui/views/views_export.h" namespace views { + +class Widget; + namespace internal { class NativeWidgetPrivate; } diff --git a/chromium/ui/views/widget/native_widget_aura.cc b/chromium/ui/views/widget/native_widget_aura.cc index 0b3370a18ac..e6661017075 100644 --- a/chromium/ui/views/widget/native_widget_aura.cc +++ b/chromium/ui/views/widget/native_widget_aura.cc @@ -10,8 +10,8 @@ #include "base/strings/string_util.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" -#include "services/ui/public/interfaces/window_manager.mojom.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_manager.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/cursor_client.h" @@ -101,11 +101,12 @@ void SetIcon(aura::Window* window, // NativeWidgetAura, public: NativeWidgetAura::NativeWidgetAura(internal::NativeWidgetDelegate* delegate, - bool is_parallel_widget_in_window_manager) + bool is_parallel_widget_in_window_manager, + aura::Env* env) : delegate_(delegate), is_parallel_widget_in_window_manager_( is_parallel_widget_in_window_manager), - window_(new aura::Window(this)), + window_(new aura::Window(this, aura::client::WINDOW_TYPE_UNKNOWN, env)), ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET), destroying_(false), cursor_(gfx::kNullCursor), @@ -153,9 +154,9 @@ void NativeWidgetAura::InitNativeWidget(const Widget::InitParams& params) { ownership_ = params.ownership; RegisterNativeWidgetForWindow(this, window_); - // MusClient has assertions that ui::mojom::WindowType matches + // MusClient has assertions that ws::mojom::WindowType matches // views::Widget::InitParams::Type. - aura::SetWindowType(window_, static_cast<ui::mojom::WindowType>(params.type)); + aura::SetWindowType(window_, static_cast<ws::mojom::WindowType>(params.type)); if (params.corner_radius) { window_->SetProperty(aura::client::kWindowCornerRadiusKey, *params.corner_radius); @@ -237,8 +238,8 @@ void NativeWidgetAura::InitNativeWidget(const Widget::InitParams& params) { SetBounds(window_bounds); window_->SetEventTargetingPolicy( params.accept_events - ? ui::mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS - : ui::mojom::EventTargetingPolicy::NONE); + ? ws::mojom::EventTargetingPolicy::TARGET_AND_DESCENDANTS + : ws::mojom::EventTargetingPolicy::NONE); DCHECK(GetWidget()->GetRootView()); if (params.type != Widget::InitParams::TYPE_TOOLTIP) tooltip_manager_.reset(new views::TooltipManagerAura(GetWidget())); @@ -546,30 +547,20 @@ void NativeWidgetAura::CloseNow() { delete window_; } -void NativeWidgetAura::Show() { - ShowWithWindowState(ui::SHOW_STATE_NORMAL); -} - -void NativeWidgetAura::Hide() { - if (window_) - window_->Hide(); -} - -void NativeWidgetAura::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - SetRestoreBounds(window_, restored_bounds); - ShowWithWindowState(ui::SHOW_STATE_MAXIMIZED); -} - -void NativeWidgetAura::ShowWithWindowState(ui::WindowShowState state) { +void NativeWidgetAura::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { if (!window_) return; - if (state == ui::SHOW_STATE_MAXIMIZED || state == ui::SHOW_STATE_FULLSCREEN) - window_->SetProperty(aura::client::kShowStateKey, state); + if (show_state == ui::SHOW_STATE_MAXIMIZED && !restore_bounds.IsEmpty()) + SetRestoreBounds(window_, restore_bounds); + if (show_state == ui::SHOW_STATE_MAXIMIZED || + show_state == ui::SHOW_STATE_FULLSCREEN) { + window_->SetProperty(aura::client::kShowStateKey, show_state); + } window_->Show(); if (delegate_->CanActivate()) { - if (state != ui::SHOW_STATE_INACTIVE) + if (show_state != ui::SHOW_STATE_INACTIVE) Activate(); // SetInitialFocus() should be always be called, even for // SHOW_STATE_INACTIVE. If the window has to stay inactive, the method will @@ -577,15 +568,20 @@ void NativeWidgetAura::ShowWithWindowState(ui::WindowShowState state) { // Activate() might fail if the window is non-activatable. In this case, we // should pass SHOW_STATE_INACTIVE to SetInitialFocus() to stop the initial // focused view from getting focused. See crbug.com/515594 for example. - SetInitialFocus(IsActive() ? state : ui::SHOW_STATE_INACTIVE); + SetInitialFocus(IsActive() ? show_state : ui::SHOW_STATE_INACTIVE); } // On desktop aura, a window is activated first even when it is shown as // minimized. Do the same for consistency. - if (state == ui::SHOW_STATE_MINIMIZED) + if (show_state == ui::SHOW_STATE_MINIMIZED) Minimize(); } +void NativeWidgetAura::Hide() { + if (window_) + window_->Hide(); +} + bool NativeWidgetAura::IsVisible() const { return window_ && window_->IsVisible(); } @@ -717,6 +713,11 @@ bool NativeWidgetAura::IsMouseEventsEnabled() const { return cursor_client ? cursor_client->IsMouseEventsEnabled() : true; } +bool NativeWidgetAura::IsMouseButtonDown() const { + return window_ ? window_->env()->IsMouseButtonDown() + : aura::Env::GetInstance()->IsMouseButtonDown(); +} + void NativeWidgetAura::ClearNativeFocus() { aura::client::FocusClient* client = aura::client::GetFocusClient(window_); if (window_ && client && window_->Contains(client->GetFocusedWindow())) @@ -798,11 +799,15 @@ bool NativeWidgetAura::IsTranslucentWindowOpacitySupported() const { return true; } +ui::GestureRecognizer* NativeWidgetAura::GetGestureRecognizer() { + return window_->env()->gesture_recognizer(); +} + void NativeWidgetAura::OnSizeConstraintsChanged() { if (is_parallel_widget_in_window_manager_) return; - int32_t behavior = ui::mojom::kResizeBehaviorNone; + int32_t behavior = ws::mojom::kResizeBehaviorNone; if (GetWidget()->widget_delegate()) behavior = GetWidget()->widget_delegate()->GetResizeBehavior(); window_->SetProperty(aura::client::kResizeBehaviorKey, behavior); @@ -827,7 +832,7 @@ gfx::Size NativeWidgetAura::GetMaximumSize() const { // A window should not have a maximum size and also be maximizable. DCHECK(delegate_->GetMaximumSize().IsEmpty() || !(window_->GetProperty(aura::client::kResizeBehaviorKey) & - ui::mojom::kResizeBehaviorCanMaximize)); + ws::mojom::kResizeBehaviorCanMaximize)); return delegate_->GetMaximumSize(); } @@ -890,8 +895,12 @@ void NativeWidgetAura::OnWindowDestroying(aura::Window* window) { void NativeWidgetAura::OnWindowDestroyed(aura::Window* window) { window_ = NULL; + // |OnNativeWidgetDestroyed| may delete |this| if the object does not own + // itself. + bool should_delete_this = + (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET); delegate_->OnNativeWidgetDestroyed(); - if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + if (should_delete_this) delete this; } @@ -1097,8 +1106,15 @@ namespace internal { // static NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( + const Widget::InitParams& init_params, internal::NativeWidgetDelegate* delegate) { - return new NativeWidgetAura(delegate); + aura::Env* env = nullptr; + if (init_params.parent) + env = init_params.parent->env(); + else if (init_params.context) + env = init_params.context->env(); + return new NativeWidgetAura( + delegate, /*is_parallel_widget_in_window_manager*/ false, env); } // static @@ -1208,11 +1224,6 @@ void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, } // static -bool NativeWidgetPrivate::IsMouseButtonDown() { - return aura::Env::GetInstance()->IsMouseButtonDown(); -} - -// static gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() { #if defined(OS_WIN) NONCLIENTMETRICS_XP ncm; diff --git a/chromium/ui/views/widget/native_widget_aura.h b/chromium/ui/views/widget/native_widget_aura.h index b0276c0158b..768f0bde976 100644 --- a/chromium/ui/views/widget/native_widget_aura.h +++ b/chromium/ui/views/widget/native_widget_aura.h @@ -21,6 +21,7 @@ #include "ui/wm/public/activation_delegate.h" namespace aura { +class Env; class Window; } @@ -43,7 +44,8 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, // NativeWidgetAura is created in the window manager to represent a client // window, in all other cases it's false. explicit NativeWidgetAura(internal::NativeWidgetDelegate* delegate, - bool is_parallel_widget_in_window_manager = false); + bool is_parallel_widget_in_window_manager = false, + aura::Env* env = nullptr); // Called internally by NativeWidgetAura and DesktopNativeWidgetAura to // associate |native_widget| with |window|. @@ -103,10 +105,9 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, void SetShape(std::unique_ptr<Widget::ShapeRects> shape) override; void Close() override; void CloseNow() override; - void Show() override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; void Hide() override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; - void ShowWithWindowState(ui::WindowShowState state) override; bool IsVisible() const override; void Activate() override; void Deactivate() override; @@ -133,6 +134,7 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, void SchedulePaintInRect(const gfx::Rect& rect) override; void SetCursor(gfx::NativeCursor cursor) override; bool IsMouseEventsEnabled() const override; + bool IsMouseButtonDown() const override; void ClearNativeFocus() override; gfx::Rect GetWorkAreaBoundsInScreen() const override; Widget::MoveLoopResult RunMoveLoop( @@ -145,6 +147,7 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, void SetVisibilityAnimationTransition( Widget::VisibilityTransition transition) override; bool IsTranslucentWindowOpacitySupported() const override; + ui::GestureRecognizer* GetGestureRecognizer() override; void OnSizeConstraintsChanged() override; void RepostNativeEvent(gfx::NativeEvent native_event) override; std::string GetName() const override; diff --git a/chromium/ui/views/widget/native_widget_aura_unittest.cc b/chromium/ui/views/widget/native_widget_aura_unittest.cc index b1ef3a6805f..ee5f354c7c0 100644 --- a/chromium/ui/views/widget/native_widget_aura_unittest.cc +++ b/chromium/ui/views/widget/native_widget_aura_unittest.cc @@ -9,7 +9,7 @@ #include "base/command_line.h" #include "base/macros.h" #include "base/run_loop.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/env.h" @@ -310,9 +310,9 @@ class PropertyTestLayoutManager : public TestLayoutManagerBase { private: // aura::LayoutManager: void OnWindowAddedToLayout(aura::Window* child) override { - EXPECT_EQ(ui::mojom::kResizeBehaviorCanResize | - ui::mojom::kResizeBehaviorCanMaximize | - ui::mojom::kResizeBehaviorCanMinimize, + EXPECT_EQ(ws::mojom::kResizeBehaviorCanResize | + ws::mojom::kResizeBehaviorCanMaximize | + ws::mojom::kResizeBehaviorCanMinimize, child->GetProperty(aura::client::kResizeBehaviorKey)); added_ = true; } diff --git a/chromium/ui/views/widget/native_widget_mac.h b/chromium/ui/views/widget/native_widget_mac.h index d515251a082..5bb34bd75c3 100644 --- a/chromium/ui/views/widget/native_widget_mac.h +++ b/chromium/ui/views/widget/native_widget_mac.h @@ -19,9 +19,11 @@ namespace views { namespace test { class HitTestNativeWidgetMac; class MockNativeWidgetMac; +class WidgetTest; } class BridgedNativeWidget; +class BridgedNativeWidgetHostImpl; class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { public: @@ -30,13 +32,11 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { // Retrieves the bridge associated with the given NSWindow. Returns null if // the supplied handle has no associated Widget. + static BridgedNativeWidgetHostImpl* GetBridgeHostImplForNativeWindow( + gfx::NativeWindow window); static BridgedNativeWidget* GetBridgeForNativeWindow( gfx::NativeWindow window); - // Return true if the delegate's modal type is window-modal. These display as - // a native window "sheet", and have a different lifetime to regular windows. - bool IsWindowModalSheet() const; - // Informs |delegate_| that the native widget is about to be destroyed. // BridgedNativeWidget::OnWindowWillClose() invokes this early when the // NSWindowDelegate informs the bridge that the window is being closed (later, @@ -96,10 +96,9 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { void SetShape(std::unique_ptr<Widget::ShapeRects> shape) override; void Close() override; void CloseNow() override; - void Show() override; + void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) override; void Hide() override; - void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) override; - void ShowWithWindowState(ui::WindowShowState state) override; bool IsVisible() const override; void Activate() override; void Deactivate() override; @@ -126,6 +125,7 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { void SchedulePaintInRect(const gfx::Rect& rect) override; void SetCursor(gfx::NativeCursor cursor) override; bool IsMouseEventsEnabled() const override; + bool IsMouseButtonDown() const override; void ClearNativeFocus() override; gfx::Rect GetWorkAreaBoundsInScreen() const override; Widget::MoveLoopResult RunMoveLoop( @@ -138,6 +138,7 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { void SetVisibilityAnimationTransition( Widget::VisibilityTransition transition) override; bool IsTranslucentWindowOpacitySupported() const override; + ui::GestureRecognizer* GetGestureRecognizer() override; void OnSizeConstraintsChanged() override; void RepostNativeEvent(gfx::NativeEvent native_event) override; std::string GetName() const override; @@ -145,6 +146,10 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { protected: // Creates the NSWindow that will be passed to the BridgedNativeWidget. // Called by InitNativeWidget. The return value will be autoreleased. + // Note that some tests (in particular, views_unittests that interact + // with ScopedFakeNSWindowFullscreen, on 10.10) assume that these windows + // are autoreleased, and will crash if the window has a more precise + // lifetime. virtual NativeWidgetMacNSWindow* CreateNSWindow( const Widget::InitParams& params); @@ -152,13 +157,18 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { virtual void OnWindowDestroying(NSWindow* window) {} internal::NativeWidgetDelegate* delegate() { return delegate_; } + BridgedNativeWidget* bridge() const; + BridgedNativeWidgetHostImpl* bridge_host_for_testing() const { + return bridge_host_.get(); + } private: friend class test::MockNativeWidgetMac; friend class test::HitTestNativeWidgetMac; + friend class views::test::WidgetTest; internal::NativeWidgetDelegate* delegate_; - std::unique_ptr<BridgedNativeWidget> bridge_; + std::unique_ptr<BridgedNativeWidgetHostImpl> bridge_host_; Widget::InitParams::Ownership ownership_; diff --git a/chromium/ui/views/widget/native_widget_mac.mm b/chromium/ui/views/widget/native_widget_mac.mm index 3cf9af6b957..decbcdf69ac 100644 --- a/chromium/ui/views/widget/native_widget_mac.mm +++ b/chromium/ui/views/widget/native_widget_mac.mm @@ -9,6 +9,7 @@ #include <utility> #include "base/bind.h" +#include "base/lazy_instance.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_nsobject.h" #include "base/strings/sys_string_conversions.h" @@ -18,6 +19,7 @@ #import "ui/base/cocoa/window_size_constants.h" #include "ui/display/display.h" #include "ui/display/screen.h" +#include "ui/events/gestures/gesture_recognizer_impl_mac.h" #include "ui/gfx/font_list.h" #import "ui/gfx/mac/coordinate_conversion.h" #import "ui/gfx/mac/nswindow_frame_controls.h" @@ -25,6 +27,7 @@ #include "ui/native_theme/native_theme_mac.h" #import "ui/views/cocoa/bridged_content_view.h" #import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views/cocoa/bridged_native_widget_host_impl.h" #include "ui/views/cocoa/cocoa_mouse_capture.h" #import "ui/views/cocoa/drag_drop_client_mac.h" #import "ui/views/cocoa/native_widget_mac_nswindow.h" @@ -33,21 +36,25 @@ #include "ui/views/widget/widget_delegate.h" #include "ui/views/window/native_frame_view.h" -// Self-owning animation delegate that starts a hide animation, then calls -// -[NSWindow close] when the animation ends, releasing itself. -@interface ViewsNSWindowCloseAnimator : NSObject<NSAnimationDelegate> { - @private - base::scoped_nsobject<NSWindow> window_; - base::scoped_nsobject<NSAnimation> animation_; -} - -+ (void)closeWindowWithAnimation:(NSWindow*)window; - -@end +using views_bridge_mac::mojom::WindowVisibilityState; namespace views { namespace { +base::LazyInstance<ui::GestureRecognizerImplMac>::Leaky + g_gesture_recognizer_instance = LAZY_INSTANCE_INITIALIZER; + +NativeWidgetMac* GetNativeWidgetMacForNativeWindow( + gfx::NativeWindow native_window) { + id<NSWindowDelegate> window_delegate = [native_window delegate]; + if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) { + ViewsNSWindowDelegate* delegate = + base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate); + return [delegate nativeWidgetMac]; + } + return nullptr; // Not created by NativeWidgetMac. +} + NSInteger StyleMaskForParams(const Widget::InitParams& params) { // If the Widget is modal, it will be displayed as a sheet. This works best if // it has NSTitledWindowMask. For example, with NSBorderlessWindowMask, the @@ -75,9 +82,8 @@ NSInteger StyleMaskForParams(const Widget::InitParams& params) { NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate) : delegate_(delegate), - bridge_(new BridgedNativeWidget(this)), - ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) { -} + bridge_host_(new BridgedNativeWidgetHostImpl(this)), + ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {} NativeWidgetMac::~NativeWidgetMac() { if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) @@ -89,19 +95,17 @@ NativeWidgetMac::~NativeWidgetMac() { // static BridgedNativeWidget* NativeWidgetMac::GetBridgeForNativeWindow( gfx::NativeWindow window) { - id<NSWindowDelegate> window_delegate = [window delegate]; - if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) { - ViewsNSWindowDelegate* delegate = - base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate); - return [delegate nativeWidgetMac]->bridge_.get(); - } + if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window)) + return widget->bridge(); return nullptr; // Not created by NativeWidgetMac. } -bool NativeWidgetMac::IsWindowModalSheet() const { - return bridge_ && bridge_->parent() && - GetWidget()->widget_delegate()->GetModalType() == - ui::MODAL_TYPE_WINDOW; +// static +BridgedNativeWidgetHostImpl* NativeWidgetMac::GetBridgeHostImplForNativeWindow( + gfx::NativeWindow window) { + if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window)) + return widget->bridge_host_.get(); + return nullptr; // Not created by NativeWidgetMac. } void NativeWidgetMac::WindowDestroying() { @@ -110,10 +114,14 @@ void NativeWidgetMac::WindowDestroying() { } void NativeWidgetMac::WindowDestroyed() { - DCHECK(bridge_); - bridge_.reset(); + DCHECK(bridge()); + bridge_host_.reset(); + // |OnNativeWidgetDestroyed| may delete |this| if the object does not own + // itself. + bool should_delete_this = + (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET); delegate_->OnNativeWidgetDestroyed(); - if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + if (should_delete_this) delete this; } @@ -129,9 +137,10 @@ int NativeWidgetMac::SheetPositionY() { void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) { ownership_ = params.ownership; name_ = params.name; - base::scoped_nsobject<NSWindow> window([CreateNSWindow(params) retain]); - [window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. - bridge_->Init(window, params); + base::scoped_nsobject<NativeWidgetMacNSWindow> window( + [CreateNSWindow(params) retain]); + bridge()->SetWindow(window); + bridge_host_->InitWindow(params); // Only set always-on-top here if it is true since setting it may affect how // the window is treated by Expose. @@ -141,21 +150,20 @@ void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) { delegate_->OnNativeWidgetCreated(true); DCHECK(GetWidget()->GetRootView()); - bridge_->SetRootView(GetWidget()->GetRootView()); + bridge_host_->SetRootView(GetWidget()->GetRootView()); + bridge()->CreateContentView(GetWidget()->GetRootView()->bounds()); + bridge()->CreateDragDropClient(GetWidget()->GetRootView()); if (auto* focus_manager = GetWidget()->GetFocusManager()) { - [window makeFirstResponder:bridge_->ns_view()]; - bridge_->SetFocusManager(focus_manager); + bridge()->MakeFirstResponder(); + bridge_host_->SetFocusManager(focus_manager); } - // "Infer" must be handled by ViewsDelegate::OnBeforeWidgetInit(). - DCHECK_NE(Widget::InitParams::INFER_OPACITY, params.opacity); - bool translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW; - bridge_->CreateLayer(params.layer_type, translucent); + bridge_host_->CreateCompositor(params); } void NativeWidgetMac::OnWidgetInitDone() { OnSizeConstraintsChanged(); - bridge_->OnWidgetInitDone(); + bridge_host_->OnWidgetInitDone(); } NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() { @@ -193,7 +201,7 @@ gfx::NativeView NativeWidgetMac::GetNativeView() const { } gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const { - return bridge_ ? bridge_->ns_window() : nil; + return bridge() ? bridge()->ns_window() : nil; } Widget* NativeWidgetMac::GetTopLevelWidget() { @@ -202,60 +210,61 @@ Widget* NativeWidgetMac::GetTopLevelWidget() { } const ui::Compositor* NativeWidgetMac::GetCompositor() const { - return bridge_ && bridge_->layer() ? bridge_->layer()->GetCompositor() - : nullptr; + return bridge_host_ && bridge_host_->layer() + ? bridge_host_->layer()->GetCompositor() + : nullptr; } const ui::Layer* NativeWidgetMac::GetLayer() const { - return bridge_ ? bridge_->layer() : nullptr; + return bridge_host_ ? bridge_host_->layer() : nullptr; } void NativeWidgetMac::ReorderNativeViews() { - if (bridge_) - bridge_->ReorderChildViews(); + if (bridge()) + bridge()->ReorderChildViews(); } void NativeWidgetMac::ViewRemoved(View* view) { - DragDropClientMac* client = bridge_ ? bridge_->drag_drop_client() : nullptr; + DragDropClientMac* client = bridge() ? bridge()->drag_drop_client() : nullptr; if (client) client->drop_helper()->ResetTargetViewIfEquals(view); } void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) { - if (bridge_) - bridge_->SetNativeWindowProperty(name, value); + if (bridge()) + bridge()->SetNativeWindowProperty(name, value); } void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const { - if (bridge_) - return bridge_->GetNativeWindowProperty(name); + if (bridge()) + return bridge()->GetNativeWindowProperty(name); return nullptr; } TooltipManager* NativeWidgetMac::GetTooltipManager() const { - if (bridge_) - return bridge_->tooltip_manager(); + if (bridge()) + return bridge()->tooltip_manager(); return nullptr; } void NativeWidgetMac::SetCapture() { - if (bridge_ && !bridge_->HasCapture()) - bridge_->AcquireCapture(); + if (bridge()) + bridge()->AcquireCapture(); } void NativeWidgetMac::ReleaseCapture() { - if (bridge_) - bridge_->ReleaseCapture(); + if (bridge()) + bridge()->ReleaseCapture(); } bool NativeWidgetMac::HasCapture() const { - return bridge_ && bridge_->HasCapture(); + return bridge_host_ && bridge_host_->IsMouseCaptureActive(); } ui::InputMethod* NativeWidgetMac::GetInputMethod() { - return bridge_ ? bridge_->GetInputMethod() : nullptr; + return bridge_host_ ? bridge_host_->GetInputMethod() : nullptr; } void NativeWidgetMac::CenterWindow(const gfx::Size& size) { @@ -280,14 +289,9 @@ void NativeWidgetMac::GetWindowPlacement( } bool NativeWidgetMac::SetWindowTitle(const base::string16& title) { - NSWindow* window = GetNativeWindow(); - NSString* current_title = [window title]; - NSString* new_title = base::SysUTF16ToNSString(title); - if ([current_title isEqualToString:new_title]) + if (!bridge_host_) return false; - - [window setTitle:new_title]; - return true; + return bridge_host_->SetWindowTitle(title); } void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon, @@ -307,23 +311,21 @@ void NativeWidgetMac::InitModalType(ui::ModalType modal_type) { // A peculiarity of the constrained window framework is that it permits a // dialog of MODAL_TYPE_WINDOW to have a null parent window; falling back to // a non-modal window in this case. - DCHECK(bridge_->parent() || modal_type == ui::MODAL_TYPE_WINDOW); + DCHECK(bridge()->parent() || modal_type == ui::MODAL_TYPE_WINDOW); // Everything happens upon show. } gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const { - return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]); + return bridge_host_ ? bridge_host_->GetWindowBoundsInScreen() : gfx::Rect(); } gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const { - NSWindow* window = GetNativeWindow(); - return gfx::ScreenRectFromNSRect( - [window contentRectForFrameRect:[window frame]]); + return bridge_host_ ? bridge_host_->GetContentBoundsInScreen() : gfx::Rect(); } gfx::Rect NativeWidgetMac::GetRestoredBounds() const { - return bridge_ ? bridge_->GetRestoredBounds() : gfx::Rect(); + return bridge_host_ ? bridge_host_->GetRestoredBounds() : gfx::Rect(); } std::string NativeWidgetMac::GetWorkspace() const { @@ -331,18 +333,18 @@ std::string NativeWidgetMac::GetWorkspace() const { } void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) { - if (bridge_) - bridge_->SetBounds(bounds); + if (bridge_host_) + bridge_host_->SetBounds(bounds); } void NativeWidgetMac::SetBoundsConstrained(const gfx::Rect& bounds) { - if (!bridge_) + if (!bridge()) return; gfx::Rect new_bounds(bounds); NativeWidgetPrivate* ancestor = - bridge_ && bridge_->parent() - ? GetNativeWidgetForNativeWindow(bridge_->parent()->GetNSWindow()) + bridge() && bridge()->parent() + ? GetNativeWidgetForNativeWindow(bridge()->parent()->GetNSWindow()) : nullptr; if (!ancestor) { new_bounds = ConstrainBoundsToDisplayWorkArea(new_bounds); @@ -373,87 +375,23 @@ void NativeWidgetMac::SetShape(std::unique_ptr<Widget::ShapeRects> shape) { } void NativeWidgetMac::Close() { - if (!bridge_) - return; - - // Keep |window| on the stack so that the ObjectiveC block below can capture - // it and properly increment the reference count bound to the posted task. - NSWindow* window = GetNativeWindow(); - - if (IsWindowModalSheet()) { - // Sheets can't be closed normally. This starts the sheet closing. Once the - // sheet has finished animating, it will call sheetDidEnd: on the parent - // window's delegate. Note it still needs to be asynchronous, since code - // calling Widget::Close() doesn't expect things to be deleted upon return. - // Ensure |window| is retained by a block. Note in some cases during - // teardown, [window sheetParent] may be nil. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(base::RetainBlock(^{ - [NSApp endSheet:window]; - }))); - return; - } - - // For other modal types, animate the close. - if (bridge_->ShouldRunCustomAnimationFor(Widget::ANIMATE_HIDE)) { - [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window]; - return; - } - - // Clear the view early to suppress repaints. - bridge_->SetRootView(nullptr); - - // Widget::Close() ensures [Non]ClientView::CanClose() returns true, so there - // is no need to call the NSWindow or its delegate's -windowShouldClose: - // implementation in the manner of -[NSWindow performClose:]. But, - // like -performClose:, first remove the window from AppKit's display - // list to avoid crashes like http://crbug.com/156101. - [window orderOut:nil]; - - // Many tests assume that base::RunLoop().RunUntilIdle() is always sufficient - // to execute a close. However, in rare cases, -performSelector:..afterDelay:0 - // does not do this. So post a regular task. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(base::RetainBlock(^{ - [window close]; - }))); + if (bridge()) + bridge()->CloseWindow(); } void NativeWidgetMac::CloseNow() { - if (!bridge_) - return; - - // NSWindows must be retained until -[NSWindow close] returns. - base::scoped_nsobject<NSWindow> window(GetNativeWindow(), - base::scoped_policy::RETAIN); - - // If there's a bridge at this point, it means there must be a window as well. - DCHECK(window); - [window close]; - // Note: |this| is deleted here when ownership_ == NATIVE_WIDGET_OWNS_WIDGET. + if (bridge()) + bridge()->CloseWindowNow(); + // Note: |bridge_host_| will be deleted her, and |this| will be deleted here + // when ownership_ == NATIVE_WIDGET_OWNS_WIDGET, } -void NativeWidgetMac::Show() { - ShowWithWindowState(ui::SHOW_STATE_NORMAL); -} - -void NativeWidgetMac::Hide() { - if (!bridge_) +void NativeWidgetMac::Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) { + if (!bridge()) return; - bridge_->SetVisibilityState(BridgedNativeWidget::HIDE_WINDOW); -} - -void NativeWidgetMac::ShowMaximizedWithBounds( - const gfx::Rect& restored_bounds) { - NOTIMPLEMENTED(); -} - -void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) { - if (!bridge_) - return; - - switch (state) { + switch (show_state) { case ui::SHOW_STATE_DEFAULT: case ui::SHOW_STATE_NORMAL: case ui::SHOW_STATE_INACTIVE: @@ -467,24 +405,30 @@ void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) { NOTREACHED(); break; } - bridge_->SetVisibilityState(state == ui::SHOW_STATE_INACTIVE - ? BridgedNativeWidget::SHOW_INACTIVE - : BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW); + bridge()->SetVisibilityState( + show_state == ui::SHOW_STATE_INACTIVE + ? WindowVisibilityState::kShowInactive + : WindowVisibilityState::kShowAndActivateWindow); // Ignore the SetInitialFocus() result. BridgedContentView should get // firstResponder status regardless. - delegate_->SetInitialFocus(state); + delegate_->SetInitialFocus(show_state); +} + +void NativeWidgetMac::Hide() { + if (!bridge()) + return; + bridge()->SetVisibilityState(WindowVisibilityState::kHideWindow); } bool NativeWidgetMac::IsVisible() const { - return bridge_ && bridge_->window_visible(); + return bridge_host_ && bridge_host_->IsVisible(); } void NativeWidgetMac::Activate() { - if (!bridge_) + if (!bridge()) return; - - bridge_->SetVisibilityState(BridgedNativeWidget::SHOW_AND_ACTIVATE_WINDOW); + bridge()->SetVisibilityState(WindowVisibilityState::kShowAndActivateWindow); } void NativeWidgetMac::Deactivate() { @@ -492,7 +436,7 @@ void NativeWidgetMac::Deactivate() { } bool NativeWidgetMac::IsActive() const { - return [GetNativeWindow() isKeyWindow]; + return bridge_host_ ? bridge_host_->IsWindowKey() : false; } void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) { @@ -504,7 +448,9 @@ bool NativeWidgetMac::IsAlwaysOnTop() const { } void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) { - gfx::SetNSWindowVisibleOnAllWorkspaces(GetNativeWindow(), always_visible); + if (!bridge()) + return; + bridge()->SetVisibleOnAllSpaces(always_visible); } bool NativeWidgetMac::IsVisibleOnAllWorkspaces() const { @@ -516,13 +462,9 @@ void NativeWidgetMac::Maximize() { } void NativeWidgetMac::Minimize() { - NSWindow* window = GetNativeWindow(); - // Calling performMiniaturize: will momentarily highlight the button, but - // AppKit will reject it if there is no miniaturize button. - if ([window styleMask] & NSMiniaturizableWindowMask) - [window performMiniaturize:nil]; - else - [window miniaturize:nil]; + if (!bridge()) + return; + bridge()->SetMiniaturized(true); } bool NativeWidgetMac::IsMaximized() const { @@ -532,32 +474,38 @@ bool NativeWidgetMac::IsMaximized() const { } bool NativeWidgetMac::IsMinimized() const { - return [GetNativeWindow() isMiniaturized]; + if (!bridge_host_) + return false; + return bridge_host_->IsMiniaturized(); } void NativeWidgetMac::Restore() { - SetFullscreen(false); - [GetNativeWindow() deminiaturize:nil]; + if (!bridge()) + return; + bridge()->SetFullscreen(false); + bridge()->SetMiniaturized(false); } void NativeWidgetMac::SetFullscreen(bool fullscreen) { - if (!bridge_ || fullscreen == IsFullscreen()) + if (!bridge_host_) return; - - bridge_->ToggleDesiredFullscreenState(); + bridge_host_->SetFullscreen(fullscreen); } bool NativeWidgetMac::IsFullscreen() const { - return bridge_ && bridge_->target_fullscreen_state(); + return bridge_host_ && bridge_host_->target_fullscreen_state(); } void NativeWidgetMac::SetOpacity(float opacity) { - [GetNativeWindow() setAlphaValue:opacity]; + if (!bridge()) + return; + bridge()->SetOpacity(opacity); } void NativeWidgetMac::SetAspectRatio(const gfx::SizeF& aspect_ratio) { - [GetNativeWindow() setContentAspectRatio:NSMakeSize(aspect_ratio.width(), - aspect_ratio.height())]; + if (!bridge()) + return; + bridge()->SetContentAspectRatio(aspect_ratio); } void NativeWidgetMac::FlashFrame(bool flash_frame) { @@ -569,7 +517,7 @@ void NativeWidgetMac::RunShellDrag(View* view, const gfx::Point& location, int operation, ui::DragDropTypes::DragEventSource source) { - bridge_->drag_drop_client()->StartDragAndDrop(view, data, operation, source); + bridge()->drag_drop_client()->StartDragAndDrop(view, data, operation, source); } void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) { @@ -582,13 +530,13 @@ void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) { target_rect.origin.y = NSHeight(client_rect) - target_rect.origin.y - NSHeight(target_rect); [GetNativeView() setNeedsDisplayInRect:target_rect]; - if (bridge_ && bridge_->layer()) - bridge_->layer()->SchedulePaint(rect); + if (bridge_host_ && bridge_host_->layer()) + bridge_host_->layer()->SchedulePaint(rect); } void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) { - if (bridge_) - bridge_->SetCursor(cursor); + if (bridge()) + bridge()->SetCursor(cursor); } bool NativeWidgetMac::IsMouseEventsEnabled() const { @@ -598,35 +546,42 @@ bool NativeWidgetMac::IsMouseEventsEnabled() const { return true; } +bool NativeWidgetMac::IsMouseButtonDown() const { + return [NSEvent pressedMouseButtons] != 0; +} + void NativeWidgetMac::ClearNativeFocus() { // To quote DesktopWindowTreeHostX11, "This method is weird and misnamed." // The goal is to set focus to the content window, thereby removing focus from // any NSView in the window that doesn't belong to toolkit-views. - [GetNativeWindow() makeFirstResponder:GetNativeView()]; + if (!bridge()) + return; + bridge()->MakeFirstResponder(); } gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const { - return gfx::ScreenRectFromNSRect([[GetNativeWindow() screen] visibleFrame]); + return bridge_host_ ? bridge_host_->GetCurrentDisplay().work_area() + : gfx::Rect(); } Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop( const gfx::Vector2d& drag_offset, Widget::MoveLoopSource source, Widget::MoveLoopEscapeBehavior escape_behavior) { - if (!bridge_) + if (!bridge()) return Widget::MOVE_LOOP_CANCELED; - return bridge_->RunMoveLoop(drag_offset); + return bridge()->RunMoveLoop(drag_offset); } void NativeWidgetMac::EndMoveLoop() { - if (bridge_) - bridge_->EndMoveLoop(); + if (bridge()) + bridge()->EndMoveLoop(); } void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) { - if (bridge_) - bridge_->SetAnimationEnabled(value); + if (bridge()) + bridge()->SetAnimationEnabled(value); } void NativeWidgetMac::SetVisibilityAnimationDuration( @@ -636,16 +591,24 @@ void NativeWidgetMac::SetVisibilityAnimationDuration( void NativeWidgetMac::SetVisibilityAnimationTransition( Widget::VisibilityTransition transition) { - if (bridge_) - bridge_->set_transitions_to_animate(transition); + if (bridge()) + bridge()->set_transitions_to_animate(transition); } bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const { return false; } +ui::GestureRecognizer* NativeWidgetMac::GetGestureRecognizer() { + return g_gesture_recognizer_instance.Pointer(); +} + void NativeWidgetMac::OnSizeConstraintsChanged() { - bridge_->OnSizeConstraintsChanged(); + Widget* widget = GetWidget(); + bridge()->SetSizeConstraints(widget->GetMinimumSize(), + widget->GetMaximumSize(), + widget->widget_delegate()->CanResize(), + widget->widget_delegate()->CanMaximize()); } void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) { @@ -668,6 +631,10 @@ NativeWidgetMacNSWindow* NativeWidgetMac::CreateNSWindow( defer:NO] autorelease]; } +BridgedNativeWidget* NativeWidgetMac::bridge() const { + return bridge_host_ ? bridge_host_->bridge_impl() : nullptr; +} + //////////////////////////////////////////////////////////////////////////////// // Widget, public: @@ -709,6 +676,7 @@ namespace internal { // static NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( + const Widget::InitParams& init_params, internal::NativeWidgetDelegate* delegate) { return new NativeWidgetMac(delegate); } @@ -721,13 +689,9 @@ NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( // static NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( - gfx::NativeWindow native_window) { - id<NSWindowDelegate> window_delegate = [native_window delegate]; - if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) { - ViewsNSWindowDelegate* delegate = - base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate); - return [delegate nativeWidgetMac]; - } + gfx::NativeWindow window) { + if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window)) + return widget; return nullptr; // Not created by NativeWidgetMac. } @@ -842,11 +806,6 @@ void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, } // static -bool NativeWidgetPrivate::IsMouseButtonDown() { - return [NSEvent pressedMouseButtons] != 0; -} - -// static gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() { NOTIMPLEMENTED(); return gfx::FontList(); @@ -860,29 +819,3 @@ gfx::NativeView NativeWidgetPrivate::GetGlobalCapture( } // namespace internal } // namespace views - -@implementation ViewsNSWindowCloseAnimator - -- (id)initWithWindow:(NSWindow*)window { - if ((self = [super init])) { - window_.reset([window retain]); - animation_.reset( - [[ConstrainedWindowAnimationHide alloc] initWithWindow:window]); - [animation_ setDelegate:self]; - [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; - [animation_ startAnimation]; - } - return self; -} - -+ (void)closeWindowWithAnimation:(NSWindow*)window { - [[ViewsNSWindowCloseAnimator alloc] initWithWindow:window]; -} - -- (void)animationDidEnd:(NSAnimation*)animation { - [window_ close]; - [animation_ setDelegate:nil]; - [self release]; -} - -@end diff --git a/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm b/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm index f6d68885a92..bb6fe00eaeb 100644 --- a/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm +++ b/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm @@ -12,7 +12,7 @@ #include "ui/base/test/ui_controls.h" #import "ui/base/test/windowed_nsnotification_observer.h" #import "ui/events/test/cocoa_test_event_utils.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/test/test_widget_observer.h" #include "ui/views/test/views_interactive_ui_test_base.h" diff --git a/chromium/ui/views/widget/native_widget_mac_unittest.mm b/chromium/ui/views/widget/native_widget_mac_unittest.mm index 8fcb213c579..ebb9ea6cd6d 100644 --- a/chromium/ui/views/widget/native_widget_mac_unittest.mm +++ b/chromium/ui/views/widget/native_widget_mac_unittest.mm @@ -27,7 +27,7 @@ #import "ui/events/test/cocoa_test_event_utils.h" #include "ui/events/test/event_generator.h" #import "ui/gfx/mac/coordinate_conversion.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #import "ui/views/cocoa/bridged_content_view.h" #import "ui/views/cocoa/bridged_native_widget.h" #import "ui/views/cocoa/native_widget_mac_nswindow.h" @@ -103,15 +103,11 @@ class BridgedNativeWidgetTestApi { // Simulate a frame swap from the compositor. void SimulateFrameSwap(const gfx::Size& size) { const float kScaleFactor = 1.0f; - ui::CALayerFrameSink* ca_layer_frame_sink = - ui::CALayerFrameSink::FromAcceleratedWidget( - bridge_->compositor_->widget()->accelerated_widget()); gfx::CALayerParams ca_layer_params; ca_layer_params.is_empty = false; ca_layer_params.pixel_size = size; ca_layer_params.scale_factor = kScaleFactor; - ca_layer_frame_sink->UpdateCALayerTree(ca_layer_params); - bridge_->AcceleratedWidgetCALayerParamsUpdated(); + bridge_->SetCALayerParams(ca_layer_params); } NSAnimation* show_animation() { @@ -1756,7 +1752,7 @@ class CustomTitleWidgetDelegate : public WidgetDelegate { }; // Test that undocumented title-hiding API we're using does the job. -TEST_F(NativeWidgetMacTest, DoesHideTitle) { +TEST_F(NativeWidgetMacTest, DISABLED_DoesHideTitle) { // Same as CreateTopLevelPlatformWidget but with a custom delegate. Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); Widget* widget = new Widget; @@ -1922,10 +1918,13 @@ TEST_F(NativeWidgetMacTest, SchedulePaintInRect_Titled) { NSWindow* window = widget->GetNativeWindow(); base::scoped_nsobject<MockBridgedView> mock_bridged_view( [[MockBridgedView alloc] init]); + // Reset drawRect count. + [mock_bridged_view setDrawRectCount:0]; [window setContentView:mock_bridged_view]; // Ensure the initial draw of the window is done. - base::RunLoop().RunUntilIdle(); + while ([mock_bridged_view drawRectCount] == 0) + base::RunLoop().RunUntilIdle(); // Add a dummy view to the widget. This will cause SchedulePaint to be called // on the dummy view. @@ -1937,7 +1936,8 @@ TEST_F(NativeWidgetMacTest, SchedulePaintInRect_Titled) { widget->GetContentsView()->AddChildView(dummy_view); // SchedulePaint is asyncronous. Wait for drawRect: to be called. - base::RunLoop().RunUntilIdle(); + while ([mock_bridged_view drawRectCount] == 0) + base::RunLoop().RunUntilIdle(); EXPECT_EQ(1u, [mock_bridged_view drawRectCount]); int client_area_height = widget->GetClientAreaBoundsInScreen().height(); @@ -1964,10 +1964,13 @@ TEST_F(NativeWidgetMacTest, SchedulePaintInRect_Borderless) { NSWindow* window = widget->GetNativeWindow(); base::scoped_nsobject<MockBridgedView> mock_bridged_view( [[MockBridgedView alloc] init]); + // Reset drawRect count. + [mock_bridged_view setDrawRectCount:0]; [window setContentView:mock_bridged_view]; // Ensure the initial draw of the window is done. - base::RunLoop().RunUntilIdle(); + while ([mock_bridged_view drawRectCount] == 0) + base::RunLoop().RunUntilIdle(); // Add a dummy view to the widget. This will cause SchedulePaint to be called // on the dummy view. @@ -1979,7 +1982,8 @@ TEST_F(NativeWidgetMacTest, SchedulePaintInRect_Borderless) { widget->GetRootView()->AddChildView(dummy_view); // SchedulePaint is asyncronous. Wait for drawRect: to be called. - base::RunLoop().RunUntilIdle(); + while ([mock_bridged_view drawRectCount] == 0) + base::RunLoop().RunUntilIdle(); EXPECT_EQ(1u, [mock_bridged_view drawRectCount]); // These are expected dummy_view bounds in AppKit coordinate system. The y diff --git a/chromium/ui/views/widget/native_widget_private.h b/chromium/ui/views/widget/native_widget_private.h index 53c60d77ace..6aedb90e714 100644 --- a/chromium/ui/views/widget/native_widget_private.h +++ b/chromium/ui/views/widget/native_widget_private.h @@ -12,6 +12,7 @@ #include "ui/base/ui_base_types.h" #include "ui/gfx/native_widget_types.h" #include "ui/views/widget/native_widget.h" +#include "ui/views/widget/widget.h" namespace gfx { class FontList; @@ -21,6 +22,7 @@ class Rect; namespace ui { class InputMethod; +class GestureRecognizer; class OSExchangeData; } @@ -49,6 +51,7 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { // Creates an appropriate default NativeWidgetPrivate implementation for the // current OS/circumstance. static NativeWidgetPrivate* CreateNativeWidget( + const Widget::InitParams& init_params, internal::NativeWidgetDelegate* delegate); static NativeWidgetPrivate* GetNativeWidgetForNativeView( @@ -68,9 +71,6 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { static void ReparentNativeView(gfx::NativeView native_view, gfx::NativeView new_parent); - // Returns true if any mouse button is currently down. - static bool IsMouseButtonDown(); - static gfx::FontList GetWindowTitleFontList(); // Returns the NativeView with capture, otherwise NULL if there is no current @@ -184,12 +184,9 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { virtual void SetShape(std::unique_ptr<Widget::ShapeRects> shape) = 0; virtual void Close() = 0; virtual void CloseNow() = 0; - virtual void Show() = 0; + virtual void Show(ui::WindowShowState show_state, + const gfx::Rect& restore_bounds) = 0; virtual void Hide() = 0; - // Invoked if the initial show should maximize the window. |restored_bounds| - // is the bounds of the window when not maximized. - virtual void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) = 0; - virtual void ShowWithWindowState(ui::WindowShowState show_state) = 0; virtual bool IsVisible() const = 0; virtual void Activate() = 0; virtual void Deactivate() = 0; @@ -216,6 +213,8 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { virtual void SchedulePaintInRect(const gfx::Rect& rect) = 0; virtual void SetCursor(gfx::NativeCursor cursor) = 0; virtual bool IsMouseEventsEnabled() const = 0; + // Returns true if any mouse button is currently down. + virtual bool IsMouseButtonDown() const = 0; virtual void ClearNativeFocus() = 0; virtual gfx::Rect GetWorkAreaBoundsInScreen() const = 0; virtual Widget::MoveLoopResult RunMoveLoop( @@ -229,6 +228,7 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { virtual void SetVisibilityAnimationTransition( Widget::VisibilityTransition transition) = 0; virtual bool IsTranslucentWindowOpacitySupported() const = 0; + virtual ui::GestureRecognizer* GetGestureRecognizer() = 0; virtual void OnSizeConstraintsChanged() = 0; // Repost an unhandled event to the native widget for default OS processing. diff --git a/chromium/ui/views/widget/root_view_unittest.cc b/chromium/ui/views/widget/root_view_unittest.cc index 82959179009..d1bee1793e7 100644 --- a/chromium/ui/views/widget/root_view_unittest.cc +++ b/chromium/ui/views/widget/root_view_unittest.cc @@ -8,6 +8,7 @@ #include "base/memory/ptr_util.h" #include "build/build_config.h" #include "ui/events/event_utils.h" +#include "ui/events/keycodes/dom/dom_code.h" #include "ui/views/context_menu_controller.h" #include "ui/views/test/views_test_base.h" #include "ui/views/view_targeter.h" @@ -133,7 +134,8 @@ TEST_F(RootViewTest, ContextMenuFromKeyEvent) { focused_view->RequestFocus(); // No context menu should be shown for a keypress of 'A'. - ui::KeyEvent nomenu_key_event('a', ui::VKEY_A, ui::EF_NONE); + ui::KeyEvent nomenu_key_event('a', ui::VKEY_A, ui::DomCode::NONE, + ui::EF_NONE); ui::EventDispatchDetails details = root_view->OnEventFromSource(&nomenu_key_event); EXPECT_FALSE(details.target_destroyed); diff --git a/chromium/ui/views/widget/widget.cc b/chromium/ui/views/widget/widget.cc index e4e7fc6ce96..5ad48b616fa 100644 --- a/chromium/ui/views/widget/widget.cc +++ b/chromium/ui/views/widget/widget.cc @@ -39,6 +39,11 @@ #include "ui/views/window/custom_frame_view.h" #include "ui/views/window/dialog_delegate.h" +#if defined(USE_AURA) +#include "ui/aura/env.h" // nogncheck +#include "ui/aura/window.h" // nogncheck +#endif + namespace views { namespace { @@ -70,7 +75,7 @@ NativeWidget* CreateNativeWidget(const Widget::InitParams& params, if (native_widget) return native_widget; } - return internal::NativeWidgetPrivate::CreateNativeWidget(delegate); + return internal::NativeWidgetPrivate::CreateNativeWidget(params, delegate); } void NotifyCaretBoundsChanged(ui::InputMethod* input_method) { @@ -334,11 +339,9 @@ void Widget::Init(const InitParams& in_params) { native_widget_ = CreateNativeWidget(params, this)->AsNativeWidgetPrivate(); root_view_.reset(CreateRootView()); default_theme_provider_.reset(new ui::DefaultThemeProvider); - if (params.type == InitParams::TYPE_MENU) { - is_mouse_button_pressed_ = - internal::NativeWidgetPrivate::IsMouseButtonDown(); - } native_widget_->InitNativeWidget(params); + if (params.type == InitParams::TYPE_MENU) + is_mouse_button_pressed_ = native_widget_->IsMouseButtonDown(); if (RequiresNonClientView(params.type)) { non_client_view_ = new NonClientView; non_client_view_->SetFrameView(CreateNonClientFrameView()); @@ -617,19 +620,20 @@ void Widget::Show() { if (saved_show_state_ == ui::SHOW_STATE_MAXIMIZED && !initial_restored_bounds_.IsEmpty() && !IsFullscreen()) { - native_widget_->ShowMaximizedWithBounds(initial_restored_bounds_); + native_widget_->Show(ui::SHOW_STATE_MAXIMIZED, initial_restored_bounds_); } else { - native_widget_->ShowWithWindowState( - IsFullscreen() ? ui::SHOW_STATE_FULLSCREEN : saved_show_state_); + native_widget_->Show( + IsFullscreen() ? ui::SHOW_STATE_FULLSCREEN : saved_show_state_, + gfx::Rect()); } // |saved_show_state_| only applies the first time the window is shown. // If we don't reset the value the window may be shown maximized every time // it is subsequently shown after being hidden. saved_show_state_ = ui::SHOW_STATE_NORMAL; } else { - CanActivate() - ? native_widget_->Show() - : native_widget_->ShowWithWindowState(ui::SHOW_STATE_INACTIVE); + native_widget_->Show( + CanActivate() ? ui::SHOW_STATE_NORMAL : ui::SHOW_STATE_INACTIVE, + gfx::Rect()); } } @@ -647,7 +651,7 @@ void Widget::ShowInactive() { SetBounds(initial_restored_bounds_); saved_show_state_ = ui::SHOW_STATE_NORMAL; } - native_widget_->ShowWithWindowState(ui::SHOW_STATE_INACTIVE); + native_widget_->Show(ui::SHOW_STATE_INACTIVE, gfx::Rect()); } void Widget::Activate() { @@ -836,10 +840,6 @@ void Widget::UpdateWindowTitle() { return; non_client_view_->UpdateWindowTitle(); - - // If the non-client view is rendering its own title, it'll need to relayout - // now and to get a paint update later on. - non_client_view_->Layout(); } void Widget::UpdateWindowIcon() { @@ -953,7 +953,7 @@ void Widget::SetCapture(View* view) { return; } - if (internal::NativeWidgetPrivate::IsMouseButtonDown()) + if (native_widget_->IsMouseButtonDown()) is_mouse_button_pressed_ = true; root_view_->SetMouseHandler(view); } @@ -981,7 +981,12 @@ gfx::Rect Widget::GetWorkAreaBoundsInScreen() const { void Widget::SynthesizeMouseMoveEvent() { // In screen coordinate. - gfx::Point mouse_location = EventMonitor::GetLastMouseLocation(); + gfx::Point mouse_location = +#if defined(USE_AURA) + GetNativeWindow()->env()->last_mouse_location(); +#else + display::Screen::GetScreen()->GetCursorScreenPoint(); +#endif if (!GetWindowBoundsInScreen().Contains(mouse_location)) return; @@ -998,6 +1003,10 @@ bool Widget::IsTranslucentWindowOpacitySupported() const { return native_widget_->IsTranslucentWindowOpacitySupported(); } +ui::GestureRecognizer* Widget::GetGestureRecognizer() { + return native_widget_->GetGestureRecognizer(); +} + void Widget::OnSizeConstraintsChanged() { native_widget_->OnSizeConstraintsChanged(); if (non_client_view_) @@ -1202,7 +1211,7 @@ void Widget::OnMouseEvent(ui::MouseEvent* event) { // mouse-button is still down before attempting to do a capture. if (root_view && root_view->OnMousePressed(*event) && widget_deletion_observer.IsWidgetAlive() && IsVisible() && - internal::NativeWidgetPrivate::IsMouseButtonDown() && + native_widget_->IsMouseButtonDown() && current_capture == internal::NativeWidgetPrivate::GetGlobalCapture( native_widget_->GetNativeView())) { is_mouse_button_pressed_ = true; diff --git a/chromium/ui/views/widget/widget.h b/chromium/ui/views/widget/widget.h index 6862cfb45d0..2efbe5c4d89 100644 --- a/chromium/ui/views/widget/widget.h +++ b/chromium/ui/views/widget/widget.h @@ -34,9 +34,6 @@ #if defined(IsMinimized) #undef IsMinimized #endif -#if defined(CreateWindow) -#undef CreateWindow -#endif #endif namespace base { @@ -52,6 +49,7 @@ namespace ui { class Accelerator; class Compositor; class DefaultThemeProvider; +class GestureRecognizer; class InputMethod; class Layer; class NativeTheme; @@ -772,6 +770,10 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // Whether the widget supports translucency. bool IsTranslucentWindowOpacitySupported() const; + // Returns the gesture recognizer which can handle touch/gesture events on + // this. + ui::GestureRecognizer* GetGestureRecognizer(); + // Called when the delegate's CanResize or CanMaximize changes. void OnSizeConstraintsChanged(); @@ -891,7 +893,7 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, base::ObserverList<WidgetObserver> observers_; - base::ObserverList<WidgetRemovalsObserver> removals_observers_; + base::ObserverList<WidgetRemovalsObserver>::Unchecked removals_observers_; // Non-owned pointer to the Widget's delegate. If a NULL delegate is supplied // to Init() a default WidgetDelegate is created. diff --git a/chromium/ui/views/widget/widget_delegate.cc b/chromium/ui/views/widget/widget_delegate.cc index f1a5004ec93..f87e90d76e4 100644 --- a/chromium/ui/views/widget/widget_delegate.cc +++ b/chromium/ui/views/widget/widget_delegate.cc @@ -6,7 +6,7 @@ #include "base/logging.h" #include "base/strings/utf_string_conversions.h" -#include "services/ui/public/interfaces/window_tree_constants.mojom.h" +#include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/gfx/image/image_skia.h" #include "ui/views/view.h" #include "ui/views/views_delegate.h" @@ -57,13 +57,13 @@ bool WidgetDelegate::CanMinimize() const { } int32_t WidgetDelegate::GetResizeBehavior() const { - int32_t behavior = ui::mojom::kResizeBehaviorNone; + int32_t behavior = ws::mojom::kResizeBehaviorNone; if (CanResize()) - behavior |= ui::mojom::kResizeBehaviorCanResize; + behavior |= ws::mojom::kResizeBehaviorCanResize; if (CanMaximize()) - behavior |= ui::mojom::kResizeBehaviorCanMaximize; + behavior |= ws::mojom::kResizeBehaviorCanMaximize; if (CanMinimize()) - behavior |= ui::mojom::kResizeBehaviorCanMinimize; + behavior |= ws::mojom::kResizeBehaviorCanMinimize; return behavior; } diff --git a/chromium/ui/views/widget/widget_delegate.h b/chromium/ui/views/widget/widget_delegate.h index c1cfaebc417..5e394416c1e 100644 --- a/chromium/ui/views/widget/widget_delegate.h +++ b/chromium/ui/views/widget/widget_delegate.h @@ -62,7 +62,7 @@ class VIEWS_EXPORT WidgetDelegate { // Returns true if the window can be minimized. virtual bool CanMinimize() const; - // Returns a bitmask of ui::mojom::kResizeBehavior values. + // Returns a bitmask of ws::mojom::kResizeBehavior values. virtual int32_t GetResizeBehavior() const; // Returns true if the window can be activated. diff --git a/chromium/ui/views/widget/widget_interactive_uitest.cc b/chromium/ui/views/widget/widget_interactive_uitest.cc index 54744d6d8ed..ab1cd09214e 100644 --- a/chromium/ui/views/widget/widget_interactive_uitest.cc +++ b/chromium/ui/views/widget/widget_interactive_uitest.cc @@ -1341,10 +1341,6 @@ TEST_F(WidgetTestInteractive, InactiveWidgetDoesNotGrabActivation) { // Test that window state is not changed after getting out of full screen. TEST_F(WidgetTestInteractive, MAYBE_ExitFullscreenRestoreState) { - // TODO(http://crbug.com/864618): Fails flakily in mus with ws2. - if (IsMus()) - return; - Widget* toplevel = CreateTopLevelPlatformWidget(); toplevel->Show(); diff --git a/chromium/ui/views/widget/widget_observer.h b/chromium/ui/views/widget/widget_observer.h index fc13afae682..aa3e17f2047 100644 --- a/chromium/ui/views/widget/widget_observer.h +++ b/chromium/ui/views/widget/widget_observer.h @@ -5,6 +5,7 @@ #ifndef UI_VIEWS_WIDGET_WIDGET_OBSERVER_H_ #define UI_VIEWS_WIDGET_WIDGET_OBSERVER_H_ +#include "base/observer_list_types.h" #include "ui/views/views_export.h" namespace gfx { @@ -16,7 +17,7 @@ namespace views { class Widget; // Observers can listen to various events on the Widgets. -class VIEWS_EXPORT WidgetObserver { +class VIEWS_EXPORT WidgetObserver : public base::CheckedObserver { public: // The closing notification is sent immediately in response to (i.e. in the // same call stack as) a request to close the Widget (via Close() or @@ -46,7 +47,7 @@ class VIEWS_EXPORT WidgetObserver { const gfx::Rect& new_bounds) {} protected: - virtual ~WidgetObserver() {} + ~WidgetObserver() override {} }; } // namespace views diff --git a/chromium/ui/views/widget/widget_unittest.cc b/chromium/ui/views/widget/widget_unittest.cc index 3c77de47e4e..24c23336a45 100644 --- a/chromium/ui/views/widget/widget_unittest.cc +++ b/chromium/ui/views/widget/widget_unittest.cc @@ -20,7 +20,7 @@ #include "ui/events/test/event_generator.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/native_widget_types.h" -#include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/event_monitor.h" #include "ui/views/test/native_widget_factory.h" @@ -640,17 +640,8 @@ TEST_F(WidgetWithDestroyedNativeViewTest, Test) { class WidgetObserverTest : public WidgetTest, public WidgetObserver { public: - WidgetObserverTest() - : active_(nullptr), - widget_closed_(nullptr), - widget_activated_(nullptr), - widget_shown_(nullptr), - widget_hidden_(nullptr), - widget_bounds_changed_(nullptr), - widget_to_close_on_hide_(nullptr) { - } - - ~WidgetObserverTest() override {} + WidgetObserverTest() = default; + ~WidgetObserverTest() override = default; // Set a widget to Close() the next time the Widget being observed is hidden. void CloseOnNextHide(Widget* widget) { @@ -721,16 +712,16 @@ class WidgetObserverTest : public WidgetTest, public WidgetObserver { const Widget* widget_bounds_changed() const { return widget_bounds_changed_; } private: - Widget* active_; + Widget* active_ = nullptr; - Widget* widget_closed_; - Widget* widget_activated_; - Widget* widget_deactivated_; - Widget* widget_shown_; - Widget* widget_hidden_; - Widget* widget_bounds_changed_; + Widget* widget_closed_ = nullptr; + Widget* widget_activated_ = nullptr; + Widget* widget_deactivated_ = nullptr; + Widget* widget_shown_ = nullptr; + Widget* widget_hidden_ = nullptr; + Widget* widget_bounds_changed_ = nullptr; - Widget* widget_to_close_on_hide_; + Widget* widget_to_close_on_hide_ = nullptr; }; // This test appears to be flaky on Mac. @@ -741,21 +732,14 @@ class WidgetObserverTest : public WidgetTest, public WidgetObserver { #endif TEST_F(WidgetObserverTest, MAYBE_ActivationChange) { - // TODO(http://crbug.com/864800): Fails flakily in mus with ws2. - if (IsMus()) - return; - - WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget()); WidgetAutoclosePtr toplevel1(NewWidget()); WidgetAutoclosePtr toplevel2(NewWidget()); toplevel1->Show(); toplevel2->Show(); - reset(); toplevel1->Activate(); - RunPendingMessages(); EXPECT_EQ(toplevel1.get(), widget_activated()); @@ -1947,7 +1931,8 @@ TEST_F(WidgetTest, DestroyedWithCaptureViaEventMonitor) { widget->SetCapture(view_handler); ClosingEventHandler monitor_handler(widget); - auto monitor = EventMonitor::CreateApplicationMonitor(&monitor_handler); + auto monitor = EventMonitor::CreateApplicationMonitor( + &monitor_handler, widget->GetNativeWindow()); ui::test::EventGenerator generator( IsMus() ? widget->GetNativeWindow() : GetContext(), @@ -1959,6 +1944,29 @@ TEST_F(WidgetTest, DestroyedWithCaptureViaEventMonitor) { EXPECT_TRUE(observer.widget_closed()); } +// Widget used to destroy itself when OnNativeWidgetDestroyed is called. +class TestNativeWidgetDestroyedWidget : public Widget { + public: + // Overridden from NativeWidgetDelegate: + void OnNativeWidgetDestroyed() override; +}; + +void TestNativeWidgetDestroyedWidget::OnNativeWidgetDestroyed() { + Widget::OnNativeWidgetDestroyed(); + delete this; +} + +// Verifies that widget destroyed itself in OnNativeWidgetDestroyed does not +// crash in ASan. +TEST_F(WidgetTest, WidgetDestroyedItselfDoesNotCrash) { + TestDesktopWidgetDelegate delegate(new TestNativeWidgetDestroyedWidget); + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + delegate.InitWidget(params); + delegate.GetWidget()->Show(); + delegate.GetWidget()->CloseNow(); +} + // Verifies WindowClosing() is invoked correctly on the delegate when a Widget // is closed. TEST_F(WidgetTest, SingleWindowClosing) { diff --git a/chromium/ui/views/win/hwnd_message_handler.cc b/chromium/ui/views/win/hwnd_message_handler.cc index d5f442f71da..54eb0fcd0bf 100644 --- a/chromium/ui/views/win/hwnd_message_handler.cc +++ b/chromium/ui/views/win/hwnd_message_handler.cc @@ -31,6 +31,7 @@ #include "ui/base/ime/text_input_type.h" #include "ui/base/ui_base_features.h" #include "ui/base/view_prop.h" +#include "ui/base/win/hwnd_metrics.h" #include "ui/base/win/internal_constants.h" #include "ui/base/win/lock_state.h" #include "ui/base/win/mouse_wheel_util.h" @@ -615,52 +616,57 @@ void HWNDMessageHandler::StackAtTop() { SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); } -void HWNDMessageHandler::Show() { - if (IsWindow(hwnd())) { - if (!(GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) && - !(GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) { - ShowWindowWithState(ui::SHOW_STATE_NORMAL); - } else { - ShowWindowWithState(ui::SHOW_STATE_INACTIVE); - } - } -} - -void HWNDMessageHandler::ShowWindowWithState(ui::WindowShowState show_state) { - TRACE_EVENT0("views", "HWNDMessageHandler::ShowWindowWithState"); +void HWNDMessageHandler::Show(ui::WindowShowState show_state, + const gfx::Rect& pixel_restore_bounds) { + TRACE_EVENT0("views", "HWNDMessageHandler::Show"); DWORD native_show_state; - switch (show_state) { - case ui::SHOW_STATE_INACTIVE: - native_show_state = SW_SHOWNOACTIVATE; - break; - case ui::SHOW_STATE_MAXIMIZED: - native_show_state = SW_SHOWMAXIMIZED; - break; - case ui::SHOW_STATE_MINIMIZED: - native_show_state = SW_SHOWMINIMIZED; - break; - case ui::SHOW_STATE_NORMAL: - native_show_state = SW_SHOWNORMAL; - break; - case ui::SHOW_STATE_FULLSCREEN: - native_show_state = SW_SHOWNORMAL; - SetFullscreen(true); - break; - default: - native_show_state = delegate_->GetInitialShowState(); - break; - } + if (show_state == ui::SHOW_STATE_MAXIMIZED && + !pixel_restore_bounds.IsEmpty()) { + WINDOWPLACEMENT placement = {0}; + placement.length = sizeof(WINDOWPLACEMENT); + placement.showCmd = SW_SHOWMAXIMIZED; + placement.rcNormalPosition = pixel_restore_bounds.ToRECT(); + SetWindowPlacement(hwnd(), &placement); + native_show_state = SW_SHOWMAXIMIZED; + } else { + switch (show_state) { + case ui::SHOW_STATE_INACTIVE: + native_show_state = SW_SHOWNOACTIVATE; + break; + case ui::SHOW_STATE_MAXIMIZED: + native_show_state = SW_SHOWMAXIMIZED; + break; + case ui::SHOW_STATE_MINIMIZED: + native_show_state = SW_SHOWMINIMIZED; + break; + case ui::SHOW_STATE_NORMAL: + if ((GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_TRANSPARENT) || + (GetWindowLong(hwnd(), GWL_EXSTYLE) & WS_EX_NOACTIVATE)) { + native_show_state = SW_SHOWNOACTIVATE; + } else { + native_show_state = SW_SHOWNORMAL; + } + break; + case ui::SHOW_STATE_FULLSCREEN: + native_show_state = SW_SHOWNORMAL; + SetFullscreen(true); + break; + default: + native_show_state = delegate_->GetInitialShowState(); + break; + } - ShowWindow(hwnd(), native_show_state); - // When launched from certain programs like bash and Windows Live Messenger, - // show_state is set to SW_HIDE, so we need to correct that condition. We - // don't just change show_state to SW_SHOWNORMAL because MSDN says we must - // always first call ShowWindow with the specified value from STARTUPINFO, - // otherwise all future ShowWindow calls will be ignored (!!#@@#!). Instead, - // we call ShowWindow again in this case. - if (native_show_state == SW_HIDE) { - native_show_state = SW_SHOWNORMAL; ShowWindow(hwnd(), native_show_state); + // When launched from certain programs like bash and Windows Live + // Messenger, show_state is set to SW_HIDE, so we need to correct that + // condition. We don't just change show_state to SW_SHOWNORMAL because + // MSDN says we must always first call ShowWindow with the specified + // value from STARTUPINFO, otherwise all future ShowWindow calls will be + // ignored (!!#@@#!). Instead, we call ShowWindow again in this case. + if (native_show_state == SW_HIDE) { + native_show_state = SW_SHOWNORMAL; + ShowWindow(hwnd(), native_show_state); + } } // We need to explicitly activate the window if we've been shown with a state @@ -675,19 +681,6 @@ void HWNDMessageHandler::ShowWindowWithState(ui::WindowShowState show_state) { SetInitialFocus(); } -void HWNDMessageHandler::ShowMaximizedWithBounds(const gfx::Rect& bounds) { - WINDOWPLACEMENT placement = { 0 }; - placement.length = sizeof(WINDOWPLACEMENT); - placement.showCmd = SW_SHOWMAXIMIZED; - placement.rcNormalPosition = bounds.ToRECT(); - SetWindowPlacement(hwnd(), &placement); - - // We need to explicitly activate the window, because if we're opened from a - // desktop shortcut while an existing window is already running it doesn't - // seem to be enough to use SW_SHOWMAXIMIZED to activate the window. - Activate(); -} - void HWNDMessageHandler::Hide() { if (IsWindow(hwnd())) { // NOTE: Be careful not to activate any windows here (for example, calling @@ -1359,8 +1352,9 @@ void HWNDMessageHandler::ClientAreaSizeChanged() { sent_window_size_changing_ = false; } -bool HWNDMessageHandler::GetClientAreaInsets(gfx::Insets* insets) const { - if (delegate_->GetClientAreaInsets(insets)) +bool HWNDMessageHandler::GetClientAreaInsets(gfx::Insets* insets, + HMONITOR monitor) const { + if (delegate_->GetClientAreaInsets(insets, monitor)) return true; DCHECK(insets->IsEmpty()); @@ -1372,11 +1366,11 @@ bool HWNDMessageHandler::GetClientAreaInsets(gfx::Insets* insets) const { if (IsMaximized()) { // Windows automatically adds a standard width border to all sides when a // window is maximized. - int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); + int frame_thickness = ui::GetFrameThickness(monitor); if (!delegate_->HasFrame()) - border_thickness -= 1; - *insets = gfx::Insets( - border_thickness, border_thickness, border_thickness, border_thickness); + frame_thickness -= 1; + *insets = gfx::Insets(frame_thickness, frame_thickness, frame_thickness, + frame_thickness); return true; } @@ -1648,7 +1642,7 @@ LRESULT HWNDMessageHandler::OnDpiChanged(UINT msg, dpi = display::win::GetDPIFromScalingFactor(scaling_factor); } else { dpi = LOWORD(w_param); - scaling_factor = display::win::GetScalingFactorFromDPI(dpi_); + scaling_factor = display::win::GetScalingFactorFromDPI(dpi); } // The first WM_DPICHANGED originates from EnableChildWindowDpiMessage during @@ -1997,16 +1991,36 @@ LRESULT HWNDMessageHandler::OnNCCalcSize(BOOL mode, LPARAM l_param) { } } + RECT* client_rect = + mode ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0]) + : reinterpret_cast<RECT*>(l_param); + + HMONITOR monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONULL); + if (!monitor) { + // We might end up here if the window was previously minimized and the + // user clicks on the taskbar button to restore it in the previous + // position. In that case WM_NCCALCSIZE is sent before the window + // coordinates are restored to their previous values, so our (left,top) + // would probably be (-32000,-32000) like all minimized windows. So the + // above MonitorFromWindow call fails, but if we check the window rect + // given with WM_NCCALCSIZE (which is our previous restored window + // position) we will get the correct monitor handle. + monitor = MonitorFromRect(client_rect, MONITOR_DEFAULTTONULL); + if (!monitor) { + // This is probably an extreme case that we won't hit, but if we don't + // intersect any monitor, let us not adjust the client rect since our + // window will not be visible anyway. + return 0; + } + } + gfx::Insets insets; - bool got_insets = GetClientAreaInsets(&insets); + bool got_insets = GetClientAreaInsets(&insets, monitor); if (!got_insets && !IsFullscreen() && !(mode && !delegate_->HasFrame())) { SetMsgHandled(FALSE); return 0; } - RECT* client_rect = mode ? - &(reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0]) : - reinterpret_cast<RECT*>(l_param); client_rect->left += insets.left(); client_rect->top += insets.top(); client_rect->bottom -= insets.bottom(); @@ -2016,24 +2030,6 @@ LRESULT HWNDMessageHandler::OnNCCalcSize(BOOL mode, LPARAM l_param) { // thickness of the auto-hide taskbar on each such edge, so the window isn't // treated as a "fullscreen app", which would cause the taskbars to // disappear. - HMONITOR monitor = MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONULL); - if (!monitor) { - // We might end up here if the window was previously minimized and the - // user clicks on the taskbar button to restore it in the previously - // maximized position. In that case WM_NCCALCSIZE is sent before the - // window coordinates are restored to their previous values, so our - // (left,top) would probably be (-32000,-32000) like all minimized - // windows. So the above MonitorFromWindow call fails, but if we check - // the window rect given with WM_NCCALCSIZE (which is our previous - // restored window position) we will get the correct monitor handle. - monitor = MonitorFromRect(client_rect, MONITOR_DEFAULTTONULL); - if (!monitor) { - // This is probably an extreme case that we won't hit, but if we don't - // intersect any monitor, let us not adjust the client rect since our - // window will not be visible anyway. - return 0; - } - } const int autohide_edges = GetAppbarAutohideEdges(monitor); if (autohide_edges & ViewsDelegate::EDGE_LEFT) client_rect->left += kAutoHideTaskbarThicknessPx; @@ -2611,7 +2607,7 @@ void HWNDMessageHandler::OnWindowPosChanging(WINDOWPOS* window_pos) { // Windows automatically adds a standard width border to all sides when // window is maximized. We should take this into account. gfx::Insets client_area_insets; - if (GetClientAreaInsets(&client_area_insets)) + if (GetClientAreaInsets(&client_area_insets, monitor)) expected_maximized_bounds.Inset(client_area_insets.Scale(-1)); } // Sometimes Windows incorrectly changes bounds of maximized windows after diff --git a/chromium/ui/views/win/hwnd_message_handler.h b/chromium/ui/views/win/hwnd_message_handler.h index 91afedec8e2..593c3c0e95b 100644 --- a/chromium/ui/views/win/hwnd_message_handler.h +++ b/chromium/ui/views/win/hwnd_message_handler.h @@ -107,9 +107,10 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, void StackAbove(HWND other_hwnd); void StackAtTop(); - void Show(); - void ShowWindowWithState(ui::WindowShowState show_state); - void ShowMaximizedWithBounds(const gfx::Rect& bounds); + // Shows the window. If |show_state| is maximized, |pixel_restore_bounds| is + // the bounds to restore the window to when going back to normal. + void Show(ui::WindowShowState show_state, + const gfx::Rect& pixel_restore_bounds); void Hide(); void Maximize(); @@ -270,9 +271,14 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, // or subsequently. void ClientAreaSizeChanged(); - // Returns the insets of the client area relative to the non-client area of - // the window. - bool GetClientAreaInsets(gfx::Insets* insets) const; + // Returns true if |insets| was modified to define a custom client area for + // the window, false if the default client area should be used. If false is + // returned, |insets| is not modified. |monitor| is the monitor this + // window is on. Normally that would be determined from the HWND, but + // during WM_NCCALCSIZE Windows does not return the correct monitor for the + // HWND, so it must be passed in explicitly (see HWNDMessageHandler:: + // OnNCCalcSize for more details). + bool GetClientAreaInsets(gfx::Insets* insets, HMONITOR monitor) const; // Resets the window region for the current widget bounds if necessary. // If |force| is true, the window region is reset to NULL even for native diff --git a/chromium/ui/views/win/hwnd_message_handler_delegate.h b/chromium/ui/views/win/hwnd_message_handler_delegate.h index 1b2d98a8573..b3f670dddf8 100644 --- a/chromium/ui/views/win/hwnd_message_handler_delegate.h +++ b/chromium/ui/views/win/hwnd_message_handler_delegate.h @@ -87,8 +87,13 @@ class VIEWS_EXPORT HWNDMessageHandlerDelegate { // Returns true if the delegate modifies |insets| to define a custom client // area for the window, false if the default client area should be used. If - // false is returned, |insets| is not modified. - virtual bool GetClientAreaInsets(gfx::Insets* insets) const = 0; + // false is returned, |insets| is not modified. |monitor| is the monitor + // this window is on. Normally that would be determined from the HWND, but + // during WM_NCCALCSIZE Windows does not return the correct monitor for the + // HWND, so it must be passed in explicitly (see HWNDMessageHandler:: + // OnNCCalcSize for more details). + virtual bool GetClientAreaInsets(gfx::Insets* insets, + HMONITOR monitor) const = 0; // Returns the minimum and maximum size the window can be resized to by the // user. diff --git a/chromium/ui/views/win/pen_event_processor.cc b/chromium/ui/views/win/pen_event_processor.cc index b5e70938413..b820c51be5e 100644 --- a/chromium/ui/views/win/pen_event_processor.cc +++ b/chromium/ui/views/win/pen_event_processor.cc @@ -45,12 +45,12 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateEvent( // have to check if previously the pointer type is an eraser. if (pointer_pen_info.penFlags & PEN_FLAG_ERASER) { input_type = ui::EventPointerType::POINTER_TYPE_ERASER; - DCHECK(eraser_pointer_id_ == -1 || eraser_pointer_id_ == mapped_pointer_id); + DCHECK(!eraser_pointer_id_ || *eraser_pointer_id_ == mapped_pointer_id); eraser_pointer_id_ = mapped_pointer_id; - } else if (eraser_pointer_id_ == mapped_pointer_id && + } else if (eraser_pointer_id_ && *eraser_pointer_id_ == mapped_pointer_id && message == WM_POINTERUP) { input_type = ui::EventPointerType::POINTER_TYPE_ERASER; - eraser_pointer_id_ = -1; + eraser_pointer_id_.reset(); } // convert pressure into a float [0, 1]. The range of the pressure is diff --git a/chromium/ui/views/win/pen_event_processor.h b/chromium/ui/views/win/pen_event_processor.h index 87d949c01bf..ab10799c7be 100644 --- a/chromium/ui/views/win/pen_event_processor.h +++ b/chromium/ui/views/win/pen_event_processor.h @@ -9,6 +9,7 @@ #include <memory> +#include "base/optional.h" #include "ui/events/event.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/sequential_id_generator.h" @@ -54,7 +55,7 @@ class VIEWS_EXPORT PenEventProcessor { bool send_touch_for_pen_ = false; bool sent_mouse_down_ = false; bool sent_touch_start_ = false; - int eraser_pointer_id_ = -1; + base::Optional<unsigned int> eraser_pointer_id_; DISALLOW_COPY_AND_ASSIGN(PenEventProcessor); }; diff --git a/chromium/ui/views/win/windows_session_change_observer.cc b/chromium/ui/views/win/windows_session_change_observer.cc index e2021dbcdc3..c2de1e1b3e4 100644 --- a/chromium/ui/views/win/windows_session_change_observer.cc +++ b/chromium/ui/views/win/windows_session_change_observer.cc @@ -15,7 +15,7 @@ #include "base/macros.h" #include "base/memory/singleton.h" #include "base/observer_list.h" -#include "base/task_scheduler/post_task.h" +#include "base/task/post_task.h" #include "ui/gfx/win/singleton_hwnd.h" #include "ui/gfx/win/singleton_hwnd_observer.h" #include "ui/views/views_delegate.h" @@ -98,7 +98,8 @@ class WindowsSessionChangeObserver::WtsRegistrationNotificationManager { observer.ClearCallback(); } - base::ObserverList<WindowsSessionChangeObserver, true> observer_list_; + base::ObserverList<WindowsSessionChangeObserver, true>::Unchecked + observer_list_; std::unique_ptr<gfx::SingletonHwndObserver> singleton_hwnd_observer_; DISALLOW_COPY_AND_ASSIGN(WtsRegistrationNotificationManager); diff --git a/chromium/ui/views/window/custom_frame_view.cc b/chromium/ui/views/window/custom_frame_view.cc index 22d0bed6be8..0ae8ebd9ad3 100644 --- a/chromium/ui/views/window/custom_frame_view.cc +++ b/chromium/ui/views/window/custom_frame_view.cc @@ -197,8 +197,12 @@ void CustomFrameView::UpdateWindowIcon() { } void CustomFrameView::UpdateWindowTitle() { - if (frame_->widget_delegate()->ShouldShowWindowTitle()) + if (frame_->widget_delegate()->ShouldShowWindowTitle() && + // If this is still unset, we haven't laid out window controls yet. + maximum_title_bar_x_ > -1) { + LayoutTitleBar(); SchedulePaintInRect(title_bounds_); + } } void CustomFrameView::SizeConstraintsChanged() { diff --git a/chromium/ui/views/window/dialog_delegate.h b/chromium/ui/views/window/dialog_delegate.h index 83019bb77d9..bbba8b1be6e 100644 --- a/chromium/ui/views/window/dialog_delegate.h +++ b/chromium/ui/views/window/dialog_delegate.h @@ -145,7 +145,7 @@ class VIEWS_EXPORT DialogDelegate : public ui::DialogModel, base::TimeTicks creation_time_; // Observers for DialogModel changes. - base::ObserverList<DialogObserver> observer_list_; + base::ObserverList<DialogObserver>::Unchecked observer_list_; DISALLOW_COPY_AND_ASSIGN(DialogDelegate); }; diff --git a/chromium/ui/views/window/dialog_delegate_unittest.cc b/chromium/ui/views/window/dialog_delegate_unittest.cc index bb2b8d54a35..5fd4c862b0e 100644 --- a/chromium/ui/views/window/dialog_delegate_unittest.cc +++ b/chromium/ui/views/window/dialog_delegate_unittest.cc @@ -214,25 +214,23 @@ TEST_F(DialogTest, HitTest_HiddenTitle) { // Ensure that BubbleFrameView hit-tests as expected when the title is hidden. const NonClientView* view = dialog()->GetWidget()->non_client_view(); BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view()); - const int border = frame->bubble_border()->GetBorderThickness(); struct { const int point; const int hit; } cases[] = { - {border, HTSYSMENU}, - {border + 10, HTSYSMENU}, - {border + 20, HTNOWHERE}, - {border + 50, HTCLIENT /* Space is reserved for the close button. */}, - {border + 60, HTCLIENT}, + {0, HTSYSMENU}, + {10, HTSYSMENU}, + {20, HTNOWHERE}, + {50, HTCLIENT /* Space is reserved for the close button. */}, + {60, HTCLIENT}, {1000, HTNOWHERE}, }; for (size_t i = 0; i < arraysize(cases); ++i) { gfx::Point point(cases[i].point, cases[i].point); EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point)) - << " case " << i << " with border: " << border << ", at point " - << cases[i].point; + << " case " << i << " at point " << cases[i].point; } } @@ -243,22 +241,19 @@ TEST_F(DialogTest, HitTest_HiddenTitleNoCloseButton) { const NonClientView* view = dialog()->GetWidget()->non_client_view(); BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view()); - const int border = frame->bubble_border()->GetBorderThickness(); struct { const int point; const int hit; } cases[] = { - {border, HTSYSMENU}, {border + 10, HTSYSMENU}, - {border + 20, HTCLIENT}, {border + 50, HTCLIENT}, - {border + 60, HTCLIENT}, {1000, HTNOWHERE}, + {0, HTSYSMENU}, {10, HTSYSMENU}, {20, HTCLIENT}, + {50, HTCLIENT}, {60, HTCLIENT}, {1000, HTNOWHERE}, }; for (size_t i = 0; i < arraysize(cases); ++i) { gfx::Point point(cases[i].point, cases[i].point); EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point)) - << " case " << i << " with border: " << border << ", at point " - << cases[i].point; + << " case " << i << " at point " << cases[i].point; } } @@ -268,24 +263,19 @@ TEST_F(DialogTest, HitTest_WithTitle) { dialog()->set_title(base::ASCIIToUTF16("Title")); dialog()->GetWidget()->UpdateWindowTitle(); BubbleFrameView* frame = static_cast<BubbleFrameView*>(view->frame_view()); - const int border = frame->bubble_border()->GetBorderThickness(); struct { const int point; const int hit; } cases[] = { - { border, HTSYSMENU }, - { border + 10, HTSYSMENU }, - { border + 20, HTCAPTION }, - { border + 50, HTCLIENT }, - { border + 60, HTCLIENT }, - { 1000, HTNOWHERE }, + {0, HTSYSMENU}, {10, HTSYSMENU}, {20, HTCAPTION}, + {50, HTCLIENT}, {60, HTCLIENT}, {1000, HTNOWHERE}, }; for (size_t i = 0; i < arraysize(cases); ++i) { gfx::Point point(cases[i].point, cases[i].point); EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point)) - << " with border: " << border << ", at point " << cases[i].point; + << " at point " << cases[i].point; } } diff --git a/chromium/ui/views/window/hit_test_utils.cc b/chromium/ui/views/window/hit_test_utils.cc new file mode 100644 index 00000000000..30d8e45eb8c --- /dev/null +++ b/chromium/ui/views/window/hit_test_utils.cc @@ -0,0 +1,38 @@ +// 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/window/hit_test_utils.h" + +#include "ui/base/hit_test.h" +#include "ui/gfx/geometry/point.h" +#include "ui/views/view.h" +#include "ui/views/view_properties.h" + +namespace views { + +int GetHitTestComponent(View* view, const gfx::Point& point_in_widget) { + gfx::Point point_in_view(point_in_widget); + View::ConvertPointFromWidget(view, &point_in_view); + + if (!view->GetLocalBounds().Contains(point_in_view)) + return HTNOWHERE; + + View* target_view = view->GetEventHandlerForPoint(point_in_view); + while (target_view) { + int component = target_view->GetProperty(kHitTestComponentKey); + if (component != HTNOWHERE) + return component; + if (target_view == view) + break; + target_view = target_view->parent(); + } + + return HTNOWHERE; +} + +void SetHitTestComponent(View* view, int hit_test_id) { + view->SetProperty(kHitTestComponentKey, hit_test_id); +} + +} // namespace views diff --git a/chromium/ui/views/window/hit_test_utils.h b/chromium/ui/views/window/hit_test_utils.h new file mode 100644 index 00000000000..3709e185151 --- /dev/null +++ b/chromium/ui/views/window/hit_test_utils.h @@ -0,0 +1,29 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_VIEWS_WINDOW_HIT_TEST_UTILS_H_ +#define UI_VIEWS_WINDOW_HIT_TEST_UTILS_H_ + +#include "ui/views/views_export.h" + +namespace gfx { +class Point; +} + +namespace views { + +class View; + +// Returns the inner most non-HTNOWHERE |kHitTestComponentKey| value at +// |point_in_widget| within the hierarchy of |view|, otherwise returns +// HTNOWHERE. +VIEWS_EXPORT int GetHitTestComponent(View* view, + const gfx::Point& point_in_widget); + +// Sets the |kHitTestComponentKey| property of |view|. +VIEWS_EXPORT void SetHitTestComponent(View* view, int hit_test_id); + +} // namespace views + +#endif // UI_VIEWS_WINDOW_HIT_TEST_UTILS_H_ diff --git a/chromium/ui/views/window/hit_test_utils_unittest.cc b/chromium/ui/views/window/hit_test_utils_unittest.cc new file mode 100644 index 00000000000..a2f2f9ab53a --- /dev/null +++ b/chromium/ui/views/window/hit_test_utils_unittest.cc @@ -0,0 +1,66 @@ +// 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/window/hit_test_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/hit_test.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/view.h" +#include "ui/views/view_properties.h" +#include "ui/views/widget/widget.h" + +namespace views { + +using GetHitTestComponentTest = ViewsTestBase; + +TEST_F(GetHitTestComponentTest, BasicTests) { + Widget* widget = new Widget; + widget->Init(CreateParams(Widget::InitParams::TYPE_WINDOW)); + + // Testing arrangement diagram: + // *=============root:HTCLIENT=============* + // | *=left:HTLEFT=* *=nowhere:HTNOWHERE=* | + // | | | | *=right:HTRIGHT=* | | + // | | | | | | | | + // | | | | *===============* | | + // | *=============* *===================* | + // *=======================================* + View* root = widget->GetRootView(); + root->SetProperty(views::kHitTestComponentKey, static_cast<int>(HTCLIENT)); + root->SetBounds(0, 0, 100, 100); + + View* left = new View; + left->SetProperty(views::kHitTestComponentKey, static_cast<int>(HTLEFT)); + left->SetBounds(10, 10, 30, 80); + root->AddChildView(left); + + View* nowhere = new View; + nowhere->SetBounds(60, 10, 30, 80); + root->AddChildView(nowhere); + + View* right = new View; + right->SetProperty(views::kHitTestComponentKey, static_cast<int>(HTRIGHT)); + right->SetBounds(10, 10, 10, 60); + nowhere->AddChildView(right); + + // Hit the root view. + EXPECT_EQ(GetHitTestComponent(root, gfx::Point(50, 50)), HTCLIENT); + + // Hit the left view. + EXPECT_EQ(GetHitTestComponent(root, gfx::Point(25, 50)), HTLEFT); + + // Hit the nowhere view, should return the root view's value. + EXPECT_EQ(GetHitTestComponent(root, gfx::Point(65, 50)), HTCLIENT); + + // Hit the right view. + EXPECT_EQ(GetHitTestComponent(root, gfx::Point(75, 50)), HTRIGHT); + + // Hit outside the root view. + EXPECT_EQ(GetHitTestComponent(root, gfx::Point(200, 50)), HTNOWHERE); + + widget->CloseNow(); +} + +} // namespace views |