diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-24 12:15:48 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 13:30:04 +0000 |
commit | b014812705fc80bff0a5c120dfcef88f349816dc (patch) | |
tree | 25a2e2d9fa285f1add86aa333389a839f81a39ae /chromium/ui/views | |
parent | 9f4560b1027ae06fdb497023cdcaf91b8511fa74 (diff) | |
download | qtwebengine-chromium-b014812705fc80bff0a5c120dfcef88f349816dc.tar.gz |
BASELINE: Update Chromium to 68.0.3440.125
Change-Id: I23f19369e01f688e496f5bf179abb521ad73874f
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/views')
184 files changed, 4037 insertions, 1948 deletions
diff --git a/chromium/ui/views/BUILD.gn b/chromium/ui/views/BUILD.gn index 32f534f12e3..c9ecb17ed42 100644 --- a/chromium/ui/views/BUILD.gn +++ b/chromium/ui/views/BUILD.gn @@ -115,7 +115,6 @@ jumbo_component("views") { "controls/combobox/combobox_listener.h", "controls/focus_ring.h", "controls/focusable_border.h", - "controls/glow_hover_controller.h", "controls/image_view.h", "controls/label.h", "controls/link.h", @@ -245,6 +244,7 @@ jumbo_component("views") { "widget/widget_deletion_observer.h", "widget/widget_observer.h", "widget/widget_removals_observer.h", + "widget/widget_utils_mac.h", "window/client_view.h", "window/custom_frame_view.h", "window/dialog_client_view.h", @@ -313,7 +313,6 @@ jumbo_component("views") { "controls/combobox/combobox.cc", "controls/focus_ring.cc", "controls/focusable_border.cc", - "controls/glow_hover_controller.cc", "controls/image_view.cc", "controls/label.cc", "controls/link.cc", @@ -369,6 +368,8 @@ jumbo_component("views") { "controls/tree/tree_view_controller.cc", "controls/tree/tree_view_drawing_provider.cc", "controls/views_text_services_context_menu.cc", + "controls/views_text_services_context_menu_base.cc", + "controls/views_text_services_context_menu_base.h", "controls/views_text_services_context_menu_mac.mm", "debug_utils.cc", "drag_utils.cc", @@ -418,6 +419,7 @@ jumbo_component("views") { "views_touch_selection_controller_factory_mac.cc", "widget/drop_helper.cc", "widget/native_widget_mac.mm", + "widget/native_widget_private.cc", "widget/root_view.cc", "widget/root_view_targeter.cc", "widget/tooltip_manager.cc", @@ -425,6 +427,7 @@ jumbo_component("views") { "widget/widget_aura_utils.cc", "widget/widget_delegate.cc", "widget/widget_deletion_observer.cc", + "widget/widget_utils_mac.mm", "window/client_view.cc", "window/custom_frame_view.cc", "window/dialog_client_view.cc", @@ -629,6 +632,7 @@ jumbo_component("views") { "widget/desktop_aura/desktop_focus_rules.cc", "widget/desktop_aura/desktop_native_cursor_manager.cc", "widget/desktop_aura/desktop_native_widget_aura.cc", + "widget/desktop_aura/desktop_screen.cc", "widget/desktop_aura/desktop_screen_position_client.cc", "widget/focus_manager_event_handler.cc", "widget/native_widget_aura.cc", @@ -686,7 +690,8 @@ jumbo_component("views") { "widget/desktop_aura/desktop_window_tree_host_win.h", ] } else if (use_ozone) { - sources += [ "widget/desktop_aura/desktop_window_tree_host_ozone.cc" ] + public += [ "widget/desktop_aura/desktop_screen_ozone.h" ] + sources += [ "widget/desktop_aura/desktop_screen_ozone.cc" ] } if (is_linux) { sources += [ @@ -694,15 +699,16 @@ 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) { - sources += [ "widget/desktop_aura/desktop_window_tree_host_platform.cc" ] - public += [ "widget/desktop_aura/desktop_window_tree_host_platform.h" ] - } - if (is_mac) { sources -= [ "controls/views_text_services_context_menu.cc" ] deps += [ diff --git a/chromium/ui/views/OWNERS b/chromium/ui/views/OWNERS index c4ad8565841..4aca57c69ca 100644 --- a/chromium/ui/views/OWNERS +++ b/chromium/ui/views/OWNERS @@ -5,4 +5,9 @@ sadrul@chromium.org sky@chromium.org tapted@chromium.org +# Prefer ellyjones@ for any changes to these files. +per-file *_mac.*=ellyjones@chromium.org +per-file *_cocoa.*=ellyjones@chromium.org +per-file *.mm=ellyjones@chromium.org + # COMPONENT: Internals>Views diff --git a/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc b/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc index 27a8229d2f1..d3821559c0a 100644 --- a/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc +++ b/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc @@ -40,7 +40,7 @@ class AXSystemCaretWinTest : public test::WidgetTest { gl::GLSurfaceTestSupport::InitializeOneOff(); ui::RegisterPathProvider(); base::FilePath ui_test_pak_path; - ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); + ASSERT_TRUE(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); test::WidgetTest::SetUp(); diff --git a/chromium/ui/views/accessibility/native_view_accessibility_auralinux.cc b/chromium/ui/views/accessibility/native_view_accessibility_auralinux.cc index 1d155c6fa74..a098c1b5877 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_auralinux.cc +++ b/chromium/ui/views/accessibility/native_view_accessibility_auralinux.cc @@ -123,6 +123,26 @@ class AuraLinuxApplication return gfx::kNullAcceleratedWidget; } + int GetTableRowCount() const override { return 0; } + + int GetTableColCount() const override { return 0; } + + std::vector<int32_t> GetColHeaderNodeIds(int32_t col_index) const override { + return std::vector<int32_t>(); + } + + std::vector<int32_t> GetRowHeaderNodeIds(int32_t row_index) const override { + return std::vector<int32_t>(); + } + + int32_t GetCellId(int32_t row_index, int32_t col_index) const override { + return -1; + } + + int32_t CellIdToIndex(int32_t cell_id) const override { return -1; } + + int32_t CellIndexToId(int32_t cell_index) const override { return -1; } + bool AccessibilityPerformAction(const ui::AXActionData& data) override { return false; } diff --git a/chromium/ui/views/accessibility/native_view_accessibility_base.cc b/chromium/ui/views/accessibility/native_view_accessibility_base.cc index e895c9d3418..d22d3778b41 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_base.cc +++ b/chromium/ui/views/accessibility/native_view_accessibility_base.cc @@ -291,8 +291,9 @@ void NativeViewAccessibilityBase::OnAutofillShown() { } void NativeViewAccessibilityBase::OnAutofillHidden() { - DCHECK(fake_focus_view_id_ == GetUniqueId().Get()) - << "Cannot clear fake focus on an object that did not have fake focus."; + DCHECK(fake_focus_view_id_) << "No autofill fake focus set."; + DCHECK_EQ(fake_focus_view_id_, GetUniqueId().Get()) + << "Cannot clear autofill fake focus on an object that did not have it."; fake_focus_view_id_ = 0; ui::AXPlatformNode::OnAutofillHidden(); } @@ -322,6 +323,37 @@ NativeViewAccessibilityBase::GetTargetForNativeAccessibilityEvent() { return gfx::kNullAcceleratedWidget; } +int NativeViewAccessibilityBase::GetTableRowCount() const { + return 0; +} + +int NativeViewAccessibilityBase::GetTableColCount() const { + return 0; +} + +std::vector<int32_t> NativeViewAccessibilityBase::GetColHeaderNodeIds( + int32_t col_index) const { + return std::vector<int32_t>(); +} + +std::vector<int32_t> NativeViewAccessibilityBase::GetRowHeaderNodeIds( + int32_t row_index) const { + return std::vector<int32_t>(); +} + +int32_t NativeViewAccessibilityBase::GetCellId(int32_t row_index, + int32_t col_index) const { + return 0; +} + +int32_t NativeViewAccessibilityBase::CellIdToIndex(int32_t cell_id) const { + return -1; +} + +int32_t NativeViewAccessibilityBase::CellIndexToId(int32_t cell_index) const { + return 0; +} + bool NativeViewAccessibilityBase::AccessibilityPerformAction( const ui::AXActionData& data) { return view()->HandleAccessibleAction(data); diff --git a/chromium/ui/views/accessibility/native_view_accessibility_base.h b/chromium/ui/views/accessibility/native_view_accessibility_base.h index cb3d15a8eec..11f0d7e778b 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_base.h +++ b/chromium/ui/views/accessibility/native_view_accessibility_base.h @@ -54,6 +54,13 @@ class VIEWS_EXPORT NativeViewAccessibilityBase ui::AXPlatformNode* GetFromNodeID(int32_t id) override; int GetIndexInParent() const override; gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; + int GetTableRowCount() const override; + int GetTableColCount() const override; + std::vector<int32_t> GetColHeaderNodeIds(int32_t col_index) const override; + std::vector<int32_t> GetRowHeaderNodeIds(int32_t row_index) const override; + int32_t GetCellId(int32_t row_index, int32_t col_index) const override; + int32_t CellIdToIndex(int32_t cell_id) const override; + int32_t CellIndexToId(int32_t cell_index) const override; bool AccessibilityPerformAction(const ui::AXActionData& data) override; bool ShouldIgnoreHoveredStateForTesting() override; bool IsOffscreen() const override; diff --git a/chromium/ui/views/accessible_pane_view.cc b/chromium/ui/views/accessible_pane_view.cc index 87cb386a30c..cfd86c97ac1 100644 --- a/chromium/ui/views/accessible_pane_view.cc +++ b/chromium/ui/views/accessible_pane_view.cc @@ -4,7 +4,6 @@ #include "ui/views/accessible_pane_view.h" -#include "base/message_loop/message_loop.h" #include "ui/accessibility/ax_node_data.h" #include "ui/views/focus/focus_search.h" #include "ui/views/view_tracker.h" @@ -25,8 +24,9 @@ class AccessiblePaneViewFocusSearch : public FocusSearch { protected: View* GetParent(View* v) override { - return accessible_pane_view_->ContainsForFocusSearch(root(), v) ? - accessible_pane_view_->GetParentForFocusSearch(v) : NULL; + return accessible_pane_view_->ContainsForFocusSearch(root(), v) + ? accessible_pane_view_->GetParentForFocusSearch(v) + : nullptr; } // Returns true if |v| is contained within the hierarchy rooted at |root|. @@ -43,7 +43,7 @@ class AccessiblePaneViewFocusSearch : public FocusSearch { AccessiblePaneView::AccessiblePaneView() : pane_has_focus_(false), allow_deactivate_on_esc_(false), - focus_manager_(NULL), + focus_manager_(nullptr), home_key_(ui::VKEY_HOME, ui::EF_NONE), end_key_(ui::VKEY_END, ui::EF_NONE), escape_key_(ui::VKEY_ESCAPE, ui::EF_NONE), @@ -109,7 +109,7 @@ bool AccessiblePaneView::SetPaneFocusAndFocusDefault() { } views::View* AccessiblePaneView::GetDefaultFocusableChild() { - return NULL; + return nullptr; } View* AccessiblePaneView::GetParentForFocusSearch(View* v) { @@ -135,7 +135,10 @@ views::View* AccessiblePaneView::GetFirstFocusableChild() { FocusTraversable* dummy_focus_traversable; views::View* dummy_focus_traversable_view; return focus_search_->FindNextFocusableView( - NULL, false, views::FocusSearch::DOWN, false, + nullptr, FocusSearch::SearchDirection::kForwards, + FocusSearch::TraversalDirection::kDown, + FocusSearch::StartingViewPolicy::kSkipStartingView, + FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog, &dummy_focus_traversable, &dummy_focus_traversable_view); } @@ -143,7 +146,10 @@ views::View* AccessiblePaneView::GetLastFocusableChild() { FocusTraversable* dummy_focus_traversable; views::View* dummy_focus_traversable_view; return focus_search_->FindNextFocusableView( - this, true, views::FocusSearch::DOWN, false, + this, FocusSearch::SearchDirection::kBackwards, + FocusSearch::TraversalDirection::kDown, + FocusSearch::StartingViewPolicy::kSkipStartingView, + FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog, &dummy_focus_traversable, &dummy_focus_traversable_view); } @@ -154,7 +160,7 @@ views::FocusTraversable* AccessiblePaneView::GetPaneFocusTraversable() { if (pane_has_focus_) return this; else - return NULL; + return nullptr; } bool AccessiblePaneView::AcceleratorPressed( @@ -247,12 +253,12 @@ views::FocusSearch* AccessiblePaneView::GetFocusSearch() { views::FocusTraversable* AccessiblePaneView::GetFocusTraversableParent() { DCHECK(pane_has_focus_); - return NULL; + return nullptr; } views::View* AccessiblePaneView::GetFocusTraversableParentView() { DCHECK(pane_has_focus_); - return NULL; + return nullptr; } } // namespace views diff --git a/chromium/ui/views/animation/ink_drop_host_view.cc b/chromium/ui/views/animation/ink_drop_host_view.cc index 922600f1375..1d024628715 100644 --- a/chromium/ui/views/animation/ink_drop_host_view.cc +++ b/chromium/ui/views/animation/ink_drop_host_view.cc @@ -4,6 +4,7 @@ #include "ui/views/animation/ink_drop_host_view.h" +#include "build/build_config.h" #include "ui/events/event.h" #include "ui/events/scoped_target_handler.h" #include "ui/gfx/color_palette.h" @@ -21,16 +22,16 @@ namespace { // The scale factor to compute the large size of the default // SquareInkDropRipple. -const float kLargeInkDropScale = 1.333f; +constexpr float kLargeInkDropScale = 1.333f; // Default opacity of the ink drop when it is visible. -const float kInkDropVisibleOpacity = 0.175f; +constexpr float kInkDropVisibleOpacity = 0.175f; -} // namespace +// Default corner radii used for the SquareInkDropRipple. +constexpr int kInkDropSmallCornerRadius = 2; +constexpr int kInkDropLargeCornerRadius = 4; -// static -constexpr int InkDropHostView::kInkDropSmallCornerRadius; -constexpr int InkDropHostView::kInkDropLargeCornerRadius; +} // namespace // An EventHandler that is guaranteed to be invoked and is not prone to // InkDropHostView descendents who do not call @@ -119,6 +120,8 @@ InkDropHostView::InkDropHostView() ink_drop_(nullptr), ink_drop_visible_opacity_( PlatformStyle::kUseRipples ? kInkDropVisibleOpacity : 0), + ink_drop_small_corner_radius_(kInkDropSmallCornerRadius), + ink_drop_large_corner_radius_(kInkDropLargeCornerRadius), old_paint_to_layer_(false), destroying_(false) {} @@ -175,8 +178,8 @@ std::unique_ptr<InkDropRipple> InkDropHostView::CreateDefaultInkDropRipple( const gfx::Point& center_point, const gfx::Size& size) const { std::unique_ptr<InkDropRipple> ripple(new SquareInkDropRipple( - CalculateLargeInkDropSize(size), kInkDropLargeCornerRadius, size, - kInkDropSmallCornerRadius, center_point, GetInkDropBaseColor(), + CalculateLargeInkDropSize(size), ink_drop_large_corner_radius_, size, + ink_drop_small_corner_radius_, center_point, GetInkDropBaseColor(), ink_drop_visible_opacity())); return ripple; } @@ -184,8 +187,9 @@ std::unique_ptr<InkDropRipple> InkDropHostView::CreateDefaultInkDropRipple( std::unique_ptr<InkDropHighlight> InkDropHostView::CreateDefaultInkDropHighlight(const gfx::PointF& center_point, const gfx::Size& size) const { - std::unique_ptr<InkDropHighlight> highlight(new InkDropHighlight( - size, kInkDropSmallCornerRadius, center_point, GetInkDropBaseColor())); + std::unique_ptr<InkDropHighlight> highlight( + new InkDropHighlight(size, ink_drop_small_corner_radius_, center_point, + GetInkDropBaseColor())); highlight->set_explode_size(gfx::SizeF(CalculateLargeInkDropSize(size))); return highlight; } @@ -297,12 +301,9 @@ InkDrop* InkDropHostView::GetInkDrop() { } void InkDropHostView::InstallInkDropMask(ui::Layer* ink_drop_layer) { -// Layer masks don't work on Windows. See crbug.com/713359 -#if !defined(OS_WIN) ink_drop_mask_ = CreateInkDropMask(); if (ink_drop_mask_) ink_drop_layer->SetMaskLayer(ink_drop_mask_->layer()); -#endif } void InkDropHostView::ResetInkDropMask() { diff --git a/chromium/ui/views/animation/ink_drop_host_view.h b/chromium/ui/views/animation/ink_drop_host_view.h index 81d018254a5..14929b2564b 100644 --- a/chromium/ui/views/animation/ink_drop_host_view.h +++ b/chromium/ui/views/animation/ink_drop_host_view.h @@ -64,6 +64,17 @@ class VIEWS_EXPORT InkDropHostView : public View, public InkDropHost { } float ink_drop_visible_opacity() const { return ink_drop_visible_opacity_; } + void set_ink_drop_corner_radii(int small_radius, int large_radius) { + ink_drop_small_corner_radius_ = small_radius; + ink_drop_large_corner_radius_ = large_radius; + } + int ink_drop_small_corner_radius() const { + return ink_drop_small_corner_radius_; + } + int ink_drop_large_corner_radius() const { + return ink_drop_large_corner_radius_; + } + // Animates |ink_drop_| to the desired |ink_drop_state|. Caches |event| as the // last_ripple_triggering_event(). // @@ -74,9 +85,6 @@ class VIEWS_EXPORT InkDropHostView : public View, public InkDropHost { void AnimateInkDrop(InkDropState state, const ui::LocatedEvent* event); protected: - static constexpr int kInkDropSmallCornerRadius = 2; - static constexpr int kInkDropLargeCornerRadius = 4; - // Size used for the default SquareInkDropRipple. static constexpr int kDefaultInkDropSize = 24; @@ -162,6 +170,10 @@ class VIEWS_EXPORT InkDropHostView : public View, public InkDropHost { float ink_drop_visible_opacity_; + // Radii used for the SquareInkDropRipple. + int ink_drop_small_corner_radius_; + int ink_drop_large_corner_radius_; + // Determines whether the view was already painting to layer before adding ink // drop layer. bool old_paint_to_layer_; diff --git a/chromium/ui/views/bubble/bubble_border.cc b/chromium/ui/views/bubble/bubble_border.cc index 44e7b93fddc..6961a92ef6f 100644 --- a/chromium/ui/views/bubble/bubble_border.cc +++ b/chromium/ui/views/bubble/bubble_border.cc @@ -184,10 +184,9 @@ BubbleBorder::~BubbleBorder() {} // static gfx::Insets BubbleBorder::GetBorderAndShadowInsets( base::Optional<int> elevation) { - if (elevation.has_value()) { - return -gfx::ShadowValue::GetMargin(GetShadowValues(elevation)) + - gfx::Insets(kBorderThicknessDip); - } + // Borders with custom shadow elevations do not draw the 1px border. + if (elevation.has_value()) + return -gfx::ShadowValue::GetMargin(GetShadowValues(elevation)); constexpr gfx::Insets blur(kShadowBlur + kBorderThicknessDip); constexpr gfx::Insets offset(-kShadowVerticalOffset, 0, kShadowVerticalOffset, @@ -218,11 +217,14 @@ gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& anchor_rect, // Apply the border part of the inset before calculating coordinates because // the border should align with the anchor's border. For the purposes of // positioning, the border is rounded up to a dip, which may mean we have - // misalignment in scale factors greater than 1. + // misalignment in scale factors greater than 1. Borders with custom shadow + // elevations do not draw the 1px border. // TODO(estade): when it becomes possible to provide px bounds instead of // dip bounds, fix this. const gfx::Insets border_insets = - shadow_ == NO_ASSETS ? gfx::Insets() : gfx::Insets(kBorderThicknessDip); + shadow_ == NO_ASSETS || md_shadow_elevation_.has_value() + ? gfx::Insets() + : gfx::Insets(kBorderThicknessDip); const gfx::Insets shadow_insets = GetInsets() - border_insets; contents_bounds.Inset(-border_insets); if (arrow_ == TOP_RIGHT) { diff --git a/chromium/ui/views/bubble/bubble_border.h b/chromium/ui/views/bubble/bubble_border.h index 400366fd808..778d93070d8 100644 --- a/chromium/ui/views/bubble/bubble_border.h +++ b/chromium/ui/views/bubble/bubble_border.h @@ -188,11 +188,14 @@ class VIEWS_EXPORT BubbleBorder : public Border { void (cc::PaintCanvas::*draw)(const T&, const cc::PaintFlags&), gfx::Canvas* canvas, base::Optional<int> shadow_elevation = base::nullopt) { - // Provide a 1 px border outside the bounds. - const int kBorderStrokeThicknessPx = 1; - const SkScalar one_pixel = - SkFloatToScalar(kBorderStrokeThicknessPx / canvas->image_scale()); - rect.outset(one_pixel, one_pixel); + // Borders with custom shadow elevations do not draw the 1px border. + if (!shadow_elevation.has_value()) { + // Provide a 1 px border outside the bounds. + const int kBorderStrokeThicknessPx = 1; + const SkScalar one_pixel = + SkFloatToScalar(kBorderStrokeThicknessPx / canvas->image_scale()); + rect.outset(one_pixel, one_pixel); + } (canvas->sk_canvas()->*draw)(rect, GetBorderAndShadowFlags(shadow_elevation)); diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate.cc b/chromium/ui/views/bubble/bubble_dialog_delegate.cc index 5856ea9b8b1..eb5a9fd9b42 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate.cc @@ -16,6 +16,7 @@ #include "ui/views/bubble/bubble_frame_view.h" #include "ui/views/layout/layout_manager.h" #include "ui/views/layout/layout_provider.h" +#include "ui/views/view_properties.h" #include "ui/views/view_tracker.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/widget.h" @@ -26,10 +27,30 @@ #include "ui/base/win/shell.h" #endif +#if defined(OS_MACOSX) +#include "ui/views/widget/widget_utils_mac.h" +#endif + namespace views { namespace { +// The frame view for bubble dialog widgets. These are not user-sizable so have +// simplified logic for minimum and maximum sizes to avoid repeated calls to +// CalculatePreferredSize(). +class BubbleDialogFrameView : public BubbleFrameView { + public: + explicit BubbleDialogFrameView(const gfx::Insets& title_margins) + : BubbleFrameView(title_margins, gfx::Insets()) {} + + // View: + gfx::Size GetMinimumSize() const override { return gfx::Size(); } + gfx::Size GetMaximumSize() const override { return gfx::Size(); } + + private: + DISALLOW_COPY_AND_ASSIGN(BubbleDialogFrameView); +}; + // Create a widget to host the bubble. Widget* CreateBubbleWidget(BubbleDialogDelegateView* bubble) { Widget* bubble_widget = new Widget(); @@ -113,7 +134,7 @@ ClientView* BubbleDialogDelegateView::CreateClientView(Widget* widget) { NonClientFrameView* BubbleDialogDelegateView::CreateNonClientFrameView( Widget* widget) { - BubbleFrameView* frame = new BubbleFrameView(title_margins_, gfx::Insets()); + BubbleFrameView* frame = new BubbleDialogFrameView(title_margins_); frame->set_footnote_margins( LayoutProvider::Get()->GetInsetsMetric(INSETS_DIALOG_SUBSECTION)); @@ -218,6 +239,16 @@ void BubbleDialogDelegateView::OnAnchorBoundsChanged() { SizeToContents(); } +void BubbleDialogDelegateView::EnableFocusTraversalFromAnchorView() { + DCHECK(GetWidget()); + DCHECK(GetAnchorView()); + GetWidget()->SetFocusTraversableParent( + anchor_widget()->GetFocusTraversable()); + GetWidget()->SetFocusTraversableParentView(GetAnchorView()); + GetAnchorView()->SetProperty(kAnchoredDialogKey, + static_cast<BubbleDialogDelegateView*>(this)); +} + BubbleDialogDelegateView::BubbleDialogDelegateView() : BubbleDialogDelegateView(nullptr, BubbleBorder::TOP_LEFT) {} @@ -266,6 +297,17 @@ ax::mojom::Role BubbleDialogDelegateView::GetAccessibleWindowRole() const { return ax::mojom::Role::kAlertDialog; } +gfx::Size BubbleDialogDelegateView::GetMinimumSize() const { + // Note that although BubbleDialogFrameView will never invoke this, a subclass + // may override CreateNonClientFrameView() to provide a NonClientFrameView + // that does. See http://crbug.com/844359. + return gfx::Size(); +} + +gfx::Size BubbleDialogDelegateView::GetMaximumSize() const { + return gfx::Size(); +} + void BubbleDialogDelegateView::OnNativeThemeChanged( const ui::NativeTheme* theme) { UpdateColorsFromTheme(theme); @@ -274,6 +316,9 @@ void BubbleDialogDelegateView::OnNativeThemeChanged( void BubbleDialogDelegateView::Init() {} void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) { + if (GetAnchorView()) + GetAnchorView()->ClearProperty(kAnchoredDialogKey); + // When the anchor view gets set the associated anchor widget might // change as well. if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) { @@ -305,7 +350,16 @@ void BubbleDialogDelegateView::SetAnchorRect(const gfx::Rect& rect) { } void BubbleDialogDelegateView::SizeToContents() { - GetWidget()->SetBounds(GetBubbleBounds()); + gfx::Rect bubble_bounds = GetBubbleBounds(); +#if defined(OS_MACOSX) + // GetBubbleBounds() doesn't take the Mac NativeWindow's style mask into + // account, so we need to adjust the size. + gfx::Size actual_size = + GetWindowSizeForClientSize(GetWidget(), bubble_bounds.size()); + bubble_bounds.set_size(actual_size); +#endif + + GetWidget()->SetBounds(bubble_bounds); } BubbleFrameView* BubbleDialogDelegateView::GetBubbleFrameView() const { diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate.h b/chromium/ui/views/bubble/bubble_dialog_delegate.h index fa45ae39055..c564cb85dcf 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate.h +++ b/chromium/ui/views/bubble/bubble_dialog_delegate.h @@ -47,7 +47,7 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, // Create and initialize the bubble Widget(s) with proper bounds. static Widget* CreateBubble(BubbleDialogDelegateView* bubble_delegate); - // WidgetDelegateView: + // DialogDelegateView: BubbleDialogDelegateView* AsBubbleDialogDelegate() override; bool ShouldShowCloseButton() const override; ClientView* CreateClientView(Widget* widget) override; @@ -126,6 +126,11 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, // bounds change as a result of the widget's bounds changing. void OnAnchorBoundsChanged(); + // If this is called, enables focus to traverse from the anchor view + // to inside this dialog and back out. This may become the default in + // the future. + void EnableFocusTraversalFromAnchorView(); + protected: BubbleDialogDelegateView(); // |shadow| usually doesn't need to be explicitly set, just uses the default @@ -139,10 +144,16 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, // Get bubble bounds from the anchor rect and client view's preferred size. virtual gfx::Rect GetBubbleBounds(); - // DialogDelegateView overrides: + // DialogDelegateView: ax::mojom::Role GetAccessibleWindowRole() const override; - // View overrides: + // Disallow overrides of GetMinimumSize and GetMaximumSize(). These would only + // be called by the FrameView, but the BubbleFrameView ignores these. Bubbles + // are not user-sizable and always size to their preferred size (plus any + // border / frame). + gfx::Size GetMinimumSize() const final; + gfx::Size GetMaximumSize() const final; + void OnNativeThemeChanged(const ui::NativeTheme* theme) override; // Perform view initialization on the contents for bubble sizing. diff --git a/chromium/ui/views/bubble/bubble_frame_view.cc b/chromium/ui/views/bubble/bubble_frame_view.cc index fd0748f3639..5021792e327 100644 --- a/chromium/ui/views/bubble/bubble_frame_view.cc +++ b/chromium/ui/views/bubble/bubble_frame_view.cc @@ -134,7 +134,7 @@ Button* BubbleFrameView::CreateCloseButton(ButtonListener* listener) { ImageButton* close_button = nullptr; if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { close_button = CreateVectorImageButton(listener); - SetImageFromVectorIcon(close_button, vector_icons::kClose16Icon); + SetImageFromVectorIcon(close_button, vector_icons::kCloseRoundedIcon); } else { ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); close_button = new ImageButton(listener); diff --git a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc index edabed53af6..9b16c5a3247 100644 --- a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc +++ b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc @@ -615,7 +615,6 @@ class TestBubbleDialogDelegateView : public BubbleDialogDelegateView { destroyed_ = true; } int GetDialogButtons() const override { return ui::DIALOG_BUTTON_OK; } - gfx::Size GetMinimumSize() const override { return gfx::Size(); } gfx::Size CalculatePreferredSize() const override { return gfx::Size(200, 200); } diff --git a/chromium/ui/views/bubble/tray_bubble_view.cc b/chromium/ui/views/bubble/tray_bubble_view.cc index 1a5f43ef3df..80ae22b84af 100644 --- a/chromium/ui/views/bubble/tray_bubble_view.cc +++ b/chromium/ui/views/bubble/tray_bubble_view.cc @@ -381,15 +381,10 @@ base::string16 TrayBubbleView::GetAccessibleWindowTitle() const { } gfx::Size TrayBubbleView::CalculatePreferredSize() const { + DCHECK_LE(preferred_width_, params_.max_width); return gfx::Size(preferred_width_, GetHeightForWidth(preferred_width_)); } -gfx::Size TrayBubbleView::GetMaximumSize() const { - gfx::Size size = GetPreferredSize(); - size.set_width(params_.max_width); - return size; -} - int TrayBubbleView::GetHeightForWidth(int width) const { int height = GetInsets().height(); width = std::max(width - GetInsets().width(), 0); diff --git a/chromium/ui/views/bubble/tray_bubble_view.h b/chromium/ui/views/bubble/tray_bubble_view.h index 92f3ed00638..bd78fbc3509 100644 --- a/chromium/ui/views/bubble/tray_bubble_view.h +++ b/chromium/ui/views/bubble/tray_bubble_view.h @@ -150,7 +150,6 @@ class VIEWS_EXPORT TrayBubbleView : public BubbleDialogDelegateView, // Overridden from views::View. gfx::Size CalculatePreferredSize() const override; - gfx::Size GetMaximumSize() const override; int GetHeightForWidth(int width) const override; void OnMouseEntered(const ui::MouseEvent& event) override; void OnMouseExited(const ui::MouseEvent& event) override; diff --git a/chromium/ui/views/cocoa/bridged_content_view.h b/chromium/ui/views/cocoa/bridged_content_view.h index bb53eab7765..5bfbe297e5c 100644 --- a/chromium/ui/views/cocoa/bridged_content_view.h +++ b/chromium/ui/views/cocoa/bridged_content_view.h @@ -44,13 +44,6 @@ class View; // The last tooltip text, used to limit updates. base::string16 lastTooltipText_; - - // Whether to draw an almost-transparent background with rounded corners so - // that OSX correctly blurs the background showing through. - BOOL drawMenuBackgroundForBlur_; - - // The cached window mask. Only used for non-rectangular windows on 10.9. - base::scoped_nsobject<NSBezierPath> windowMask_; } @property(readonly, nonatomic) views::View* hostedView; @@ -72,9 +65,6 @@ class View; // contentRect (also this NSView's frame), as given by a ui::LocatedEvent. - (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent; -// Update windowMask_ depending on the current view bounds. -- (void)updateWindowMask; - // Notifies the associated FocusManager whether full keyboard access is enabled // or not. - (void)updateFullKeyboardAccess; diff --git a/chromium/ui/views/cocoa/bridged_content_view.mm b/chromium/ui/views/cocoa/bridged_content_view.mm index c55fb600d20..17f7339f7d8 100644 --- a/chromium/ui/views/cocoa/bridged_content_view.mm +++ b/chromium/ui/views/cocoa/bridged_content_view.mm @@ -48,17 +48,6 @@ namespace { NSString* const kFullKeyboardAccessChangedNotification = @"com.apple.KeyboardUIModeDidChange"; -// Returns true if all four corners of |rect| are contained inside |path|. -bool IsRectInsidePath(NSRect rect, NSBezierPath* path) { - return [path containsPoint:rect.origin] && - [path containsPoint:NSMakePoint(rect.origin.x + rect.size.width, - rect.origin.y)] && - [path containsPoint:NSMakePoint(rect.origin.x, - rect.origin.y + rect.size.height)] && - [path containsPoint:NSMakePoint(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height)]; -} - // Convert a |point| in |source_window|'s AppKit coordinate system (origin at // the bottom left of the window) to |target_window|'s content rect, with the // origin at the top left of the content area. @@ -369,30 +358,6 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } } -- (void)updateWindowMask { - DCHECK(![self inLiveResize]); - DCHECK(base::mac::IsOS10_9()); - DCHECK(hostedView_); - - views::Widget* widget = hostedView_->GetWidget(); - if (!widget->non_client_view()) - return; - - const NSRect frameRect = [self bounds]; - gfx::Path mask; - widget->non_client_view()->GetWindowMask(gfx::Size(frameRect.size), &mask); - if (mask.isEmpty()) - return; - - windowMask_.reset([gfx::CreateNSBezierPathFromSkPath(mask) retain]); - - // Convert to AppKit coordinate system. - NSAffineTransform* flipTransform = [NSAffineTransform transform]; - [flipTransform translateXBy:0.0 yBy:frameRect.size.height]; - [flipTransform scaleXBy:1.0 yBy:-1.0]; - [windowMask_ transformUsingAffineTransform:flipTransform]; -} - - (void)updateFullKeyboardAccess { if (!hostedView_) return; @@ -675,67 +640,6 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { hostedView_->SetSize(gfx::Size(newSize.width, newSize.height)); } -- (void)viewDidEndLiveResize { - [super viewDidEndLiveResize]; - - // We prevent updating the window mask and clipping the border around the - // view, during a live resize. Hence update the window mask and redraw the - // view after resize has completed. - if (base::mac::IsOS10_9()) { - [self updateWindowMask]; - [self setNeedsDisplay:YES]; - } -} - -- (void)drawRect:(NSRect)dirtyRect { - // Note that BridgedNativeWidget uses -[NSWindow setAutodisplay:NO] to - // suppress calls to this when the window is known to be hidden. - if (!hostedView_) - return; - - if (drawMenuBackgroundForBlur_) { - const CGFloat radius = views::MenuConfig::instance().corner_radius; - [skia::SkColorToSRGBNSColor(0x01000000) set]; - [[NSBezierPath bezierPathWithRoundedRect:[self bounds] - xRadius:radius - yRadius:radius] fill]; - } - - // On OS versions earlier than Yosemite, to generate a drop shadow, we set an - // opaque background. This causes windows with non rectangular shapes to have - // square corners. To get around this, fill the path outside the window - // boundary with clearColor and tell Cococa to regenerate drop shadow. See - // crbug.com/543671. - if (windowMask_ && ![self inLiveResize] && - !IsRectInsidePath(dirtyRect, windowMask_)) { - DCHECK(base::mac::IsOS10_9()); - gfx::ScopedNSGraphicsContextSaveGState state; - - // The outer rectangular path corresponding to the window. - NSBezierPath* outerPath = [NSBezierPath bezierPathWithRect:[self bounds]]; - - [outerPath appendBezierPath:windowMask_]; - [outerPath setWindingRule:NSEvenOddWindingRule]; - [[NSGraphicsContext currentContext] - setCompositingOperation:NSCompositeCopy]; - [[NSColor clearColor] set]; - - // Fill the region between windowMask_ and its outer rectangular path - // with clear color. This causes the window to have the shape described - // by windowMask_. - [outerPath fill]; - // Regerate drop shadow around the window boundary. - [[self window] invalidateShadow]; - } - - // If there's a layer, painting occurs in BridgedNativeWidget::OnPaintLayer(). - if (hostedView_->GetWidget()->GetLayer()) - return; - - // TODO(tapted): Add a NOTREACHED() here. At the moment, low-level - // BridgedNativeWidget unit tests may not have a ui::Layer. -} - - (BOOL)isOpaque { if (!hostedView_) return NO; diff --git a/chromium/ui/views/cocoa/bridged_native_widget.h b/chromium/ui/views/cocoa/bridged_native_widget.h index 9af5c2db7db..ec83dfb3089 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget.h +++ b/chromium/ui/views/cocoa/bridged_native_widget.h @@ -14,6 +14,7 @@ #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/display_ca_layer_tree.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/compositor/layer_owner.h" #import "ui/views/cocoa/bridged_native_widget_owner.h" @@ -271,9 +272,7 @@ class VIEWS_EXPORT BridgedNativeWidget // Overridden from ui::AcceleratedWidgetMac: NSView* AcceleratedWidgetGetNSView() const override; - void AcceleratedWidgetGetVSyncParameters( - base::TimeTicks* timebase, base::TimeDelta* interval) const override; - void AcceleratedWidgetSwapCompleted() override; + void AcceleratedWidgetCALayerParamsUpdated() override; // Overridden from BridgedNativeWidgetOwner: NSWindow* GetNSWindow() override; @@ -302,6 +301,7 @@ class VIEWS_EXPORT BridgedNativeWidget base::scoped_nsobject<NSView> compositor_superview_; std::unique_ptr<ui::AcceleratedWidgetMac> compositor_widget_; + std::unique_ptr<ui::DisplayCALayerTree> display_ca_layer_tree_; std::unique_ptr<ui::Compositor> compositor_; viz::ParentLocalSurfaceIdAllocator parent_local_surface_id_allocator_; diff --git a/chromium/ui/views/cocoa/bridged_native_widget.mm b/chromium/ui/views/cocoa/bridged_native_widget.mm index 64ea16b86ef..c37fd628634 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget.mm @@ -467,7 +467,13 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { // See http://crbug.com/667602. Alternatives: call -setAlphaValue:0 and // -setIgnoresMouseEvents:YES on the NSWindow, or dismiss the sheet before // hiding. - DCHECK(![window_ attachedSheet]); + // + // TODO(ellyjones): Sort this entire situation out. This DCHECK doesn't + // trigger in shipped builds, but it does trigger when the browser exits + // "abnormally" (not via one of the UI paths to exiting), such as in browser + // tests, so this breaks a slew of browser tests in MacViews mode. See also + // https://crbug.com/834926. + // DCHECK(![window_ attachedSheet]); [window_ orderOut:nil]; DCHECK(!window_visible_); @@ -528,8 +534,9 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { // duration of the animation, but would keep it smooth. The window also // hasn't yet received a frame from the compositor at this stage, so it is // fully transparent until the GPU sends a frame swap IPC. For the blocking - // option, the animation needs to wait until AcceleratedWidgetSwapCompleted - // has been called at least once, otherwise it will animate nothing. + // option, the animation needs to wait until + // AcceleratedWidgetCALayerParamsUpdated has been called at least once, + // otherwise it will animate nothing. [show_animation_ setAnimationBlockingMode:NSAnimationNonblocking]; [show_animation_ startAnimation]; } @@ -616,7 +623,7 @@ void BridgedNativeWidget::OnWindowWillClose() { Widget* widget = native_widget_mac_->GetWidget(); if (DialogDelegate* dialog = widget->widget_delegate()->AsDialogDelegate()) dialog->RemoveObserver(this); - widget->OnNativeWidgetDestroying(); + native_widget_mac_->WindowDestroying(); // Ensure BridgedNativeWidget does not have capture, otherwise // OnMouseCaptureLost() may reference a deleted |native_widget_mac_| when @@ -636,7 +643,7 @@ void BridgedNativeWidget::OnWindowWillClose() { DCHECK(!show_animation_); [window_ setDelegate:nil]; - native_widget_mac_->OnWindowDestroyed(); + native_widget_mac_->WindowDestroyed(); // Note: |this| is deleted here. } @@ -723,14 +730,6 @@ void BridgedNativeWidget::OnSizeChanged() { if ([window_ inLiveResize]) MaybeWaitForFrame(new_size); } - - // 10.9 is unable to generate a window shadow from the composited CALayer, so - // use Quartz. - // We don't update the window mask during a live resize, instead it is done - // after the resize is completed in viewDidEndLiveResize: in - // BridgedContentView. - if (base::mac::IsOS10_9() && ![window_ inLiveResize]) - [bridged_view_ updateWindowMask]; } void BridgedNativeWidget::OnPositionChanged() { @@ -1038,19 +1037,19 @@ NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const { return compositor_superview_; } -void BridgedNativeWidget::AcceleratedWidgetGetVSyncParameters( - base::TimeTicks* timebase, base::TimeDelta* interval) const { - // TODO(tapted): Add vsync support. - *timebase = base::TimeTicks(); - *interval = base::TimeDelta(); -} - -void BridgedNativeWidget::AcceleratedWidgetSwapCompleted() { +void BridgedNativeWidget::AcceleratedWidgetCALayerParamsUpdated() { // Ignore frames arriving "late" for an old size. A frame at the new size // should arrive soon. if (!compositor_widget_->HasFrameOfSize(GetClientAreaSize())) return; + // 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); + if (initial_visibility_suppressed_) { initial_visibility_suppressed_ = false; [window_ setAlphaValue:1.0]; @@ -1241,11 +1240,11 @@ void BridgedNativeWidget::AddCompositorSuperview() { [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES]; } + // Set the layer first to create a layer-hosting view (not layer-backed), and + // set the compositor output to go to that layer. base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]); - [background_layer - setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable]; - - // Set the layer first to create a layer-hosting view (not layer-backed). + display_ca_layer_tree_ = + std::make_unique<ui::DisplayCALayerTree>(background_layer.get()); [compositor_superview_ setLayer:background_layer]; [compositor_superview_ setWantsLayer:YES]; diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc index 8a2e4dff278..5fa2f6f1ebb 100644 --- a/chromium/ui/views/controls/button/button.cc +++ b/chromium/ui/views/controls/button/button.cc @@ -22,6 +22,7 @@ #include "ui/views/controls/button/menu_button.h" #include "ui/views/controls/button/radio_button.h" #include "ui/views/controls/button/toggle_button.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/painter.h" #include "ui/views/style/platform_style.h" #include "ui/views/widget/widget.h" @@ -145,6 +146,13 @@ void Button::SetAnimationDuration(int duration) { hover_animation_.SetSlideDuration(duration); } +void Button::SetInstallFocusRingOnFocus(bool install) { + if (install) + focus_ring_ = FocusRing::Install(this); + else + focus_ring_.reset(); +} + void Button::SetHotTracked(bool is_hot_tracked) { if (state_ != STATE_DISABLED) SetState(is_hot_tracked ? STATE_HOVERED : STATE_NORMAL); @@ -443,7 +451,7 @@ void Button::OnBlur() { std::unique_ptr<InkDrop> Button::CreateInkDrop() { std::unique_ptr<views::InkDropImpl> ink_drop = CreateDefaultInkDropImpl(); - ink_drop->SetShowHighlightOnFocus(true); + ink_drop->SetShowHighlightOnFocus(!focus_ring_); ink_drop->SetAutoHighlightModeForPlatform(); return std::move(ink_drop); } diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h index 3942d790bb4..6f0ce20627b 100644 --- a/chromium/ui/views/controls/button/button.h +++ b/chromium/ui/views/controls/button/button.h @@ -13,6 +13,7 @@ #include "ui/native_theme/native_theme.h" #include "ui/views/animation/ink_drop_host_view.h" #include "ui/views/animation/ink_drop_state.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/painter.h" namespace views { @@ -143,6 +144,7 @@ class VIEWS_EXPORT Button : public InkDropHostView, void set_has_ink_drop_action_on_click(bool has_ink_drop_action_on_click) { has_ink_drop_action_on_click_ = has_ink_drop_action_on_click; } + void SetInstallFocusRingOnFocus(bool install_focus_ring_on_focus); void SetHotTracked(bool is_hot_tracked); bool IsHotTracked() const; @@ -291,6 +293,9 @@ class VIEWS_EXPORT Button : public InkDropHostView, // The color of the ripple and hover. SkColor ink_drop_base_color_; + // The focus ring for this Button. + std::unique_ptr<FocusRing> focus_ring_; + std::unique_ptr<Painter> focus_painter_; DISALLOW_COPY_AND_ASSIGN(Button); diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc index ecdcda3d503..cfe84643274 100644 --- a/chromium/ui/views/controls/button/checkbox.cc +++ b/chromium/ui/views/controls/button/checkbox.cc @@ -30,47 +30,6 @@ namespace views { -constexpr int kFocusRingThicknessDip = 2; - -// View used to paint the focus ring around the Checkbox icon. -// The icon is painted separately. -class IconFocusRing : public View { - public: - explicit IconFocusRing(Checkbox* checkbox); - ~IconFocusRing() override = default; - - private: - // View: - void Layout() override; - void OnPaint(gfx::Canvas* canvas) override; - - Checkbox* checkbox_; - - DISALLOW_COPY_AND_ASSIGN(IconFocusRing); -}; - -IconFocusRing::IconFocusRing(Checkbox* checkbox) : checkbox_(checkbox) { - FocusRing::InitFocusRing(this); -} - -void IconFocusRing::Layout() { - gfx::Rect focus_bounds = checkbox_->image()->bounds(); - focus_bounds.Inset(-kFocusRingThicknessDip, -kFocusRingThicknessDip); - SetBoundsRect(focus_bounds); -} - -void IconFocusRing::OnPaint(gfx::Canvas* canvas) { - cc::PaintFlags focus_flags; - focus_flags.setAntiAlias(true); - focus_flags.setColor( - SkColorSetA(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor), - 0x66)); - focus_flags.setStyle(cc::PaintFlags::kStroke_Style); - focus_flags.setStrokeWidth(2); - checkbox_->PaintFocusRing(this, canvas, focus_flags); -} - // static const char Checkbox::kViewClassName[] = "Checkbox"; @@ -88,9 +47,7 @@ Checkbox::Checkbox(const base::string16& label, bool force_md) set_request_focus_on_press(false); SetInkDropMode(InkDropMode::ON); set_has_ink_drop_action_on_click(true); - focus_ring_ = new IconFocusRing(this); - focus_ring_->SetVisible(false); - AddChildView(focus_ring_); + 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. @@ -200,16 +157,12 @@ void Checkbox::OnFocus() { LabelButton::OnFocus(); if (!UseMd()) UpdateImage(); - else - focus_ring_->SetVisible(true); } void Checkbox::OnBlur() { LabelButton::OnBlur(); if (!UseMd()) UpdateImage(); - else - focus_ring_->SetVisible(false); } void Checkbox::OnNativeThemeChanged(const ui::NativeTheme* theme) { @@ -262,6 +215,20 @@ std::unique_ptr<LabelButtonBorder> Checkbox::CreateDefaultBorder() const { return border; } +void Checkbox::Layout() { + LabelButton::Layout(); + if (focus_ring_ && !image()->bounds().IsEmpty()) + focus_ring_->SetPath(GetFocusRingPath()); +} + +SkPath Checkbox::GetFocusRingPath() const { + SkPath path; + gfx::Rect bounds = image()->bounds(); + bounds.Inset(1, 1); + path.addRect(RectToSkRect(bounds)); + return path; +} + void Checkbox::SetCustomImage(bool checked, bool focused, ButtonState for_state, @@ -272,14 +239,6 @@ void Checkbox::SetCustomImage(bool checked, UpdateImage(); } -void Checkbox::PaintFocusRing(View* view, - gfx::Canvas* canvas, - const cc::PaintFlags& flags) { - gfx::RectF bounds(view->GetLocalBounds()); - bounds.Inset(kFocusRingThicknessDip, kFocusRingThicknessDip); - canvas->DrawRoundRect(bounds, kFocusRingThicknessDip, flags); -} - const gfx::VectorIcon& Checkbox::GetVectorIcon() const { return checked() ? kCheckboxActiveIcon : kCheckboxNormalIcon; } diff --git a/chromium/ui/views/controls/button/checkbox.h b/chromium/ui/views/controls/button/checkbox.h index ca8da3cb2af..de681d69271 100644 --- a/chromium/ui/views/controls/button/checkbox.h +++ b/chromium/ui/views/controls/button/checkbox.h @@ -12,6 +12,7 @@ #include "base/strings/string16.h" #include "cc/paint/paint_flags.h" #include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/focus_ring.h" namespace gfx { struct VectorIcon; @@ -63,6 +64,7 @@ class VIEWS_EXPORT Checkbox : public LabelButton { SkColor GetInkDropBaseColor() const override; gfx::ImageSkia GetImage(ButtonState for_state) const override; 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]. @@ -71,14 +73,12 @@ class VIEWS_EXPORT Checkbox : public LabelButton { ButtonState for_state, const gfx::ImageSkia& image); - // Paints a focus indicator for the view. Overridden in RadioButton. - virtual void PaintFocusRing(View* view, - gfx::Canvas* canvas, - const cc::PaintFlags& flags); - // Gets the vector icon to use based on the current state of |checked_|. virtual const gfx::VectorIcon& GetVectorIcon() const; + // Returns the path to draw the focus ring around for this Checkbox. + virtual SkPath GetFocusRingPath() const; + private: friend class IconFocusRing; @@ -97,15 +97,15 @@ class VIEWS_EXPORT Checkbox : public LabelButton { // True if the checkbox is checked. bool checked_; - // FocusRing used in MD mode - View* focus_ring_ = nullptr; - // 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/image_button.cc b/chromium/ui/views/controls/button/image_button.cc index 6d945e4579c..677d4dc3d84 100644 --- a/chromium/ui/views/controls/button/image_button.cc +++ b/chromium/ui/views/controls/button/image_button.cc @@ -83,6 +83,13 @@ void ImageButton::SetImageAlignment(HorizontalAlignment h_align, SchedulePaint(); } +void ImageButton::SetBackgroundImageAlignment(HorizontalAlignment h_align, + VerticalAlignment v_align) { + h_background_alignment_ = h_align; + v_background_alignment_ = v_align; + SchedulePaint(); +} + void ImageButton::SetMinimumImageSize(const gfx::Size& size) { if (minimum_image_size_ == size) return; @@ -138,10 +145,21 @@ void ImageButton::PaintButtonContents(gfx::Canvas* canvas) { canvas->Scale(-1, 1); } - gfx::Point position = ComputeImagePaintPosition(img); - if (!background_image_.isNull()) - canvas->DrawImageInt(background_image_, position.x(), position.y()); + if (!background_image_.isNull()) { + // If the background image alignment was not set, use the image + // alignment. + HorizontalAlignment h_alignment = + h_background_alignment_.value_or(h_alignment_); + VerticalAlignment v_alignment = + v_background_alignment_.value_or(v_alignment_); + gfx::Point background_position = ComputeImagePaintPosition( + background_image_, h_alignment, v_alignment); + canvas->DrawImageInt(background_image_, background_position.x(), + background_position.y()); + } + gfx::Point position = + ComputeImagePaintPosition(img, h_alignment_, v_alignment_); canvas->DrawImageInt(img, position.x(), position.y()); } } @@ -166,11 +184,13 @@ gfx::ImageSkia ImageButton::GetImageToPaint() { //////////////////////////////////////////////////////////////////////////////// // ImageButton, private: -gfx::Point ImageButton::ComputeImagePaintPosition(const gfx::ImageSkia& image) { +const gfx::Point ImageButton::ComputeImagePaintPosition( + const gfx::ImageSkia& image, + HorizontalAlignment h_alignment, + VerticalAlignment v_alignment) { int x = 0, y = 0; gfx::Rect rect = GetContentsBounds(); - HorizontalAlignment h_alignment = h_alignment_; if (draw_image_mirrored_) { if (h_alignment == ALIGN_RIGHT) h_alignment = ALIGN_LEFT; @@ -185,7 +205,7 @@ gfx::Point ImageButton::ComputeImagePaintPosition(const gfx::ImageSkia& image) { if (v_alignment_ == ALIGN_MIDDLE) y = (rect.height() - image.height()) / 2; - else if (v_alignment_ == ALIGN_BOTTOM) + else if (v_alignment == ALIGN_BOTTOM) y = rect.height() - image.height(); x += rect.x(); diff --git a/chromium/ui/views/controls/button/image_button.h b/chromium/ui/views/controls/button/image_button.h index fb73e6c1e06..bc69e6e350b 100644 --- a/chromium/ui/views/controls/button/image_button.h +++ b/chromium/ui/views/controls/button/image_button.h @@ -60,6 +60,10 @@ class VIEWS_EXPORT ImageButton : public Button { void SetImageAlignment(HorizontalAlignment h_align, VerticalAlignment v_align); + // Sets how the background is laid out within the button's bounds. + void SetBackgroundImageAlignment(HorizontalAlignment h_align, + VerticalAlignment v_align); + // The minimum size of the contents (not including the border). The contents // will be at least this size, but may be larger if the image itself is // larger. @@ -97,17 +101,25 @@ class VIEWS_EXPORT ImageButton : public Button { FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, ImagePositionWithBorder); FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, LeftAlignedMirrored); FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, RightAlignedMirrored); + FRIEND_TEST_ALL_PREFIXES(ImageButtonTest, ImagePositionWithBackground); FRIEND_TEST_ALL_PREFIXES(ImageButtonFactoryTest, CreateVectorImageButton); // Returns the correct position of the image for painting. - gfx::Point ComputeImagePaintPosition(const gfx::ImageSkia& image); + const gfx::Point ComputeImagePaintPosition(const gfx::ImageSkia& image, + HorizontalAlignment h_alignment, + VerticalAlignment v_alignment); // Image alignment. HorizontalAlignment h_alignment_; VerticalAlignment v_alignment_; gfx::Size minimum_image_size_; + // Background alignment. If these are not set, the background image uses the + // image alignment. + base::Optional<HorizontalAlignment> h_background_alignment_; + base::Optional<VerticalAlignment> v_background_alignment_; + // Whether we draw our resources horizontally flipped. This can happen in the // linux titlebar, where image resources were designed to be flipped so a // small curved corner in the close button designed to fit into the frame diff --git a/chromium/ui/views/controls/button/image_button_factory_unittest.cc b/chromium/ui/views/controls/button/image_button_factory_unittest.cc index 52728ff3516..6a2e776d07d 100644 --- a/chromium/ui/views/controls/button/image_button_factory_unittest.cc +++ b/chromium/ui/views/controls/button/image_button_factory_unittest.cc @@ -26,14 +26,14 @@ TEST_F(ImageButtonFactoryTest, CreateVectorImageButton) { TEST_F(ImageButtonFactoryTest, SetImageFromVectorIcon) { ImageButton* button = CreateVectorImageButton(nullptr); - SetImageFromVectorIcon(button, vector_icons::kClose16Icon, SK_ColorRED); + SetImageFromVectorIcon(button, vector_icons::kCloseRoundedIcon, SK_ColorRED); EXPECT_FALSE(button->GetImage(Button::STATE_NORMAL).isNull()); EXPECT_FALSE(button->GetImage(Button::STATE_DISABLED).isNull()); EXPECT_EQ(color_utils::DeriveDefaultIconColor(SK_ColorRED), button->GetInkDropBaseColor()); // Default to GoogleGrey900. - SetImageFromVectorIcon(button, vector_icons::kClose16Icon); + SetImageFromVectorIcon(button, vector_icons::kCloseRoundedIcon); EXPECT_EQ(color_utils::DeriveDefaultIconColor(gfx::kGoogleGrey900), button->GetInkDropBaseColor()); delete button; diff --git a/chromium/ui/views/controls/button/image_button_unittest.cc b/chromium/ui/views/controls/button/image_button_unittest.cc index baf9a75d18b..4c595a5d7ef 100644 --- a/chromium/ui/views/controls/button/image_button_unittest.cc +++ b/chromium/ui/views/controls/button/image_button_unittest.cc @@ -39,6 +39,13 @@ class Parent : public views::View { namespace views { +namespace { +const ImageButton::HorizontalAlignment kDefaultHorizontalAlignment = + ImageButton::ALIGN_LEFT; +const ImageButton::VerticalAlignment kDefaultVerticalAlignment = + ImageButton::ALIGN_TOP; +} // namespace + typedef ViewsTestBase ImageButtonTest; TEST_F(ImageButtonTest, Basics) { @@ -122,24 +129,39 @@ TEST_F(ImageButtonTest, ImagePositionWithBorder) { // The image should be painted at the top-left corner. EXPECT_EQ(gfx::Point().ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, kDefaultHorizontalAlignment, + kDefaultVerticalAlignment) + .ToString()); button.SetBorder(views::CreateEmptyBorder(10, 5, 0, 0)); EXPECT_EQ(gfx::Point(5, 10).ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, kDefaultHorizontalAlignment, + kDefaultVerticalAlignment) + .ToString()); button.SetBorder(NullBorder()); button.SetBounds(0, 0, 50, 50); EXPECT_EQ(gfx::Point().ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, kDefaultHorizontalAlignment, + kDefaultVerticalAlignment) + .ToString()); button.SetImageAlignment(ImageButton::ALIGN_CENTER, ImageButton::ALIGN_MIDDLE); EXPECT_EQ(gfx::Point(15, 10).ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, ImageButton::ALIGN_CENTER, + ImageButton::ALIGN_MIDDLE) + .ToString()); button.SetBorder(views::CreateEmptyBorder(10, 10, 0, 0)); EXPECT_EQ(gfx::Point(20, 15).ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, ImageButton::ALIGN_CENTER, + ImageButton::ALIGN_MIDDLE) + .ToString()); // The entire button's size should take the border into account. EXPECT_EQ(gfx::Size(30, 40).ToString(), button.GetPreferredSize().ToString()); @@ -161,7 +183,10 @@ TEST_F(ImageButtonTest, LeftAlignedMirrored) { // Because the coordinates are flipped, we should expect this to draw as if // it were ALIGN_RIGHT. EXPECT_EQ(gfx::Point(30, 0).ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, ImageButton::ALIGN_LEFT, + ImageButton::ALIGN_BOTTOM) + .ToString()); } TEST_F(ImageButtonTest, RightAlignedMirrored) { @@ -176,7 +201,10 @@ TEST_F(ImageButtonTest, RightAlignedMirrored) { // Because the coordinates are flipped, we should expect this to draw as if // it were ALIGN_LEFT. EXPECT_EQ(gfx::Point(0, 0).ToString(), - button.ComputeImagePaintPosition(image).ToString()); + button + .ComputeImagePaintPosition(image, ImageButton::ALIGN_RIGHT, + ImageButton::ALIGN_BOTTOM) + .ToString()); } TEST_F(ImageButtonTest, PreferredSizeInvalidation) { diff --git a/chromium/ui/views/controls/button/label_button.cc b/chromium/ui/views/controls/button/label_button.cc index d56f4cb2f73..60907bd19cd 100644 --- a/chromium/ui/views/controls/button/label_button.cc +++ b/chromium/ui/views/controls/button/label_button.cc @@ -398,7 +398,7 @@ std::unique_ptr<views::InkDropHighlight> LabelButton::CreateInkDropHighlight() const { return ShouldUseFloodFillInkDrop() ? std::make_unique<views::InkDropHighlight>( - size(), kInkDropSmallCornerRadius, + size(), ink_drop_small_corner_radius(), gfx::RectF(GetLocalBounds()).CenterPoint(), GetInkDropBaseColor()) : CreateDefaultInkDropHighlight( diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc index 60f6ca0ffb3..e3c0f6e9dc2 100644 --- a/chromium/ui/views/controls/button/md_text_button.cc +++ b/chromium/ui/views/controls/button/md_text_button.cc @@ -77,6 +77,7 @@ MdTextButton* MdTextButton::Create(ButtonListener* listener, MdTextButton* button = new MdTextButton(listener, button_context); button->SetText(text); button->SetFocusForPlatform(); + return button; } @@ -95,6 +96,11 @@ void MdTextButton::SetBgColorOverride(const base::Optional<SkColor>& color) { UpdateColors(); } +void MdTextButton::set_corner_radius(float radius) { + corner_radius_ = radius; + set_ink_drop_corner_radii(corner_radius_, corner_radius_); +} + void MdTextButton::OnPaintBackground(gfx::Canvas* canvas) { LabelButton::OnPaintBackground(canvas); if (hover_animation().is_animating() || state() == STATE_HOVERED) { @@ -104,16 +110,6 @@ void MdTextButton::OnPaintBackground(gfx::Canvas* canvas) { } } -void MdTextButton::OnFocus() { - LabelButton::OnFocus(); - FocusRing::Install(this); -} - -void MdTextButton::OnBlur() { - LabelButton::OnBlur(); - FocusRing::Uninstall(this); -} - void MdTextButton::OnNativeThemeChanged(const ui::NativeTheme* theme) { LabelButton::OnNativeThemeChanged(theme); UpdateColors(); @@ -179,16 +175,17 @@ void MdTextButton::UpdateStyleToIndicateDefaultStatus() { MdTextButton::MdTextButton(ButtonListener* listener, int button_context) : LabelButton(listener, base::string16(), button_context), - is_prominent_(false), - corner_radius_(kInkDropSmallCornerRadius) { + is_prominent_(false) { SetInkDropMode(InkDropMode::ON); set_has_ink_drop_action_on_click(true); + set_corner_radius(LayoutProvider::Get()->GetCornerRadiusMetric(EMPHASIS_LOW)); SetHorizontalAlignment(gfx::ALIGN_CENTER); SetFocusForPlatform(); const int minimum_width = LayoutProvider::Get()->GetDistanceMetric( DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH); SetMinSize(gfx::Size(minimum_width, 0)); SetFocusPainter(nullptr); + SetInstallFocusRingOnFocus(true); label()->SetAutoColorReadabilityEnabled(false); set_request_focus_on_press(false); @@ -226,8 +223,9 @@ void MdTextButton::UpdatePadding() { // GetControlHeightForFont(). It can't because that only returns a correct // result with --secondary-ui-md, and MdTextButtons appear in top chrome // without that. - const int kBaseHeight = 28; - int target_height = std::max(kBaseHeight + size_delta * 2, + const int base_height = + ui::MaterialDesignController::IsNewerMaterialUi() ? 32 : 28; + int target_height = std::max(base_height + size_delta * 2, label()->font_list().GetFontSize() * 2); int label_height = label()->GetPreferredSize().height(); diff --git a/chromium/ui/views/controls/button/md_text_button.h b/chromium/ui/views/controls/button/md_text_button.h index 25f65ce5e87..eafa8a817ba 100644 --- a/chromium/ui/views/controls/button/md_text_button.h +++ b/chromium/ui/views/controls/button/md_text_button.h @@ -9,6 +9,7 @@ #include "base/optional.h" #include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/style/typography.h" namespace views { @@ -36,14 +37,12 @@ class VIEWS_EXPORT MdTextButton : public LabelButton { // Override the default corner radius of the round rect used for the // background and ink drop effects. - void set_corner_radius(float radius) { corner_radius_ = radius; } + void set_corner_radius(float radius); // View: void OnPaintBackground(gfx::Canvas* canvas) override; // LabelButton: - void OnFocus() override; - void OnBlur() override; void OnNativeThemeChanged(const ui::NativeTheme* theme) override; std::unique_ptr<InkDrop> CreateInkDrop() override; std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override; diff --git a/chromium/ui/views/controls/button/menu_button.cc b/chromium/ui/views/controls/button/menu_button.cc index b6996aab742..7f38894e57c 100644 --- a/chromium/ui/views/controls/button/menu_button.cc +++ b/chromium/ui/views/controls/button/menu_button.cc @@ -287,7 +287,7 @@ bool MenuButton::OnKeyReleased(const ui::KeyEvent& event) { void MenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) { Button::GetAccessibleNodeData(node_data); node_data->role = ax::mojom::Role::kPopUpButton; - node_data->AddState(ax::mojom::State::kHaspopup); + node_data->SetHasPopup(ax::mojom::HasPopup::kMenu); if (enabled()) node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen); } @@ -385,7 +385,8 @@ void MenuButton::DecrementPressedLocked() { if (should_disable_after_press_) { desired_state = STATE_DISABLED; should_disable_after_press_ = false; - } else if (ShouldEnterHoveredState()) { + } else if (GetWidget() && !GetWidget()->dragged_view() && + ShouldEnterHoveredState()) { desired_state = STATE_HOVERED; GetInkDrop()->SetHovered(true); } diff --git a/chromium/ui/views/controls/button/radio_button.cc b/chromium/ui/views/controls/button/radio_button.cc index e8499db3311..efadd78b3e9 100644 --- a/chromium/ui/views/controls/button/radio_button.cc +++ b/chromium/ui/views/controls/button/radio_button.cc @@ -157,15 +157,14 @@ void RadioButton::SetChecked(bool checked) { Checkbox::SetChecked(checked); } -void RadioButton::PaintFocusRing(View* view, - gfx::Canvas* canvas, - const cc::PaintFlags& flags) { - canvas->DrawCircle(gfx::RectF(view->GetLocalBounds()).CenterPoint(), - image()->width() / 2, flags); -} - const gfx::VectorIcon& RadioButton::GetVectorIcon() const { return checked() ? kRadioButtonActiveIcon : kRadioButtonNormalIcon; } +SkPath RadioButton::GetFocusRingPath() const { + SkPath path; + path.addOval(gfx::RectToSkRect(image()->bounds())); + return path; +} + } // namespace views diff --git a/chromium/ui/views/controls/button/radio_button.h b/chromium/ui/views/controls/button/radio_button.h index b91ed088182..02cf91dcf8d 100644 --- a/chromium/ui/views/controls/button/radio_button.h +++ b/chromium/ui/views/controls/button/radio_button.h @@ -8,6 +8,7 @@ #include "base/macros.h" #include "base/strings/string16.h" #include "ui/views/controls/button/checkbox.h" +#include "ui/views/controls/focus_ring.h" namespace views { @@ -38,10 +39,8 @@ class VIEWS_EXPORT RadioButton : public Checkbox { // Overridden from Checkbox: void SetChecked(bool checked) override; - void PaintFocusRing(View* view, - gfx::Canvas* canvas, - const cc::PaintFlags& flags) override; const gfx::VectorIcon& GetVectorIcon() const override; + SkPath GetFocusRingPath() const override; private: DISALLOW_COPY_AND_ASSIGN(RadioButton); diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc index 67f9af2eaee..2cb22fea6dc 100644 --- a/chromium/ui/views/controls/combobox/combobox.cc +++ b/chromium/ui/views/controls/combobox/combobox.cc @@ -462,6 +462,9 @@ Combobox::Combobox(ui::ComboboxModel* model, Style style) arrow_image_ = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( IDR_MENU_DROPARROW); } + + if (UseMd()) + focus_ring_ = FocusRing::Install(this); } Combobox::~Combobox() { @@ -533,11 +536,9 @@ void Combobox::SetInvalid(bool invalid) { invalid_ = invalid; - if (HasFocus() && UseMd()) { - FocusRing::Install(this, invalid_ - ? ui::NativeTheme::kColorId_AlertSeverityHigh - : ui::NativeTheme::kColorId_NumColors); - } + if (focus_ring_) + focus_ring_->SetInvalid(invalid); + UpdateBorder(); SchedulePaint(); } @@ -746,11 +747,6 @@ void Combobox::OnFocus() { View::OnFocus(); // Border renders differently when focused. SchedulePaint(); - if (UseMd()) { - FocusRing::Install(this, invalid_ - ? ui::NativeTheme::kColorId_AlertSeverityHigh - : ui::NativeTheme::kColorId_NumColors); - } } void Combobox::OnBlur() { @@ -761,8 +757,6 @@ void Combobox::OnBlur() { selector_->OnViewBlur(); // Border renders differently when focused. SchedulePaint(); - if (UseMd()) - FocusRing::Uninstall(this); } void Combobox::GetAccessibleNodeData(ui::AXNodeData* node_data) { diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h index 7977c36feda..d6132543edd 100644 --- a/chromium/ui/views/controls/combobox/combobox.h +++ b/chromium/ui/views/controls/combobox/combobox.h @@ -11,6 +11,7 @@ #include "base/time/time.h" #include "ui/base/models/combobox_model.h" #include "ui/views/controls/button/button.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/controls/prefix_delegate.h" namespace gfx { @@ -113,7 +114,7 @@ class VIEWS_EXPORT Combobox : public View, void SetSelectedRow(int row) override; base::string16 GetTextForRow(int row) override; - // Overriden from ButtonListener: + // Overridden from ButtonListener: void ButtonPressed(Button* sender, const ui::Event& event) override; protected: @@ -165,6 +166,9 @@ class VIEWS_EXPORT Combobox : public View, // Returns the width of the combobox's arrow container. int GetArrowContainerWidth() const; + // Returns the color to use for the combobox's focus ring. + SkColor GetFocusRingColor() const; + // Optionally used to tie the lifetime of the model to this combobox. See // constructor. std::unique_ptr<ui::ComboboxModel> owned_model_; @@ -232,6 +236,9 @@ class VIEWS_EXPORT Combobox : public View, // true, the parent view must relayout in ChildPreferredSizeChanged(). bool size_to_largest_label_; + // The focus ring for this Combobox. + std::unique_ptr<FocusRing> focus_ring_; + // Used for making calbacks. base::WeakPtrFactory<Combobox> weak_ptr_factory_; diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc index 1cf8348a308..c17bc868e2f 100644 --- a/chromium/ui/views/controls/focus_ring.cc +++ b/chromium/ui/views/controls/focus_ring.cc @@ -5,70 +5,52 @@ #include "ui/views/controls/focus_ring.h" #include "ui/gfx/canvas.h" - -namespace views { +#include "ui/views/controls/focusable_border.h" +#include "ui/views/style/platform_style.h" namespace { -// The default stroke width of the focus border in dp. -constexpr float kFocusHaloThicknessDp = 2.f; - -FocusRing* GetFocusRing(View* parent) { - for (int i = 0; i < parent->child_count(); ++i) { - if (parent->child_at(i)->GetClassName() == FocusRing::kViewClassName) - return static_cast<FocusRing*>(parent->child_at(i)); - } - return nullptr; +ui::NativeTheme::ColorId ColorIdForValidity(bool valid) { + return valid ? ui::NativeTheme::kColorId_FocusedBorderColor + : ui::NativeTheme::kColorId_AlertSeverityHigh; } } // namespace +namespace views { + const char FocusRing::kViewClassName[] = "FocusRing"; // static -FocusRing* FocusRing::Install(View* parent, - SkColor color, - float corner_radius) { - FocusRing* ring = GetFocusRing(parent); - if (!ring) { - ring = new FocusRing(color, corner_radius); - parent->AddChildView(ring); - } else { - ring->color_ = color; - ring->corner_radius_ = corner_radius; - } +std::unique_ptr<FocusRing> FocusRing::Install(View* parent) { + auto ring = base::WrapUnique<FocusRing>(new FocusRing(parent)); + ring->set_owned_by_client(); + parent->AddChildView(ring.get()); + parent->AddObserver(ring.get()); ring->Layout(); ring->SchedulePaint(); return ring; } // static -FocusRing* FocusRing::Install(View* parent, - ui::NativeTheme::ColorId override_color_id) { - SkColor ring_color = parent->GetNativeTheme()->GetSystemColor( - override_color_id == ui::NativeTheme::kColorId_NumColors - ? ui::NativeTheme::kColorId_FocusedBorderColor - : override_color_id); - FocusRing* ring = Install(parent, ring_color); - DCHECK(ring); - ring->override_color_id_ = override_color_id; - if (!ring->view_observer_.IsObserving(parent)) - ring->view_observer_.Add(parent); - return ring; +bool FocusRing::IsPathUseable(const SkPath& path) { + return path.isRect(nullptr) || path.isOval(nullptr) || path.isRRect(nullptr); } -// static -void FocusRing::Uninstall(View* parent) { - delete GetFocusRing(parent); +void FocusRing::SetPath(const SkPath& path) { + DCHECK(IsPathUseable(path)); + path_ = path; + SchedulePaint(); } -// static -void FocusRing::InitFocusRing(View* view) { - // A layer is necessary to paint beyond the parent's bounds. - view->SetPaintToLayer(); - view->layer()->SetFillsBoundsOpaquely(false); - // Don't allow the view to process events. - view->set_can_process_events_within_subtree(false); +void FocusRing::SetInvalid(bool invalid) { + invalid_ = invalid; + SchedulePaint(); +} + +void FocusRing::SetHasFocusPredicate(const ViewPredicate& predicate) { + has_focus_predicate_ = predicate; + SchedulePaint(); } const char* FocusRing::GetClassName() const { @@ -79,37 +61,87 @@ void FocusRing::Layout() { // The focus ring handles its own sizing, which is simply to fill the parent // and extend a little beyond its borders. gfx::Rect focus_bounds = parent()->GetLocalBounds(); - focus_bounds.Inset(gfx::Insets(-kFocusHaloThicknessDp)); + focus_bounds.Inset(gfx::Insets(PlatformStyle::kFocusHaloInset)); SetBoundsRect(focus_bounds); } void FocusRing::OnPaint(gfx::Canvas* canvas) { - cc::PaintFlags flags; - flags.setAntiAlias(true); - flags.setColor(SkColorSetA(color_, 0x66)); - flags.setStyle(cc::PaintFlags::kStroke_Style); - flags.setStrokeWidth(kFocusHaloThicknessDp); - gfx::RectF rect(GetLocalBounds()); - rect.Inset(gfx::InsetsF(kFocusHaloThicknessDp / 2.f)); - // The focus indicator should hug the normal border, when present (as in the - // case of text buttons). Since it's drawn outside the parent view, increase - // the rounding slightly by adding half the ring thickness. - canvas->DrawRoundRect(rect, corner_radius_ + kFocusHaloThicknessDp / 2.f, - flags); + if (!has_focus_predicate_(parent())) + return; + + SkColor base_color = + GetNativeTheme()->GetSystemColor(ColorIdForValidity(!invalid_)); + + cc::PaintFlags paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetA(base_color, 0x66)); + paint.setStyle(cc::PaintFlags::kStroke_Style); + paint.setStrokeWidth(PlatformStyle::kFocusHaloThickness); + + SkPath path = path_; + if (path.isEmpty()) + path.addRect(RectToSkRect(parent()->GetLocalBounds())); + + DCHECK(IsPathUseable(path)); + SkRect bounds; + SkRRect rbounds; + if (path.isRect(&bounds)) { + canvas->sk_canvas()->drawRRect(RingRectFromPathRect(bounds), paint); + } else if (path.isOval(&bounds)) { + gfx::RectF rect = gfx::SkRectToRectF(bounds); + View::ConvertRectToTarget(view_, this, &rect); + canvas->sk_canvas()->drawRRect(SkRRect::MakeOval(gfx::RectFToSkRect(rect)), + paint); + } else if (path.isRRect(&rbounds)) { + canvas->sk_canvas()->drawRRect(RingRectFromPathRect(rbounds), paint); + } } -void FocusRing::OnViewNativeThemeChanged(View* observed_view) { - if (override_color_id_) { - color_ = observed_view->GetNativeTheme()->GetSystemColor( - override_color_id_.value()); - } +void FocusRing::OnViewFocused(View* view) { + SchedulePaint(); } -FocusRing::FocusRing(SkColor color, float corner_radius) - : color_(color), corner_radius_(corner_radius), view_observer_(this) { - InitFocusRing(this); +void FocusRing::OnViewBlurred(View* view) { + SchedulePaint(); } -FocusRing::~FocusRing() {} +FocusRing::FocusRing(View* parent) : view_(parent) { + // A layer is necessary to paint beyond the parent's bounds. + SetPaintToLayer(); + layer()->SetFillsBoundsOpaquely(false); + // Don't allow the view to process events. + set_can_process_events_within_subtree(false); + + has_focus_predicate_ = [](View* p) -> bool { return p->HasFocus(); }; +} + +FocusRing::~FocusRing() { + if (parent()) + parent()->RemoveObserver(this); +} + +SkRRect FocusRing::RingRectFromPathRect(const SkRect& rect) const { + double thickness = PlatformStyle::kFocusHaloThickness / 2.f; + double corner_radius = FocusableBorder::kCornerRadiusDp + thickness; + return RingRectFromPathRect( + SkRRect::MakeRectXY(rect, corner_radius, corner_radius)); +} + +SkRRect FocusRing::RingRectFromPathRect(const SkRRect& rrect) const { + double thickness = PlatformStyle::kFocusHaloThickness / 2.f; + gfx::RectF r = gfx::SkRectToRectF(rrect.rect()); + View::ConvertRectToTarget(view_, this, &r); + + SkRRect skr = + rrect.makeOffset(r.x() - rrect.rect().x(), r.y() - rrect.rect().y()); + + // The focus indicator should hug the normal border, when present (as in the + // case of text buttons). Since it's drawn outside the parent view, increase + // the rounding slightly by adding half the ring thickness. + skr.inset(PlatformStyle::kFocusHaloInset, PlatformStyle::kFocusHaloInset); + skr.inset(thickness, thickness); + + return skr; +} } // namespace views diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h index df1f1b330ca..1b4d99ca2b1 100644 --- a/chromium/ui/views/controls/focus_ring.h +++ b/chromium/ui/views/controls/focus_ring.h @@ -17,31 +17,55 @@ namespace views { // FocusRing is a View that is designed to act as an indicator of focus for its // parent. It is a stand-alone view that paints to a layer which extends beyond // the bounds of its parent view. +// +// Using FocusRing looks something like this: +// +// class MyView : public View { +// ... +// private: +// std::unique_ptr<FocusRing> focus_ring_; +// }; +// +// MyView::MyView() { +// focus_ring_ = FocusRing::Install(this); +// ... +// } +// +// If MyView should show a rounded rectangular focus ring when it has focus and +// hide the ring when it loses focus, no other configuration is necessary. In +// other cases, it might be necessary to use the Set*() functions on FocusRing; +// these take care of repainting it when the state changes. class VIEWS_EXPORT FocusRing : public View, public ViewObserver { public: static const char kViewClassName[]; - // Create a FocusRing and adds it to |parent|, or updates the one that already - // exists with the given |color| and |corner_radius|. - // TODO(crbug.com/831926): Prefer using the below Install() method - this one - // should eventually be removed. - static FocusRing* Install( - View* parent, - SkColor color, - float corner_radius = FocusableBorder::kCornerRadiusDp); + using ViewPredicate = std::function<bool(View* view)>; - // Similar to FocusRing::Install(View, SkColor, float), but - // |override_color_id| will be used in place of the default coloration - // when provided. - static FocusRing* Install(View* parent, - ui::NativeTheme::ColorId override_color_id = - ui::NativeTheme::kColorId_NumColors); + ~FocusRing() override; + + // Create a FocusRing and adds it to |parent|. The returned focus ring is + // owned by the client (the code calling FocusRing::Install), *not* by + // |parent|. + static std::unique_ptr<FocusRing> Install(View* parent); + + // Returns whether this class can draw a focus ring from |path|. Not all paths + // are useable since not all paths can be easily outset. + static bool IsPathUseable(const SkPath& path); - // Removes the FocusRing from |parent|. - static void Uninstall(View* parent); + // Sets the path to draw this FocusRing around. This path is in the parent + // view's coordinate system, *not* in the FocusRing's coordinate system. + void SetPath(const SkPath& path); - // Configure |view| for painting focus ring highlights. - static void InitFocusRing(View* view); + // Sets whether the FocusRing should show an invalid state for the View it + // encloses. + void SetInvalid(bool invalid); + + // Sets the predicate function used to tell when the parent has focus. The + // parent is passed into this predicate; it should return whether the parent + // should be treated as focused. This is useful when, for example, the parent + // wraps an inner view and the inner view is the one that actually receives + // focus, but the FocusRing sits on the parent instead of the inner view. + void SetHasFocusPredicate(const ViewPredicate& predicate); // View: const char* GetClassName() const override; @@ -49,17 +73,33 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver { void OnPaint(gfx::Canvas* canvas) override; // ViewObserver: - void OnViewNativeThemeChanged(View* observed_view) override; - - protected: - FocusRing(SkColor color, float corner_radius); - ~FocusRing() override; + void OnViewFocused(View* view) override; + void OnViewBlurred(View* view) override; private: - SkColor color_; - float corner_radius_; - base::Optional<ui::NativeTheme::ColorId> override_color_id_; - ScopedObserver<View, FocusRing> view_observer_; + explicit FocusRing(View* parent); + + // Translates the provided SkRect or SkRRect, which is in the parent's + // coordinate system, into this view's coordinate system, then insets it + // appropriately to produce the focus ring "halo" effect. If the supplied rect + // is an SkRect, it will have the default focus ring corner radius applied as + // well. + SkRRect RingRectFromPathRect(const SkRect& rect) const; + SkRRect RingRectFromPathRect(const SkRRect& rect) const; + + // The View this focus ring is installed on. + View* view_ = nullptr; + + // The path to draw this focus ring around. IsPathUseable(path_) is always + // true. + SkPath path_; + + // Whether the enclosed View is in an invalid state, which controls whether + // the focus ring shows an invalid appearance (usually a different color). + bool invalid_ = false; + + // The predicate used to determine whether the parent has focus. + ViewPredicate has_focus_predicate_; DISALLOW_COPY_AND_ASSIGN(FocusRing); }; diff --git a/chromium/ui/views/controls/glow_hover_controller.cc b/chromium/ui/views/controls/glow_hover_controller.cc deleted file mode 100644 index ef08308f7e7..00000000000 --- a/chromium/ui/views/controls/glow_hover_controller.cc +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2012 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/glow_hover_controller.h" - -#include "ui/views/view.h" - -namespace views { - -// Amount to scale the opacity. -static const double kSubtleOpacityScale = 0.45; -static const double kPronouncedOpacityScale = 1.0; - -// How long the hover state takes. -static const int kTrackHoverDurationMs = 400; - -GlowHoverController::GlowHoverController(views::View* view) - : view_(view), - animation_(this), - opacity_scale_(kSubtleOpacityScale) { - animation_.set_delegate(this); -} - -GlowHoverController::~GlowHoverController() { -} - -void GlowHoverController::SetAnimationContainer( - gfx::AnimationContainer* container) { - animation_.SetContainer(container); -} - -void GlowHoverController::SetLocation(const gfx::Point& location) { - location_ = location; - if (ShouldDraw()) - view_->SchedulePaint(); -} - -void GlowHoverController::Show(Style style) { - switch (style) { - case SUBTLE: - opacity_scale_ = kSubtleOpacityScale; - animation_.SetSlideDuration(kTrackHoverDurationMs); - animation_.SetTweenType(gfx::Tween::EASE_OUT); - animation_.Show(); - break; - case PRONOUNCED: - opacity_scale_ = kPronouncedOpacityScale; - // Force the end state to show immediately. - animation_.Show(); - animation_.End(); - break; - } -} - -void GlowHoverController::Hide() { - animation_.SetTweenType(gfx::Tween::EASE_IN); - animation_.Hide(); -} - -void GlowHoverController::HideImmediately() { - if (ShouldDraw()) - view_->SchedulePaint(); - animation_.Reset(); -} - -double GlowHoverController::GetAnimationValue() const { - return animation_.GetCurrentValue(); -} - -SkAlpha GlowHoverController::GetAlpha() const { - return static_cast<SkAlpha>(animation_.CurrentValueBetween( - 0, gfx::ToRoundedInt(255 * opacity_scale_))); -} - -bool GlowHoverController::ShouldDraw() const { - return animation_.IsShowing() || animation_.is_animating(); -} - -void GlowHoverController::AnimationEnded(const gfx::Animation* animation) { - view_->SchedulePaint(); -} - -void GlowHoverController::AnimationProgressed(const gfx::Animation* animation) { - view_->SchedulePaint(); -} - -} // namespace views diff --git a/chromium/ui/views/controls/glow_hover_controller.h b/chromium/ui/views/controls/glow_hover_controller.h deleted file mode 100644 index 49db0a8c4d9..00000000000 --- a/chromium/ui/views/controls/glow_hover_controller.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2012 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_GLOW_HOVER_CONTROLLER_H_ -#define UI_VIEWS_CONTROLS_GLOW_HOVER_CONTROLLER_H_ - -#include "base/macros.h" -#include "ui/gfx/animation/animation_delegate.h" -#include "ui/gfx/animation/slide_animation.h" -#include "ui/views/views_export.h" - -namespace gfx { -class Point; -} - -namespace views { - -class View; - -// GlowHoverController is responsible for drawing a hover effect as is used by -// the tabstrip. Typical usage: -// OnMouseEntered() -> invoke Show(). -// OnMouseMoved() -> invoke SetLocation(). -// OnMouseExited() -> invoke Hide(). -// OnPaint() -> if ShouldDraw() returns true invoke Draw(). -// Internally GlowHoverController uses an animation to animate the glow and -// invokes SchedulePaint() back on the View as necessary. -class VIEWS_EXPORT GlowHoverController : public gfx::AnimationDelegate { - public: - enum Style { - SUBTLE, - PRONOUNCED - }; - - explicit GlowHoverController(views::View* view); - ~GlowHoverController() override; - - // Sets the AnimationContainer used by the animation. - void SetAnimationContainer(gfx::AnimationContainer* container); - - // Sets the location of the hover, relative to the View passed to the - // constructor. - void SetLocation(const gfx::Point& location); - - const gfx::Point& location() const { return location_; } - - // Initiates showing the hover. - void Show(Style style); - - // Hides the hover. - void Hide(); - - // Hides the hover immediately. - void HideImmediately(); - - // Returns the value of the animation. - double GetAnimationValue() const; - - SkAlpha GetAlpha() const; - - // Returns true if there is something to be drawn. Use this instead of - // invoking Draw() if creating |mask_image| is expensive. - bool ShouldDraw() const; - - // gfx::AnimationDelegate overrides: - void AnimationEnded(const gfx::Animation* animation) override; - void AnimationProgressed(const gfx::Animation* animation) override; - - private: - // View we're drawing to. - views::View* view_; - - // Opacity of the glow ramps up over time. - gfx::SlideAnimation animation_; - - // Location of the glow, relative to view. - gfx::Point location_; - double opacity_scale_; - - DISALLOW_COPY_AND_ASSIGN(GlowHoverController); -}; - -} // namespace views - -#endif // UI_VIEWS_CONTROLS_GLOW_HOVER_CONTROLLER_H_ diff --git a/chromium/ui/views/controls/image_view.cc b/chromium/ui/views/controls/image_view.cc index 059a83652f7..5b7f65a5522 100644 --- a/chromium/ui/views/controls/image_view.cc +++ b/chromium/ui/views/controls/image_view.cc @@ -29,10 +29,10 @@ void* GetBitmapPixels(const gfx::ImageSkia& img, float image_scale) { const char ImageView::kViewClassName[] = "ImageView"; ImageView::ImageView() - : horiz_alignment_(CENTER), - vert_alignment_(CENTER), + : horizontal_alignment_(CENTER), + vertical_alignment_(CENTER), last_paint_scale_(0.f), - last_painted_bitmap_pixels_(NULL) {} + last_painted_bitmap_pixels_(nullptr) {} ImageView::~ImageView() {} @@ -40,7 +40,7 @@ void ImageView::SetImage(const gfx::ImageSkia& img) { if (IsImageEqual(img)) return; - last_painted_bitmap_pixels_ = NULL; + last_painted_bitmap_pixels_ = nullptr; gfx::Size pref_size(GetPreferredSize()); image_ = img; if (pref_size != GetPreferredSize()) @@ -93,31 +93,39 @@ gfx::Size ImageView::GetImageSize() const { gfx::Point ImageView::ComputeImageOrigin(const gfx::Size& image_size) const { gfx::Insets insets = GetInsets(); - int x; + int x = 0; // In order to properly handle alignment of images in RTL locales, we need // to flip the meaning of trailing and leading. For example, if the // horizontal alignment is set to trailing, then we'll use left alignment for // the image instead of right alignment if the UI layout is RTL. - Alignment actual_horiz_alignment = horiz_alignment_; - if (base::i18n::IsRTL() && (horiz_alignment_ != CENTER)) - actual_horiz_alignment = (horiz_alignment_ == LEADING) ? TRAILING : LEADING; - switch (actual_horiz_alignment) { - case LEADING: x = insets.left(); break; - case TRAILING: x = width() - insets.right() - image_size.width(); break; + Alignment actual_horizontal_alignment = horizontal_alignment_; + if (base::i18n::IsRTL() && (horizontal_alignment_ != CENTER)) { + actual_horizontal_alignment = + (horizontal_alignment_ == LEADING) ? TRAILING : LEADING; + } + switch (actual_horizontal_alignment) { + case LEADING: + x = insets.left(); + break; + case TRAILING: + x = width() - insets.right() - image_size.width(); + break; case CENTER: x = (width() - insets.width() - image_size.width()) / 2 + insets.left(); break; - default: NOTREACHED(); x = 0; break; } - int y; - switch (vert_alignment_) { - case LEADING: y = insets.top(); break; - case TRAILING: y = height() - insets.bottom() - image_size.height(); break; + int y = 0; + switch (vertical_alignment_) { + case LEADING: + y = insets.top(); + break; + case TRAILING: + y = height() - insets.bottom() - image_size.height(); + break; case CENTER: y = (height() - insets.height() - image_size.height()) / 2 + insets.top(); break; - default: NOTREACHED(); y = 0; break; } return gfx::Point(x, y); @@ -137,26 +145,26 @@ const char* ImageView::GetClassName() const { return kViewClassName; } -void ImageView::SetHorizontalAlignment(Alignment ha) { - if (ha != horiz_alignment_) { - horiz_alignment_ = ha; +void ImageView::SetHorizontalAlignment(Alignment alignment) { + if (alignment != horizontal_alignment_) { + horizontal_alignment_ = alignment; SchedulePaint(); } } ImageView::Alignment ImageView::GetHorizontalAlignment() const { - return horiz_alignment_; + return horizontal_alignment_; } -void ImageView::SetVerticalAlignment(Alignment va) { - if (va != vert_alignment_) { - vert_alignment_ = va; +void ImageView::SetVerticalAlignment(Alignment alignment) { + if (alignment != vertical_alignment_) { + vertical_alignment_ = alignment; SchedulePaint(); } } ImageView::Alignment ImageView::GetVerticalAlignment() const { - return vert_alignment_; + return vertical_alignment_; } void ImageView::SetTooltipText(const base::string16& tooltip) { @@ -184,7 +192,7 @@ gfx::Size ImageView::CalculatePreferredSize() const { views::PaintInfo::ScaleType ImageView::GetPaintScaleType() const { // ImageView contains an image which is rastered at the device scale factor. - // By default, the paint commands are recorded at a scale factor slighlty + // By default, the paint commands are recorded at a scale factor slightly // different from the device scale factor. Re-rastering the image at this // paint recording scale will result in a distorted image. Paint recording // scale might also not be uniform along the x & y axis, thus resulting in @@ -197,7 +205,7 @@ views::PaintInfo::ScaleType ImageView::GetPaintScaleType() const { void ImageView::OnPaintImage(gfx::Canvas* canvas) { last_paint_scale_ = canvas->image_scale(); - last_painted_bitmap_pixels_ = NULL; + last_painted_bitmap_pixels_ = nullptr; if (image_.isNull()) return; diff --git a/chromium/ui/views/controls/image_view.h b/chromium/ui/views/controls/image_view.h index 178c50e5087..c19a3c5ea8e 100644 --- a/chromium/ui/views/controls/image_view.h +++ b/chromium/ui/views/controls/image_view.h @@ -106,10 +106,10 @@ class VIEWS_EXPORT ImageView : public View { gfx::ImageSkia image_; // Horizontal alignment. - Alignment horiz_alignment_; + Alignment horizontal_alignment_; // Vertical alignment. - Alignment vert_alignment_; + Alignment vertical_alignment_; // The current tooltip text. base::string16 tooltip_text_; diff --git a/chromium/ui/views/controls/menu/menu_config.cc b/chromium/ui/views/controls/menu/menu_config.cc index 62fbbca1d1d..c398009a47d 100644 --- a/chromium/ui/views/controls/menu/menu_config.cc +++ b/chromium/ui/views/controls/menu/menu_config.cc @@ -5,7 +5,9 @@ #include "ui/views/controls/menu/menu_config.h" #include "base/macros.h" +#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_image_util.h" +#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/round_rect_painter.h" namespace views { @@ -18,28 +20,36 @@ MenuConfig::MenuConfig() item_bottom_margin(3), item_no_icon_top_margin(4), item_no_icon_bottom_margin(4), - fixed_text_item_height(0), - fixed_menu_width(0), + minimum_text_item_height(0), + minimum_container_item_height(0), + minimum_menu_width(0), item_left_margin(10), touchable_item_left_margin(16), label_to_arrow_padding(10), arrow_to_edge_padding(5), icon_to_label_padding(10), - touchable_icon_to_label_padding(22), + touchable_icon_to_label_padding(16), touchable_icon_size(20), - touchable_icon_color(SkColorSetA(SK_ColorBLACK, 0xDE)), + touchable_icon_color(SkColorSetRGB(0x5F, 0x63, 0x60)), check_width(kMenuCheckSize), check_height(kMenuCheckSize), arrow_width(kSubmenuArrowSize), separator_height(11), + double_separator_height(18), separator_upper_height(3), separator_lower_height(4), separator_spacing_height(3), separator_thickness(1), + double_separator_thickness(2), show_mnemonics(false), + use_mnemonics(true), scroll_arrow_height(3), label_to_minor_text_padding(10), item_min_height(0), + actionable_submenu_arrow_to_edge_padding(14), + actionable_submenu_width(37), + actionable_submenu_vertical_separator_height(18), + actionable_submenu_vertical_separator_width(1), show_accelerators(true), always_use_icon_to_label_padding(false), align_arrow_and_shortcut(false), @@ -49,17 +59,44 @@ MenuConfig::MenuConfig() check_selected_combobox_item(false), show_delay(400), corner_radius(0), + auxiliary_corner_radius(0), touchable_corner_radius(8), touchable_anchor_offset(8), touchable_menu_height(36), touchable_menu_width(256), touchable_menu_shadow_elevation(12), - vertical_touchable_menu_item_padding(8) { + vertical_touchable_menu_item_padding(8), + padded_separator_left_margin(64), + arrow_key_selection_wraps(true), + show_context_menu_accelerators(true) { Init(); } MenuConfig::~MenuConfig() {} +int MenuConfig::CornerRadiusForMenu(const MenuController* controller) const { + if (controller && controller->use_touchable_layout()) + return touchable_corner_radius; + if (controller && (controller->is_combobox() || controller->IsContextMenu())) + return auxiliary_corner_radius; + return corner_radius; +} + +bool MenuConfig::ShouldShowAcceleratorText(const MenuItemView* item, + base::string16* text) const { + if (!show_accelerators || !item->GetDelegate() || !item->GetCommand()) + return false; + ui::Accelerator accelerator; + if (!item->GetDelegate()->GetAccelerator(item->GetCommand(), &accelerator)) + return false; + if (item->GetMenuController() && item->GetMenuController()->IsContextMenu() && + !show_context_menu_accelerators) { + return false; + } + *text = accelerator.GetShortcutText(); + return true; +} + // static const MenuConfig& MenuConfig::instance() { CR_DEFINE_STATIC_LOCAL(MenuConfig, instance, ()); diff --git a/chromium/ui/views/controls/menu/menu_config.h b/chromium/ui/views/controls/menu/menu_config.h index 3fbb794646a..c07195b1be6 100644 --- a/chromium/ui/views/controls/menu/menu_config.h +++ b/chromium/ui/views/controls/menu/menu_config.h @@ -11,6 +11,9 @@ namespace views { +class MenuController; +class MenuItemView; + // Layout type information for menu items. Use the instance() method to obtain // the MenuConfig for the current platform. struct VIEWS_EXPORT MenuConfig { @@ -19,6 +22,16 @@ struct VIEWS_EXPORT MenuConfig { static const MenuConfig& instance(); + // Helper methods to simplify access to MenuConfig: + // Returns the appropriate corner radius for the menu controlled by + // |controller|, or the default corner radius if |controller| is nullptr. + int CornerRadiusForMenu(const MenuController* controller) const; + + // Returns whether |item_view| should show accelerator text. If so, returns + // the text to show. + bool ShouldShowAcceleratorText(const MenuItemView* item_view, + base::string16* text) const; + // Font list used by menus. gfx::FontList font_list; @@ -44,12 +57,12 @@ struct VIEWS_EXPORT MenuConfig { int item_no_icon_top_margin; int item_no_icon_bottom_margin; - // Fixed dimensions used for entire items. If these are nonzero, they override - // the vertical margin constants given above - the item's text and icon are - // vertically centered within these heights. - int fixed_text_item_height; - int fixed_container_item_height; - int fixed_menu_width; + // Minimum dimensions used for entire items. If these are nonzero, they + // override the vertical margin constants given above - the item's text and + // icon are vertically centered within these heights. + int minimum_text_item_height; + int minimum_container_item_height; + int minimum_menu_width; // Margins between the left of the item and the icon. int item_left_margin; @@ -87,6 +100,9 @@ struct VIEWS_EXPORT MenuConfig { // Height of a normal separator (ui::NORMAL_SEPARATOR). int separator_height; + // Height of a double separator (ui::DOUBLE_SEPARATOR). + int double_separator_height; + // Height of a ui::UPPER_SEPARATOR. int separator_upper_height; @@ -99,9 +115,15 @@ struct VIEWS_EXPORT MenuConfig { // Thickness of the drawn separator line in pixels. int separator_thickness; + // Thickness of the drawn separator line in pixels for double separator. + int double_separator_thickness; + // Are mnemonics shown? bool show_mnemonics; + // Are mnemonics used to activate items? + bool use_mnemonics; + // Height of the scroll arrow. int scroll_arrow_height; @@ -112,6 +134,18 @@ struct VIEWS_EXPORT MenuConfig { // Minimum height of menu item. int item_min_height; + // Edge padding for an actionable submenu arrow. + int actionable_submenu_arrow_to_edge_padding; + + // Width of the submenu in an actionable submenu. + int actionable_submenu_width; + + // The height of the vertical separator used in an actionable submenu. + int actionable_submenu_vertical_separator_height; + + // The width of the vertical separator used in an actionable submenu. + int actionable_submenu_vertical_separator_width; + // Whether the keyboard accelerators are visible. bool show_accelerators; @@ -140,6 +174,10 @@ struct VIEWS_EXPORT MenuConfig { // Radius of the rounded corners of the menu border. Must be >= 0. int corner_radius; + // Radius of "auxiliary" rounded corners - comboboxes and context menus. + // Must be >= 0. + int auxiliary_corner_radius; + // Radius of the rounded corners of the touchable menu border int touchable_corner_radius; @@ -158,6 +196,15 @@ struct VIEWS_EXPORT MenuConfig { // Vertical padding for touchable menus. int vertical_touchable_menu_item_padding; + // Left margin of padded separator (ui::PADDED_SEPARATOR). + int padded_separator_left_margin; + + // Whether arrow keys should wrap around the end of the menu when selecting. + bool arrow_key_selection_wraps; + + // Whether to show accelerators in context menus. + bool show_context_menu_accelerators; + 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 e950582f43f..7854af9d373 100644 --- a/chromium/ui/views/controls/menu/menu_config_mac.mm +++ b/chromium/ui/views/controls/menu/menu_config_mac.mm @@ -17,9 +17,9 @@ void InitMaterialMenuConfig(views::MenuConfig* config) { config->menu_vertical_border_size = 8; config->menu_horizontal_border_size = 0; config->submenu_horizontal_inset = 0; - config->fixed_text_item_height = 32; - config->fixed_container_item_height = 48; - config->fixed_menu_width = 320; + config->minimum_text_item_height = 32; + config->minimum_container_item_height = 48; + config->minimum_menu_width = 320; config->item_left_margin = 8; config->label_to_arrow_padding = 0; config->arrow_to_edge_padding = 16; @@ -28,12 +28,16 @@ void InitMaterialMenuConfig(views::MenuConfig* config) { config->check_height = 16; config->arrow_width = 8; config->separator_height = 17; + config->separator_lower_height = 9; + config->separator_upper_height = 9; + config->separator_spacing_height = 9; config->separator_thickness = 1; config->label_to_minor_text_padding = 8; config->align_arrow_and_shortcut = true; config->use_outer_border = false; config->icons_in_label = true; config->corner_radius = 8; + config->auxiliary_corner_radius = 4; } } // namespace @@ -43,6 +47,9 @@ namespace views { void MenuConfig::Init() { font_list = gfx::FontList(gfx::Font([NSFont menuFontOfSize:0.0])); check_selected_combobox_item = true; + arrow_key_selection_wraps = false; + use_mnemonics = false; + show_context_menu_accelerators = false; if (ui::MaterialDesignController::IsSecondaryUiMaterial()) InitMaterialMenuConfig(this); } diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc index 46713d7170a..00590f09d91 100644 --- a/chromium/ui/views/controls/menu/menu_controller.cc +++ b/chromium/ui/views/controls/menu/menu_controller.cc @@ -165,7 +165,7 @@ View* GetNextFocusableView(View* ancestor, View* start_at, bool forward) { return NULL; } -#if defined(OS_WIN) || defined(OS_CHROMEOS) +#if defined(OS_WIN) // Determines the correct coordinates and window to repost |event| to, if it is // a mouse or touch event. static void RepostEventImpl(const ui::LocatedEvent* event, @@ -252,7 +252,7 @@ static void RepostEventImpl(const ui::LocatedEvent* event, PostMessage(target_window, event_type, target, window_coords); return; } -#endif +#endif // defined(OS_WIN) #if defined(USE_AURA) if (!window) @@ -276,7 +276,7 @@ static void RepostEventImpl(const ui::LocatedEvent* event, root->GetHost()->dispatcher()->RepostEvent(located_event.get()); #endif // defined(USE_AURA) } -#endif // defined(OS_WIN) || defined(OS_CHROMEOS) +#endif // defined(OS_WIN) } // namespace @@ -442,9 +442,8 @@ void MenuController::Run(Widget* parent, // If we are already showing, this new menu is being nested. Such as context // menus on top of normal menus. if (showing_) { - // Only support nesting of blocking_run menus, nesting of - // blocking/non-blocking shouldn't be needed. - DCHECK(blocking_run_); + // Nesting (context menus) is not used for drag and drop. + DCHECK(!for_drop_); state_.hot_button = hot_button_; hot_button_ = nullptr; @@ -477,7 +476,10 @@ void MenuController::Run(Widget* parent, // Set the selection, which opens the initial menu. SetSelection(root, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); - if (!blocking_run_) { + if (button) + pressed_lock_ = std::make_unique<MenuButton::PressedLock>(button); + + if (for_drop_) { if (!is_nested_drag) { // Start the timer to hide the menu. This is needed as we get no // notification when the drag has finished. @@ -486,9 +488,6 @@ void MenuController::Run(Widget* parent, return; } - if (button) - pressed_lock_.reset(new MenuButton::PressedLock(button)); - // Make sure Chrome doesn't attempt to shut down while the menu is showing. if (ViewsDelegate::GetInstance()) ViewsDelegate::GetInstance()->AddRef(); @@ -516,7 +515,7 @@ void MenuController::Cancel(ExitType type) { // Hide windows immediately. SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); - if (!blocking_run_) { + if (for_drop_) { // If we didn't block the caller we need to notify the menu, which // triggers deleting us. DCHECK(selected); @@ -550,6 +549,10 @@ void MenuController::AddNestedDelegate( delegate_ = delegate; } +bool MenuController::IsContextMenu() const { + return state_.context_menu; +} + bool MenuController::OnMousePressed(SubmenuView* source, const ui::MouseEvent& event) { // We should either have no current_mouse_event_target_, or should have a @@ -607,7 +610,7 @@ bool MenuController::OnMouseDragged(SubmenuView* source, MenuPart part = GetMenuPart(source, event.location()); UpdateScrolling(part); - if (!blocking_run_) + if (for_drop_) return false; if (possible_drag_) { @@ -664,12 +667,11 @@ void MenuController::OnMouseReleased(SubmenuView* source, return; } - if (!blocking_run_) + if (for_drop_) return; DCHECK(state_.item); possible_drag_ = false; - DCHECK(blocking_run_); MenuPart part = GetMenuPart(source, event.location()); if (event.IsRightMouseButton() && part.type == MenuPart::MENU_ITEM) { MenuItemView* menu = part.menu; @@ -694,7 +696,7 @@ void MenuController::OnMouseReleased(SubmenuView* source, // for selected folder menu items. If it's only a left click, show the // contents of the folder. if (!part.is_scroll() && part.menu && - !(part.menu->HasSubmenu() && + !(part.should_submenu_show && part.menu->HasSubmenu() && (event.flags() & ui::EF_LEFT_MOUSE_BUTTON))) { if (active_mouse_view_tracker_->view()) { SendMouseReleaseToActiveView(source, event); @@ -819,7 +821,7 @@ void MenuController::OnGestureEvent(SubmenuView* source, } } else if (event->type() == ui::ET_GESTURE_TAP) { if (!part.is_scroll() && part.menu && - !(part.menu->HasSubmenu())) { + !(part.should_submenu_show && part.menu->HasSubmenu())) { if (part.menu->GetDelegate()->IsTriggerableEvent( part.menu, *event)) { item_selected_by_touch_ = true; @@ -1004,7 +1006,7 @@ int MenuController::OnPerformDrop(SubmenuView* source, if (drop_target->id() == MenuItemView::kEmptyMenuItemViewID) drop_target = drop_target->GetParentMenuItem(); - if (!IsBlockingRun()) { + if (for_drop_) { delegate_->OnMenuClosed( internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE, item->GetRootMenuItem(), accept_event_flags_); @@ -1165,7 +1167,14 @@ void MenuController::SetSelection(MenuItemView* menu_item, size_t current_size = current_path.size(); size_t new_size = new_path.size(); - bool pending_item_changed = pending_state_.item != menu_item; + // ACTIONABLE_SUBMENUs can change without changing the pending item, this + // occurs when selection moves from the COMMAND area to the SUBMENU area of + // the ACTIONABLE_SUBMENU. + const bool pending_item_changed = + pending_state_.item != menu_item || + pending_state_.submenu_open != + !!(selection_types & SELECTION_OPEN_SUBMENU); + if (pending_item_changed && pending_state_.item) SetHotTrackedButton(nullptr); @@ -1174,7 +1183,8 @@ void MenuController::SetSelection(MenuItemView* menu_item, current_path.empty() ? NULL : current_path.front()->GetDelegate(); for (size_t i = paths_differ_at; i < current_size; ++i) { if (current_delegate && - current_path[i]->GetType() == MenuItemView::SUBMENU) { + (current_path[i]->GetType() == MenuItemView::SUBMENU || + current_path[i]->GetType() == MenuItemView::ACTIONABLE_SUBMENU)) { current_delegate->WillHideMenu(current_path[i]); } current_path[i]->SetSelected(false); @@ -1184,6 +1194,14 @@ void MenuController::SetSelection(MenuItemView* menu_item, for (size_t i = paths_differ_at; i < new_size; ++i) { new_path[i]->ScrollRectToVisible(new_path[i]->GetLocalBounds()); new_path[i]->SetSelected(true); + if (new_path[i]->GetType() == MenuItemView::ACTIONABLE_SUBMENU) { + new_path[i]->SetSelectionOfActionableSubmenu( + (selection_types & SELECTION_OPEN_SUBMENU) != 0); + } + } + if (menu_item && menu_item->GetType() == MenuItemView::ACTIONABLE_SUBMENU) { + menu_item->SetSelectionOfActionableSubmenu( + (selection_types & SELECTION_OPEN_SUBMENU) != 0); } if (menu_item && menu_item->GetDelegate()) @@ -1206,16 +1224,17 @@ void MenuController::SetSelection(MenuItemView* menu_item, StartShowTimer(); // Notify an accessibility focus event on all menu items except for the root. - if (menu_item && - (MenuDepth(menu_item) != 1 || - menu_item->GetType() != MenuItemView::SUBMENU)) { + if (menu_item && (MenuDepth(menu_item) != 1 || + menu_item->GetType() != MenuItemView::SUBMENU || + (menu_item->GetType() == MenuItemView::ACTIONABLE_SUBMENU && + (selection_types & SELECTION_OPEN_SUBMENU) == 0))) { menu_item->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true); } } void MenuController::SetSelectionOnPointerDown(SubmenuView* source, const ui::LocatedEvent* event) { - if (!blocking_run_) + if (for_drop_) return; DCHECK(!active_mouse_view_tracker_->view()); @@ -1255,7 +1274,7 @@ void MenuController::SetSelectionOnPointerDown(SubmenuView* source, possible_drag_ = true; press_pt_ = event->location(); } - if (part.menu->HasSubmenu()) + if (part.menu->HasSubmenu() && part.should_submenu_show) selection_types |= SELECTION_OPEN_SUBMENU; } SetSelection(part.menu, selection_types); @@ -1298,7 +1317,7 @@ void MenuController::StartDrag(SubmenuView* source, void MenuController::OnKeyDown(ui::KeyboardCode key_code) { // Do not process while performing drag-and-drop - if (!blocking_run_) + if (for_drop_) return; switch (key_code) { @@ -1405,9 +1424,9 @@ void MenuController::OnKeyDown(ui::KeyboardCode key_code) { } } -MenuController::MenuController(bool blocking, +MenuController::MenuController(bool for_drop, internal::MenuControllerDelegate* delegate) - : blocking_run_(blocking), + : for_drop_(for_drop), active_mouse_view_tracker_(std::make_unique<ViewTracker>()), delegate_(delegate) { delegate_stack_.push_back(delegate_); @@ -1445,11 +1464,6 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds, bool context_menu) { pending_state_.context_menu = context_menu; pending_state_.initial_bounds = bounds; - if (bounds.height() > 1) { - // Inset the bounds slightly, otherwise drag coordinates don't line up - // nicely and menus close prematurely. - pending_state_.initial_bounds.Inset(0, 1); - } // Reverse anchor position for RTL languages. if (base::i18n::IsRTL() && @@ -1491,7 +1505,7 @@ void MenuController::Accept(MenuItemView* item, int event_flags) { } void MenuController::ReallyAccept(MenuItemView* item, int event_flags) { - DCHECK(IsBlockingRun()); + DCHECK(!for_drop_); result_ = item; #if defined(OS_MACOSX) // Reset the closure animation since it's now finished - this also unblocks @@ -1562,9 +1576,8 @@ bool MenuController::ShowSiblingMenu(SubmenuView* source, // It is currently not possible to show a submenu recursively in a bubble. DCHECK(!MenuItemView::IsBubble(anchor)); - // Subtract 1 from the height to make the popup flush with the button border. UpdateInitialLocation(gfx::Rect(screen_menu_loc.x(), screen_menu_loc.y(), - button->width(), button->height() - 1), + button->width(), button->height()), anchor, state_.context_menu); alt_menu->PrepareForRun( false, has_mnemonics, @@ -1699,11 +1712,19 @@ bool MenuController::GetMenuPartByScreenCoordinateImpl( part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y()); part->type = MenuPart::MENU_ITEM; part->submenu = menu; + part->should_submenu_show = + part->submenu && part->menu && + (part->menu->GetType() == MenuItemView::SUBMENU || + IsLocationOverSubmenuAreaOfActionableSubmenu(part->menu, screen_loc)); if (!part->menu) part->parent = menu->GetMenuItem(); return true; } + // Return false for points on touchable menu shadows, to search parent menus. + if (use_touchable_layout_) + return false; + // While the mouse isn't over a menu item or the scroll buttons of menu, it // is contained by menu and so we return true. If we didn't return true other // menus would be searched, even though they are likely obscured by us. @@ -1735,7 +1756,20 @@ bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu, gfx::Point view_loc = screen_loc; View::ConvertPointFromScreen(submenu, &view_loc); gfx::Rect vis_rect = submenu->GetVisibleBounds(); - return vis_rect.Contains(view_loc.x(), view_loc.y()); + return vis_rect.Contains(view_loc); +} + +bool MenuController::IsLocationOverSubmenuAreaOfActionableSubmenu( + MenuItemView* item, + const gfx::Point& screen_loc) const { + if (!item || item->GetType() != MenuItemView::ACTIONABLE_SUBMENU) + return false; + + gfx::Point view_loc = screen_loc; + View::ConvertPointFromScreen(item, &view_loc); + if (base::i18n::IsRTL()) + view_loc.set_x(item->GetMirroredXInView(view_loc.x())); + return item->GetSubmenuAreaOfActionableSubmenu().Contains(view_loc); } void MenuController::CommitPendingSelection() { @@ -1847,7 +1881,7 @@ void MenuController::OpenMenuImpl(MenuItemView* item, bool show) { CalculateBubbleMenuBounds(item, prefer_leading, &resulting_direction) : CalculateMenuBounds(item, prefer_leading, &resulting_direction); state_.open_leading.push_back(resulting_direction); - bool do_capture = (!did_capture_ && blocking_run_); + bool do_capture = (!did_capture_ && !for_drop_); showing_submenu_ = true; if (show) { // Menus are the only place using kGroupingPropertyKey, so any value (other @@ -2127,7 +2161,6 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, bool prefer_leading, bool* is_leading) { DCHECK(item); - DCHECK(!item->GetParentMenuItem()); // Assume we can honor prefer_leading. *is_leading = prefer_leading; @@ -2136,103 +2169,153 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, DCHECK(submenu); gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); - const gfx::Rect& owner_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(); - // 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; - } else if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) { - max_width = state_.monitor_bounds.right() - owner_bounds.right() + - kBubbleTipSizeLeftRight; - } else if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) { - max_height = owner_bounds.y() - state_.monitor_bounds.y() + - kBubbleTipSizeTopBottom; - } else if (state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { - max_height = state_.monitor_bounds.bottom() - owner_bounds.bottom() + - kBubbleTipSizeTopBottom; - } - // The space for the menu to cover should never get empty. - 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)); - } - // Also make sure that the menu does not go too wide. - pref.set_width(std::min(pref.width(), - item->GetDelegate()->GetMaxWidthForMenu(item))); - + int x = 0; + int y = 0; const MenuConfig& menu_config = MenuConfig::instance(); // Shadow insets are built into MenuScrollView's preferred size so it must be // compensated for when determining the bounds of touchable menus. - gfx::Insets shadow_insets = BubbleBorder::GetBorderAndShadowInsets( - menu_config.touchable_menu_shadow_elevation); + const gfx::Insets border_and_shadow_insets = + BubbleBorder::GetBorderAndShadowInsets( + menu_config.touchable_menu_shadow_elevation); - int x, y; - 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; - else - y = owner_bounds.bottom() - kBubbleTipSizeTopBottom; - - x = owner_bounds.CenterPoint().x() - pref.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(); + 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; + + // 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(); + // 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; + } else if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) { + max_width = state_.monitor_bounds.right() - owner_bounds.right() + + kBubbleTipSizeLeftRight; + } else if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) { + max_height = owner_bounds.y() - state_.monitor_bounds.y() + + kBubbleTipSizeTopBottom; + } else if (state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { + max_height = state_.monitor_bounds.bottom() - owner_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)); } - submenu->GetScrollViewContainer()->SetBubbleArrowOffset( - pref.width() / 2 - x + x_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() - shadow_insets.left(); - y = owner_bounds.origin().y() - pref.height() + shadow_insets.bottom() - - menu_config.touchable_anchor_offset; - // Align the right of the container with the right of the app icon. - if (x + pref.width() > state_.monitor_bounds.width()) - x = owner_bounds.right() - pref.width() + shadow_insets.right(); - // Align the top of the menu with the bottom of the anchor. - if (y < 0) { - y = owner_bounds.bottom() - shadow_insets.top() + + // Respect the delegate's maximum width. + pref.set_width( + std::min(pref.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; + else + y = owner_bounds.bottom() - kBubbleTipSizeTopBottom; + + x = owner_bounds.CenterPoint().x() - pref.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); + } 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() + + border_and_shadow_insets.bottom() - 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() + shadow_insets.right() - - menu_config.touchable_anchor_offset; - y = owner_bounds.origin().y() - shadow_insets.top(); - // Align the left of the menu with the right of the anchor. - if (x < 0) { - x = owner_bounds.right() + shadow_insets.left() + + // 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() + + border_and_shadow_insets.right(); + } + // Align the top of the menu with the bottom of the anchor. + if (y < 0) { + y = owner_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() + + border_and_shadow_insets.right() - menu_config.touchable_anchor_offset; + y = owner_bounds.origin().y() - border_and_shadow_insets.top(); + // Align the left of the menu with the right of the anchor. + if (x < 0) { + x = owner_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() + + 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); } - // 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() + shadow_insets.bottom(); } else { - if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) - x = owner_bounds.right() - kBubbleTipSizeLeftRight; - else - x = owner_bounds.x() - pref.width() + kBubbleTipSizeLeftRight; + if (!use_touchable_layout_) { + NOTIMPLEMENTED() + << "Nested bubble menus are only implemented for touchable menus."; + } - 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(); + // This is a sub-menu, position it relative to the parent menu. + const gfx::Rect item_bounds = item->GetBoundsInScreen(); + // 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) { + 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())) { + *is_leading = prefer_leading; + x = item_bounds.x() - menu_config.touchable_menu_width - + border_and_shadow_insets.right(); + } + } 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()) { + *is_leading = !prefer_leading; + x = item_bounds.x() + menu_config.touchable_menu_width - + border_and_shadow_insets.left(); + } } - submenu->GetScrollViewContainer()->SetBubbleArrowOffset( - pref.height() / 2 - y + y_old); + 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(); } return gfx::Rect(x, y, pref.width(), pref.height()); } @@ -2309,14 +2392,20 @@ MenuItemView* MenuController::FindNextSelectableMenuItem( // Loop through the menu items skipping any invisible menus. The loop stops // when we wrap or find a visible and enabled child. do { + if (!MenuConfig::instance().arrow_key_selection_wraps) { + if (index == 0 && direction == INCREMENT_SELECTION_UP) + return nullptr; + if (index == parent_count - 1 && direction == INCREMENT_SELECTION_DOWN) + return nullptr; + } index = (index + delta + parent_count) % parent_count; if (index == stop_index && !include_all_items) - return NULL; + return nullptr; MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index); if (child->visible() && child->enabled()) return child; } while (index != stop_index); - return NULL; + return nullptr; } void MenuController::OpenSubmenuChangeSelectionIfCan() { @@ -2398,8 +2487,8 @@ void MenuController::AcceptOrSelect(MenuItemView* parent, } void MenuController::SelectByChar(base::char16 character) { - // Do not process while performing drag-and-drop - if (!blocking_run_) + // Do not process while performing drag-and-drop. + if (for_drop_) return; if (!character) return; @@ -2483,14 +2572,6 @@ void MenuController::RepostEventAndCancel(SubmenuView* source, exit_type = EXIT_OUTERMOST; } Cancel(exit_type); - -#if defined(OS_CHROMEOS) - // We're going to exit the menu and want to repost the event so that is - // is handled normally after the context menu has exited. We call - // RepostEvent after Cancel so that event capture has been released so - // that finding the event target is unaffected by the current capture. - RepostEventImpl(event, screen_loc, native_view, window); -#endif } void MenuController::SetDropMenuItem( @@ -2723,14 +2804,15 @@ void MenuController::HandleMouseLocation(SubmenuView* source, UpdateScrolling(part); - if (!blocking_run_) + if (for_drop_) return; if (part.type == MenuPart::NONE && ShowSiblingMenu(source, mouse_location)) return; if (part.type == MenuPart::MENU_ITEM && part.menu) { - SetSelection(part.menu, SELECTION_OPEN_SUBMENU); + SetSelection(part.menu, part.should_submenu_show ? SELECTION_OPEN_SUBMENU + : SELECTION_DEFAULT); } else if (!part.is_scroll() && pending_state_.item && pending_state_.item->GetParentMenuItem() && !pending_state_.item->SubmenuIsShowing()) { diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h index b2080448720..67b8849bca5 100644 --- a/chromium/ui/views/controls/menu/menu_controller.h +++ b/chromium/ui/views/controls/menu/menu_controller.h @@ -95,8 +95,7 @@ class VIEWS_EXPORT MenuController bool context_menu, bool is_nested_drag); - // Whether or not Run blocks. - bool IsBlockingRun() const { return blocking_run_; } + bool for_drop() const { return for_drop_; } bool in_nested_run() const { return !menu_stack_.empty(); } @@ -144,6 +143,8 @@ class VIEWS_EXPORT MenuController void set_is_combobox(bool is_combobox) { is_combobox_ = is_combobox; } bool is_combobox() const { return is_combobox_; } + bool IsContextMenu() const; + // Various events, forwarded from the submenu. // // NOTE: the coordinates of the events are in that of the @@ -294,13 +295,11 @@ class VIEWS_EXPORT MenuController SCROLL_DOWN }; - MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {} - // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP. bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; } // Type of part. - Type type; + Type type = NONE; // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise // this is NULL. @@ -308,14 +307,17 @@ class VIEWS_EXPORT MenuController // but is over a menu (for example, the mouse is over a separator or // empty menu), this is NULL and parent is the menu the mouse was // clicked on. - MenuItemView* menu; + MenuItemView* menu = nullptr; // If type is MENU_ITEM but the mouse is not over a menu item this is the // parent of the menu item the user clicked on. Otherwise this is NULL. - MenuItemView* parent; + MenuItemView* parent = nullptr; // This is the submenu the mouse is over. - SubmenuView* submenu; + SubmenuView* submenu = nullptr; + + // Whether the controller should apply SELECTION_OPEN_SUBMENU to this item. + bool should_submenu_show = false; }; // Sets the selection to |menu_item|. A value of NULL unselects @@ -334,10 +336,8 @@ class VIEWS_EXPORT MenuController // Key processing. void OnKeyDown(ui::KeyboardCode key_code); - // Creates a MenuController. If |blocking| is true a nested run loop is - // started in |Run|. - MenuController(bool blocking, - internal::MenuControllerDelegate* delegate); + // Creates a MenuController. See |for_drop_| member for details on |for_drop|. + MenuController(bool for_drop, internal::MenuControllerDelegate* delegate); ~MenuController() override; @@ -419,6 +419,11 @@ class VIEWS_EXPORT MenuController bool DoesSubmenuContainLocation(SubmenuView* submenu, const gfx::Point& screen_loc); + // Returns whether the location is over the ACTIONABLE_SUBMENU's submenu area. + bool IsLocationOverSubmenuAreaOfActionableSubmenu( + MenuItemView* item, + const gfx::Point& screen_loc) const; + // Opens/Closes the necessary menus such that state_ matches that of // pending_state_. This is invoked if submenus are not opened immediately, // but after a delay. @@ -576,11 +581,10 @@ class VIEWS_EXPORT MenuController // The active instance. static MenuController* active_instance_; - // If true, Run blocks. If false, Run doesn't block and this is used for - // drag and drop. Note that the semantics for drag and drop are slightly - // different: cancel timer is kicked off any time the drag moves outside the - // menu, mouse events do nothing... - bool blocking_run_; + // If true the menu is shown for a drag and drop. Note that the semantics for + // drag and drop are slightly different: cancel timer is kicked off any time + // the drag moves outside the menu, mouse events do nothing... + const bool for_drop_; // If true, we're showing. bool showing_ = false; diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc index 22fa8335af1..2d1c138bdc0 100644 --- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc @@ -294,6 +294,11 @@ class MenuControllerTest : public ViewsTestBase { event_generator_->PressKey(key_code, 0); } + void DispatchKey(ui::KeyboardCode key_code) { + ui::KeyEvent event(ui::EventType::ET_KEY_PRESSED, key_code, 0); + menu_controller_->OnWillDispatchKeyEvent(&event); + } + #if defined(USE_AURA) // Verifies that a non-nested menu fully closes when receiving an escape key. void TestAsyncEscapeKey() { @@ -330,8 +335,9 @@ class MenuControllerTest : public ViewsTestBase { void TestMenuControllerReplacementDuringDrag() { DestroyMenuController(); menu_item()->GetSubmenu()->Close(); + const bool for_drop = false; menu_controller_ = - new MenuController(true, menu_controller_delegate_.get()); + new MenuController(for_drop, menu_controller_delegate_.get()); menu_controller_->owner_ = owner_.get(); menu_controller_->showing_ = true; } @@ -485,7 +491,7 @@ class MenuControllerTest : public ViewsTestBase { } MenuController* menu_controller() { return menu_controller_; } const MenuItemView* pending_state_item() const { - return menu_controller_->pending_state_.item; + return menu_controller_->pending_state_.item; } MenuController::ExitType menu_exit_type() const { return menu_controller_->exit_type_; @@ -533,6 +539,10 @@ class MenuControllerTest : public ViewsTestBase { int CountOwnerOnGestureEvent() const { return owner_->gesture_count(); } + bool SelectionWraps() { + return MenuConfig::instance().arrow_key_selection_wraps; + } + private: void Init() { owner_ = std::make_unique<GestureTestWidget>(); @@ -558,8 +568,9 @@ class MenuControllerTest : public ViewsTestBase { void SetupMenuController() { menu_controller_delegate_.reset(new TestMenuControllerDelegate); + const bool for_drop = false; menu_controller_ = - new MenuController(true, menu_controller_delegate_.get()); + new MenuController(for_drop, menu_controller_delegate_.get()); menu_controller_->owner_ = owner_.get(); menu_controller_->showing_ = true; menu_controller_->SetSelection( @@ -644,8 +655,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { // The last selectable item should be item "Four". MenuItemView* last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(4, last_selectable->GetCommand()); + if (SelectionWraps()) { + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(4, last_selectable->GetCommand()); + } else { + ASSERT_EQ(nullptr, last_selectable); + } // Leave items "One" and "Two" enabled. menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); @@ -658,8 +673,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { EXPECT_EQ(1, first_selectable->GetCommand()); // The last selectable item should be item "Two". last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(2, last_selectable->GetCommand()); + if (SelectionWraps()) { + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(2, last_selectable->GetCommand()); + } else { + ASSERT_EQ(nullptr, last_selectable); + } // Leave only a single item "One" enabled. menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); @@ -672,8 +691,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { EXPECT_EQ(1, first_selectable->GetCommand()); // The last selectable item should be item "One". last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(1, last_selectable->GetCommand()); + if (SelectionWraps()) { + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(1, last_selectable->GetCommand()); + } else { + ASSERT_EQ(nullptr, last_selectable); + } // Leave only a single item "Three" enabled. menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); @@ -686,8 +709,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { EXPECT_EQ(3, first_selectable->GetCommand()); // The last selectable item should be item "Three". last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(3, last_selectable->GetCommand()); + if (SelectionWraps()) { + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(3, last_selectable->GetCommand()); + } else { + ASSERT_EQ(nullptr, last_selectable); + } // Leave only a single item ("Two") selected. It should be the first and the // last selectable item. @@ -699,8 +726,12 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { ASSERT_NE(nullptr, first_selectable); EXPECT_EQ(2, first_selectable->GetCommand()); last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(2, last_selectable->GetCommand()); + if (SelectionWraps()) { + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(2, last_selectable->GetCommand()); + } else { + ASSERT_EQ(nullptr, last_selectable); + } // There should be no next or previous selectable item since there is only a // single enabled item in the menu. @@ -730,14 +761,20 @@ TEST_F(MenuControllerTest, NextSelectedItem) { IncrementSelection(); EXPECT_EQ(4, pending_state_item()->GetCommand()); - // Wrap around. - IncrementSelection(); - EXPECT_EQ(1, pending_state_item()->GetCommand()); - - // Move up in the menu. - // Wrap around. - DecrementSelection(); - EXPECT_EQ(4, pending_state_item()->GetCommand()); + if (SelectionWraps()) { + // Wrap around. + IncrementSelection(); + EXPECT_EQ(1, pending_state_item()->GetCommand()); + + // Move up in the menu. + // Wrap around. + DecrementSelection(); + EXPECT_EQ(4, pending_state_item()->GetCommand()); + } else { + // Don't wrap. + IncrementSelection(); + EXPECT_EQ(4, pending_state_item()->GetCommand()); + } // Skip disabled item. DecrementSelection(); @@ -762,7 +799,10 @@ TEST_F(MenuControllerTest, PreviousSelectedItem) { // Move up and select a previous (in our case the last enabled) item. DecrementSelection(); - EXPECT_EQ(3, pending_state_item()->GetCommand()); + if (SelectionWraps()) + EXPECT_EQ(3, pending_state_item()->GetCommand()); + else + EXPECT_EQ(0, pending_state_item()->GetCommand()); // Clear references in menu controller to the menu item that is going away. ResetSelection(); @@ -843,7 +883,10 @@ TEST_F(MenuControllerTest, SelectChildButtonView) { // Increment selection twice to wrap around. IncrementSelection(); IncrementSelection(); - EXPECT_EQ(1, pending_state_item()->GetCommand()); + if (SelectionWraps()) + EXPECT_EQ(1, pending_state_item()->GetCommand()); + else + EXPECT_EQ(5, pending_state_item()->GetCommand()); // Clear references in menu controller to the menu item that is going away. ResetSelection(); @@ -1288,6 +1331,36 @@ TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) { EXPECT_EQ(1, nested_delegate->on_menu_closed_called()); } +TEST_F(MenuControllerTest, ArrowKeysAtEnds) { + menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); + + SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0)); + EXPECT_EQ(1, pending_state_item()->GetCommand()); + + if (SelectionWraps()) { + DispatchKey(ui::VKEY_UP); + EXPECT_EQ(4, pending_state_item()->GetCommand()); + + DispatchKey(ui::VKEY_DOWN); + EXPECT_EQ(1, pending_state_item()->GetCommand()); + } else { + DispatchKey(ui::VKEY_UP); + EXPECT_EQ(1, pending_state_item()->GetCommand()); + } + + DispatchKey(ui::VKEY_DOWN); + EXPECT_EQ(2, pending_state_item()->GetCommand()); + + DispatchKey(ui::VKEY_DOWN); + EXPECT_EQ(4, pending_state_item()->GetCommand()); + + DispatchKey(ui::VKEY_DOWN); + if (SelectionWraps()) + EXPECT_EQ(1, pending_state_item()->GetCommand()); + else + EXPECT_EQ(4, pending_state_item()->GetCommand()); +} + #if defined(USE_AURA) // Tests that when an asynchronous menu receives a cancel event, that it closes. TEST_F(MenuControllerTest, AsynchronousCancelEvent) { diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc index f1ff95b4748..f14a1c06685 100644 --- a/chromium/ui/views/controls/menu/menu_host.cc +++ b/chromium/ui/views/controls/menu/menu_host.cc @@ -116,9 +116,7 @@ void MenuHost::InitMenuHost(Widget* parent, const MenuController* menu_controller = submenu_->GetMenuItem()->GetMenuController(); const MenuConfig& menu_config = MenuConfig::instance(); - bool rounded_border = - menu_controller && (menu_controller->use_touchable_layout() || - (menu_config.corner_radius > 0)); + bool rounded_border = menu_config.CornerRadiusForMenu(menu_controller) != 0; bool bubble_border = submenu_->GetScrollViewContainer() && submenu_->GetScrollViewContainer()->HasBubbleBorder(); params.shadow_type = bubble_border ? Widget::InitParams::SHADOW_TYPE_NONE diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc index 257f71e6eaa..22a219b4660 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.cc +++ b/chromium/ui/views/controls/menu/menu_item_view.cc @@ -12,6 +12,7 @@ #include "ui/accessibility/ax_node_data.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/models/menu_model.h" +#include "ui/base/ui_base_features.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_utils.h" #include "ui/gfx/geometry/rect.h" @@ -29,6 +30,7 @@ #include "ui/views/controls/menu/menu_scroll_view_container.h" #include "ui/views/controls/menu/menu_separator.h" #include "ui/views/controls/menu/submenu_view.h" +#include "ui/views/controls/separator.h" #include "ui/views/widget/widget.h" namespace views { @@ -171,7 +173,8 @@ void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) { switch (GetType()) { case SUBMENU: - node_data->AddState(ax::mojom::State::kHaspopup); + case ACTIONABLE_SUBMENU: + node_data->SetHasPopup(ax::mojom::HasPopup::kMenu); break; case CHECKBOX: case RADIO: { @@ -267,7 +270,7 @@ MenuItemView* MenuItemView::AddMenuItemAt( item->SetMinorIcon(minor_icon); if (!icon.isNull()) item->SetIcon(icon); - if (type == SUBMENU) + if (type == SUBMENU || type == ACTIONABLE_SUBMENU) item->CreateSubmenu(); if (GetDelegate() && !GetDelegate()->IsCommandVisible(item_id)) item->SetVisible(false); @@ -398,6 +401,19 @@ void MenuItemView::SetSelected(bool selected) { SchedulePaint(); } +void MenuItemView::SetSelectionOfActionableSubmenu( + bool submenu_area_of_actionable_submenu_selected) { + DCHECK_EQ(ACTIONABLE_SUBMENU, type_); + if (submenu_area_of_actionable_submenu_selected_ == + submenu_area_of_actionable_submenu_selected) { + return; + } + + submenu_area_of_actionable_submenu_selected_ = + submenu_area_of_actionable_submenu_selected; + SchedulePaint(); +} + void MenuItemView::SetTooltip(const base::string16& tooltip, int item_id) { MenuItemView* item = GetMenuItemByID(item_id); DCHECK(item); @@ -458,6 +474,13 @@ int MenuItemView::GetHeightForWidth(int width) const { return height; } +gfx::Rect MenuItemView::GetSubmenuAreaOfActionableSubmenu() const { + DCHECK_EQ(ACTIONABLE_SUBMENU, type_); + const MenuConfig& config = MenuConfig::instance(); + return gfx::Rect(gfx::Point(vertical_separator_->bounds().right(), 0), + gfx::Size(config.actionable_submenu_width, height())); +} + const MenuItemView::MenuItemDimensions& MenuItemView::GetDimensions() const { if (!is_dimensions_valid()) dimensions_ = CalculateDimensions(); @@ -495,8 +518,10 @@ const MenuItemView* MenuItemView::GetRootMenuItem() const { } base::char16 MenuItemView::GetMnemonic() { - if (!GetRootMenuItem()->has_mnemonics_) + if (!GetRootMenuItem()->has_mnemonics_ || + !MenuConfig::instance().use_mnemonics) { return 0; + } size_t index = 0; do { @@ -580,6 +605,8 @@ void MenuItemView::Layout() { continue; if (submenu_arrow_image_view_ == child) continue; + if (vertical_separator_ == child) + continue; int width = child->GetPreferredSize().width(); child->SetBounds(x - width, 0, width, height()); x -= width + kChildXPadding; @@ -611,13 +638,25 @@ void MenuItemView::Layout() { } if (submenu_arrow_image_view_) { - int x = width() - config.arrow_width - config.arrow_to_edge_padding; + int x = width() - config.arrow_width - + (type_ == ACTIONABLE_SUBMENU + ? config.actionable_submenu_arrow_to_edge_padding + : config.arrow_to_edge_padding); int y = (height() + GetTopMargin() - GetBottomMargin() - kSubmenuArrowSize) / 2; submenu_arrow_image_view_->SetBounds(x, y, config.arrow_width, kSubmenuArrowSize); } + + if (vertical_separator_) { + const gfx::Size preferred_size = vertical_separator_->GetPreferredSize(); + int x = width() - config.actionable_submenu_width - + config.actionable_submenu_vertical_separator_width; + int y = (height() - preferred_size.height()) / 2; + vertical_separator_->SetBoundsRect( + gfx::Rect(gfx::Point(x, y), preferred_size)); + } } } @@ -682,7 +721,11 @@ void MenuItemView::UpdateMenuPartSizes() { if (has_icons_) icon_area_width_ = std::max(icon_area_width_, GetMaxIconViewWidth()); - label_start_ = config.item_left_margin + icon_area_width_; + const bool use_touchable_layout = + GetMenuController() && GetMenuController()->use_touchable_layout(); + label_start_ = (use_touchable_layout ? config.touchable_item_left_margin + : config.item_left_margin) + + icon_area_width_; int padding = 0; if (config.always_use_icon_to_label_padding) { padding = config.icon_to_label_padding; @@ -690,7 +733,7 @@ void MenuItemView::UpdateMenuPartSizes() { padding = (has_icons_ || HasChecksOrRadioButtons()) ? config.icon_to_label_padding : 0; } - if (GetMenuController() && GetMenuController()->use_touchable_layout()) + if (use_touchable_layout) padding = config.touchable_icon_to_label_padding; label_start_ += padding; @@ -705,15 +748,16 @@ void MenuItemView::Init(MenuItemView* parent, MenuItemView::Type type, MenuDelegate* delegate) { delegate_ = delegate; - controller_ = NULL; + controller_ = nullptr; canceled_ = false; parent_menu_item_ = parent; type_ = type; selected_ = false; command_ = command; - submenu_ = NULL; + submenu_ = nullptr; radio_check_image_view_ = nullptr; submenu_arrow_image_view_ = nullptr; + vertical_separator_ = nullptr; show_mnemonics_ = false; // Assign our ID, this allows SubmenuItemView to find MenuItemViews. set_id(kMenuItemViewID); @@ -729,6 +773,20 @@ void MenuItemView::Init(MenuItemView* parent, AddChildView(radio_check_image_view_); } + if (type_ == ACTIONABLE_SUBMENU) { + vertical_separator_ = new Separator(); + vertical_separator_->SetVisible(true); + vertical_separator_->SetFocusBehavior(FocusBehavior::NEVER); + const MenuConfig& config = MenuConfig::instance(); + vertical_separator_->SetColor(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_ActionableSubmenuVerticalSeparatorColor)); + vertical_separator_->SetPreferredSize( + gfx::Size(config.actionable_submenu_vertical_separator_width, + config.actionable_submenu_vertical_separator_height)); + vertical_separator_->set_can_process_events_within_subtree(false); + AddChildView(vertical_separator_); + } + if (submenu_arrow_image_view_) submenu_arrow_image_view_->SetVisible(HasSubmenu()); @@ -844,6 +902,15 @@ void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { ui::NativeTheme* native_theme = GetNativeTheme(); if (render_selection) { gfx::Rect item_bounds(0, 0, width(), height()); + if (type_ == ACTIONABLE_SUBMENU) { + if (submenu_area_of_actionable_submenu_selected_) { + item_bounds = GetSubmenuAreaOfActionableSubmenu(); + } else { + item_bounds = gfx::Rect(gfx::Size( + width() - MenuConfig::instance().actionable_submenu_width - 1, + height())); + } + } AdjustBoundsForRTLUI(&item_bounds); native_theme->Paint(canvas->sk_canvas(), @@ -962,6 +1029,10 @@ SkColor MenuItemView::GetTextColor(bool minor, if (!emphasized) color_id = ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor; } + + if (GetMenuController() && GetMenuController()->use_touchable_layout()) + color_id = ui::NativeTheme::kColorId_TouchableMenuItemLabelColor; + return GetNativeTheme()->GetSystemColor(color_id); } @@ -1012,6 +1083,8 @@ gfx::Size MenuItemView::GetChildPreferredSize() const { continue; if (submenu_arrow_image_view_ == child) continue; + if (vertical_separator_ == child) + continue; if (i) width += kChildXPadding; width += child->GetPreferredSize().width(); @@ -1034,30 +1107,18 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { const MenuConfig& menu_config = MenuConfig::instance(); if (GetMenuController() && GetMenuController()->use_touchable_layout()) { - // MenuItemViews that use the touchable layout have fixed height and width. + // Touchable layout uses a fixed size, but adjusts the height for icons. dimensions.height = menu_config.touchable_menu_height; + if (icon_view_) { + dimensions.height = icon_view_->height() + + 2 * menu_config.vertical_touchable_menu_item_padding; + } dimensions.standard_width = menu_config.touchable_menu_width; return dimensions; } const gfx::FontList& font_list = GetFontList(); base::string16 minor_text = GetMinorText(); - if (menu_config.fixed_text_item_height && - menu_config.fixed_container_item_height && menu_config.fixed_menu_width && - GetMenuController() && !GetMenuController()->is_combobox()) { - bool has_children = NonIconChildViewsCount() > 0; - dimensions.height = has_children ? menu_config.fixed_container_item_height - : menu_config.fixed_text_item_height; - dimensions.children_width = 0; - dimensions.minor_text_width = - minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, font_list); - int leave_for_minor = dimensions.minor_text_width - ? dimensions.minor_text_width + - menu_config.label_to_minor_text_padding - : 0; - dimensions.standard_width = menu_config.fixed_menu_width - leave_for_minor; - return dimensions; - } dimensions.height = child_size.height(); // Adjust item content height if menu has both items with and without icons. @@ -1069,8 +1130,10 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { dimensions.height += GetBottomMargin() + GetTopMargin(); // In case of a container, only the container size needs to be filled. - if (IsContainer()) + if (IsContainer()) { + ApplyMinimumDimensions(&dimensions); return dimensions; + } // Get Icon margin overrides for this particular item. const MenuDelegate* delegate = GetDelegate(); @@ -1105,11 +1168,37 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { font_list.GetHeight() + GetBottomMargin() + GetTopMargin()); dimensions.height = std::max(dimensions.height, MenuConfig::instance().item_min_height); + + ApplyMinimumDimensions(&dimensions); return dimensions; } +void MenuItemView::ApplyMinimumDimensions(MenuItemDimensions* dims) const { + // Don't apply minimums to menus without controllers or to comboboxes. + if (!GetMenuController() || GetMenuController()->is_combobox()) + return; + + int used = + dims->standard_width + dims->children_width + dims->minor_text_width; + const MenuConfig& config = MenuConfig::instance(); + if (used < config.minimum_menu_width) + dims->standard_width += (config.minimum_menu_width - used); + + dims->height = std::max(dims->height, + IsContainer() ? config.minimum_container_item_height + : config.minimum_text_item_height); +} + int MenuItemView::GetLabelStartForThisItem() const { const MenuConfig& config = MenuConfig::instance(); + + // Touchable items with icons do not respect |label_start_|. + if (GetMenuController() && GetMenuController()->use_touchable_layout() && + icon_view_) { + return config.touchable_item_left_margin + icon_view_->width() + + config.touchable_icon_to_label_padding; + } + int label_start = label_start_ + left_icon_margin_ + right_icon_margin_; if ((config.icons_in_label || type_ == CHECKBOX || type_ == RADIO) && icon_view_) @@ -1124,12 +1213,9 @@ base::string16 MenuItemView::GetMinorText() const { return base::string16(); } - ui::Accelerator accelerator; - if (MenuConfig::instance().show_accelerators && GetDelegate() && - GetCommand() && - GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) { - return accelerator.GetShortcutText(); - } + base::string16 accel_text; + if (MenuConfig::instance().ShouldShowAcceleratorText(this, &accel_text)) + return accel_text; return minor_text_; } @@ -1149,7 +1235,7 @@ int MenuItemView::NonIconChildViewsCount() const { // not the number of menu items. return child_count() - (icon_view_ ? 1 : 0) - (radio_check_image_view_ ? 1 : 0) - - (submenu_arrow_image_view_ ? 1 : 0); + (submenu_arrow_image_view_ ? 1 : 0) - (vertical_separator_ ? 1 : 0); } int MenuItemView::GetMaxIconViewWidth() const { diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h index be24fb73cf7..6276932ceaf 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.h +++ b/chromium/ui/views/controls/menu/menu_item_view.h @@ -17,7 +17,6 @@ #include "build/build_config.h" #include "ui/base/models/menu_separator_types.h" #include "ui/gfx/image/image_skia.h" -#include "ui/views/controls/menu/menu_config.h" #include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_types.h" #include "ui/views/view.h" @@ -45,6 +44,7 @@ class TestMenuItemViewShown; class MenuController; class MenuDelegate; +class Separator; class TestMenuItemView; class SubmenuView; @@ -83,15 +83,16 @@ class VIEWS_EXPORT MenuItemView : public View { // ID used to identify empty menu items. static const int kEmptyMenuItemViewID; - // Different types of menu items. EMPTY is a special type for empty - // menus that is only used internally. + // Different types of menu items. enum Type { - NORMAL, - SUBMENU, - CHECKBOX, - RADIO, - SEPARATOR, - EMPTY + NORMAL, // Performs an action when selected. + SUBMENU, // Presents a submenu within another menu. + ACTIONABLE_SUBMENU, // A SUBMENU that is also a COMMAND. + CHECKBOX, // Can be selected/checked to toggle a boolean state. + RADIO, // Can be selected/checked among a group of choices. + SEPARATOR, // Shows a horizontal line separator. + EMPTY, // EMPTY is a special type for empty menus that is only used + // internally. }; // Where the menu should be drawn, above or below the bounds (when @@ -257,6 +258,15 @@ class VIEWS_EXPORT MenuItemView : public View { // Returns true if the item is selected. bool IsSelected() const { return selected_; } + // Sets whether the submenu area of an ACTIONABLE_SUBMENU is selected. + void SetSelectionOfActionableSubmenu( + bool submenu_area_of_actionable_submenu_selected); + + // Whether the submenu area of an ACTIONABLE_SUBMENU is selected. + bool IsSubmenuAreaOfActionableSubmenuSelected() const { + return submenu_area_of_actionable_submenu_selected_; + } + // Sets the |tooltip| for a menu item view with |item_id| identifier. void SetTooltip(const base::string16& tooltip, int item_id); @@ -288,6 +298,9 @@ class VIEWS_EXPORT MenuItemView : public View { // dimensions. int GetHeightForWidth(int width) const override; + // Returns the bounds of the submenu part of the ACTIONABLE_SUBMENU. + gfx::Rect GetSubmenuAreaOfActionableSubmenu() const; + // Return the preferred dimensions of the item in pixel. const MenuItemDimensions& GetDimensions() const; @@ -430,6 +443,14 @@ class VIEWS_EXPORT MenuItemView : public View { // Calculates and returns the MenuItemDimensions. MenuItemDimensions CalculateDimensions() const; + // Imposes MenuConfig's minimum sizes, if any, on the supplied + // dimensions and returns the new dimensions. It is guaranteed that: + // ApplyMinimumDimensions(x).standard_width >= x.standard_width + // ApplyMinimumDimensions(x).children_width == x.children_width + // ApplyMinimumDimensions(x).minor_text_width == x.minor_text_width + // ApplyMinimumDimensions(x).height >= x.height + void ApplyMinimumDimensions(MenuItemDimensions* dims) const; + // Get the horizontal position at which to draw the menu item's label. int GetLabelStartForThisItem() const; @@ -484,6 +505,9 @@ class VIEWS_EXPORT MenuItemView : public View { // Whether we're selected. bool selected_; + // Whether the submenu area of an ACTIONABLE_SUBMENU is selected. + bool submenu_area_of_actionable_submenu_selected_; + // Command id. int command_; @@ -565,6 +589,10 @@ class VIEWS_EXPORT MenuItemView : public View { // The forced visual selection state of this item, if any. base::Optional<bool> forced_visual_selection_; + // The vertical separator that separates the actionable and submenu regions of + // an ACTIONABLE_SUBMENU. + Separator* vertical_separator_; + DISALLOW_COPY_AND_ASSIGN(MenuItemView); }; diff --git a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc index bdc22a6b784..07273f5de1a 100644 --- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc @@ -41,6 +41,8 @@ class TestMenuItemView : public MenuItemView { void AddEmptyMenus() { MenuItemView::AddEmptyMenus(); } + void SetHasMnemonics(bool has_mnemonics) { has_mnemonics_ = has_mnemonics; } + private: DISALLOW_COPY_AND_ASSIGN(TestMenuItemView); }; @@ -70,15 +72,14 @@ TEST(MenuItemViewUnitTest, TestMenuItemViewWithFlexibleWidthChild) { ASSERT_EQ(flexible_view, submenu->GetMenuItemAt(1)); gfx::Size flexible_size = flexible_view->GetPreferredSize(); - // The flexible view's "preferred size" should be 1x1... - EXPECT_EQ(flexible_size, gfx::Size(1, 1)); + EXPECT_EQ(1, flexible_size.width()); // ...but it should use whatever space is available to make a square. int flex_height = flexible_view->GetHeightForWidth(label_size.width()); EXPECT_EQ(label_size.width(), flex_height); - // The submenu should be tall enough to allow for both menu items at the given - // width. + // The submenu should be tall enough to allow for both menu items at the + // given width. EXPECT_EQ(label_size.height() + flex_height, submenu->GetPreferredSize().height()); } @@ -147,6 +148,24 @@ TEST(MenuItemViewUnitTest, TestEmptySubmenuWhenAllChildItemsAreHidden) { empty_item->title()); } +TEST(MenuItemViewUnitTest, UseMnemonicOnPlatform) { + TestMenuItemView root_menu; + views::MenuItemView* item1 = + root_menu.AppendMenuItemWithLabel(1, base::ASCIIToUTF16("&Item 1")); + views::MenuItemView* item2 = + root_menu.AppendMenuItemWithLabel(2, base::ASCIIToUTF16("I&tem 2")); + + root_menu.SetHasMnemonics(true); + + if (MenuConfig::instance().use_mnemonics) { + EXPECT_EQ('i', item1->GetMnemonic()); + EXPECT_EQ('t', item2->GetMnemonic()); + } else { + EXPECT_EQ(0, item1->GetMnemonic()); + EXPECT_EQ(0, item2->GetMnemonic()); + } +} + class MenuItemViewPaintUnitTest : public ViewsTestBase { public: MenuItemViewPaintUnitTest() {} diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc index e1a2339bddb..e9cfe1c466c 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter.cc +++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc @@ -80,6 +80,9 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model, case ui::MenuModel::TYPE_SUBMENU: type = MenuItemView::SUBMENU; break; + case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU: + type = MenuItemView::ACTIONABLE_SUBMENU; + break; } if (*type == MenuItemView::SEPARATOR) { @@ -267,9 +270,11 @@ void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) { for (int i = 0; i < item_count; ++i) { MenuItemView* item = AppendMenuItem(menu, model, i); - if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) { + if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU || + model->GetTypeAt(i) == ui::MenuModel::TYPE_ACTIONABLE_SUBMENU) { DCHECK(item); - DCHECK_EQ(MenuItemView::SUBMENU, item->GetType()); + DCHECK(item->GetType() == MenuItemView::SUBMENU || + item->GetType() == MenuItemView::ACTIONABLE_SUBMENU); ui::MenuModel* submodel = model->GetSubmenuModelAt(i); DCHECK(submodel); BuildMenuImpl(item, submodel); 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 c5df9547874..df3bb3ef251 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc @@ -16,8 +16,9 @@ namespace { // Base command id for test menu and its submenu. -const int kRootIdBase = 100; -const int kSubmenuIdBase = 200; +constexpr int kRootIdBase = 100; +constexpr int kSubmenuIdBase = 200; +constexpr int kActionableSubmenuIdBase = 300; class MenuModelBase : public ui::MenuModel { public: @@ -139,61 +140,68 @@ class SubmenuModel : public MenuModelBase { DISALLOW_COPY_AND_ASSIGN(SubmenuModel); }; +class ActionableSubmenuModel : public MenuModelBase { + public: + ActionableSubmenuModel() : MenuModelBase(kActionableSubmenuIdBase) { + items_.push_back(Item(TYPE_COMMAND, "actionable submenu item 0", NULL)); + items_.push_back(Item(TYPE_COMMAND, "actionable submenu item 1", NULL)); + } + ~ActionableSubmenuModel() override = default; + + private: + DISALLOW_COPY_AND_ASSIGN(ActionableSubmenuModel); +}; + class RootModel : public MenuModelBase { public: RootModel() : MenuModelBase(kRootIdBase) { - submenu_model_.reset(new SubmenuModel); + 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_CHECK, "check 1", NULL)); items_.push_back(Item(TYPE_SEPARATOR, "", NULL)); items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get())); items_.push_back(Item(TYPE_RADIO, "radio 4", NULL)); + items_.push_back(Item(TYPE_ACTIONABLE_SUBMENU, "actionable 5", + actionable_submenu_model_.get())); } ~RootModel() override {} private: std::unique_ptr<MenuModel> submenu_model_; + std::unique_ptr<MenuModel> actionable_submenu_model_; DISALLOW_COPY_AND_ASSIGN(RootModel); }; -} // namespace - -namespace views { - -typedef ViewsTestBase MenuModelAdapterTest; - -TEST_F(MenuModelAdapterTest, BasicTest) { - // Build model and adapter. - RootModel model; - views::MenuModelAdapter delegate(&model); - - // Create menu. Build menu twice to check that rebuilding works properly. - MenuItemView* menu = new views::MenuItemView(&delegate); - // MenuRunner takes ownership of menu. - std::unique_ptr<MenuRunner> menu_runner(new MenuRunner(menu, 0)); - delegate.BuildMenu(menu); - delegate.BuildMenu(menu); - EXPECT_TRUE(menu->HasSubmenu()); +void CheckSubmenu(const RootModel& model, + views::MenuItemView* menu, + views::MenuModelAdapter* delegate, + int submenu_id, + int expected_children, + int submenu_model_index, + int id_base) { + views::MenuItemView* submenu = menu->GetMenuItemByID(submenu_id); + views::SubmenuView* subitem_container = submenu->GetSubmenu(); + EXPECT_EQ(expected_children, subitem_container->child_count()); - // Check top level menu items. - views::SubmenuView* item_container = menu->GetSubmenu(); - EXPECT_EQ(5, item_container->child_count()); + for (int i = 0; i < subitem_container->child_count(); ++i) { + MenuModelBase* submodel = static_cast<MenuModelBase*>( + model.GetSubmenuModelAt(submenu_model_index)); + EXPECT_TRUE(submodel); - for (int i = 0; i < item_container->child_count(); ++i) { - const MenuModelBase::Item& model_item = model.GetItemDefinition(i); + const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i); - const int id = i + kRootIdBase; - MenuItemView* item = menu->GetMenuItemByID(id); + const int id = i + id_base; + views::MenuItemView* item = menu->GetMenuItemByID(id); if (!item) { EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type); continue; } - // Check placement. - EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item)); + EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item)); // Check type. switch (model_item.type) { @@ -212,27 +220,45 @@ TEST_F(MenuModelAdapterTest, BasicTest) { case ui::MenuModel::TYPE_SUBMENU: EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType()); break; + case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU: + EXPECT_EQ(views::MenuItemView::ACTIONABLE_SUBMENU, item->GetType()); + break; } // Check activation. - static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id); - EXPECT_EQ(i, model.last_activation()); - model.set_last_activation(-1); + static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id); + EXPECT_EQ(i, submodel->last_activation()); + submodel->set_last_activation(-1); } +} - // Check submenu items. - views::MenuItemView* submenu = menu->GetMenuItemByID(103); - views::SubmenuView* subitem_container = submenu->GetSubmenu(); - EXPECT_EQ(2, subitem_container->child_count()); +} // namespace - for (int i = 0; i < subitem_container->child_count(); ++i) { - MenuModelBase* submodel = static_cast<MenuModelBase*>( - model.GetSubmenuModelAt(3)); - EXPECT_TRUE(submodel); +namespace views { - const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i); +typedef ViewsTestBase MenuModelAdapterTest; + +TEST_F(MenuModelAdapterTest, BasicTest) { + // Build model and adapter. + RootModel model; + views::MenuModelAdapter delegate(&model); - const int id = i + kSubmenuIdBase; + // Create menu. Build menu twice to check that rebuilding works properly. + MenuItemView* menu = new views::MenuItemView(&delegate); + // MenuRunner takes ownership of menu. + std::unique_ptr<MenuRunner> menu_runner(new MenuRunner(menu, 0)); + delegate.BuildMenu(menu); + delegate.BuildMenu(menu); + EXPECT_TRUE(menu->HasSubmenu()); + + // Check top level menu items. + views::SubmenuView* item_container = menu->GetSubmenu(); + EXPECT_EQ(6, item_container->child_count()); + + for (int i = 0; i < item_container->child_count(); ++i) { + const MenuModelBase::Item& model_item = model.GetItemDefinition(i); + + const int id = i + kRootIdBase; MenuItemView* item = menu->GetMenuItemByID(id); if (!item) { EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type); @@ -240,7 +266,7 @@ TEST_F(MenuModelAdapterTest, BasicTest) { } // Check placement. - EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item)); + EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item)); // Check type. switch (model_item.type) { @@ -259,14 +285,27 @@ TEST_F(MenuModelAdapterTest, BasicTest) { case ui::MenuModel::TYPE_SUBMENU: EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType()); break; + case ui::MenuModel::TYPE_ACTIONABLE_SUBMENU: + EXPECT_EQ(views::MenuItemView::ACTIONABLE_SUBMENU, item->GetType()); + break; } // Check activation. static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id); - EXPECT_EQ(i, submodel->last_activation()); - submodel->set_last_activation(-1); + EXPECT_EQ(i, model.last_activation()); + model.set_last_activation(-1); } + // Check the submenu. + const int submenu_index = 3; + CheckSubmenu(model, menu, &delegate, kRootIdBase + submenu_index, 2, + submenu_index, kSubmenuIdBase); + + // Check the actionable submenu. + const int actionable_submenu_index = 5; + CheckSubmenu(model, menu, &delegate, kRootIdBase + actionable_submenu_index, + 2, actionable_submenu_index, kActionableSubmenuIdBase); + // Check that selecting the root item is safe. The MenuModel does // not care about the root so MenuModelAdapter should do nothing // (not hit the NOTREACHED check) when the root is selected. 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 8bd58e67d17..ae0c8bafd05 100644 --- a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm +++ b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm @@ -12,8 +12,10 @@ #include "base/test/test_timeouts.h" #include "base/threading/thread_task_runner_handle.h" #import "testing/gtest_mac.h" +#include "ui/base/accelerators/accelerator.h" #include "ui/base/models/simple_menu_model.h" #include "ui/events/event_utils.h" +#import "ui/events/test/cocoa_test_event_utils.h" #include "ui/views/controls/menu/menu_runner_impl_adapter.h" #include "ui/views/test/views_test_base.h" @@ -21,14 +23,16 @@ namespace views { namespace test { namespace { +constexpr int kTestCommandId = 0; + class TestModel : public ui::SimpleMenuModel { public: TestModel() : ui::SimpleMenuModel(&delegate_), delegate_(this) {} void set_checked_command(int command) { checked_command_ = command; } - void set_menu_open_callback(const base::Closure& callback) { - menu_open_callback_ = callback; + void set_menu_open_callback(base::OnceClosure callback) { + menu_open_callback_ = std::move(callback); } private: @@ -43,7 +47,17 @@ class TestModel : public ui::SimpleMenuModel { void MenuWillShow(SimpleMenuModel* source) override { if (!model_->menu_open_callback_.is_null()) - model_->menu_open_callback_.Run(); + std::move(model_->menu_open_callback_).Run(); + } + + bool GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) const override { + if (command_id == kTestCommandId) { + *accelerator = ui::Accelerator(ui::VKEY_E, ui::EF_CONTROL_DOWN); + return true; + } + return false; } private: @@ -55,7 +69,7 @@ class TestModel : public ui::SimpleMenuModel { private: int checked_command_ = -1; Delegate delegate_; - base::Closure menu_open_callback_; + base::OnceClosure menu_open_callback_; DISALLOW_COPY_AND_ASSIGN(TestModel); }; @@ -84,7 +98,7 @@ class MenuRunnerCocoaTest : public ViewsTestBase, ViewsTestBase::SetUp(); menu_.reset(new TestModel()); - menu_->AddCheckItem(0, base::ASCIIToUTF16("Menu Item")); + menu_->AddCheckItem(kTestCommandId, base::ASCIIToUTF16("Menu Item")); parent_ = new views::Widget(); parent_->Init(CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS)); @@ -114,16 +128,17 @@ class MenuRunnerCocoaTest : public ViewsTestBase, int IsAsync() const { return GetParam() == MenuType::VIEWS; } // Runs the menu after registering |callback| as the menu open callback. - void RunMenu(const base::Closure& callback) { + void RunMenu(base::OnceClosure callback) { if (IsAsync()) { // Cancelling an async menu under MenuControllerCocoa::OpenMenuImpl() // (which invokes WillShowMenu()) will cause a UAF when that same function // tries to show the menu. So post a task instead. - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + std::move(callback)); } else { menu_->set_menu_open_callback( - base::Bind(&MenuRunnerCocoaTest::RunMenuWrapperCallback, - base::Unretained(this), callback)); + base::BindOnce(&MenuRunnerCocoaTest::RunMenuWrapperCallback, + base::Unretained(this), std::move(callback))); } runner_->RunMenuAt(parent_, nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, @@ -140,13 +155,15 @@ class MenuRunnerCocoaTest : public ViewsTestBase, // go up by one (the anchor view) while the menu is shown. EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); - base::Closure callback = - base::Bind(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback, - base::Unretained(this)); - if (IsAsync()) - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); - else - menu_->set_menu_open_callback(callback); + base::OnceClosure callback = + base::BindOnce(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback, + base::Unretained(this)); + if (IsAsync()) { + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + std::move(callback)); + } else { + menu_->set_menu_open_callback(std::move(callback)); + } runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT, MenuRunner::COMBOBOX); @@ -178,6 +195,44 @@ class MenuRunnerCocoaTest : public ViewsTestBase, QuitAsyncRunLoop(); } + NSMenu* GetNativeNSMenu() { + if (GetParam() == MenuType::VIEWS) + return nil; + + internal::MenuRunnerImplCocoa* cocoa_runner = + static_cast<internal::MenuRunnerImplCocoa*>(runner_); + return [cocoa_runner->menu_controller_ menu]; + } + + void ModelDeleteThenSelectItemCallback() { + // AppKit may retain a reference to the NSMenu. + base::scoped_nsobject<NSMenu> native_menu(GetNativeNSMenu(), + base::scoped_policy::RETAIN); + + // A View showing a menu typically owns a MenuRunner unique_ptr, which will + // will be destroyed (releasing the MenuRunnerImpl) alongside the MenuModel. + runner_->Release(); + runner_ = nullptr; + menu_ = nullptr; + + // The menu is closing (yet "alive"), but the model is destroyed. The user + // may have already made an event to select an item in the menu. This + // doesn't bother views menus (see MenuRunnerImpl::empty_delegate_) but + // Cocoa menu items are refcounted and have access to a raw weak pointer in + // the MenuController. + if (GetParam() == MenuType::VIEWS) { + QuitAsyncRunLoop(); + return; + } + + EXPECT_TRUE(native_menu.get()); + + // Simulate clicking the item using its accelerator. + NSEvent* accelerator = cocoa_test_event_utils::KeyEventWithKeyCode( + 'e', 'e', NSKeyDown, NSCommandKeyMask); + [native_menu performKeyEquivalent:accelerator]; + } + void MenuCancelAndDeleteCallback() { runner_->Cancel(); runner_->Release(); @@ -192,9 +247,9 @@ class MenuRunnerCocoaTest : public ViewsTestBase, int menu_close_count_ = 0; private: - void RunMenuWrapperCallback(const base::Closure& callback) { + void RunMenuWrapperCallback(base::OnceClosure callback) { EXPECT_TRUE(runner_->IsRunning()); - callback.Run(); + std::move(callback).Run(); } void ComboboxRunMenuAtCallback() { @@ -248,8 +303,8 @@ class MenuRunnerCocoaTest : public ViewsTestBase, TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) { base::TimeTicks min_time = ui::EventTimeForNow(); - RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback, - base::Unretained(this))); + RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuCancelCallback, + base::Unretained(this))); EXPECT_EQ(1, menu_close_count_); EXPECT_FALSE(runner_->IsRunning()); @@ -271,17 +326,26 @@ TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) { } TEST_P(MenuRunnerCocoaTest, RunMenuAndDelete) { - RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuDeleteCallback, - base::Unretained(this))); + RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuDeleteCallback, + base::Unretained(this))); // Note the close callback is NOT invoked for deleted menus. EXPECT_EQ(0, menu_close_count_); } +// Tests a potential lifetime issue using the Cocoa MenuController, which has a +// weak reference to the model. +TEST_P(MenuRunnerCocoaTest, RunMenuAndDeleteThenSelectItem) { + RunMenu( + base::BindOnce(&MenuRunnerCocoaTest::ModelDeleteThenSelectItemCallback, + base::Unretained(this))); + EXPECT_EQ(0, menu_close_count_); +} + // Ensure a menu can be safely released immediately after a call to Cancel() in // the same run loop iteration. TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) { - RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback, - base::Unretained(this))); + RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback, + base::Unretained(this))); if (IsAsync()) { EXPECT_EQ(1, menu_close_count_); @@ -294,8 +358,8 @@ TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) { TEST_P(MenuRunnerCocoaTest, RunMenuTwice) { for (int i = 0; i < 2; ++i) { - RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback, - base::Unretained(this))); + RunMenu(base::BindOnce(&MenuRunnerCocoaTest::MenuCancelCallback, + base::Unretained(this))); EXPECT_FALSE(runner_->IsRunning()); EXPECT_EQ(i + 1, menu_close_count_); } @@ -339,7 +403,7 @@ TEST_P(MenuRunnerCocoaTest, ComboboxAnchoring) { combobox_rect.width(), 0), last_anchor_frame_); - menu_->set_checked_command(0); + menu_->set_checked_command(kTestCommandId); RunMenuAt(anchor_rect); // Native constant used by MenuRunnerImplCocoa. diff --git a/chromium/ui/views/controls/menu/menu_runner_impl.cc b/chromium/ui/views/controls/menu/menu_runner_impl.cc index cd9ac6df3ff..1c7b5c4c72a 100644 --- a/chromium/ui/views/controls/menu/menu_runner_impl.cc +++ b/chromium/ui/views/controls/menu/menu_runner_impl.cc @@ -92,9 +92,9 @@ void MenuRunnerImpl::RunMenuAt(Widget* parent, MenuController* controller = MenuController::GetActiveInstance(); if (controller) { if ((run_types & MenuRunner::IS_NESTED) != 0) { - if (!controller->IsBlockingRun()) { + if (controller->for_drop()) { controller->CancelAll(); - controller = NULL; + controller = nullptr; } else { // Only nest the delegate when not cancelling drag-and-drop. When // cancelling this will become the root delegate of the new @@ -112,7 +112,7 @@ void MenuRunnerImpl::RunMenuAt(Widget* parent, } // Drop menus don't block the message loop, so it's ok to create a new // MenuController. - controller = NULL; + controller = nullptr; } } @@ -122,7 +122,7 @@ void MenuRunnerImpl::RunMenuAt(Widget* parent, owns_controller_ = false; if (!controller) { // No menus are showing, show one. - controller = new MenuController(!for_drop_, this); + controller = new MenuController(for_drop_, this); owns_controller_ = true; } controller->set_is_combobox((run_types & MenuRunner::COMBOBOX) != 0); @@ -204,6 +204,8 @@ bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) { show_mnemonics |= ui::win::IsAltPressed(); #elif defined(USE_X11) show_mnemonics |= ui::IsAltPressed(); +#elif defined(OS_MACOSX) + show_mnemonics = false; #endif return show_mnemonics; } diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h index c89e9cee9c7..599000dd285 100644 --- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h +++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.h @@ -16,6 +16,9 @@ @class MenuControllerCocoa; namespace views { +namespace test { +class MenuRunnerCocoaTest; +} namespace internal { // A menu runner implementation that uses NSMenu to show a context menu. @@ -35,6 +38,8 @@ class VIEWS_EXPORT MenuRunnerImplCocoa : public MenuRunnerImplInterface { base::TimeTicks GetClosingEventTime() const override; private: + friend class views::test::MenuRunnerCocoaTest; + ~MenuRunnerImplCocoa() override; // The Cocoa menu controller that this instance is bridging. diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm index e68a76f83ed..8ccd3382da9 100644 --- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm +++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm @@ -148,7 +148,12 @@ void MenuRunnerImplCocoa::Release() { return; // We already canceled. delete_after_run_ = true; - [menu_controller_ cancel]; + + // Reset |menu_controller_| to ensure it clears itself as a delegate to + // prevent NSMenu attempting to access the weak pointer to the ui::MenuModel + // it holds (which is not owned by |this|). Toolkit-views menus use + // MenuRunnerImpl::empty_delegate_ to handle this case. + menu_controller_.reset(); } else { delete this; } diff --git a/chromium/ui/views/controls/menu/menu_runner_unittest.cc b/chromium/ui/views/controls/menu/menu_runner_unittest.cc index 79875e46f84..4c6c320cd93 100644 --- a/chromium/ui/views/controls/menu/menu_runner_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_runner_unittest.cc @@ -10,6 +10,7 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" #include "ui/base/ui_base_types.h" #include "ui/events/test/event_generator.h" #include "ui/views/controls/menu/menu_controller.h" @@ -179,8 +180,9 @@ TEST_F(MenuRunnerTest, LatinMnemonic) { EXPECT_NE(nullptr, delegate->on_menu_closed_menu()); } +#if !defined(OS_WIN) // Tests that a key press on a non-US keyboard layout activates the correct menu -// item. +// item. Disabled on Windows because a WM_CHAR event does not activate an item. TEST_F(MenuRunnerTest, NonLatinMnemonic) { // TODO: test uses GetContext(), which is not applicable to aura-mus. // http://crbug.com/663809. @@ -204,6 +206,7 @@ TEST_F(MenuRunnerTest, NonLatinMnemonic) { EXPECT_EQ(1, delegate->on_menu_closed_called()); EXPECT_NE(nullptr, delegate->on_menu_closed_menu()); } +#endif // !defined(OS_WIN) // 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 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 2e2e6b2aa26..abcc8e28e7e 100644 --- a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc +++ b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc @@ -250,10 +250,8 @@ void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas* canvas) { gfx::Rect bounds(0, 0, width(), height()); NativeTheme::ExtraParams extra; const MenuConfig& menu_config = MenuConfig::instance(); - extra.menu_background.corner_radius = - content_view_->GetMenuItem()->GetMenuController()->use_touchable_layout() - ? menu_config.touchable_corner_radius - : menu_config.corner_radius; + extra.menu_background.corner_radius = menu_config.CornerRadiusForMenu( + content_view_->GetMenuItem()->GetMenuController()); GetNativeTheme()->Paint(canvas->sk_canvas(), NativeTheme::kMenuPopupBackground, NativeTheme::kNormal, bounds, extra); } @@ -277,8 +275,14 @@ void MenuScrollViewContainer::CreateDefaultBorder() { bubble_border_ = nullptr; const MenuConfig& menu_config = MenuConfig::instance(); - - int padding = menu_config.use_outer_border && menu_config.corner_radius > 0 + const ui::NativeTheme* native_theme = GetNativeTheme(); + MenuController* controller = + content_view_->GetMenuItem()->GetMenuController(); + bool use_outer_border = + menu_config.use_outer_border || + (native_theme && native_theme->UsesHighContrastColors()); + int corner_radius = menu_config.CornerRadiusForMenu(controller); + int padding = use_outer_border && corner_radius > 0 ? kBorderPaddingDueToRoundedCorners : 0; @@ -286,14 +290,13 @@ void MenuScrollViewContainer::CreateDefaultBorder() { const int horizontal_inset = menu_config.menu_horizontal_border_size + padding; - if (menu_config.use_outer_border) { + if (use_outer_border) { SkColor color = GetNativeTheme() ? GetNativeTheme()->GetSystemColor( ui::NativeTheme::kColorId_MenuBorderColor) : gfx::kPlaceholderColor; SetBorder(views::CreateBorderPainter( - std::make_unique<views::RoundRectPainter>(color, - menu_config.corner_radius), + std::make_unique<views::RoundRectPainter>(color, corner_radius), gfx::Insets(vertical_inset, horizontal_inset))); } else { SetBorder(CreateEmptyBorder(vertical_inset, horizontal_inset, diff --git a/chromium/ui/views/controls/menu/menu_separator.cc b/chromium/ui/views/controls/menu/menu_separator.cc index 55bdcf82428..aa27d3c260b 100644 --- a/chromium/ui/views/controls/menu/menu_separator.cc +++ b/chromium/ui/views/controls/menu/menu_separator.cc @@ -23,6 +23,8 @@ void MenuSeparator::OnPaint(gfx::Canvas* canvas) { const MenuConfig& menu_config = MenuConfig::instance(); int pos = 0; int separator_thickness = menu_config.separator_thickness; + if (type_ == ui::DOUBLE_SEPARATOR) + separator_thickness = menu_config.double_separator_thickness; switch (type_) { case ui::LOWER_SEPARATOR: pos = height() - separator_thickness; @@ -30,12 +32,14 @@ void MenuSeparator::OnPaint(gfx::Canvas* canvas) { case ui::UPPER_SEPARATOR: break; default: - pos = height() / 2; + pos = (height() - separator_thickness) / 2; break; } gfx::Rect paint_rect(0, pos, width(), separator_thickness); - if (menu_config.use_outer_border) + if (type_ == ui::PADDED_SEPARATOR) + paint_rect.Inset(menu_config.padded_separator_left_margin, 0, 0, 0); + else if (menu_config.use_outer_border) paint_rect.Inset(1, 0); #if defined(OS_WIN) @@ -70,6 +74,12 @@ gfx::Size MenuSeparator::CalculatePreferredSize() const { case ui::UPPER_SEPARATOR: height = menu_config.separator_upper_height; break; + case ui::DOUBLE_SEPARATOR: + height = menu_config.double_separator_height; + break; + case ui::PADDED_SEPARATOR: + height = menu_config.separator_thickness; + break; default: height = menu_config.separator_height; break; diff --git a/chromium/ui/views/controls/menu/menu_types.h b/chromium/ui/views/controls/menu/menu_types.h index d95ce0b33c0..03a0c72a5db 100644 --- a/chromium/ui/views/controls/menu/menu_types.h +++ b/chromium/ui/views/controls/menu/menu_types.h @@ -9,8 +9,7 @@ namespace views { // Where a popup menu should be anchored to for non-RTL languages. The opposite // position will be used if base::i18n:IsRTL() is true. The BUBBLE flags are -// used when the menu should get enclosed by a bubble. Note that BUBBLE flags -// should only be used with menus which have no children. The Fixed flags are +// used when the menu should get enclosed by a bubble. The Fixed flags are // used for the menus that have a fixed anchor position. enum MenuAnchorPosition { MENU_ANCHOR_TOPLEFT, diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc index 9b244c9e9ea..b69a318e69f 100644 --- a/chromium/ui/views/controls/menu/submenu_view.cc +++ b/chromium/ui/views/controls/menu/submenu_view.cc @@ -182,8 +182,9 @@ gfx::Size SubmenuView::CalculatePreferredSize() const { minimum_preferred_width_ - 2 * insets.width())); if (GetMenuItem()->GetMenuController() && - GetMenuItem()->GetMenuController()->use_touchable_layout()) + GetMenuItem()->GetMenuController()->use_touchable_layout()) { width = std::max(touchable_minimum_width, width); + } // Then, the height for that width. int height = 0; diff --git a/chromium/ui/views/controls/message_box_view.cc b/chromium/ui/views/controls/message_box_view.cc index 27dc783cf4f..36db5787075 100644 --- a/chromium/ui/views/controls/message_box_view.cc +++ b/chromium/ui/views/controls/message_box_view.cc @@ -7,7 +7,6 @@ #include <stddef.h> #include "base/i18n/rtl.h" -#include "base/message_loop/message_loop.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_node_data.h" 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 28033ad99d5..097bcd4879b 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.cc +++ b/chromium/ui/views/controls/native/native_view_host_aura.cc @@ -150,7 +150,8 @@ void NativeViewHostAura::RemovedFromWidget() { bool NativeViewHostAura::SetCornerRadius(int corner_radius) { #if defined(OS_WIN) - // Layer masks don't work on Windows. See crbug.com/713359 + // TODO(crbug/843250): On Aura, layer masks don't play with HiDPI. Fix this + // and enable this on Windows. return false; #else mask_ = views::Painter::CreatePaintedLayer( diff --git a/chromium/ui/views/controls/native/native_view_host_mac.mm b/chromium/ui/views/controls/native/native_view_host_mac.mm index e6f31f85f3d..0ca2c4a6a41 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac.mm +++ b/chromium/ui/views/controls/native/native_view_host_mac.mm @@ -12,6 +12,13 @@ #include "ui/views/widget/native_widget_mac.h" #include "ui/views/widget/widget.h" +// NSViews that can be drawn as a ui::Layer directly will implement this +// interface. Calling cr_setParentLayer will embed the ui::Layer of the NSView +// under |parentUiLayer|. +@interface NSView (UICompositor) +- (void)cr_setParentUiLayer:(ui::Layer*)parentUiLayer; +@end + namespace views { namespace { @@ -32,6 +39,8 @@ void EnsureNativeViewHasNoChildWidgets(NSView* native_view) { } // namespace NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) { + // Ensure that |host_| have its own ui::Layer and that it draw nothing. + host_->SetPaintToLayer(ui::LAYER_NOT_DRAWN); } NativeViewHostMac::~NativeViewHostMac() { @@ -45,6 +54,9 @@ void NativeViewHostMac::AttachNativeView() { DCHECK(!native_view_); native_view_.reset([host_->native_view() retain]); + if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)]) + [native_view_ cr_setParentUiLayer:host_->layer()]; + EnsureNativeViewHasNoChildWidgets(native_view_); BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( host_->GetWidget()->GetNativeWindow()); @@ -71,6 +83,9 @@ void NativeViewHostMac::NativeViewDetaching(bool destroyed) { [host_->native_view() setHidden:YES]; [host_->native_view() removeFromSuperview]; + if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)]) + [native_view_ cr_setParentUiLayer:nullptr]; + EnsureNativeViewHasNoChildWidgets(host_->native_view()); BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( host_->GetWidget()->GetNativeWindow()); diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc index b1195d51c67..6590a8e0b05 100644 --- a/chromium/ui/views/controls/prefix_selector.cc +++ b/chromium/ui/views/controls/prefix_selector.cc @@ -89,6 +89,12 @@ bool PrefixSelector::HasCompositionText() const { return false; } +ui::TextInputClient::FocusReason PrefixSelector::GetFocusReason() const { + // TODO(https://crbug.com/824604): Implement this correctly. + NOTIMPLEMENTED_LOG_ONCE(); + return ui::TextInputClient::FOCUS_REASON_OTHER; +} + bool PrefixSelector::GetTextRange(gfx::Range* range) const { *range = gfx::Range(); return false; @@ -145,6 +151,12 @@ const std::string& PrefixSelector::GetClientSourceInfo() const { return base::EmptyString(); } +bool PrefixSelector::ShouldDoLearning() { + // TODO(https://crbug.com/311180): Implement this method. + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + void PrefixSelector::OnTextInput(const base::string16& text) { // Small hack to filter out 'tab' and 'enter' input, as the expectation is // that they are control characters and will not affect the currently-active diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h index 1e70d1ad3ed..e3654eebbb3 100644 --- a/chromium/ui/views/controls/prefix_selector.h +++ b/chromium/ui/views/controls/prefix_selector.h @@ -44,6 +44,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { bool GetCompositionCharacterBounds(uint32_t index, gfx::Rect* rect) const override; bool HasCompositionText() const override; + FocusReason GetFocusReason() const override; bool GetTextRange(gfx::Range* range) const override; bool GetCompositionTextRange(gfx::Range* range) const override; bool GetSelectionRange(gfx::Range* range) const override; @@ -60,6 +61,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { bool IsTextEditCommandEnabled(ui::TextEditCommand command) const override; void SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) override; const std::string& GetClientSourceInfo() const override; + bool ShouldDoLearning() override; private: // Invoked when text is typed. Tries to change the selection appropriately. diff --git a/chromium/ui/views/controls/scroll_view.cc b/chromium/ui/views/controls/scroll_view.cc index 06a8b1625c7..e58ef7d3cdc 100644 --- a/chromium/ui/views/controls/scroll_view.cc +++ b/chromium/ui/views/controls/scroll_view.cc @@ -8,6 +8,8 @@ #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" #include "ui/gfx/canvas.h" #include "ui/native_theme/native_theme.h" @@ -24,15 +26,6 @@ const char ScrollView::kViewClassName[] = "ScrollView"; namespace { -const base::Feature kToolkitViewsScrollWithLayers { - "ToolkitViewsScrollWithLayers", -#if defined(OS_MACOSX) - base::FEATURE_ENABLED_BY_DEFAULT -#else - base::FEATURE_DISABLED_BY_DEFAULT -#endif -}; - class ScrollCornerView : public View { public: ScrollCornerView() {} @@ -187,8 +180,8 @@ ScrollView::ScrollView() min_height_(-1), max_height_(-1), hide_horizontal_scrollbar_(false), - scroll_with_layers_enabled_( - base::FeatureList::IsEnabled(kToolkitViewsScrollWithLayers)) { + scroll_with_layers_enabled_(base::FeatureList::IsEnabled( + features::kUiCompositorScrollWithLayers)) { set_notify_enter_exit_on_child(true); AddChildView(contents_viewport_); @@ -221,6 +214,14 @@ ScrollView::ScrollView() more_content_bottom_->SetPaintToLayer(); } 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_; + }); + } } ScrollView::~ScrollView() { @@ -334,17 +335,10 @@ void ScrollView::SetHasFocusIndicator(bool has_focus_indicator) { return; draw_focus_indicator_ = has_focus_indicator; - if (ui::MaterialDesignController::IsSecondaryUiMaterial()) { - DCHECK_EQ(draw_focus_indicator_, !focus_ring_); - if (has_focus_indicator) { - focus_ring_ = FocusRing::Install(this); - } else { - FocusRing::Uninstall(this); - focus_ring_ = nullptr; - } - } else { + if (ui::MaterialDesignController::IsSecondaryUiMaterial()) + focus_ring_->SchedulePaint(); + else UpdateBorder(); - } SchedulePaint(); } @@ -519,6 +513,30 @@ void ScrollView::Layout() { container_size.SetToMax(viewport_bounds.size()); contents_->SetBoundsRect(gfx::Rect(container_size)); contents_->layer()->SetScrollable(viewport_bounds.size()); + + // Flip the viewport with layer transforms under RTL. Note the net effect is + // to flip twice, so the text is not mirrored. This is necessary because + // compositor scrolling is not RTL-aware. So although a toolkit-views layout + // will flip, increasing a horizontal gfx::ScrollOffset will move content to + // the left, regardless of RTL. A gfx::ScrollOffset must be positive, so to + // move (unscrolled) content to the right, we need to flip the viewport + // layer. That would flip all the content as well, so flip (and translate) + // the content layer. Compensating in this way allows the scrolling/offset + // logic to remain the same when scrolling via layers or bounds offsets. + if (base::i18n::IsRTL()) { + gfx::Transform flip; + flip.Translate(viewport_bounds.width(), 0); + flip.Scale(-1, 1); + contents_viewport_->layer()->SetTransform(flip); + + // Add `contents_->width() - viewport_width` to the translation step. This + // is to prevent the top-left of the (flipped) contents aligning to the + // top-left of the viewport. Instead, the top-right should align in RTL. + gfx::Transform shift; + shift.Translate(2 * contents_->width() - viewport_bounds.width(), 0); + shift.Scale(-1, 1); + contents_->layer()->SetTransform(shift); + } } header_viewport_->SetBounds(contents_x, contents_y, @@ -552,6 +570,7 @@ bool ScrollView::OnKeyPressed(const ui::KeyEvent& event) { bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) { bool processed = false; + // TODO(https://crbug.com/615948): Use composited scrolling. if (vert_sb_->visible()) processed = vert_sb_->OnMouseWheel(e); @@ -562,13 +581,18 @@ bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) { } void ScrollView::OnScrollEvent(ui::ScrollEvent* event) { -#if defined(OS_MACOSX) if (!contents_) return; - // TODO(tapted): Send |event| to a cc::InputHandler. For now, there's nothing - // to do because Widget::OnScrollEvent() will automatically process an - // unhandled ScrollEvent as a MouseWheelEvent. + ui::ScrollInputHandler* compositor_scroller = + GetWidget()->GetCompositor()->scroll_input_handler(); + if (compositor_scroller) { + DCHECK(scroll_with_layers_enabled_); + if (compositor_scroller->OnScrollEvent(*event, contents_->layer())) { + event->SetHandled(); + event->StopPropagation(); + } + } // A direction might not be known when the event stream starts, notify both // scrollbars that they may be about scroll, or that they may need to cancel @@ -577,7 +601,6 @@ void ScrollView::OnScrollEvent(ui::ScrollEvent* event) { horiz_sb_->ObserveScrollEvent(*event); if (vert_sb_) vert_sb_->ObserveScrollEvent(*event); -#endif } void ScrollView::OnGestureEvent(ui::GestureEvent* event) { @@ -589,6 +612,7 @@ void ScrollView::OnGestureEvent(ui::GestureEvent* event) { event->type() == ui::ET_GESTURE_SCROLL_END || event->type() == ui::ET_SCROLL_FLING_START; + // TODO(https://crbug.com/615948): Use composited scrolling. if (vert_sb_->visible()) { if (vert_sb_->bounds().Contains(event->location()) || scroll_event) vert_sb_->OnGestureEvent(event); diff --git a/chromium/ui/views/controls/scroll_view.h b/chromium/ui/views/controls/scroll_view.h index ff7f71b19c3..e0e279ce914 100644 --- a/chromium/ui/views/controls/scroll_view.h +++ b/chromium/ui/views/controls/scroll_view.h @@ -11,6 +11,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" #include "ui/native_theme/native_theme.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/controls/scrollbar/scroll_bar.h" namespace gfx { @@ -246,12 +247,12 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController { // it overflows. bool draw_overflow_indicator_ = true; - // Focus ring, if one is installed. - View* focus_ring_ = nullptr; - // Set to true if the scroll with layers feature is enabled. const bool scroll_with_layers_enabled_; + // The focus ring for this ScrollView. + std::unique_ptr<FocusRing> focus_ring_; + DISALLOW_COPY_AND_ASSIGN(ScrollView); }; diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc index 6b818c25420..d2596d71109 100644 --- a/chromium/ui/views/controls/scroll_view_unittest.cc +++ b/chromium/ui/views/controls/scroll_view_unittest.cc @@ -6,9 +6,13 @@ #include "base/macros.h" #include "base/run_loop.h" +#include "base/test/icu_test_util.h" +#include "base/test/scoped_feature_list.h" #include "base/test/test_timeouts.h" #include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/ui_base_features.h" #include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/events/test/event_generator.h" #include "ui/views/border.h" @@ -229,8 +233,8 @@ class ScrollViewTest : public ViewsTestBase { class WidgetScrollViewTest : public test::WidgetTest, public ui::CompositorObserver { public: - static const int kDefaultHeight = 100; - static const int kDefaultWidth = 100; + static constexpr int kDefaultHeight = 100; + static constexpr int kDefaultWidth = 100; WidgetScrollViewTest() {} @@ -303,6 +307,9 @@ class WidgetScrollViewTest : public test::WidgetTest, WidgetTest::TearDown(); } + protected: + Widget* widget() const { return widget_; } + private: // ui::CompositorObserver: void OnCompositingDidCommit(ui::Compositor* compositor) override { @@ -330,8 +337,74 @@ class WidgetScrollViewTest : public test::WidgetTest, DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTest); }; -const int WidgetScrollViewTest::kDefaultHeight; -const int WidgetScrollViewTest::kDefaultWidth; +constexpr int WidgetScrollViewTest::kDefaultHeight; +constexpr int WidgetScrollViewTest::kDefaultWidth; + +// A gtest parameter to permute over whether ScrollView uses a left-to-right or +// right-to-left locale, or whether it uses ui::Layers or View bounds offsets to +// position contents (i.e. features::kUiCompositorScrollWithLayers). +enum class UiConfig { kLtr, kLtrWithLayers, kRtl, kRtlWithLayers }; + +class WidgetScrollViewTestRTLAndLayers + : public WidgetScrollViewTest, + public ::testing::WithParamInterface<UiConfig> { + public: + WidgetScrollViewTestRTLAndLayers() : locale_(IsTestingRtl() ? "he" : "en") { + if (IsTestingLayers()) { + layer_config_.InitAndEnableFeature( + features::kUiCompositorScrollWithLayers); + } else { + layer_config_.InitAndDisableFeature( + features::kUiCompositorScrollWithLayers); + } + } + + bool IsTestingRtl() const { + return GetParam() == UiConfig::kRtl || + GetParam() == UiConfig::kRtlWithLayers; + } + + bool IsTestingLayers() const { + return GetParam() == UiConfig::kLtrWithLayers || + GetParam() == UiConfig::kRtlWithLayers; + } + + // Returns a point in the coordinate space of |target| by translating a point + // inset one pixel from the top of the Widget and one pixel on the leading + // side of the Widget. There should be no scroll bar on this side. If + // |flip_result| is true, automatically account for flipped coordinates in + // RTL. + gfx::Point HitTestInCorner(View* target, bool flip_result = true) const { + const gfx::Point test_mouse_point_in_root = + IsTestingRtl() ? gfx::Point(kDefaultWidth - 1, 1) : gfx::Point(1, 1); + gfx::Point point = test_mouse_point_in_root; + View::ConvertPointToTarget(widget()->GetRootView(), target, &point); + if (flip_result) + return gfx::Point(target->GetMirroredXInView(point.x()), point.y()); + return point; + } + + private: + base::test::ScopedRestoreICUDefaultLocale locale_; + base::test::ScopedFeatureList layer_config_; + + DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTestRTLAndLayers); +}; + +std::string UiConfigToString(const testing::TestParamInfo<UiConfig>& info) { + switch (info.param) { + case UiConfig::kLtr: + return "LTR"; + case UiConfig::kLtrWithLayers: + return "LTR_LAYERS"; + case UiConfig::kRtl: + return "RTL"; + case UiConfig::kRtlWithLayers: + return "RTL_LAYERS"; + } + NOTREACHED(); + return std::string(); +} // Verifies the viewport is sized to fit the available space. TEST_F(ScrollViewTest, ViewportSizedToFit) { @@ -1436,26 +1509,136 @@ TEST_F(WidgetScrollViewTest, EventLocation) { contents->last_location()); } +// Ensure behavior of ScrollRectToVisible() is consistent when scrolling with +// and without layers, and under LTR and RTL. +TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetWithoutLayers) { + // Set up with both scrollers. And a nested view hierarchy like: + // +-------------+ + // |XX | + // | +----------| + // | | | + // | | +-------| + // | | | | + // | | | etc. | + // | | | | + // +-------------+ + // Note that "XX" indicates the size of the viewport. + constexpr int kNesting = 5; + constexpr int kCellWidth = kDefaultWidth; + constexpr int kCellHeight = kDefaultHeight; + constexpr gfx::Size kContentSize(kCellWidth * kNesting, + kCellHeight * kNesting); + ScrollView* scroll_view = AddScrollViewWithContentSize(kContentSize, false); + ScrollViewTestApi test_api(scroll_view); + EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset()); + + // Sanity check that the contents has a layer iff testing layers. + EXPECT_EQ(IsTestingLayers(), !!scroll_view->contents()->layer()); + + if (IsTestingRtl()) { + // Sanity check the hit-testing logic on the root view. That is, verify that + // coordinates really do flip in RTL. The difference inside the viewport is + // that the flipping should occur consistently in the entire contents (not + // just the visible contents), and take into account the scroll offset. + EXPECT_EQ(gfx::Point(kDefaultWidth - 1, 1), + HitTestInCorner(scroll_view->GetWidget()->GetRootView(), false)); + EXPECT_EQ(gfx::Point(kContentSize.width() - 1, 1), + HitTestInCorner(scroll_view->contents(), false)); + } else { + EXPECT_EQ(gfx::Point(1, 1), + HitTestInCorner(scroll_view->GetWidget()->GetRootView(), false)); + EXPECT_EQ(gfx::Point(1, 1), + HitTestInCorner(scroll_view->contents(), false)); + } + + // Test vertical scrolling using coordinates on the contents canvas. + gfx::Rect offset(0, kCellHeight * 2, kCellWidth, kCellHeight); + scroll_view->contents()->ScrollRectToVisible(offset); + EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset()); + + // Rely on auto-flipping for this and future HitTestInCorner() calls. + EXPECT_EQ(gfx::Point(1, kCellHeight * 2 + 1), + HitTestInCorner(scroll_view->contents())); + + // Test horizontal scrolling. + offset.set_x(kCellWidth * 2); + scroll_view->contents()->ScrollRectToVisible(offset); + EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), + test_api.CurrentOffset()); + EXPECT_EQ(gfx::Point(kCellWidth * 2 + 1, kCellHeight * 2 + 1), + HitTestInCorner(scroll_view->contents())); + + // Reset the scrolling. + scroll_view->contents()->ScrollRectToVisible(gfx::Rect(0, 0, 1, 1)); + + // Test transformations through a nested view hierarchy. + View* deepest_view = scroll_view->contents(); + constexpr gfx::Rect kCellRect(kCellWidth, kCellHeight, kContentSize.width(), + kContentSize.height()); + for (int i = 1; i < kNesting; ++i) { + SCOPED_TRACE(testing::Message("Nesting = ") << i); + View* child = new View; + child->SetBoundsRect(kCellRect); + deepest_view->AddChildView(child); + deepest_view = child; + + // Add a view in one quadrant. Scrolling just this view should only scroll + // far enough for it to become visible. That is, it should be positioned at + // the bottom right of the viewport, not the top-left. But since there are + // scroll bars, the scroll offset needs to go "a bit more". + View* partial_view = new View; + partial_view->SetSize(gfx::Size(kCellWidth / 3, kCellHeight / 3)); + deepest_view->AddChildView(partial_view); + partial_view->ScrollViewToVisible(); + int x_offset_in_cell = kCellWidth - partial_view->width(); + if (!scroll_view->horizontal_scroll_bar()->OverlapsContent()) + x_offset_in_cell -= scroll_view->horizontal_scroll_bar()->GetThickness(); + int y_offset_in_cell = kCellHeight - partial_view->height(); + if (!scroll_view->vertical_scroll_bar()->OverlapsContent()) + y_offset_in_cell -= scroll_view->vertical_scroll_bar()->GetThickness(); + EXPECT_EQ(gfx::ScrollOffset(kCellWidth * i - x_offset_in_cell, + kCellHeight * i - y_offset_in_cell), + test_api.CurrentOffset()); + + // Now scroll the rest. + deepest_view->ScrollViewToVisible(); + EXPECT_EQ(gfx::ScrollOffset(kCellWidth * i, kCellHeight * i), + test_api.CurrentOffset()); + + // The partial view should now be at the top-left of the viewport (top-right + // in RTL). + EXPECT_EQ(gfx::Point(1, 1), HitTestInCorner(partial_view)); + + gfx::Point origin; + View::ConvertPointToWidget(partial_view, &origin); + constexpr gfx::Point kTestPointRTL(kDefaultWidth - kCellWidth / 3, 0); + EXPECT_EQ(IsTestingRtl() ? kTestPointRTL : gfx::Point(), origin); + } + + // Scrolling to the deepest view should have moved the viewport so that the + // (kNesting - 1) parent views are all off-screen. + EXPECT_EQ(gfx::ScrollOffset(kCellWidth * (kNesting - 1), + kCellHeight * (kNesting - 1)), + test_api.CurrentOffset()); +} + // Test that views scroll offsets are in sync with the layer scroll offsets. -TEST_F(WidgetScrollViewTest, ScrollOffsetUsingLayers) { - // Set up with a vertical scroller, but don't commit the layer changes yet. - ScrollView* scroll_view = - AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5), false); +TEST_P(WidgetScrollViewTestRTLAndLayers, ScrollOffsetUsingLayers) { + // Set up with both scrollers, but don't commit the layer changes yet. + ScrollView* scroll_view = AddScrollViewWithContentSize( + gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5), false); ScrollViewTestApi test_api(scroll_view); EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset()); // UI code may request a scroll before layer changes are committed. - gfx::Rect offset(0, kDefaultHeight * 2, 1, kDefaultHeight); + gfx::Rect offset(0, kDefaultHeight * 2, kDefaultWidth, kDefaultHeight); scroll_view->contents()->ScrollRectToVisible(offset); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset()); // The following only makes sense when layered scrolling is enabled. View* container = scroll_view->contents(); -#if defined(OS_MACOSX) - // Sanity check: Mac should always scroll with layers. - EXPECT_TRUE(container->layer()); -#endif + EXPECT_EQ(IsTestingLayers(), !!container->layer()); if (!container->layer()) return; @@ -1493,6 +1676,60 @@ TEST_F(WidgetScrollViewTest, ScrollOffsetUsingLayers) { EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset)); EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset); + + // Test horizontal scrolling. + offset.set_x(kDefaultWidth * 2); + scroll_view->contents()->ScrollRectToVisible(offset); + EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), + test_api.CurrentOffset()); + + EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset)); + EXPECT_EQ(gfx::ScrollOffset(offset.x(), offset.y()), impl_offset); } +// Tests to see the scroll events are handled correctly in composited and +// non-composited scrolling. +TEST_F(WidgetScrollViewTest, CompositedScrollEvents) { + // Set up with a vertical scroll bar. + ScrollView* scroll_view = + AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5)); + ScrollViewTestApi test_api(scroll_view); + + // Create a fake scroll event and send it to the scroll view. + ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(), base::TimeTicks::Now(), 0, + 0, -10, 0, -10, 3); + EXPECT_FALSE(scroll.handled()); + EXPECT_FALSE(scroll.stopped_propagation()); + scroll_view->OnScrollEvent(&scroll); + + // Check to see if the scroll event is handled by the scroll view. + if (base::FeatureList::IsEnabled(features::kUiCompositorScrollWithLayers)) { + // If UiCompositorScrollWithLayers is enabled, the event is set handled + // and its propagation is stopped. + EXPECT_TRUE(scroll.handled()); + EXPECT_TRUE(scroll.stopped_propagation()); + } else { + // If UiCompositorScrollWithLayers is disabled, the event isn't handled. + // This informs Widget::OnScrollEvent() to convert to a MouseWheel event + // and dispatch again. Simulate that. + EXPECT_FALSE(scroll.handled()); + EXPECT_FALSE(scroll.stopped_propagation()); + EXPECT_EQ(gfx::ScrollOffset(), test_api.CurrentOffset()); + + ui::MouseWheelEvent wheel(scroll); + scroll_view->OnMouseEvent(&wheel); + } + + // Check if the scroll view has been offset. + EXPECT_EQ(gfx::ScrollOffset(0, 10), test_api.CurrentOffset()); +} + +INSTANTIATE_TEST_CASE_P(, + WidgetScrollViewTestRTLAndLayers, + ::testing::Values(UiConfig::kLtr, + UiConfig::kRtl, + UiConfig::kLtrWithLayers, + UiConfig::kRtlWithLayers), + &UiConfigToString); + } // namespace views diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc b/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc index 9add6bb5776..bf652746430 100644 --- a/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc +++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc @@ -8,7 +8,6 @@ #include "base/bind_helpers.h" #include "base/callback.h" #include "base/compiler_specific.h" -#include "base/message_loop/message_loop.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" diff --git a/chromium/ui/views/controls/slider.cc b/chromium/ui/views/controls/slider.cc index 015c695e102..cf767aee4c2 100644 --- a/chromium/ui/views/controls/slider.cc +++ b/chromium/ui/views/controls/slider.cc @@ -7,7 +7,7 @@ #include <algorithm> #include "base/logging.h" -#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_current.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "cc/paint/paint_flags.h" @@ -138,7 +138,7 @@ void Slider::SetValueInternal(float value, SliderChangeReason reason) { if (listener_) listener_->SliderValueChanged(this, value_, old_value, reason); - if (old_value_valid && base::MessageLoop::current()) { + if (old_value_valid && base::MessageLoopCurrent::Get()) { // Do not animate when setting the value of the slider for the first time. // There is no message-loop when running tests. So we cannot animate then. if (!move_animation_) { diff --git a/chromium/ui/views/controls/styled_label.cc b/chromium/ui/views/controls/styled_label.cc index 5e7982dcdb9..1f2d6fbcd4e 100644 --- a/chromium/ui/views/controls/styled_label.cc +++ b/chromium/ui/views/controls/styled_label.cc @@ -123,9 +123,7 @@ StyledLabel::StyledLabel(const base::string16& text, width_at_last_layout_(0), displayed_on_background_color_(SkColorSetRGB(0xFF, 0xFF, 0xFF)), displayed_on_background_color_set_(false), - auto_color_readability_enabled_(true), - horizontal_alignment_(base::i18n::IsRTL() ? gfx::ALIGN_RIGHT - : gfx::ALIGN_LEFT) { + auto_color_readability_enabled_(true) { base::TrimWhitespace(text, base::TRIM_TRAILING, &text_); } @@ -271,7 +269,7 @@ void StyledLabel::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { if (horizontal_alignment_ == alignment) return; horizontal_alignment_ = alignment; - SchedulePaint(); + PreferredSizeChanged(); } void StyledLabel::ClearStyleRanges() { diff --git a/chromium/ui/views/controls/styled_label.h b/chromium/ui/views/controls/styled_label.h index 3268ad50e2c..ae5bac5511a 100644 --- a/chromium/ui/views/controls/styled_label.h +++ b/chromium/ui/views/controls/styled_label.h @@ -219,7 +219,7 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { // The horizontal alignment. This value is flipped for RTL. The default // behavior is to align left in LTR UI and right in RTL UI. - gfx::HorizontalAlignment horizontal_alignment_; + gfx::HorizontalAlignment horizontal_alignment_ = gfx::ALIGN_LEFT; DISALLOW_COPY_AND_ASSIGN(StyledLabel); }; diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc index 7aa780d2c8f..4f18b6880c4 100644 --- a/chromium/ui/views/controls/styled_label_unittest.cc +++ b/chromium/ui/views/controls/styled_label_unittest.cc @@ -9,8 +9,10 @@ #include <memory> #include <string> +#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" @@ -707,48 +709,64 @@ TEST_F(StyledLabelTest, LineWrapperWithCustomView) { styled()->GetHeightForWidth(100)); } -TEST_F(StyledLabelTest, LeftAlignment) { - const std::string multiline_text("one\ntwo\nthree"); - InitStyledLabel(multiline_text); - styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); +TEST_F(StyledLabelTest, AlignmentInLTR) { + const std::string text("text"); + InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); styled()->Layout(); - ASSERT_EQ(3, styled()->child_count()); + ASSERT_EQ(1, styled()->child_count()); + + // Test the default alignment puts the text on the leading side (left). EXPECT_EQ(0, styled()->child_at(0)->bounds().x()); - EXPECT_EQ(0, styled()->child_at(1)->bounds().x()); - EXPECT_EQ(0, styled()->child_at(2)->bounds().x()); -} -TEST_F(StyledLabelTest, RightAlignment) { - const std::string multiline_text("one\ntwo\nthree"); - InitStyledLabel(multiline_text); styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); - styled()->SetBounds(0, 0, 1000, 1000); styled()->Layout(); - ASSERT_EQ(3, styled()->child_count()); EXPECT_EQ(1000, styled()->child_at(0)->bounds().right()); - EXPECT_EQ(1000, styled()->child_at(1)->bounds().right()); - EXPECT_EQ(1000, styled()->child_at(2)->bounds().right()); -} -TEST_F(StyledLabelTest, CenterAlignment) { - const std::string multiline_text("one\ntwo\nthree"); - InitStyledLabel(multiline_text); + styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); + styled()->Layout(); + EXPECT_EQ(0, styled()->child_at(0)->bounds().x()); + styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER); + styled()->Layout(); + Label label(ASCIIToUTF16(text)); + EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2, + styled()->child_at(0)->bounds().x()); +} + +TEST_F(StyledLabelTest, AlignmentInRTL) { + // |g_icu_text_direction| is cached to prevent reading new commandline switch. + // Set |g_icu_text_direction| to |UNKNOWN_DIRECTION| in order to read the new + // commandline switch. + base::test::ScopedRestoreICUDefaultLocale scoped_locale("en_US"); + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kForceUIDirection, switches::kForceDirectionRTL); + + const std::string text("text"); + InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); styled()->Layout(); + ASSERT_EQ(1, styled()->child_count()); - Label label_one(ASCIIToUTF16("one")); - Label label_two(ASCIIToUTF16("two")); - Label label_three(ASCIIToUTF16("three")); + // Test the default alignment puts the text on the leading side (right). + // Note that x-coordinates in RTL place the origin (0) on the right. + EXPECT_EQ(0, styled()->child_at(0)->bounds().x()); - ASSERT_EQ(3, styled()->child_count()); - EXPECT_EQ((1000 - label_one.GetPreferredSize().width()) / 2, + // Setting |ALIGN_LEFT| should be flipped to |ALIGN_RIGHT|. + styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); + styled()->Layout(); + EXPECT_EQ(1000, styled()->child_at(0)->bounds().right()); + + // Setting |ALIGN_RIGHT| should be flipped to |ALIGN_LEFT|. + styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); + styled()->Layout(); + EXPECT_EQ(0, styled()->child_at(0)->bounds().x()); + + styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER); + styled()->Layout(); + Label label(ASCIIToUTF16(text)); + EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2, styled()->child_at(0)->bounds().x()); - EXPECT_EQ((1000 - label_two.GetPreferredSize().width()) / 2, - styled()->child_at(1)->bounds().x()); - EXPECT_EQ((1000 - label_three.GetPreferredSize().width()) / 2, - styled()->child_at(2)->bounds().x()); } TEST_P(MDStyledLabelTest, ViewsCenteredWithLinkAndCustomView) { diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc index bc33e72aca4..17f9259f012 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc @@ -36,17 +36,17 @@ namespace { // TODO(markusheintz|msw): Use NativeTheme colors. constexpr SkColor kTabTitleColor_InactiveBorder = - SkColorSetARGBMacro(0xFF, 0x64, 0x64, 0x64); + SkColorSetARGB(0xFF, 0x64, 0x64, 0x64); constexpr SkColor kTabTitleColor_InactiveHighlight = - SkColorSetARGBMacro(0xFF, 0x80, 0x86, 0x8B); + SkColorSetARGB(0xFF, 0x80, 0x86, 0x8B); constexpr SkColor kTabTitleColor_ActiveBorder = SK_ColorBLACK; constexpr SkColor kTabTitleColor_ActiveHighlight = - SkColorSetARGBMacro(0xFF, 0x42, 0x85, 0xF4); + SkColorSetARGB(0xFF, 0x42, 0x85, 0xF4); const SkColor kTabTitleColor_Hovered = SK_ColorBLACK; const SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8); const SkScalar kTabBorderThickness = 1.0f; constexpr SkColor kTabHighlightBackgroundColor = - SkColorSetARGBMacro(0xFF, 0xE8, 0xF0, 0xFE); + SkColorSetARGB(0xFF, 0xE8, 0xF0, 0xFE); constexpr int kTabHighlightBorderRadius = 32; constexpr int kTabHighlightPreferredHeight = 32; constexpr int kTabHighlightPreferredWidth = 208; diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc index f499e202ee4..5ad416d35b1 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_unittest.cc @@ -7,7 +7,6 @@ #include <memory> #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/accessibility/ax_action_data.h" diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc index d7f37cf103f..c4325de2745 100644 --- a/chromium/ui/views/controls/table/table_view.cc +++ b/chromium/ui/views/controls/table/table_view.cc @@ -31,15 +31,12 @@ #include "ui/views/layout/layout_provider.h" #include "ui/views/style/typography.h" -// Size of images. -static const int kImageSize = 16; - -static const int kGroupingIndicatorSize = 6; - namespace views { namespace { +constexpr int kGroupingIndicatorSize = 6; + // Returns result, unless ascending is false in which case -result is returned. int SwapCompareResult(int result, bool ascending) { return ascending ? result : -result; @@ -588,14 +585,15 @@ void TableView::OnPaint(gfx::Canvas* canvas) { if (j == 0 && table_type_ == ICON_AND_TEXT) { gfx::ImageSkia image = model_->GetIcon(model_index); if (!image.isNull()) { - int image_x = GetMirroredXWithWidthInView(text_x, kImageSize); + int image_x = + GetMirroredXWithWidthInView(text_x, ui::TableModel::kIconSize); canvas->DrawImageInt( - image, 0, 0, image.width(), image.height(), - image_x, - cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2, - kImageSize, kImageSize, true); + image, 0, 0, image.width(), image.height(), image_x, + cell_bounds.y() + + (cell_bounds.height() - ui::TableModel::kIconSize) / 2, + ui::TableModel::kIconSize, ui::TableModel::kIconSize, true); } - text_x += kImageSize + cell_element_spacing; + text_x += ui::TableModel::kIconSize + cell_element_spacing; } if (text_x < cell_bounds.right() - cell_margin) { canvas->DrawStringRectWithFlags( @@ -743,7 +741,7 @@ void TableView::AdjustCellBoundsForText(int visible_column_index, if (grouper_) text_x += kGroupingIndicatorSize + cell_element_spacing; if (table_type_ == ICON_AND_TEXT) - text_x += kImageSize + cell_element_spacing; + text_x += ui::TableModel::kIconSize + cell_element_spacing; } bounds->set_x(text_x); bounds->set_width(std::max(0, bounds->right() - cell_margin - text_x)); @@ -770,7 +768,7 @@ void TableView::UpdateVisibleColumnSizes() { const int cell_element_spacing = GetCellElementSpacing(); int first_column_padding = 0; if (table_type_ == ICON_AND_TEXT && header_) - first_column_padding += kImageSize + cell_element_spacing; + first_column_padding += ui::TableModel::kIconSize + cell_element_spacing; if (grouper_) first_column_padding += kGroupingIndicatorSize + cell_element_spacing; diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc index 82cdf5b8669..3c1d189c674 100644 --- a/chromium/ui/views/controls/textfield/textfield.cc +++ b/chromium/ui/views/controls/textfield/textfield.cc @@ -57,7 +57,6 @@ #if defined(OS_WIN) #include "base/win/win_util.h" -#include "ui/base/ime/win/osk_display_manager.h" #endif #if defined(OS_LINUX) && !defined(OS_CHROMEOS) @@ -83,12 +82,6 @@ namespace views { namespace { #if defined(OS_MACOSX) -const ui::EventFlags kPlatformModifier = ui::EF_COMMAND_DOWN; -#else -const ui::EventFlags kPlatformModifier = ui::EF_CONTROL_DOWN; -#endif // OS_MACOSX - -#if defined(OS_MACOSX) const gfx::SelectionBehavior kLineSelectionBehavior = gfx::SELECTION_EXTEND; const gfx::SelectionBehavior kWordSelectionBehavior = gfx::SELECTION_CARET; const gfx::SelectionBehavior kMoveParagraphSelectionBehavior = @@ -299,6 +292,9 @@ Textfield::Textfield() UpdateBorder(); SetFocusBehavior(FocusBehavior::ALWAYS); + if (use_focus_ring_) + focus_ring_ = FocusRing::Install(this); + #if !defined(OS_MACOSX) // Do not map accelerators on Mac. E.g. They might not reflect custom // keybindings that a user has set. But also on Mac, these commands dispatch @@ -595,12 +591,8 @@ void Textfield::SetInvalid(bool invalid) { return; invalid_ = invalid; UpdateBorder(); - - if (HasFocus() && use_focus_ring_) { - FocusRing::Install(this, invalid_ - ? ui::NativeTheme::kColorId_AlertSeverityHigh - : ui::NativeTheme::kColorId_NumColors); - } + if (focus_ring_) + focus_ring_->SetInvalid(invalid); } void Textfield::ClearEditHistory() { @@ -646,9 +638,9 @@ const char* Textfield::GetClassName() const { } void Textfield::SetBorder(std::unique_ptr<Border> b) { - if (use_focus_ring_ && HasFocus()) - FocusRing::Uninstall(this); use_focus_ring_ = false; + if (focus_ring_) + focus_ring_.reset(); View::SetBorder(std::move(b)); } @@ -664,16 +656,22 @@ gfx::NativeCursor Textfield::GetCursor(const ui::MouseEvent& event) { bool Textfield::OnMousePressed(const ui::MouseEvent& event) { const bool had_focus = HasFocus(); bool handled = controller_ && controller_->HandleMouseEvent(this, event); + + // If the controller triggered the focus, then record the focus reason as + // other. + if (!had_focus && HasFocus()) + focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER; + if (!handled && (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton())) { if (!had_focus) - RequestFocus(); + RequestFocusWithPointer(ui::EventPointerType::POINTER_TYPE_MOUSE); ShowImeIfNeeded(); } #if defined(OS_LINUX) && !defined(OS_CHROMEOS) if (!handled && !had_focus && event.IsOnlyMiddleMouseButton()) - RequestFocus(); + RequestFocusWithPointer(ui::EventPointerType::POINTER_TYPE_MOUSE); #endif return selection_controller_.OnMousePressed( @@ -747,7 +745,7 @@ bool Textfield::OnKeyReleased(const ui::KeyEvent& event) { void Textfield::OnGestureEvent(ui::GestureEvent* event) { switch (event->type()) { case ui::ET_GESTURE_TAP_DOWN: - RequestFocus(); + RequestFocusWithPointer(event->details().primary_pointer_type()); ShowImeIfNeeded(); event->SetHandled(); break; @@ -776,13 +774,6 @@ void Textfield::OnGestureEvent(ui::GestureEvent* event) { OnAfterUserAction(); } CreateTouchSelectionControllerAndNotifyIt(); -#if defined(OS_WIN) - if (!read_only()) { - DCHECK(ui::OnScreenKeyboardDisplayManager::GetInstance()); - ui::OnScreenKeyboardDisplayManager::GetInstance() - ->DisplayVirtualKeyboard(nullptr); - } -#endif event->SetHandled(); break; case ui::ET_GESTURE_LONG_PRESS: @@ -857,6 +848,28 @@ bool Textfield::CanHandleAccelerators() const { return GetRenderText()->focused() && View::CanHandleAccelerators(); } +void Textfield::RequestFocusWithPointer(ui::EventPointerType pointer_type) { + if (HasFocus()) + return; + + switch (pointer_type) { + case ui::EventPointerType::POINTER_TYPE_MOUSE: + focus_reason_ = ui::TextInputClient::FOCUS_REASON_MOUSE; + break; + case ui::EventPointerType::POINTER_TYPE_PEN: + focus_reason_ = ui::TextInputClient::FOCUS_REASON_PEN; + break; + case ui::EventPointerType::POINTER_TYPE_TOUCH: + focus_reason_ = ui::TextInputClient::FOCUS_REASON_TOUCH; + break; + default: + focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER; + break; + } + + View::RequestFocus(); +} + void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { SelectAll(PlatformStyle::kTextfieldScrollsToStartOnFocusChange); } @@ -1074,6 +1087,10 @@ void Textfield::OnPaint(gfx::Canvas* canvas) { } void Textfield::OnFocus() { + // Set focus reason if focused was gained without mouse or touch input. + if (focus_reason_ == ui::TextInputClient::FOCUS_REASON_NONE) + focus_reason_ = ui::TextInputClient::FOCUS_REASON_OTHER; + #if defined(OS_MACOSX) if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) password_input_enabler_.reset(new ui::ScopedPasswordInputEnabler()); @@ -1089,16 +1106,13 @@ void Textfield::OnFocus() { OnCaretBoundsChanged(); if (ShouldBlinkCursor()) StartBlinkingCursor(); - if (use_focus_ring_) { - FocusRing::Install(this, invalid_ - ? ui::NativeTheme::kColorId_AlertSeverityHigh - : ui::NativeTheme::kColorId_NumColors); - } SchedulePaint(); View::OnFocus(); } void Textfield::OnBlur() { + focus_reason_ = ui::TextInputClient::FOCUS_REASON_NONE; + gfx::RenderText* render_text = GetRenderText(); render_text->set_focused(false); @@ -1118,8 +1132,6 @@ void Textfield::OnBlur() { DestroyTouchSelection(); - if (use_focus_ring_) - FocusRing::Uninstall(this); SchedulePaint(); View::OnBlur(); @@ -1354,23 +1366,23 @@ bool Textfield::GetAcceleratorForCommandId(int command_id, ui::Accelerator* accelerator) const { switch (command_id) { case IDS_APP_UNDO: - *accelerator = ui::Accelerator(ui::VKEY_Z, kPlatformModifier); + *accelerator = ui::Accelerator(ui::VKEY_Z, ui::EF_PLATFORM_ACCELERATOR); return true; case IDS_APP_CUT: - *accelerator = ui::Accelerator(ui::VKEY_X, kPlatformModifier); + *accelerator = ui::Accelerator(ui::VKEY_X, ui::EF_PLATFORM_ACCELERATOR); return true; case IDS_APP_COPY: - *accelerator = ui::Accelerator(ui::VKEY_C, kPlatformModifier); + *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_PLATFORM_ACCELERATOR); return true; case IDS_APP_PASTE: - *accelerator = ui::Accelerator(ui::VKEY_V, kPlatformModifier); + *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_PLATFORM_ACCELERATOR); return true; case IDS_APP_SELECT_ALL: - *accelerator = ui::Accelerator(ui::VKEY_A, kPlatformModifier); + *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_PLATFORM_ACCELERATOR); return true; case IDS_CONTENT_CONTEXT_EMOJI: @@ -1535,6 +1547,10 @@ bool Textfield::HasCompositionText() const { return model_->HasCompositionText(); } +ui::TextInputClient::FocusReason Textfield::GetFocusReason() const { + return focus_reason_; +} + bool Textfield::GetTextRange(gfx::Range* range) const { if (!ImeEditingAllowed()) return false; @@ -1601,13 +1617,14 @@ bool Textfield::ChangeTextDirectionAndLayoutAlignment( // Restore text directionality mode when the indicated direction matches the // current forced mode; otherwise, force the mode indicated. This helps users // manage BiDi text layout without getting stuck in forced LTR or RTL modes. - const gfx::DirectionalityMode mode = direction == base::i18n::RIGHT_TO_LEFT - ? gfx::DIRECTIONALITY_FORCE_RTL - : gfx::DIRECTIONALITY_FORCE_LTR; - if (mode == GetRenderText()->directionality_mode()) - GetRenderText()->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT); - else - GetRenderText()->SetDirectionalityMode(mode); + const bool default_rtl = direction == base::i18n::RIGHT_TO_LEFT; + const auto new_mode = default_rtl ? gfx::DIRECTIONALITY_FORCE_RTL + : gfx::DIRECTIONALITY_FORCE_LTR; + auto* render_text = GetRenderText(); + const bool modes_match = new_mode == render_text->directionality_mode(); + render_text->SetDirectionalityMode(modes_match ? gfx::DIRECTIONALITY_FROM_TEXT + : new_mode); + SetHorizontalAlignment(default_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT); SchedulePaint(); return true; } @@ -1732,6 +1749,12 @@ const std::string& Textfield::GetClientSourceInfo() const { return base::EmptyString(); } +bool Textfield::ShouldDoLearning() { + // TODO(https://crbug.com/311180): Implement this method. + NOTIMPLEMENTED_LOG_ONCE(); + return false; +} + //////////////////////////////////////////////////////////////////////////////// // Textfield, protected: @@ -2157,7 +2180,9 @@ void Textfield::OnCaretBoundsChanged() { #if defined(OS_MACOSX) // On Mac, the context menu contains a look up item which displays the // selected text. As such, the menu needs to be updated if the selection has - // changed. + // changed. Be careful to reset the MenuRunner first so it doesn't reference + // the old model. + context_menu_runner_.reset(); context_menu_contents_.reset(); #endif diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h index 8f26353fa19..3df902cef6d 100644 --- a/chromium/ui/views/controls/textfield/textfield.h +++ b/chromium/ui/views/controls/textfield/textfield.h @@ -28,6 +28,7 @@ #include "ui/gfx/selection_model.h" #include "ui/gfx/text_constants.h" #include "ui/views/context_menu_controller.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/controls/textfield/textfield_model.h" #include "ui/views/drag_controller.h" #include "ui/views/selection_controller.h" @@ -343,6 +344,7 @@ class VIEWS_EXPORT Textfield : public View, bool GetCompositionCharacterBounds(uint32_t index, gfx::Rect* rect) const override; bool HasCompositionText() const override; + FocusReason GetFocusReason() const override; bool GetTextRange(gfx::Range* range) const override; bool GetCompositionTextRange(gfx::Range* range) const override; bool GetSelectionRange(gfx::Range* range) const override; @@ -358,6 +360,7 @@ class VIEWS_EXPORT Textfield : public View, bool IsTextEditCommandEnabled(ui::TextEditCommand command) const override; void SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) override; const std::string& GetClientSourceInfo() const override; + bool ShouldDoLearning() override; protected: // Inserts or appends a character in response to an IME operation. @@ -481,6 +484,13 @@ class VIEWS_EXPORT Textfield : public View, // Textfield::GetCaretBlinkMs(). void OnCursorBlinkTimerFired(); + // Like RequestFocus, but explicitly states that the focus is triggered by + // a pointer event. + void RequestFocusWithPointer(ui::EventPointerType pointer_type); + + // Returns the color to use for the FocusRing, if one is present. + SkColor GetFocusRingColor() const; + // The text model. std::unique_ptr<TextfieldModel> model_; @@ -602,6 +612,13 @@ class VIEWS_EXPORT Textfield : public View, std::unique_ptr<ui::ScopedPasswordInputEnabler> password_input_enabler_; #endif // defined(OS_MACOSX) + // How this textfield was focused. + ui::TextInputClient::FocusReason focus_reason_ = + ui::TextInputClient::FOCUS_REASON_NONE; + + // The focus ring for this TextField. + std::unique_ptr<FocusRing> focus_ring_; + // Used to bind callback functions to this object. base::WeakPtrFactory<Textfield> weak_ptr_factory_; diff --git a/chromium/ui/views/controls/textfield/textfield_model.cc b/chromium/ui/views/controls/textfield/textfield_model.cc index 7b06af51838..4d77de34ffc 100644 --- a/chromium/ui/views/controls/textfield/textfield_model.cc +++ b/chromium/ui/views/controls/textfield/textfield_model.cc @@ -36,16 +36,15 @@ class Edit { // Revert the change made by this edit in |model|. void Undo(TextfieldModel* model) { - model->ModifyText(new_text_start_, new_text_end(), - old_text_, old_text_start_, - old_cursor_pos_); + model->ModifyText(new_text_start_, new_text_end(), old_text_, + old_text_start_, old_selection_); } // Apply the change of this edit to the |model|. void Redo(TextfieldModel* model) { - model->ModifyText(old_text_start_, old_text_end(), - new_text_, new_text_start_, - new_cursor_pos_); + model->ModifyText(old_text_start_, old_text_end(), new_text_, + new_text_start_, + gfx::Range(new_cursor_pos_, new_cursor_pos_)); } // Try to merge the |edit| into this edit and returns true on success. The @@ -72,23 +71,22 @@ class Edit { Edit(Type type, MergeType merge_type, - size_t old_cursor_pos, const base::string16& old_text, size_t old_text_start, + gfx::Range old_selection, bool delete_backward, size_t new_cursor_pos, const base::string16& new_text, size_t new_text_start) : type_(type), merge_type_(merge_type), - old_cursor_pos_(old_cursor_pos), old_text_(old_text), old_text_start_(old_text_start), + old_selection_(old_selection), delete_backward_(delete_backward), new_cursor_pos_(new_cursor_pos), new_text_(new_text), - new_text_start_(new_text_start) { - } + new_text_start_(new_text_start) {} // Each type of edit provides its own specific merge implementation. virtual bool DoMerge(const Edit* edit) = 0; @@ -131,12 +129,12 @@ class Edit { // The type of merging allowed. MergeType merge_type_; - // Old cursor position. - size_t old_cursor_pos_; // Deleted text by this edit. base::string16 old_text_; // The index of |old_text_|. size_t old_text_start_; + // The range of the text selection prior to the edit. + gfx::Range old_selection_; // True if the deletion is made backward. bool delete_backward_; // New cursor position. @@ -154,14 +152,13 @@ class InsertEdit : public Edit { InsertEdit(bool mergeable, const base::string16& new_text, size_t at) : Edit(INSERT_EDIT, mergeable ? MERGEABLE : DO_NOT_MERGE, - at /* old cursor */, base::string16(), at, - false /* N/A */, - at + new_text.length() /* new cursor */, + gfx::Range(at, at), + false /* N/A */, + at + new_text.length() /* new cursor */, new_text, - at) { - } + at) {} // Edit implementation. bool DoMerge(const Edit* edit) override { @@ -180,21 +177,21 @@ class ReplaceEdit : public Edit { public: ReplaceEdit(MergeType merge_type, const base::string16& old_text, - size_t old_cursor_pos, size_t old_text_start, + gfx::Range old_selection, bool backward, size_t new_cursor_pos, const base::string16& new_text, size_t new_text_start) - : Edit(REPLACE_EDIT, merge_type, - old_cursor_pos, + : Edit(REPLACE_EDIT, + merge_type, old_text, old_text_start, + old_selection, backward, new_cursor_pos, new_text, - new_text_start) { - } + new_text_start) {} // Edit implementation. bool DoMerge(const Edit* edit) override { @@ -214,17 +211,17 @@ class DeleteEdit : public Edit { DeleteEdit(bool mergeable, const base::string16& text, size_t text_start, - bool backward) + bool backward, + gfx::Range old_selection) : Edit(DELETE_EDIT, mergeable ? MERGEABLE : DO_NOT_MERGE, - (backward ? text_start + text.length() : text_start), text, text_start, + old_selection, backward, text_start, base::string16(), - text_start) { - } + text_start) {} // Edit implementation. bool DoMerge(const Edit* edit) override { @@ -334,14 +331,13 @@ bool TextfieldModel::SetText(const base::string16& new_text) { if (text() != new_text) { if (changed) // No need to remember composition. Undo(); - size_t old_cursor = GetCursorPosition(); // SetText moves the cursor to the end. size_t new_cursor = new_text.length(); - SelectAll(false); // If there is a composition text, don't merge with previous edit. // Otherwise, force merge the edits. ExecuteAndRecordReplace(changed ? DO_NOT_MERGE : FORCE_MERGE, - old_cursor, new_cursor, new_text, 0U); + gfx::Range(0, text().length()), new_cursor, + new_text, 0U); render_text_->SetCursorPosition(new_cursor); } ClearSelection(); @@ -627,11 +623,8 @@ void TextfieldModel::DeleteSelectionAndInsertTextAt( size_t position) { if (HasCompositionText()) CancelCompositionText(); - ExecuteAndRecordReplace(DO_NOT_MERGE, - GetCursorPosition(), - position + new_text.length(), - new_text, - position); + ExecuteAndRecordReplace(DO_NOT_MERGE, render_text_->selection(), + position + new_text.length(), new_text, position); } base::string16 TextfieldModel::GetTextFromRange(const gfx::Range& range) const { @@ -776,8 +769,9 @@ void TextfieldModel::ExecuteAndRecordDelete(gfx::Range range, bool mergeable) { size_t old_text_start = range.GetMin(); const base::string16 old_text = text().substr(old_text_start, range.length()); bool backward = range.is_reversed(); + gfx::Range curr_selection = render_text_->selection(); auto edit = std::make_unique<DeleteEdit>(mergeable, old_text, old_text_start, - backward); + backward, curr_selection); edit->Redo(this); AddOrMergeEditHistory(std::move(edit)); } @@ -787,23 +781,21 @@ void TextfieldModel::ExecuteAndRecordReplaceSelection( const base::string16& new_text) { size_t new_text_start = render_text_->selection().GetMin(); size_t new_cursor_pos = new_text_start + new_text.length(); - ExecuteAndRecordReplace(merge_type, - GetCursorPosition(), - new_cursor_pos, - new_text, - new_text_start); + ExecuteAndRecordReplace(merge_type, render_text_->selection(), new_cursor_pos, + new_text, new_text_start); } void TextfieldModel::ExecuteAndRecordReplace(MergeType merge_type, - size_t old_cursor_pos, + gfx::Range replacement_range, size_t new_cursor_pos, const base::string16& new_text, size_t new_text_start) { - size_t old_text_start = render_text_->selection().GetMin(); - bool backward = render_text_->selection().is_reversed(); + size_t old_text_start = replacement_range.GetMin(); + bool backward = replacement_range.is_reversed(); auto edit = std::make_unique<ReplaceEdit>( - merge_type, GetSelectedText(), old_cursor_pos, old_text_start, backward, - new_cursor_pos, new_text, new_text_start); + merge_type, GetTextFromRange(replacement_range), old_text_start, + render_text_->selection(), backward, new_cursor_pos, new_text, + new_text_start); edit->Redo(this); AddOrMergeEditHistory(std::move(edit)); } @@ -840,7 +832,7 @@ void TextfieldModel::ModifyText(size_t delete_from, size_t delete_to, const base::string16& new_text, size_t new_text_insert_at, - size_t new_cursor_pos) { + gfx::Range selection) { DCHECK_LE(delete_from, delete_to); base::string16 old_text = text(); ClearComposition(); @@ -848,8 +840,11 @@ void TextfieldModel::ModifyText(size_t delete_from, render_text_->SetText(old_text.erase(delete_from, delete_to - delete_from)); if (!new_text.empty()) render_text_->SetText(old_text.insert(new_text_insert_at, new_text)); - render_text_->SetCursorPosition(new_cursor_pos); - // TODO(oshima): Select text that was just undone, like Mac (but not GTK). + if (selection.start() == selection.end()) { + render_text_->SetCursorPosition(selection.start()); + } else { + render_text_->SelectRange(selection); + } } // static diff --git a/chromium/ui/views/controls/textfield/textfield_model.h b/chromium/ui/views/controls/textfield/textfield_model.h index 20c6687a97c..771d7a9c330 100644 --- a/chromium/ui/views/controls/textfield/textfield_model.h +++ b/chromium/ui/views/controls/textfield/textfield_model.h @@ -260,7 +260,7 @@ class VIEWS_EXPORT TextfieldModel { void ExecuteAndRecordReplaceSelection(internal::MergeType merge_type, const base::string16& new_text); void ExecuteAndRecordReplace(internal::MergeType merge_type, - size_t old_cursor_pos, + gfx::Range replacement_range, size_t new_cursor_pos, const base::string16& new_text, size_t new_text_start); @@ -270,15 +270,15 @@ class VIEWS_EXPORT TextfieldModel { void AddOrMergeEditHistory(std::unique_ptr<internal::Edit> edit); // Modify the text buffer in following way: - // 1) Delete the string from |delete_from| to |delte_to|. + // 1) Delete the string from |delete_from| to |delete_to|. // 2) Insert the |new_text| at the index |new_text_insert_at|. // Note that the index is after deletion. - // 3) Move the cursor to |new_cursor_pos|. + // 3) Select |selection|. void ModifyText(size_t delete_from, size_t delete_to, const base::string16& new_text, size_t new_text_insert_at, - size_t new_cursor_pos); + gfx::Range selection); void ClearComposition(); diff --git a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc index c35b12260bf..60884ff58b0 100644 --- a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc @@ -11,7 +11,6 @@ #include "base/auto_reset.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" @@ -1387,7 +1386,9 @@ TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) { EXPECT_EQ(1U, model.GetCursorPosition()); EXPECT_TRUE(model.Undo()); EXPECT_STR_EQ("ABCDE", model.text()); - EXPECT_EQ(3U, model.GetCursorPosition()); + EXPECT_EQ(1U, model.GetCursorPosition()); + EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection( + gfx::Range(1, 3))); EXPECT_TRUE(model.Undo()); EXPECT_STR_EQ("", model.text()); EXPECT_EQ(0U, model.GetCursorPosition()); @@ -1418,7 +1419,9 @@ TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) { EXPECT_EQ(1U, model.GetCursorPosition()); EXPECT_TRUE(model.Undo()); EXPECT_STR_EQ("ABCDE", model.text()); - EXPECT_EQ(3U, model.GetCursorPosition()); + EXPECT_EQ(1U, model.GetCursorPosition()); + EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection( + gfx::Range(1, 3))); EXPECT_TRUE(model.Undo()); EXPECT_STR_EQ("", model.text()); EXPECT_EQ(0U, model.GetCursorPosition()); @@ -1460,8 +1463,9 @@ TEST_F(TextfieldModelTest, UndoRedo_CutCopyPasteTest) { // An empty cut shouldn't create an edit. EXPECT_TRUE(model.Undo()); EXPECT_STR_EQ("ABCBCBCDE", model.text()); - EXPECT_EQ(3U, model.GetCursorPosition()); - + EXPECT_EQ(1U, model.GetCursorPosition()); + EXPECT_TRUE(model.render_text()->selection().EqualsIgnoringDirection( + gfx::Range(1, 3))); // Test Copy. ResetModel(&model); model.SetText(base::ASCIIToUTF16("12345")); @@ -1533,6 +1537,47 @@ TEST_F(TextfieldModelTest, UndoRedo_CursorTest) { EXPECT_FALSE(model.Redo()); } +TEST_F(TextfieldModelTest, Undo_SelectionTest) { + gfx::Range range = gfx::Range(2, 4); + TextfieldModel model(nullptr); + model.SetText(base::ASCIIToUTF16("abcdef")); + model.SelectRange(range); + EXPECT_EQ(model.render_text()->selection(), range); + + // Deleting the selected text should change the text and the range. + EXPECT_TRUE(model.Backspace()); + EXPECT_STR_EQ("abef", model.text()); + EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2)); + + // Undoing the deletion should restore the former range. + EXPECT_TRUE(model.Undo()); + EXPECT_STR_EQ("abcdef", model.text()); + EXPECT_EQ(model.render_text()->selection(), range); + + // When range.start = range.end, nothing is selected and + // range.start = range.end = cursor position + model.MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, gfx::SELECTION_NONE); + EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2)); + + // Deleting a single character should change the text and cursor location. + EXPECT_TRUE(model.Backspace()); + EXPECT_STR_EQ("acdef", model.text()); + EXPECT_EQ(model.render_text()->selection(), gfx::Range(1, 1)); + + // Undoing the deletion should restore the former range. + EXPECT_TRUE(model.Undo()); + EXPECT_STR_EQ("abcdef", model.text()); + EXPECT_EQ(model.render_text()->selection(), gfx::Range(2, 2)); + + MoveCursorTo(model, model.text().length()); + EXPECT_TRUE(model.Backspace()); + model.SelectRange(gfx::Range(1, 3)); + model.SetText(base::ASCIIToUTF16("[set]")); + EXPECT_TRUE(model.Undo()); + EXPECT_STR_EQ("abcde", model.text()); + EXPECT_EQ(model.render_text()->selection(), gfx::Range(1, 3)); +} + void RunInsertReplaceTest(TextfieldModel& model) { const bool reverse = model.render_text()->selection().is_reversed(); model.InsertChar('1'); diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc index 775c61fd31d..12e9a987863 100644 --- a/chromium/ui/views/controls/textfield/textfield_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc @@ -26,6 +26,7 @@ #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/emoji/emoji_panel_helper.h" #include "ui/base/ime/input_method_base.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/base/ime/input_method_factory.h" @@ -704,9 +705,11 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController { EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* LOOK UP */)); EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); } - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */)); - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); #endif + if (ui::IsEmojiPanelSupported()) { + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */)); + EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); + } EXPECT_EQ(can_undo, menu->IsEnabledAt(menu_index++ /* UNDO */)); EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); @@ -3432,6 +3435,69 @@ TEST_F(TextfieldTest, SendingDeletePreservesShiftFlag) { EXPECT_EQ(ui::EF_SHIFT_DOWN, textfield_->event_flags()); } +TEST_F(TextfieldTest, EmojiItem_EmptyField) { + InitTextfield(); + EXPECT_TRUE(textfield_->context_menu_controller()); + + // Enable the emoji feature. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature(features::kEnableEmojiContextMenu); + + // A normal empty field may show the Emoji option (if supported). + ui::MenuModel* context_menu = GetContextMenuModel(); + EXPECT_TRUE(context_menu); + EXPECT_GT(context_menu->GetItemCount(), 0); + // Not all OS/versions support the emoji menu. + EXPECT_EQ(ui::IsEmojiPanelSupported(), + context_menu->GetLabelAt(0) == + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); +} + +TEST_F(TextfieldTest, EmojiItem_ReadonlyField) { + InitTextfield(); + EXPECT_TRUE(textfield_->context_menu_controller()); + + // Enable the emoji feature. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature(features::kEnableEmojiContextMenu); + + textfield_->SetReadOnly(true); + // In no case is the emoji option showing on a read-only field. + ui::MenuModel* context_menu = GetContextMenuModel(); + EXPECT_TRUE(context_menu); + EXPECT_GT(context_menu->GetItemCount(), 0); + EXPECT_NE(context_menu->GetLabelAt(0), + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); +} + +TEST_F(TextfieldTest, EmojiItem_FieldWithText) { + InitTextfield(); + EXPECT_TRUE(textfield_->context_menu_controller()); + +#if defined(OS_MACOSX) + // On Mac, when there is text, the "Look up" item (+ separator) takes the top + // position, and emoji comes after. + constexpr int kExpectedEmojiIndex = 2; +#else + constexpr int kExpectedEmojiIndex = 0; +#endif + + // Enable the emoji feature. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature(features::kEnableEmojiContextMenu); + + // A field with text may still show the Emoji option (if supported). + textfield_->SetText(base::ASCIIToUTF16("some text")); + textfield_->SelectAll(false); + ui::MenuModel* context_menu = GetContextMenuModel(); + EXPECT_TRUE(context_menu); + EXPECT_GT(context_menu->GetItemCount(), 0); + // Not all OS/versions support the emoji menu. + EXPECT_EQ(ui::IsEmojiPanelSupported(), + context_menu->GetLabelAt(kExpectedEmojiIndex) == + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); +} + #if defined(OS_MACOSX) // Tests to see if the BiDi submenu items are updated correctly when the // textfield's text direction is changed. @@ -3470,6 +3536,10 @@ TEST_F(TextfieldTest, LookUpItemUpdate) { InitTextfield(); EXPECT_TRUE(textfield_->context_menu_controller()); + // Make sure the Emoji feature is disabled for this test. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndDisableFeature(features::kEnableEmojiContextMenu); + const base::string16 kTextOne = ASCIIToUTF16("crake"); textfield_->SetText(kTextOne); textfield_->SelectAll(false); @@ -3552,4 +3622,122 @@ TEST_F(TextfieldTest, AccessibilitySelectionEvents) { textfield_->GetAccessibilitySelectionFiredCount()); } +TEST_F(TextfieldTest, FocusReasonMouse) { + InitTextfield(); + widget_->GetFocusManager()->ClearFocus(); + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, + textfield_->GetFocusReason()); + + const auto& bounds = textfield_->bounds(); + MouseClick(bounds, 10); + + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_MOUSE, + textfield_->GetFocusReason()); +} + +TEST_F(TextfieldTest, FocusReasonTouchTap) { + InitTextfield(); + widget_->GetFocusManager()->ClearFocus(); + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, + textfield_->GetFocusReason()); + + ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN); + tap_details.set_primary_pointer_type( + ui::EventPointerType::POINTER_TYPE_TOUCH); + GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details); + textfield_->OnGestureEvent(&tap); + + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_TOUCH, + textfield_->GetFocusReason()); +} + +TEST_F(TextfieldTest, FocusReasonPenTap) { + InitTextfield(); + widget_->GetFocusManager()->ClearFocus(); + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, + textfield_->GetFocusReason()); + + ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN); + tap_details.set_primary_pointer_type(ui::EventPointerType::POINTER_TYPE_PEN); + GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details); + textfield_->OnGestureEvent(&tap); + + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN, + textfield_->GetFocusReason()); +} + +TEST_F(TextfieldTest, FocusReasonMultipleEvents) { + InitTextfield(); + widget_->GetFocusManager()->ClearFocus(); + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, + textfield_->GetFocusReason()); + + // Pen tap, followed by a touch tap + { + ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN); + tap_details.set_primary_pointer_type( + ui::EventPointerType::POINTER_TYPE_PEN); + GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details); + textfield_->OnGestureEvent(&tap); + } + + { + ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN); + tap_details.set_primary_pointer_type( + ui::EventPointerType::POINTER_TYPE_TOUCH); + GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details); + textfield_->OnGestureEvent(&tap); + } + + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_PEN, + textfield_->GetFocusReason()); +} + +TEST_F(TextfieldTest, FocusReasonFocusBlurFocus) { + InitTextfield(); + widget_->GetFocusManager()->ClearFocus(); + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_NONE, + textfield_->GetFocusReason()); + + // Pen tap, blur, then programmatic focus. + ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP_DOWN); + tap_details.set_primary_pointer_type(ui::EventPointerType::POINTER_TYPE_PEN); + GestureEventForTest tap(GetCursorPositionX(0), 0, tap_details); + textfield_->OnGestureEvent(&tap); + + widget_->GetFocusManager()->ClearFocus(); + + textfield_->RequestFocus(); + + EXPECT_EQ(ui::TextInputClient::FOCUS_REASON_OTHER, + textfield_->GetFocusReason()); +} + +TEST_F(TextfieldTest, ChangeTextDirectionAndLayoutAlignmentTest) { + InitTextfield(); + + textfield_->ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection::RIGHT_TO_LEFT); + EXPECT_EQ(textfield_->GetTextDirection(), + base::i18n::TextDirection::RIGHT_TO_LEFT); + EXPECT_EQ(textfield_->GetHorizontalAlignment(), + gfx::HorizontalAlignment::ALIGN_RIGHT); + + textfield_->ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection::RIGHT_TO_LEFT); + const base::string16& text = test_api_->GetRenderText()->GetDisplayText(); + base::i18n::TextDirection text_direction = + base::i18n::GetFirstStrongCharacterDirection(text); + EXPECT_EQ(textfield_->GetTextDirection(), text_direction); + EXPECT_EQ(textfield_->GetHorizontalAlignment(), + gfx::HorizontalAlignment::ALIGN_RIGHT); + + textfield_->ChangeTextDirectionAndLayoutAlignment( + base::i18n::TextDirection::LEFT_TO_RIGHT); + EXPECT_EQ(textfield_->GetTextDirection(), + base::i18n::TextDirection::LEFT_TO_RIGHT); + EXPECT_EQ(textfield_->GetHorizontalAlignment(), + gfx::HorizontalAlignment::ALIGN_LEFT); +} + } // namespace views diff --git a/chromium/ui/views/controls/tree/tree_view.cc b/chromium/ui/views/controls/tree/tree_view.cc index 4b8d78b239a..b8b07ce5377 100644 --- a/chromium/ui/views/controls/tree/tree_view.cc +++ b/chromium/ui/views/controls/tree/tree_view.cc @@ -7,7 +7,6 @@ #include <algorithm> #include "base/i18n/rtl.h" -#include "base/message_loop/message_loop.h" #include "build/build_config.h" #include "components/vector_icons/vector_icons.h" #include "ui/accessibility/ax_node_data.h" @@ -262,8 +261,13 @@ void TreeView::SetSelectedNode(TreeModelNode* model_node) { SchedulePaintForNode(selected_node_); } - if (selected_node_) - ScrollRectToVisible(GetForegroundBoundsForNode(selected_node_)); + if (selected_node_) { + // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint. + // Un-flip before passing to ScrollRectToVisible(), which uses layout + // coordinates. + ScrollRectToVisible( + GetMirroredRect(GetForegroundBoundsForNode(selected_node_))); + } // Notify controller if the old selection was empty to handle the case of // remove explicitly resetting selected_node_ before invoking this. @@ -739,6 +743,9 @@ void TreeView::LayoutEditor() { DCHECK(selected_node_); // Position the editor so that its text aligns with the text we drew. gfx::Rect row_bounds = GetForegroundBoundsForNode(selected_node_); + + // GetForegroundBoundsForNode() returns a "flipped" x for painting. First, un- + // flip it for the following calculations and ScrollRectToVisible(). row_bounds.set_x( GetMirroredXWithWidthInView(row_bounds.x(), row_bounds.width())); row_bounds.set_x(row_bounds.x() + text_offset_); diff --git a/chromium/ui/views/controls/views_text_services_context_menu.cc b/chromium/ui/views/controls/views_text_services_context_menu.cc index ec5975312cc..03a3a7b6e61 100644 --- a/chromium/ui/views/controls/views_text_services_context_menu.cc +++ b/chromium/ui/views/controls/views_text_services_context_menu.cc @@ -4,7 +4,10 @@ #include "ui/views/controls/views_text_services_context_menu.h" +#include <memory> + #include "base/logging.h" +#include "ui/views/controls/views_text_services_context_menu_base.h" namespace views { @@ -12,7 +15,7 @@ namespace views { std::unique_ptr<ViewsTextServicesContextMenu> ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu, Textfield* client) { - return nullptr; + return std::make_unique<ViewsTextServicesContextMenuBase>(menu, client); } bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting( diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.cc b/chromium/ui/views/controls/views_text_services_context_menu_base.cc new file mode 100644 index 00000000000..e5fdea933b5 --- /dev/null +++ b/chromium/ui/views/controls/views_text_services_context_menu_base.cc @@ -0,0 +1,53 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/views/controls/views_text_services_context_menu_base.h" + +#include "ui/base/emoji/emoji_panel_helper.h" +#include "ui/base/models/simple_menu_model.h" +#include "ui/resources/grit/ui_resources.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/controls/textfield/textfield.h" + +namespace views { + +ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase( + ui::SimpleMenuModel* menu, + Textfield* client) + : client_(client) { + DCHECK(client); + DCHECK(menu); + // Not inserted on read-only fields or if the OS/version doesn't support it. + if (!client_->read_only() && ui::IsEmojiPanelSupported()) { + menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR); + menu->InsertItemWithStringIdAt(0, IDS_CONTENT_CONTEXT_EMOJI, + IDS_CONTENT_CONTEXT_EMOJI); + } +} + +ViewsTextServicesContextMenuBase::~ViewsTextServicesContextMenuBase() {} + +bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const { + return command_id == IDS_CONTENT_CONTEXT_EMOJI; +} + +bool ViewsTextServicesContextMenuBase::IsCommandIdChecked( + int command_id) const { + return false; +} + +bool ViewsTextServicesContextMenuBase::IsCommandIdEnabled( + int command_id) const { + if (command_id == IDS_CONTENT_CONTEXT_EMOJI) + return true; + + return false; +} + +void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id) { + if (command_id == IDS_CONTENT_CONTEXT_EMOJI) + ui::ShowEmojiPanel(); +} + +} // namespace views
\ No newline at end of file diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.h b/chromium/ui/views/controls/views_text_services_context_menu_base.h new file mode 100644 index 00000000000..7c39439b83a --- /dev/null +++ b/chromium/ui/views/controls/views_text_services_context_menu_base.h @@ -0,0 +1,41 @@ +// 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_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_ +#define UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_ + +#include "base/macros.h" +#include "ui/views/controls/views_text_services_context_menu.h" + +namespace views { + +// This base class is used to add and handle text service items in the text +// context menu. Specific platforms may subclass and add additional items. +class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu { + public: + ViewsTextServicesContextMenuBase(ui::SimpleMenuModel* menu, + Textfield* client); + ~ViewsTextServicesContextMenuBase() override; + + // Returns true if the given |command_id| is handled by the menu. + bool SupportsCommand(int command_id) const override; + + // Methods associated with SimpleMenuModel::Delegate. + bool IsCommandIdChecked(int command_id) const override; + bool IsCommandIdEnabled(int command_id) const override; + void ExecuteCommand(int command_id) override; + + protected: + Textfield* client() const { return client_; } + + private: + // The view associated with the menu. Weak. Owns |this|. + Textfield* client_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuBase); +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_BASE_H_ diff --git a/chromium/ui/views/controls/views_text_services_context_menu_mac.mm b/chromium/ui/views/controls/views_text_services_context_menu_mac.mm index 0c4cd260bed..c7731fb7547 100644 --- a/chromium/ui/views/controls/views_text_services_context_menu_mac.mm +++ b/chromium/ui/views/controls/views_text_services_context_menu_mac.mm @@ -6,18 +6,16 @@ #import <Cocoa/Cocoa.h> -#include "base/feature_list.h" #include "ui/base/cocoa/text_services_context_menu.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/models/simple_menu_model.h" #include "ui/base/resource/resource_bundle.h" -#include "ui/base/ui_base_features.h" #include "ui/gfx/decorated_text.h" #import "ui/gfx/decorated_text_mac.h" #include "ui/resources/grit/ui_resources.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/controls/textfield/textfield.h" -#include "ui/views/view.h" +#include "ui/views/controls/views_text_services_context_menu_base.h" #include "ui/views/widget/widget.h" namespace views { @@ -28,26 +26,21 @@ namespace { // text service items in the context menu. The items include Speech, Look Up // and BiDi. class ViewsTextServicesContextMenuMac - : public ViewsTextServicesContextMenu, + : public ViewsTextServicesContextMenuBase, public ui::TextServicesContextMenu::Delegate { public: ViewsTextServicesContextMenuMac(ui::SimpleMenuModel* menu, Textfield* client) - : text_services_menu_(this), client_(client) { - // The index to use when inserting items into the menu. - int index = 0; - + : ViewsTextServicesContextMenuBase(menu, client), + text_services_menu_(this) { + // Insert the "Look up" item in the first position. base::string16 text = GetSelectedText(); if (!text.empty()) { + menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR); menu->InsertItemAt( - index++, IDS_CONTENT_CONTEXT_LOOK_UP, + 0, IDS_CONTENT_CONTEXT_LOOK_UP, l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, text)); - menu->InsertSeparatorAt(index++, ui::NORMAL_SEPARATOR); - } - if (base::FeatureList::IsEnabled(features::kEnableEmojiContextMenu)) { - menu->InsertItemWithStringIdAt(index++, IDS_CONTENT_CONTEXT_EMOJI, - IDS_CONTENT_CONTEXT_EMOJI); - menu->InsertSeparatorAt(index++, ui::NORMAL_SEPARATOR); } + text_services_menu_.AppendToContextMenu(menu); text_services_menu_.AppendEditableItems(menu); } @@ -57,13 +50,8 @@ class ViewsTextServicesContextMenuMac // ViewsTextServicesContextMenu: bool SupportsCommand(int command_id) const override { return text_services_menu_.SupportsCommand(command_id) || - command_id == IDS_CONTENT_CONTEXT_EMOJI || - command_id == IDS_CONTENT_CONTEXT_LOOK_UP; - } - - bool IsCommandIdChecked(int command_id) const override { - DCHECK_EQ(IDS_CONTENT_CONTEXT_LOOK_UP, command_id); - return false; + command_id == IDS_CONTENT_CONTEXT_LOOK_UP || + ViewsTextServicesContextMenuBase::SupportsCommand(command_id); } bool IsCommandIdEnabled(int command_id) const override { @@ -71,35 +59,32 @@ class ViewsTextServicesContextMenuMac return text_services_menu_.IsCommandIdEnabled(command_id); switch (command_id) { - case IDS_CONTENT_CONTEXT_EMOJI: - return true; - case IDS_CONTENT_CONTEXT_LOOK_UP: return true; default: - return false; + return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id); } } void ExecuteCommand(int command_id) override { switch (command_id) { - case IDS_CONTENT_CONTEXT_EMOJI: - [NSApp orderFrontCharacterPalette:nil]; - break; - case IDS_CONTENT_CONTEXT_LOOK_UP: LookUpInDictionary(); break; + + default: + ViewsTextServicesContextMenuBase::ExecuteCommand(command_id); + break; } } // TextServicesContextMenu::Delegate: base::string16 GetSelectedText() const override { - if (client_->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) + if (client()->GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD) return base::string16(); - return client_->GetSelectedText(); + return client()->GetSelectedText(); } bool IsTextDirectionEnabled( @@ -110,7 +95,7 @@ class ViewsTextServicesContextMenuMac bool IsTextDirectionChecked( base::i18n::TextDirection direction) const override { return direction != base::i18n::UNKNOWN_DIRECTION && - client_->GetTextDirection() == direction; + client()->GetTextDirection() == direction; } void UpdateTextDirection(base::i18n::TextDirection direction) override { @@ -119,7 +104,7 @@ class ViewsTextServicesContextMenuMac base::i18n::TextDirection text_direction = direction == base::i18n::LEFT_TO_RIGHT ? base::i18n::LEFT_TO_RIGHT : base::i18n::RIGHT_TO_LEFT; - client_->ChangeTextDirectionAndLayoutAlignment(text_direction); + client()->ChangeTextDirectionAndLayoutAlignment(text_direction); } private: @@ -127,10 +112,10 @@ class ViewsTextServicesContextMenuMac void LookUpInDictionary() { gfx::Point baseline_point; gfx::DecoratedText text; - if (client_->GetWordLookupDataFromSelection(&text, &baseline_point)) { - Widget* widget = client_->GetWidget(); + if (client()->GetWordLookupDataFromSelection(&text, &baseline_point)) { + Widget* widget = client()->GetWidget(); gfx::NativeView view = widget->GetNativeView(); - views::View::ConvertPointToTarget(client_, widget->GetRootView(), + views::View::ConvertPointToTarget(client(), widget->GetRootView(), &baseline_point); NSPoint lookup_point = NSMakePoint( @@ -144,9 +129,6 @@ class ViewsTextServicesContextMenuMac // Appends and handles the text service menu. ui::TextServicesContextMenu text_services_menu_; - // The view associated with the menu. Weak. Owns |this|. - Textfield* client_; - DISALLOW_COPY_AND_ASSIGN(ViewsTextServicesContextMenuMac); }; diff --git a/chromium/ui/views/controls/webview/web_dialog_view.cc b/chromium/ui/views/controls/webview/web_dialog_view.cc index ef8b793f745..323d9f895f8 100644 --- a/chromium/ui/views/controls/webview/web_dialog_view.cc +++ b/chromium/ui/views/controls/webview/web_dialog_view.cc @@ -307,19 +307,16 @@ content::WebContents* WebDialogView::OpenURLFromTab( return WebDialogWebContentsDelegate::OpenURLFromTab(source, params); } -void WebDialogView::AddNewContents(content::WebContents* source, - content::WebContents* new_contents, - WindowOpenDisposition disposition, - const gfx::Rect& initial_rect, - bool user_gesture, - bool* was_blocked) { - if (delegate_ && delegate_->HandleAddNewContents( - source, new_contents, disposition, initial_rect, user_gesture)) { - return; - } - WebDialogWebContentsDelegate::AddNewContents( - source, new_contents, disposition, initial_rect, user_gesture, - was_blocked); +void WebDialogView::AddNewContents( + content::WebContents* source, + std::unique_ptr<content::WebContents> new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_rect, + bool user_gesture, + bool* was_blocked) { + WebDialogWebContentsDelegate::AddNewContents(source, std::move(new_contents), + disposition, initial_rect, + user_gesture, was_blocked); } void WebDialogView::LoadingStateChanged(content::WebContents* source, diff --git a/chromium/ui/views/controls/webview/web_dialog_view.h b/chromium/ui/views/controls/webview/web_dialog_view.h index 3a91e99a7f5..bfba06181e7 100644 --- a/chromium/ui/views/controls/webview/web_dialog_view.h +++ b/chromium/ui/views/controls/webview/web_dialog_view.h @@ -104,7 +104,7 @@ class WEBVIEW_EXPORT WebDialogView : public views::ClientView, content::WebContents* source, const content::OpenURLParams& params) override; void AddNewContents(content::WebContents* source, - content::WebContents* new_contents, + std::unique_ptr<content::WebContents> new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_rect, bool user_gesture, diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc index 4ec50f3b056..161185af3b8 100644 --- a/chromium/ui/views/controls/webview/webview.cc +++ b/chromium/ui/views/controls/webview/webview.cc @@ -6,6 +6,7 @@ #include <utility> +#include "base/no_destructor.h" #include "build/build_config.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_context.h" @@ -23,6 +24,27 @@ namespace views { +namespace { + +// A testing stub that creates web contents. +WebView::WebContentsCreator* GetCreatorForTesting() { + static base::NoDestructor<WebView::WebContentsCreator> creator; + return creator.get(); +} + +} // namespace + +WebView::ScopedWebContentsCreatorForTesting::ScopedWebContentsCreatorForTesting( + WebContentsCreator creator) { + DCHECK(!*GetCreatorForTesting()); + *GetCreatorForTesting() = creator; +} + +WebView::ScopedWebContentsCreatorForTesting:: + ~ScopedWebContentsCreatorForTesting() { + *GetCreatorForTesting() = WebView::WebContentsCreator(); +} + // static const char WebView::kViewClassName[] = "WebView"; @@ -44,7 +66,7 @@ WebView::~WebView() { content::WebContents* WebView::GetWebContents() { if (!web_contents()) { - wc_owner_.reset(CreateWebContents(browser_context_)); + wc_owner_ = CreateWebContents(browser_context_); wc_owner_->SetDelegate(this); SetWebContents(wc_owner_.get()); } @@ -364,7 +386,6 @@ void WebView::UpdateCrashedOverlayView() { base::TERMINATION_STATUS_STILL_RUNNING && crashed_overlay_view_) { SetFocusBehavior(FocusBehavior::NEVER); - holder_->SetVisible(false); crashed_overlay_view_->SetVisible(true); return; } @@ -374,7 +395,6 @@ void WebView::UpdateCrashedOverlayView() { if (crashed_overlay_view_) crashed_overlay_view_->SetVisible(false); - holder_->SetVisible(true); } void WebView::NotifyAccessibilityWebContentsChanged() { @@ -382,12 +402,11 @@ void WebView::NotifyAccessibilityWebContentsChanged() { NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false); } -content::WebContents* WebView::CreateWebContents( - content::BrowserContext* browser_context) { - content::WebContents* contents = NULL; - if (ViewsDelegate::GetInstance()) { - contents = - ViewsDelegate::GetInstance()->CreateWebContents(browser_context, NULL); +std::unique_ptr<content::WebContents> WebView::CreateWebContents( + content::BrowserContext* browser_context) { + std::unique_ptr<content::WebContents> contents; + if (*GetCreatorForTesting()) { + contents = GetCreatorForTesting()->Run(browser_context); } if (!contents) { diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h index 99affc147bc..87d9a1e7259 100644 --- a/chromium/ui/views/controls/webview/webview.h +++ b/chromium/ui/views/controls/webview/webview.h @@ -9,6 +9,7 @@ #include <memory> +#include "base/callback.h" #include "base/macros.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" @@ -94,6 +95,20 @@ class WEBVIEW_EXPORT WebView : public View, const char* GetClassName() const override; NativeViewHost* holder() { return holder_; } + using WebContentsCreator = + base::RepeatingCallback<std::unique_ptr<content::WebContents>( + content::BrowserContext*)>; + + // An instance of this class registers a WebContentsCreator on construction + // and deregisters the WebContentsCreator on destruction. + class WEBVIEW_EXPORT ScopedWebContentsCreatorForTesting { + public: + explicit ScopedWebContentsCreatorForTesting(WebContentsCreator creator); + ~ScopedWebContentsCreatorForTesting(); + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedWebContentsCreatorForTesting); + }; protected: // Swaps the owned WebContents |wc_owner_| with |new_web_contents|. Returns @@ -154,7 +169,7 @@ class WEBVIEW_EXPORT WebView : public View, // Create a regular or test web contents (based on whether we're running // in a unit test or not). - content::WebContents* CreateWebContents( + std::unique_ptr<content::WebContents> CreateWebContents( content::BrowserContext* browser_context); NativeViewHost* const holder_; diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc index b679af9d8d2..f1d0ce7758d 100644 --- a/chromium/ui/views/controls/webview/webview_unittest.cc +++ b/chromium/ui/views/controls/webview/webview_unittest.cc @@ -33,29 +33,11 @@ namespace views { namespace { -// Provides functionality to create a test WebContents. -class WebViewTestViewsDelegate : public views::TestViewsDelegate { - public: - WebViewTestViewsDelegate() {} - ~WebViewTestViewsDelegate() override {} - - // Overriden from TestViewsDelegate. - content::WebContents* CreateWebContents( - content::BrowserContext* browser_context, - content::SiteInstance* site_instance) override { - return content::WebContentsTester::CreateTestWebContents(browser_context, - site_instance); - } - - private: - DISALLOW_COPY_AND_ASSIGN(WebViewTestViewsDelegate); -}; - // Provides functionality to observe events on a WebContents like // OnVisibilityChanged/WebContentsDestroyed. class WebViewTestWebContentsObserver : public content::WebContentsObserver { public: - WebViewTestWebContentsObserver(content::WebContents* web_contents) + explicit WebViewTestWebContentsObserver(content::WebContents* web_contents) : web_contents_(web_contents), was_shown_(false), shown_count_(0), @@ -146,8 +128,19 @@ class WebViewUnitTest : public views::test::WidgetTest { ~WebViewUnitTest() override {} + std::unique_ptr<content::WebContents> CreateWebContentsForWebView( + content::BrowserContext* browser_context) { + return content::WebContentsTester::CreateTestWebContents(browser_context, + nullptr); + } + void SetUp() override { - set_views_delegate(base::WrapUnique(new WebViewTestViewsDelegate)); + views::WebView::WebContentsCreator creator = base::BindRepeating( + &WebViewUnitTest::CreateWebContentsForWebView, base::Unretained(this)); + scoped_web_contents_creator_ = + std::make_unique<views::WebView::ScopedWebContentsCreatorForTesting>( + creator); + set_views_delegate(base::WrapUnique(new views::TestViewsDelegate)); browser_context_.reset(new content::TestBrowserContext); WidgetTest::SetUp(); // Set the test content browser client to avoid pulling in needless @@ -171,6 +164,7 @@ class WebViewUnitTest : public views::test::WidgetTest { } void TearDown() override { + scoped_web_contents_creator_.reset(); top_level_widget_->Close(); // Deletes all children and itself. RunPendingMessages(); @@ -187,18 +181,16 @@ class WebViewUnitTest : public views::test::WidgetTest { NativeViewHost* holder() const { return web_view_->holder_; } std::unique_ptr<content::WebContents> CreateWebContents() const { - return base::WrapUnique(content::WebContents::Create( - content::WebContents::CreateParams(browser_context_.get()))); + return content::WebContents::Create( + content::WebContents::CreateParams(browser_context_.get())); } private: - // TODO(lukasza): https://crbug.com/832100: Move the factory into - // TestingProfile, so individual tests don't need to worry about it. - content::ScopedMockRenderProcessHostFactory process_factory_; - content::TestBrowserThreadBundle test_browser_thread_bundle_; std::unique_ptr<content::TestBrowserContext> browser_context_; content::TestContentBrowserClient test_browser_client_; + std::unique_ptr<views::WebView::ScopedWebContentsCreatorForTesting> + scoped_web_contents_creator_; Widget* top_level_widget_ = nullptr; WebView* web_view_ = nullptr; diff --git a/chromium/ui/views/debug_utils.cc b/chromium/ui/views/debug_utils.cc index b451c2da50e..0517f1d098f 100644 --- a/chromium/ui/views/debug_utils.cc +++ b/chromium/ui/views/debug_utils.cc @@ -9,6 +9,11 @@ #include "base/logging.h" #include "ui/views/view.h" +#if !defined(NDEBUG) +#include "ui/gfx/geometry/angle_conversions.h" +#include "ui/gfx/transform_util.h" +#endif + namespace views { namespace { void PrintViewHierarchyImp(const View* view, @@ -53,6 +58,92 @@ void PrintFocusHierarchyImp(const View* view, if (next_focusable) PrintFocusHierarchyImp(next_focusable, indent, out); } + +#if !defined(NDEBUG) +std::string PrintViewGraphImpl(const View* view) { + // 64-bit pointer = 16 bytes of hex + "0x" + '\0' = 19. + const size_t kMaxPointerStringLength = 19; + + std::string result; + + // Node characteristics. + char p[kMaxPointerStringLength]; + + const std::string class_name(view->GetClassName()); + size_t base_name_index = class_name.find_last_of('/'); + if (base_name_index == std::string::npos) + base_name_index = 0; + else + base_name_index++; + + constexpr size_t kBoundsBufferSize = 512; + char bounds_buffer[kBoundsBufferSize]; + + // Information about current node. + base::snprintf(p, kBoundsBufferSize, "%p", view); + result.append(" N"); + result.append(p + 2); + result.append(" [label=\""); + + result.append(class_name.substr(base_name_index).c_str()); + + base::snprintf(bounds_buffer, kBoundsBufferSize, + "\\n bounds: (%d, %d), (%dx%d)", view->bounds().x(), + view->bounds().y(), view->bounds().width(), + view->bounds().height()); + result.append(bounds_buffer); + + gfx::DecomposedTransform decomp; + if (!view->GetTransform().IsIdentity() && + gfx::DecomposeTransform(&decomp, view->GetTransform())) { + base::snprintf(bounds_buffer, kBoundsBufferSize, + "\\n translation: (%f, %f)", decomp.translate[0], + decomp.translate[1]); + result.append(bounds_buffer); + + base::snprintf(bounds_buffer, kBoundsBufferSize, "\\n rotation: %3.2f", + gfx::RadToDeg(std::acos(decomp.quaternion.w()) * 2)); + result.append(bounds_buffer); + + base::snprintf(bounds_buffer, kBoundsBufferSize, + "\\n scale: (%2.4f, %2.4f)", decomp.scale[0], + decomp.scale[1]); + result.append(bounds_buffer); + } + + result.append("\""); + if (!view->parent()) + result.append(", shape=box"); + if (view->layer()) { + if (view->layer()->has_external_content()) + result.append(", color=green"); + else + result.append(", color=red"); + + if (view->layer()->fills_bounds_opaquely()) + result.append(", style=filled"); + } + result.append("]\n"); + + // Link to parent. + if (view->parent()) { + char pp[kMaxPointerStringLength]; + + base::snprintf(pp, kMaxPointerStringLength, "%p", view->parent()); + result.append(" N"); + result.append(pp + 2); + result.append(" -> N"); + result.append(p + 2); + result.append("\n"); + } + + for (int i = 0; i < view->child_count(); ++i) + result.append(PrintViewGraphImpl(view->child_at(i))); + + return result; +} +#endif + } // namespace void PrintViewHierarchy(const View* view) { @@ -71,4 +162,10 @@ void PrintFocusHierarchy(const View* view) { LOG(ERROR) << out.str(); } +#if !defined(NDEBUG) +std::string PrintViewGraph(const View* view) { + return "digraph {\n" + PrintViewGraphImpl(view) + "}\n"; +} +#endif + } // namespace views diff --git a/chromium/ui/views/debug_utils.h b/chromium/ui/views/debug_utils.h index feb4aaac362..3051359a207 100644 --- a/chromium/ui/views/debug_utils.h +++ b/chromium/ui/views/debug_utils.h @@ -5,6 +5,8 @@ #ifndef UI_VIEWS_DEBUG_UTILS_H_ #define UI_VIEWS_DEBUG_UTILS_H_ +#include <string> + #include "ui/views/views_export.h" namespace views { @@ -17,6 +19,13 @@ VIEWS_EXPORT void PrintViewHierarchy(const View* view); // Log the focus traversal hierarchy. VIEWS_EXPORT void PrintFocusHierarchy(const View* view); +#if !defined(NDEBUG) +// Returns string containing a graph of the views hierarchy in graphViz DOT +// language (http://graphviz.org/). Can be called within debugger and saved +// to a file to compile/view. +VIEWS_EXPORT std::string PrintViewGraph(const View* view); +#endif + } // namespace views #endif // UI_VIEWS_DEBUG_UTILS_H_ diff --git a/chromium/ui/views/examples/button_sticker_sheet.cc b/chromium/ui/views/examples/button_sticker_sheet.cc index b395387d61a..f1d0099ec9e 100644 --- a/chromium/ui/views/examples/button_sticker_sheet.cc +++ b/chromium/ui/views/examples/button_sticker_sheet.cc @@ -116,11 +116,6 @@ void ButtonStickerSheet::CreateExampleView(View* container) { AddLabelledRowToGridLayout(layout, "Pressed", {primary, secondary}); MakeButtonsInState(&primary, &secondary, this, Button::STATE_DISABLED); AddLabelledRowToGridLayout(layout, "Disabled", {primary, secondary}); - - MakeButtonsInState(&primary, &secondary, this, Button::STATE_NORMAL); - primary->OnFocus(); - secondary->OnFocus(); - AddLabelledRowToGridLayout(layout, "Focused", {primary, secondary}); } void ButtonStickerSheet::ButtonPressed(Button* button, const ui::Event& event) { diff --git a/chromium/ui/views/examples/dialog_example.cc b/chromium/ui/views/examples/dialog_example.cc index 97737f1a809..48426144dfa 100644 --- a/chromium/ui/views/examples/dialog_example.cc +++ b/chromium/ui/views/examples/dialog_example.cc @@ -59,7 +59,7 @@ class DialogExample::Delegate : public virtual DialogType { } // DialogDelegate: - View* CreateExtraView() { + View* CreateExtraView() override { if (!parent_->has_extra_button_->checked()) return nullptr; return MdTextButton::CreateSecondaryUiButton( diff --git a/chromium/ui/views/examples/examples_main.cc b/chromium/ui/views/examples/examples_main.cc index dd63355096e..9ddb537f8c5 100644 --- a/chromium/ui/views/examples/examples_main.cc +++ b/chromium/ui/views/examples/examples_main.cc @@ -83,7 +83,7 @@ int main(int argc, char** argv) { ui::RegisterPathProvider(); base::FilePath ui_test_pak_path; - CHECK(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); + CHECK(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); base::DiscardableMemoryAllocator::SetInstance( diff --git a/chromium/ui/views/examples/textfield_example.cc b/chromium/ui/views/examples/textfield_example.cc index 6e4c5fe3ab0..8217a52d909 100644 --- a/chromium/ui/views/examples/textfield_example.cc +++ b/chromium/ui/views/examples/textfield_example.cc @@ -30,6 +30,7 @@ TextfieldExample::TextfieldExample() disabled_(nullptr), read_only_(nullptr), invalid_(nullptr), + rtl_(nullptr), show_password_(nullptr), clear_all_(nullptr), append_(nullptr), @@ -51,6 +52,8 @@ void TextfieldExample::CreateExampleView(View* container) { read_only_->SetText(ASCIIToUTF16("read only")); invalid_ = new Textfield(); invalid_->SetInvalid(true); + rtl_ = new Textfield(); + rtl_->ChangeTextDirectionAndLayoutAlignment(base::i18n::RIGHT_TO_LEFT); show_password_ = new LabelButton(this, ASCIIToUTF16("Show password")); set_background_ = new LabelButton(this, ASCIIToUTF16("Set non-default background")); @@ -81,6 +84,7 @@ void TextfieldExample::CreateExampleView(View* container) { MakeRow(new Label(ASCIIToUTF16("Disabled:")), disabled_); MakeRow(new Label(ASCIIToUTF16("Read Only:")), read_only_); MakeRow(new Label(ASCIIToUTF16("Invalid:")), invalid_); + MakeRow(new Label(ASCIIToUTF16("RTL:")), rtl_); MakeRow(new Label(ASCIIToUTF16("Name:")), nullptr); MakeRow(show_password_, nullptr); MakeRow(set_background_, nullptr); @@ -124,18 +128,21 @@ void TextfieldExample::ButtonPressed(Button* sender, const ui::Event& event) { disabled_->SetText(empty); read_only_->SetText(empty); invalid_->SetText(empty); + rtl_->SetText(empty); } else if (sender == append_) { name_->AppendText(ASCIIToUTF16("[append]")); password_->AppendText(ASCIIToUTF16("[append]")); disabled_->SetText(ASCIIToUTF16("[append]")); read_only_->AppendText(ASCIIToUTF16("[append]")); invalid_->AppendText(ASCIIToUTF16("[append]")); + rtl_->AppendText(ASCIIToUTF16("[append]")); } else if (sender == set_) { name_->SetText(ASCIIToUTF16("[set]")); password_->SetText(ASCIIToUTF16("[set]")); disabled_->SetText(ASCIIToUTF16("[set]")); read_only_->SetText(ASCIIToUTF16("[set]")); invalid_->SetText(ASCIIToUTF16("[set]")); + rtl_->SetText(ASCIIToUTF16("[set]")); } else if (sender == set_style_) { if (!name_->text().empty()) { name_->SetColor(SK_ColorGREEN); diff --git a/chromium/ui/views/examples/textfield_example.h b/chromium/ui/views/examples/textfield_example.h index 730797f9c66..b2b5fd4a44f 100644 --- a/chromium/ui/views/examples/textfield_example.h +++ b/chromium/ui/views/examples/textfield_example.h @@ -47,6 +47,7 @@ class VIEWS_EXAMPLES_EXPORT TextfieldExample : public ExampleBase, Textfield* disabled_; Textfield* read_only_; Textfield* invalid_; + Textfield* rtl_; // Various buttons to control textfield. LabelButton* show_password_; diff --git a/chromium/ui/views/focus/focus_manager.cc b/chromium/ui/views/focus/focus_manager.cc index fb3b9016097..6847bae9d64 100644 --- a/chromium/ui/views/focus/focus_manager.cc +++ b/chromium/ui/views/focus/focus_manager.cc @@ -19,6 +19,7 @@ #include "ui/views/focus/focus_search.h" #include "ui/views/focus/widget_focus_manager.h" #include "ui/views/view.h" +#include "ui/views/view_properties.h" #include "ui/views/view_tracker.h" #include "ui/views/widget/root_view.h" #include "ui/views/widget/widget.h" @@ -113,7 +114,7 @@ bool FocusManager::ContainsView(View* view) { } void FocusManager::AdvanceFocus(bool reverse) { - View* v = GetNextFocusableView(focused_view_, NULL, reverse, false); + View* v = GetNextFocusableView(focused_view_, nullptr, reverse, false); // Note: Do not skip this next block when v == focused_view_. If the user // tabs past the last focusable element in a webpage, we'll get here, and if // the TabContentsContainerView is the only focusable view (possible in @@ -124,8 +125,14 @@ void FocusManager::AdvanceFocus(bool reverse) { v->AboutToRequestFocusFromTabTraversal(reverse); // AboutToRequestFocusFromTabTraversal() may have changed focus. If it did, // don't change focus again. - if (focused_view == focused_view_) - SetFocusedViewWithReason(v, kReasonFocusTraversal); + if (focused_view != focused_view_) + return; + + // Note that GetNextFocusableView may have returned a View in a different + // FocusManager. + DCHECK(v->GetWidget()); + v->GetWidget()->GetFocusManager()->SetFocusedViewWithReason( + v, kReasonFocusTraversal); } } @@ -199,9 +206,9 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, DCHECK(!focused_view_ || ContainsView(focused_view_)) << " focus_view=" << focused_view_; - FocusTraversable* focus_traversable = NULL; + FocusTraversable* focus_traversable = nullptr; - View* starting_view = NULL; + View* starting_view = nullptr; if (original_starting_view) { // Search up the containment hierarchy to see if a view is acting as // a pane, and wants to implement its own focus traversable to keep @@ -254,9 +261,15 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, FocusTraversable* new_focus_traversable = nullptr; View* new_starting_view = nullptr; // When we are going backward, the parent view might gain the next focus. - bool check_starting_view = reverse; + auto check_starting_view = + reverse ? FocusSearch::StartingViewPolicy::kCheckStartingView + : FocusSearch::StartingViewPolicy::kSkipStartingView; v = parent_focus_traversable->GetFocusSearch()->FindNextFocusableView( - starting_view, reverse, FocusSearch::UP, check_starting_view, + starting_view, + reverse ? FocusSearch::SearchDirection::kBackwards + : FocusSearch::SearchDirection::kForwards, + FocusSearch::TraversalDirection::kUp, check_starting_view, + FocusSearch::AnchoredDialogPolicy::kSkipAnchoredDialog, &new_focus_traversable, &new_starting_view); if (new_focus_traversable) { @@ -281,7 +294,7 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, return nullptr; // Easy, just clear the selection and press tab again. - // By calling with NULL as the starting view, we'll start from either + // By calling with nullptr as the starting view, we'll start from either // the starting views widget or |widget_|. Widget* widget = original_starting_view->GetWidget(); if (widget->widget_delegate()->ShouldAdvanceFocusToTopLevelWidget()) @@ -303,6 +316,7 @@ void FocusManager::SetFocusedViewWithReason(View* view, FocusChangeReason reason) { if (focused_view_ == view) return; + // TODO(oshima|achuith): This is to diagnose crbug.com/687232. // Change this to DCHECK once it's resolved. CHECK(!view || ContainsView(view)); @@ -345,13 +359,16 @@ void FocusManager::SetFocusedViewWithReason(View* view, for (FocusChangeListener& observer : focus_change_listeners_) observer.OnDidChangeFocus(old_focused_view, focused_view_); + + if (delegate_) + delegate_->OnDidChangeFocus(old_focused_view, focused_view_); } void FocusManager::ClearFocus() { - // SetFocusedView(NULL) is going to clear out the stored view to. We need to - // persist it in this case. + // SetFocusedView(nullptr) is going to clear out the stored view to. We need + // to persist it in this case. views::View* focused_view = GetStoredFocusView(); - SetFocusedView(NULL); + SetFocusedView(nullptr); ClearNativeFocus(); SetStoredFocusView(focused_view); } @@ -373,8 +390,8 @@ void FocusManager::AdvanceFocusIfNecessary() { void FocusManager::StoreFocusedView(bool clear_native_focus) { View* focused_view = focused_view_; - // Don't do anything if no focused view. Storing the view (which is NULL), in - // this case, would clobber the view that was previously saved. + // Don't do anything if no focused view. Storing the view (which is nullptr), + // in this case, would clobber the view that was previously saved. if (!focused_view_) return; @@ -389,7 +406,7 @@ void FocusManager::StoreFocusedView(bool clear_native_focus) { // ClearFocus() also stores the focused view. ClearFocus(); } else { - SetFocusedView(NULL); + SetFocusedView(nullptr); SetStoredFocusView(focused_view); } @@ -437,21 +454,32 @@ View* FocusManager::GetStoredFocusView() { View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable, View* starting_view, bool reverse) { - FocusTraversable* new_focus_traversable = NULL; - View* new_starting_view = NULL; + FocusTraversable* new_focus_traversable = nullptr; + View* new_starting_view = nullptr; + auto can_go_into_anchored_dialog = + FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog; View* v = focus_traversable->GetFocusSearch()->FindNextFocusableView( - starting_view, reverse, FocusSearch::DOWN, false, &new_focus_traversable, - &new_starting_view); + starting_view, + reverse ? FocusSearch::SearchDirection::kBackwards + : FocusSearch::SearchDirection::kForwards, + FocusSearch::TraversalDirection::kDown, + FocusSearch::StartingViewPolicy::kSkipStartingView, + can_go_into_anchored_dialog, &new_focus_traversable, &new_starting_view); // Let's go down the FocusTraversable tree as much as we can. while (new_focus_traversable) { DCHECK(!v); focus_traversable = new_focus_traversable; - new_focus_traversable = NULL; - starting_view = NULL; + new_focus_traversable = nullptr; + starting_view = nullptr; v = focus_traversable->GetFocusSearch()->FindNextFocusableView( - starting_view, reverse, FocusSearch::DOWN, false, - &new_focus_traversable, &new_starting_view); + starting_view, + reverse ? FocusSearch::SearchDirection::kBackwards + : FocusSearch::SearchDirection::kForwards, + FocusSearch::TraversalDirection::kDown, + FocusSearch::StartingViewPolicy::kSkipStartingView, + can_go_into_anchored_dialog, &new_focus_traversable, + &new_starting_view); } return v; } @@ -485,7 +513,8 @@ bool FocusManager::HasPriorityHandler( // static bool FocusManager::IsTabTraversalKeyEvent(const ui::KeyEvent& key_event) { - return key_event.key_code() == ui::VKEY_TAB && !key_event.IsControlDown(); + return key_event.key_code() == ui::VKEY_TAB && + (!key_event.IsControlDown() && !key_event.IsAltDown()); } void FocusManager::ViewRemoved(View* removed) { @@ -495,7 +524,7 @@ void FocusManager::ViewRemoved(View* removed) { // be called while the top level widget is being destroyed. DCHECK(removed); if (removed->Contains(focused_view_)) - SetFocusedView(NULL); + SetFocusedView(nullptr); } void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) { diff --git a/chromium/ui/views/focus/focus_manager_delegate.h b/chromium/ui/views/focus/focus_manager_delegate.h index a4d9186f22f..c57571bbc4d 100644 --- a/chromium/ui/views/focus/focus_manager_delegate.h +++ b/chromium/ui/views/focus/focus_manager_delegate.h @@ -13,6 +13,8 @@ class Accelerator; namespace views { +class View; + // Delegate interface for views::FocusManager. class VIEWS_EXPORT FocusManagerDelegate { public: @@ -25,6 +27,9 @@ class VIEWS_EXPORT FocusManagerDelegate { // target, and so on. // Returns true if an accelerator was activated. virtual bool ProcessAccelerator(const ui::Accelerator& accelerator) = 0; + + // Called after focus state has changed. + virtual void OnDidChangeFocus(View* focused_before, View* focused_now) = 0; }; } // namespace views diff --git a/chromium/ui/views/focus/focus_manager_unittest.cc b/chromium/ui/views/focus/focus_manager_unittest.cc index 809b6d8a3ac..3c1278fe2f3 100644 --- a/chromium/ui/views/focus/focus_manager_unittest.cc +++ b/chromium/ui/views/focus/focus_manager_unittest.cc @@ -16,11 +16,13 @@ #include "ui/base/accelerators/accelerator.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/focus/focus_manager_delegate.h" #include "ui/views/focus/focus_manager_factory.h" #include "ui/views/focus/widget_focus_manager.h" #include "ui/views/test/focus_manager_test.h" #include "ui/views/test/widget_test.h" +#include "ui/views/view_properties.h" #include "ui/views/widget/widget.h" namespace views { @@ -861,6 +863,19 @@ class AdvanceFocusWidgetDelegate : public WidgetDelegate { DISALLOW_COPY_AND_ASSIGN(AdvanceFocusWidgetDelegate); }; +class TestBubbleDialogDelegateView : public BubbleDialogDelegateView { + public: + TestBubbleDialogDelegateView(View* anchor) + : BubbleDialogDelegateView(anchor, BubbleBorder::NONE) {} + ~TestBubbleDialogDelegateView() override {} + + // ui::DialogModel override. + int GetDialogButtons() const override { return 0; } + + private: + DISALLOW_COPY_AND_ASSIGN(TestBubbleDialogDelegateView); +}; + } // namespace // Verifies focus wrapping happens in the same widget. @@ -913,4 +928,123 @@ TEST_F(FocusManagerTest, AdvanceFocusStaysInWidget) { EXPECT_EQ(widget_view, GetFocusManager()->GetFocusedView()); } +TEST_F(FocusManagerTest, NavigateIntoAnchoredDialog) { + // The parent Widget has four focusable views. A child widget dialog has + // two focusable views, and it's anchored to the 3rd parent view. Ensure + // that focus traverses into the anchored dialog after the 3rd parent + // view, and then back to the 4th parent view. + + View* parent1 = new View(); + View* parent2 = new View(); + View* parent3 = new View(); + View* parent4 = new View(); + + parent1->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent2->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent3->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent4->SetFocusBehavior(View::FocusBehavior::ALWAYS); + + GetWidget()->GetRootView()->AddChildView(parent1); + GetWidget()->GetRootView()->AddChildView(parent2); + GetWidget()->GetRootView()->AddChildView(parent3); + GetWidget()->GetRootView()->AddChildView(parent4); + + BubbleDialogDelegateView* bubble_delegate = + new TestBubbleDialogDelegateView(parent3); + test::WidgetTest::WidgetAutoclosePtr bubble_widget( + BubbleDialogDelegateView::CreateBubble(bubble_delegate)); + bubble_delegate->EnableFocusTraversalFromAnchorView(); + View* child1 = new View(); + View* child2 = new View(); + child1->SetFocusBehavior(View::FocusBehavior::ALWAYS); + child2->SetFocusBehavior(View::FocusBehavior::ALWAYS); + bubble_widget->GetRootView()->AddChildView(child1); + bubble_widget->GetRootView()->AddChildView(child2); + bubble_delegate->set_close_on_deactivate(false); + bubble_widget->Show(); + + parent1->RequestFocus(); + + // Navigate forwards + GetWidget()->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(parent2->HasFocus()); + GetWidget()->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(parent3->HasFocus()); + GetWidget()->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(child1->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(child2->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(parent4->HasFocus()); + + // Navigate backwards + GetWidget()->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(child2->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(child1->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(parent3->HasFocus()); +} + +TEST_F(FocusManagerTest, AnchoredDialogOnContainerView) { + // The parent Widget has four focusable views, with the middle two views + // inside of a non-focusable grouping View. A child widget dialog has + // two focusable views, and it's anchored to the group View. Ensure + // that focus traverses into the anchored dialog after the 3rd parent + // view, and then back to the 4th parent view. + + View* parent1 = new View(); + View* parent2 = new View(); + View* parent3 = new View(); + View* parent4 = new View(); + View* parent_group = new View(); + + parent1->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent2->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent3->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent4->SetFocusBehavior(View::FocusBehavior::ALWAYS); + + GetWidget()->GetRootView()->AddChildView(parent1); + GetWidget()->GetRootView()->AddChildView(parent_group); + parent_group->AddChildView(parent2); + parent_group->AddChildView(parent3); + GetWidget()->GetRootView()->AddChildView(parent4); + + BubbleDialogDelegateView* bubble_delegate = + new TestBubbleDialogDelegateView(parent_group); + test::WidgetTest::WidgetAutoclosePtr bubble_widget( + BubbleDialogDelegateView::CreateBubble(bubble_delegate)); + bubble_delegate->EnableFocusTraversalFromAnchorView(); + View* child1 = new View(); + View* child2 = new View(); + child1->SetFocusBehavior(View::FocusBehavior::ALWAYS); + child2->SetFocusBehavior(View::FocusBehavior::ALWAYS); + bubble_widget->GetRootView()->AddChildView(child1); + bubble_widget->GetRootView()->AddChildView(child2); + bubble_delegate->set_close_on_deactivate(false); + bubble_widget->Show(); + + parent1->RequestFocus(); + + // Navigate forwards + GetWidget()->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(parent2->HasFocus()); + GetWidget()->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(parent3->HasFocus()); + GetWidget()->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(child1->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(child2->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(parent4->HasFocus()); + + // Navigate backwards + GetWidget()->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(child2->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(child1->HasFocus()); + bubble_widget->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(parent3->HasFocus()); +} + } // namespace views diff --git a/chromium/ui/views/focus/focus_search.cc b/chromium/ui/views/focus/focus_search.cc index e91a701fd10..4c33f0b266e 100644 --- a/chromium/ui/views/focus/focus_search.cc +++ b/chromium/ui/views/focus/focus_search.cc @@ -2,10 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "ui/views/focus/focus_search.h" + #include "base/logging.h" +#include "ui/views/bubble/bubble_dialog_delegate.h" #include "ui/views/focus/focus_manager.h" -#include "ui/views/focus/focus_search.h" #include "ui/views/view.h" +#include "ui/views/view_properties.h" #include "ui/views/widget/widget.h" namespace views { @@ -22,19 +25,21 @@ FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode) #endif } -View* FocusSearch::FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view) { - *focus_traversable = NULL; - *focus_traversable_view = NULL; +View* FocusSearch::FindNextFocusableView( + View* starting_view, + FocusSearch::SearchDirection search_direction, + FocusSearch::TraversalDirection traversal_direction, + FocusSearch::StartingViewPolicy check_starting_view, + FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + *focus_traversable = nullptr; + *focus_traversable_view = nullptr; if (!root_->has_children()) { NOTREACHED(); // Nothing to focus on here. - return NULL; + return nullptr; } View* initial_starting_view = starting_view; @@ -44,43 +49,42 @@ View* FocusSearch::FindNextFocusableView(View* starting_view, if (!starting_view) { // Default to the first/last child - starting_view = reverse ? root_->child_at(root_->child_count() - 1) : - root_->child_at(0); + starting_view = search_direction == SearchDirection::kBackwards + ? root_->child_at(root_->child_count() - 1) + : root_->child_at(0); // If there was no starting view, then the one we select is a potential // focus candidate. - check_starting_view = true; + check_starting_view = StartingViewPolicy::kCheckStartingView; } else { // The starting view should be a direct or indirect child of the root. DCHECK(Contains(root_, starting_view)); } - View* v = NULL; - if (!reverse) { - v = FindNextFocusableViewImpl(starting_view, check_starting_view, - true, - (direction == DOWN), - starting_view_group, - focus_traversable, - focus_traversable_view); + View* v = nullptr; + if (search_direction == SearchDirection::kForwards) { + v = FindNextFocusableViewImpl( + starting_view, check_starting_view, true, + (traversal_direction == TraversalDirection::kDown), starting_view_group, + focus_traversable, focus_traversable_view); } else { // If the starting view is focusable, we don't want to go down, as we are // traversing the view hierarchy tree bottom-up. - bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view); - v = FindPreviousFocusableViewImpl(starting_view, check_starting_view, - true, - can_go_down, - starting_view_group, - focus_traversable, + bool can_go_down = (traversal_direction == TraversalDirection::kDown) && + !IsFocusable(starting_view); + v = FindPreviousFocusableViewImpl(starting_view, check_starting_view, true, + can_go_down, can_go_into_anchored_dialog, + starting_view_group, focus_traversable, focus_traversable_view); } // Don't set the focus to something outside of this view hierarchy. if (v && v != root_ && !Contains(root_, v)) - v = NULL; + v = nullptr; - // If |cycle_| is true, prefer to keep cycling rather than returning NULL. + // If |cycle_| is true, prefer to keep cycling rather than returning nullptr. if (cycle_ && !v && initial_starting_view) { - v = FindNextFocusableView(NULL, reverse, direction, check_starting_view, + v = FindNextFocusableView(nullptr, search_direction, traversal_direction, + check_starting_view, can_go_into_anchored_dialog, focus_traversable, focus_traversable_view); DCHECK(IsFocusable(v)); return v; @@ -93,10 +97,10 @@ View* FocusSearch::FindNextFocusableView(View* starting_view, } if (*focus_traversable) { DCHECK(*focus_traversable_view); - return NULL; + return nullptr; } // Nothing found. - return NULL; + return nullptr; } bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) { @@ -131,7 +135,7 @@ View* FocusSearch::FindSelectedViewForGroup(View* view) { } View* FocusSearch::GetParent(View* v) { - return Contains(root_, v) ? v->parent() : NULL; + return Contains(root_, v) ? v->parent() : nullptr; } bool FocusSearch::Contains(View* root, const View* v) { @@ -148,13 +152,13 @@ bool FocusSearch::Contains(View* root, const View* v) { // with a right sibling and start the search from there. View* FocusSearch::FindNextFocusableViewImpl( View* starting_view, - bool check_starting_view, + FocusSearch::StartingViewPolicy check_starting_view, bool can_go_up, bool can_go_down, int skip_group_id, FocusTraversable** focus_traversable, View** focus_traversable_view) { - if (check_starting_view) { + if (check_starting_view == StartingViewPolicy::kCheckStartingView) { if (IsViewFocusableCandidate(starting_view, skip_group_id)) { View* v = FindSelectedViewForGroup(starting_view); // The selected view might not be focusable (if it is disabled for @@ -166,29 +170,37 @@ View* FocusSearch::FindNextFocusableViewImpl( *focus_traversable = starting_view->GetFocusTraversable(); if (*focus_traversable) { *focus_traversable_view = starting_view; - return NULL; + return nullptr; } } // First let's try the left child. if (can_go_down) { if (starting_view->has_children()) { - View* v = FindNextFocusableViewImpl(starting_view->child_at(0), - true, false, true, skip_group_id, - focus_traversable, - focus_traversable_view); + View* v = FindNextFocusableViewImpl( + starting_view->child_at(0), StartingViewPolicy::kCheckStartingView, + false, true, skip_group_id, focus_traversable, + focus_traversable_view); if (v || *focus_traversable) return v; + } else { + // Check to see if we should navigate into a dialog anchored at this view. + BubbleDialogDelegateView* bubble = + starting_view->GetProperty(kAnchoredDialogKey); + if (bubble) { + *focus_traversable = bubble->GetWidget()->GetFocusTraversable(); + *focus_traversable_view = starting_view; + return nullptr; + } } } // Then try the right sibling. View* sibling = starting_view->GetNextFocusableView(); if (sibling) { - View* v = FindNextFocusableViewImpl(sibling, - true, false, true, skip_group_id, - focus_traversable, - focus_traversable_view); + View* v = FindNextFocusableViewImpl( + sibling, FocusSearch::StartingViewPolicy::kCheckStartingView, false, + true, skip_group_id, focus_traversable, focus_traversable_view); if (v || *focus_traversable) return v; } @@ -197,20 +209,26 @@ View* FocusSearch::FindNextFocusableViewImpl( if (can_go_up) { View* parent = GetParent(starting_view); while (parent && parent != root_) { + BubbleDialogDelegateView* bubble = + parent->GetProperty(kAnchoredDialogKey); + if (bubble) { + *focus_traversable = bubble->GetWidget()->GetFocusTraversable(); + *focus_traversable_view = starting_view; + return nullptr; + } + sibling = parent->GetNextFocusableView(); if (sibling) { - return FindNextFocusableViewImpl(sibling, - true, true, true, - skip_group_id, - focus_traversable, - focus_traversable_view); + return FindNextFocusableViewImpl( + sibling, StartingViewPolicy::kCheckStartingView, true, true, + skip_group_id, focus_traversable, focus_traversable_view); } parent = GetParent(parent); } } // We found nothing. - return NULL; + return nullptr; } // Strategy for finding the previous focusable view: @@ -221,12 +239,24 @@ View* FocusSearch::FindNextFocusableViewImpl( // down). View* FocusSearch::FindPreviousFocusableViewImpl( View* starting_view, - bool check_starting_view, + FocusSearch::StartingViewPolicy check_starting_view, bool can_go_up, bool can_go_down, + FocusSearch::AnchoredDialogPolicy can_go_into_anchored_dialog, int skip_group_id, FocusTraversable** focus_traversable, View** focus_traversable_view) { + // Normally when we navigate to a FocusTraversableParent, can_go_down is + // false so we don't navigate back in. However, if we just navigated out + // of an anchored dialog, allow going down in order to navigate into + // children of |starting_view| next. + if (starting_view->GetProperty(kAnchoredDialogKey) && + can_go_into_anchored_dialog == + AnchoredDialogPolicy::kSkipAnchoredDialog && + !can_go_down) { + can_go_down = true; + } + // Let's go down and right as much as we can. if (can_go_down) { // Before we go into the direct children, we have to check if this view has @@ -234,16 +264,30 @@ View* FocusSearch::FindPreviousFocusableViewImpl( *focus_traversable = starting_view->GetFocusTraversable(); if (*focus_traversable) { *focus_traversable_view = starting_view; - return NULL; + return nullptr; + } + + // Check to see if we should navigate into a dialog anchored at this view. + if (can_go_into_anchored_dialog == + AnchoredDialogPolicy::kCanGoIntoAnchoredDialog) { + BubbleDialogDelegateView* bubble = + starting_view->GetProperty(kAnchoredDialogKey); + if (bubble) { + *focus_traversable = bubble->GetWidget()->GetFocusTraversable(); + *focus_traversable_view = starting_view; + return nullptr; + } } + can_go_into_anchored_dialog = + AnchoredDialogPolicy::kCanGoIntoAnchoredDialog; if (starting_view->has_children()) { View* view = starting_view->child_at(starting_view->child_count() - 1); - View* v = FindPreviousFocusableViewImpl(view, true, false, true, - skip_group_id, - focus_traversable, - focus_traversable_view); + View* v = FindPreviousFocusableViewImpl( + view, StartingViewPolicy::kCheckStartingView, false, true, + can_go_into_anchored_dialog, skip_group_id, focus_traversable, + focus_traversable_view); if (v || *focus_traversable) return v; } @@ -251,7 +295,7 @@ View* FocusSearch::FindPreviousFocusableViewImpl( // Then look at this view. Here, we do not need to see if the view has // a FocusTraversable, since we do not want to go down any more. - if (check_starting_view && + if (check_starting_view == StartingViewPolicy::kCheckStartingView && IsViewFocusableCandidate(starting_view, skip_group_id)) { View* v = FindSelectedViewForGroup(starting_view); // The selected view might not be focusable (if it is disabled for @@ -263,26 +307,24 @@ View* FocusSearch::FindPreviousFocusableViewImpl( // Then try the left sibling. View* sibling = starting_view->GetPreviousFocusableView(); if (sibling) { - return FindPreviousFocusableViewImpl(sibling, - true, can_go_up, true, - skip_group_id, - focus_traversable, - focus_traversable_view); + return FindPreviousFocusableViewImpl( + sibling, StartingViewPolicy::kCheckStartingView, can_go_up, true, + can_go_into_anchored_dialog, skip_group_id, focus_traversable, + focus_traversable_view); } // Then go up the parent. if (can_go_up) { View* parent = GetParent(starting_view); if (parent) - return FindPreviousFocusableViewImpl(parent, - true, true, false, - skip_group_id, - focus_traversable, - focus_traversable_view); + return FindPreviousFocusableViewImpl( + parent, StartingViewPolicy::kCheckStartingView, true, false, + can_go_into_anchored_dialog, skip_group_id, focus_traversable, + focus_traversable_view); } // We found nothing. - return NULL; + return nullptr; } } // namespace views diff --git a/chromium/ui/views/focus/focus_search.h b/chromium/ui/views/focus/focus_search.h index 9da7df45ae9..4d315449d54 100644 --- a/chromium/ui/views/focus/focus_search.h +++ b/chromium/ui/views/focus/focus_search.h @@ -21,9 +21,24 @@ class VIEWS_EXPORT FocusSearch { // goal is to switch to focusable views on the same level when using the arrow // keys (ala Windows: in a dialog box, arrow keys typically move between the // dialog OK, Cancel buttons). - enum Direction { - UP = 0, - DOWN + enum class TraversalDirection { + kUp, + kDown, + }; + + enum class SearchDirection { + kForwards, + kBackwards, + }; + + enum class StartingViewPolicy { + kSkipStartingView, + kCheckStartingView, + }; + + enum class AnchoredDialogPolicy { + kSkipAnchoredDialog, + kCanGoIntoAnchoredDialog, }; // Constructor. @@ -52,22 +67,28 @@ class VIEWS_EXPORT FocusSearch { // - |starting_view| is the view that should be used as the starting point // when looking for the previous/next view. It may be NULL (in which case // the first/last view should be used depending if normal/reverse). - // - |reverse| whether we should find the next (reverse is false) or the - // previous (reverse is true) view. - // - |direction| specifies whether we are traversing down (meaning we should - // look into child views) or traversing up (don't look at child views). - // - |check_starting_view| is true if starting_view may obtain the next focus. + // - |search_direction| whether we should find the next (kForwards) or + // previous (kReverse) view. + // - |traversal_direction| specifies whether we are traversing down (meaning + // we should look into child views) or traversing up (don't look at + // child views). + // - |check_starting_view| indicated if starting_view may obtain the next + // focus. + // - |can_go_into_anchored_dialog| controls if focus is allowed to jump + // into a dialog anchored at one of the views being traversed. // - |focus_traversable| is set to the focus traversable that should be // traversed if one is found (in which case the call returns NULL). // - |focus_traversable_view| is set to the view associated with the // FocusTraversable set in the previous parameter (it is used as the // starting view when looking for the next focusable view). - virtual View* FindNextFocusableView(View* starting_view, - bool reverse, - Direction direction, - bool check_starting_view, - FocusTraversable** focus_traversable, - View** focus_traversable_view); + virtual View* FindNextFocusableView( + View* starting_view, + SearchDirection search_direction, + TraversalDirection traversal_direction, + StartingViewPolicy check_starting_view, + AnchoredDialogPolicy can_go_into_anchored_dialog, + FocusTraversable** focus_traversable, + View** focus_traversable_view); protected: // Get the parent, but stay within the root. Returns NULL if asked for @@ -102,7 +123,7 @@ class VIEWS_EXPORT FocusSearch { // traversal of the views hierarchy. |skip_group_id| specifies a group_id, // -1 means no group. All views from a group are traversed in one pass. View* FindNextFocusableViewImpl(View* starting_view, - bool check_starting_view, + StartingViewPolicy check_starting_view, bool can_go_up, bool can_go_down, int skip_group_id, @@ -110,13 +131,15 @@ class VIEWS_EXPORT FocusSearch { View** focus_traversable_view); // Same as FindNextFocusableViewImpl but returns the previous focusable view. - View* FindPreviousFocusableViewImpl(View* starting_view, - bool check_starting_view, - bool can_go_up, - bool can_go_down, - int skip_group_id, - FocusTraversable** focus_traversable, - View** focus_traversable_view); + View* FindPreviousFocusableViewImpl( + View* starting_view, + StartingViewPolicy check_starting_view, + bool can_go_up, + bool can_go_down, + AnchoredDialogPolicy can_go_into_anchored_dialog, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view); View* root_; bool cycle_; diff --git a/chromium/ui/views/layout/layout_provider.cc b/chromium/ui/views/layout/layout_provider.cc index 1902f4ff539..822088d3723 100644 --- a/chromium/ui/views/layout/layout_provider.cc +++ b/chromium/ui/views/layout/layout_provider.cc @@ -141,4 +141,27 @@ gfx::Insets LayoutProvider::GetDialogInsetsForContentType( dialog_insets.right()); } +int LayoutProvider::GetCornerRadiusMetric(EmphasisMetric emphasis_metric, + const gfx::Size& size) const { + const bool is_touch = + ui::MaterialDesignController::IsTouchOptimizedUiEnabled(); + switch (emphasis_metric) { + case EMPHASIS_LOW: + return is_touch ? 4 : 2; + case EMPHASIS_MEDIUM: + return is_touch ? 8 : 4; + case EMPHASIS_HIGH: + return is_touch ? std::min(size.width(), size.height()) / 2 : 4; + default: + NOTREACHED(); + return 0; + } +} + +int LayoutProvider::GetShadowElevationMetric( + EmphasisMetric emphasis_metric) const { + // Just return a value for now. + return 2; +} + } // namespace views diff --git a/chromium/ui/views/layout/layout_provider.h b/chromium/ui/views/layout/layout_provider.h index 991b8e51681..c61276edd2b 100644 --- a/chromium/ui/views/layout/layout_provider.h +++ b/chromium/ui/views/layout/layout_provider.h @@ -7,6 +7,7 @@ #include "base/macros.h" #include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/size.h" #include "ui/views/style/typography_provider.h" #include "ui/views/views_export.h" @@ -110,6 +111,18 @@ enum DistanceMetric { // elements that only show text. Otherwise CONTROL should be used. enum DialogContentType { CONTROL, TEXT }; +enum EmphasisMetric { + // No emphasis needed for shadows, corner radius, etc. + EMPHASIS_NONE, + // Use this to indicate low-emphasis interactive elements such as buttons and + // text fields. + EMPHASIS_LOW, + // Use this for components with medium emphasis, such as tabs or dialogs. + EMPHASIS_MEDIUM, + // High-emphasis components like the omnibox or rich suggestions. + EMPHASIS_HIGH, +}; + class VIEWS_EXPORT LayoutProvider { public: LayoutProvider(); @@ -145,6 +158,18 @@ class VIEWS_EXPORT LayoutProvider { gfx::Insets GetDialogInsetsForContentType(DialogContentType leading, DialogContentType trailing) const; + // TODO (https://crbug.com/822000): Possibly combine the following two + // functions into a single function returning a struct. Keeping them separate + // for now in case different emphasis is needed for different elements in the + // same context. Delete this TODO in Q4 2018. + + // Returns the corner radius specific to the given emphasis metric. + virtual int GetCornerRadiusMetric(EmphasisMetric emphasis_metric, + const gfx::Size& size = gfx::Size()) const; + + // Returns the shadow elevation metric for the given emphasis. + virtual int GetShadowElevationMetric(EmphasisMetric emphasis_metric) 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 26c49ae32f6..07f30dfe53a 100644 --- a/chromium/ui/views/linux_ui/linux_ui.h +++ b/chromium/ui/views/linux_ui/linux_ui.h @@ -196,6 +196,9 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory, // toolkit does not support drawing client-side navigation buttons. virtual std::unique_ptr<NavButtonProvider> CreateNavButtonProvider() = 0; #endif + + // Returns a map of KeyboardEvent code to KeyboardEvent key values. + virtual base::flat_map<std::string, std::string> GetKeyboardLayoutMap() = 0; }; } // namespace views diff --git a/chromium/ui/views/mus/BUILD.gn b/chromium/ui/views/mus/BUILD.gn index dfe239c16d5..ed64ffdeced 100644 --- a/chromium/ui/views/mus/BUILD.gn +++ b/chromium/ui/views/mus/BUILD.gn @@ -49,7 +49,6 @@ jumbo_component("mus") { "//base:i18n", "//base/third_party/dynamic_annotations", "//cc", - "//mojo/common", "//mojo/public/cpp/bindings", "//net", "//services/catalog/public/cpp", @@ -57,6 +56,7 @@ jumbo_component("mus") { "//services/service_manager/public/cpp", "//services/service_manager/public/mojom", "//services/ui/public/cpp", + "//services/ui/public/cpp/input_devices", "//services/ui/public/interfaces", "//skia", "//third_party/icu", @@ -113,7 +113,6 @@ jumbo_static_library("test_support") { sources = [ "../test/native_widget_factory_aura_mus.cc", - "test_utils.h", "views_mus_test_suite.cc", "views_mus_test_suite.h", ] diff --git a/chromium/ui/views/mus/DEPS b/chromium/ui/views/mus/DEPS index 21ea014c764..f56df79ddca 100644 --- a/chromium/ui/views/mus/DEPS +++ b/chromium/ui/views/mus/DEPS @@ -1,10 +1,8 @@ include_rules = [ "+cc", - "-cc/blink", "+components/services/font/public", "+components/gpu", "+mojo/cc", - "+mojo/common", "+mojo/converters", "+mojo/edk/embedder", "+mojo/public", diff --git a/chromium/ui/views/mus/aura_init.cc b/chromium/ui/views/mus/aura_init.cc index ea6490d4ec3..96e0ecfc398 100644 --- a/chromium/ui/views/mus/aura_init.cc +++ b/chromium/ui/views/mus/aura_init.cc @@ -21,7 +21,6 @@ #include "ui/base/ui_base_paths.h" #include "ui/views/layout/layout_provider.h" #include "ui/views/mus/mus_client.h" -#include "ui/views/style/typography_provider.h" #include "ui/views/views_delegate.h" #if defined(OS_LINUX) @@ -93,14 +92,18 @@ bool AuraInit::Init(service_manager::Connector* connector, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, Mode mode, bool register_path_provider) { - env_ = aura::Env::CreateInstance( - (mode == Mode::AURA_MUS || mode == Mode::AURA_MUS_WINDOW_MANAGER) - ? aura::Env::Mode::MUS - : aura::Env::Mode::LOCAL); - - if (mode == Mode::AURA_MUS) { - mus_client_ = - base::WrapUnique(new MusClient(connector, identity, io_task_runner)); + env_ = aura::Env::CreateInstance(aura::Env::Mode::MUS); + + if (mode == Mode::AURA_MUS || mode == Mode::AURA_MUS2) { + MusClient::InitParams params; + params.connector = connector; + params.identity = identity; + params.io_task_runner = io_task_runner; + params.wtc_config = mode == Mode::AURA_MUS2 + ? aura::WindowTreeClient::Config::kMus2 + : aura::WindowTreeClient::Config::kMash; + params.create_wm_state = true; + mus_client_ = std::make_unique<MusClient>(params); } // MaterialDesignController may have initialized already (such as happens // in the utility process). @@ -114,7 +117,7 @@ bool AuraInit::Init(service_manager::Connector* connector, // Initialize the skia font code to go ask fontconfig underneath. #if defined(OS_LINUX) font_loader_ = sk_make_sp<font_service::FontLoader>(connector); - SkFontConfigInterface::SetGlobal(font_loader_.get()); + SkFontConfigInterface::SetGlobal(font_loader_); // Initialize static default font, by running this now, before any other apps // load, we ensure all the state is set up. diff --git a/chromium/ui/views/mus/aura_init.h b/chromium/ui/views/mus/aura_init.h index 68fa0fb3c8c..c76c8314a24 100644 --- a/chromium/ui/views/mus/aura_init.h +++ b/chromium/ui/views/mus/aura_init.h @@ -39,16 +39,18 @@ 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. + // 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. + // Manager client. This is deprecated. AURA_MUS_WINDOW_MANAGER, - // Indicates AuraInit should target using ui::Window. - UI + // Targets ws2. Mode will eventually be removed entirely and this will be + // the default. + AURA_MUS2, }; ~AuraInit(); @@ -65,7 +67,7 @@ class VIEWS_MUS_EXPORT AuraInit { const std::string& resource_file, const std::string& resource_file_200 = std::string(), scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr, - Mode mode = Mode::UI, + Mode mode = Mode::AURA_MUS, bool register_path_provider = true); // Only valid if Mode::AURA_MUS was passed to constructor. diff --git a/chromium/ui/views/mus/clipboard_mus.h b/chromium/ui/views/mus/clipboard_mus.h index 1a5fff8c63c..54cbe4abea7 100644 --- a/chromium/ui/views/mus/clipboard_mus.h +++ b/chromium/ui/views/mus/clipboard_mus.h @@ -5,6 +5,7 @@ #ifndef UI_VIEWS_MUS_CLIPBOARD_MUS_H_ #define UI_VIEWS_MUS_CLIPBOARD_MUS_H_ +#include "base/containers/flat_map.h" #include "services/ui/public/interfaces/clipboard.mojom.h" #include "ui/base/clipboard/clipboard.h" #include "ui/views/mus/mus_export.h" @@ -77,7 +78,7 @@ class VIEWS_MUS_EXPORT ClipboardMus : public ui::Clipboard { // WriteObjects(), which then calls our base class DispatchObject() which // then calls into each data type specific Write() function. Once we've // collected all the data types, we then pass this to the mus server. - base::Optional<std::unordered_map<std::string, std::vector<uint8_t>>> + base::Optional<base::flat_map<std::string, std::vector<uint8_t>>> current_clipboard_; DISALLOW_COPY_AND_ASSIGN(ClipboardMus); diff --git a/chromium/ui/views/mus/clipboard_unittest.cc b/chromium/ui/views/mus/clipboard_unittest.cc index 9db8fe50732..b04292e94b7 100644 --- a/chromium/ui/views/mus/clipboard_unittest.cc +++ b/chromium/ui/views/mus/clipboard_unittest.cc @@ -4,7 +4,6 @@ #include "ui/views/mus/clipboard_mus.h" -#include "base/message_loop/message_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/platform/platform_event_source.h" #include "ui/views/mus/mus_client.h" 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 c417e738573..0233bc336cd 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.cc +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.cc @@ -280,7 +280,8 @@ float DesktopWindowTreeHostMus::GetScaleFactor() const { } void DesktopWindowTreeHostMus::SetBoundsInDIP(const gfx::Rect& bounds_in_dip) { - SetBoundsInPixels(gfx::ConvertRectToPixel(GetScaleFactor(), bounds_in_dip)); + SetBoundsInPixels(gfx::ConvertRectToPixel(GetScaleFactor(), bounds_in_dip), + viz::LocalSurfaceId()); } bool DesktopWindowTreeHostMus::ShouldSendClientAreaToServer() const { @@ -512,7 +513,11 @@ void DesktopWindowTreeHostMus::GetWindowPlacement( } gfx::Rect DesktopWindowTreeHostMus::GetWindowBoundsInScreen() const { - return gfx::ConvertRectToDIP(GetScaleFactor(), GetBoundsInPixels()); + 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; } gfx::Rect DesktopWindowTreeHostMus::GetClientAreaBoundsInScreen() const { @@ -697,7 +702,9 @@ bool DesktopWindowTreeHostMus::ShouldWindowContentsBeTransparent() const { return false; } -void DesktopWindowTreeHostMus::FrameTypeChanged() {} +void DesktopWindowTreeHostMus::FrameTypeChanged() { + native_widget_delegate_->AsWidget()->ThemeChanged(); +} void DesktopWindowTreeHostMus::SetFullscreen(bool fullscreen) { if (IsFullscreen() == fullscreen) @@ -824,7 +831,8 @@ void DesktopWindowTreeHostMus::HideImpl() { } void DesktopWindowTreeHostMus::SetBoundsInPixels( - const gfx::Rect& bounds_in_pixels) { + const gfx::Rect& bounds_in_pixels, + const viz::LocalSurfaceId& local_surface_id) { gfx::Rect final_bounds_in_pixels = bounds_in_pixels; if (GetBoundsInPixels().size() != bounds_in_pixels.size()) { gfx::Size size = bounds_in_pixels.size(); @@ -837,7 +845,8 @@ void DesktopWindowTreeHostMus::SetBoundsInPixels( final_bounds_in_pixels.set_size(size); } const gfx::Rect old_bounds_in_pixels = GetBoundsInPixels(); - WindowTreeHostMus::SetBoundsInPixels(final_bounds_in_pixels); + WindowTreeHostMus::SetBoundsInPixels(final_bounds_in_pixels, + local_surface_id); if (old_bounds_in_pixels.size() != final_bounds_in_pixels.size()) { SendClientAreaToServer(); SendHitTestMaskToServer(); 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 e1dca1c6735..def36405a77 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.h +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.h @@ -140,7 +140,8 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus // aura::WindowTreeHostMus: void ShowImpl() override; void HideImpl() override; - void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels) override; + void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels, + const viz::LocalSurfaceId& local_surface_id) override; // Accessor for DesktopNativeWidgetAura::content_window(). aura::Window* content_window(); 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 50851a87bfa..769904d73e1 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 @@ -7,6 +7,7 @@ #include "base/debug/stack_trace.h" #include "base/run_loop.h" +#include "ui/aura/client/aura_constants.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/focus_client.h" #include "ui/aura/client/transient_window_client.h" @@ -22,6 +23,7 @@ #include "ui/events/base_event_utils.h" #include "ui/events/event.h" #include "ui/views/mus/mus_client.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" @@ -366,4 +368,32 @@ TEST_F(DesktopWindowTreeHostMusTest, CreateFullscreenWidget) { } } +TEST_F(DesktopWindowTreeHostMusTest, GetWindowBoundsInScreen) { + ScreenMus* screen = MusClient::Get()->screen(); + + // Add a second display to the right of the primary. + const int64_t kSecondDisplayId = 222; + screen->display_list().AddDisplay( + display::Display(kSecondDisplayId, gfx::Rect(800, 0, 640, 480)), + display::DisplayList::Type::NOT_PRIMARY); + + // Verify bounds for a widget on the first display. + Widget widget1; + Widget::InitParams params1(Widget::InitParams::TYPE_WINDOW); + params1.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params1.bounds = gfx::Rect(0, 0, 100, 100); + widget1.Init(params1); + EXPECT_EQ(gfx::Rect(0, 0, 100, 100), widget1.GetWindowBoundsInScreen()); + + // Verify bounds for a widget on the secondary display. + 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); + widget2.Init(params2); + aura::WindowTreeHostMus::ForWindow(widget2.GetNativeWindow()) + ->set_display_id(kSecondDisplayId); + EXPECT_EQ(gfx::Rect(800, 0, 100, 100), widget2.GetWindowBoundsInScreen()); +} + } // namespace views diff --git a/chromium/ui/views/mus/mus_client.cc b/chromium/ui/views/mus/mus_client.cc index 1e049056dfb..b8d4dc77bfd 100644 --- a/chromium/ui/views/mus/mus_client.cc +++ b/chromium/ui/views/mus/mus_client.cc @@ -11,6 +11,7 @@ #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" @@ -19,7 +20,6 @@ #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" @@ -64,12 +64,11 @@ namespace views { // static MusClient* MusClient::instance_ = nullptr; -MusClient::MusClient(service_manager::Connector* connector, - const service_manager::Identity& identity, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - bool create_wm_state, - MusClientTestingState testing_state) - : identity_(identity) { +MusClient::InitParams::InitParams() = default; + +MusClient::InitParams::~InitParams() = default; + +MusClient::MusClient(const InitParams& params) : identity_(params.identity) { DCHECK(!instance_); DCHECK(aura::Env::GetInstance()); instance_ = this; @@ -81,9 +80,12 @@ MusClient::MusClient(service_manager::Connector* connector, // instance. Partially initialize the ozone cursor internals here, like we // partially initialize other ozone subsystems in // ChromeBrowserMainExtraPartsViews. - cursor_factory_ozone_ = std::make_unique<ui::CursorDataFactoryOzone>(); + if (params.create_cursor_factory) + 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); @@ -92,34 +94,49 @@ MusClient::MusClient(service_manager::Connector* connector, io_task_runner = io_thread_->task_runner(); } - // TODO(msw): Avoid this... use some default value? Allow clients to extend? property_converter_ = std::make_unique<aura::PropertyConverter>(); property_converter_->RegisterPrimitiveProperty( ::wm::kShadowElevationKey, ui::mojom::WindowManager::kShadowElevation_Property, aura::PropertyConverter::CreateAcceptAnyValueCallback()); - if (create_wm_state) + if (params.create_wm_state) wm_state_ = std::make_unique<wm::WMState>(); - if (testing_state == MusClientTestingState::CREATE_TESTING_STATE) { + service_manager::Connector* connector = params.connector; + if (params.bind_test_ws_interfaces) { connector->BindInterface(ui::mojom::kServiceName, &server_test_ptr_); connector->BindInterface(ui::mojom::kServiceName, &event_injector_); } - window_tree_client_ = aura::WindowTreeClient::CreateForWindowTreeFactory( - connector, this, true, std::move(io_task_runner)); - aura::Env::GetInstance()->SetWindowTreeClient(window_tree_client_.get()); + if (!params.window_tree_client) { + DCHECK(io_task_runner); + owned_window_tree_client_ = + aura::WindowTreeClient::CreateForWindowTreeFactory( + connector, this, true, std::move(io_task_runner), + params.wtc_config); + window_tree_client_ = owned_window_tree_client_.get(); + aura::Env::GetInstance()->SetWindowTreeClient(window_tree_client_); + } else { + window_tree_client_ = params.window_tree_client; + } pointer_watcher_event_router_ = - std::make_unique<PointerWatcherEventRouter>(window_tree_client_.get()); + 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); + input_device_client_->Connect(std::move(input_device_server)); - screen_ = std::make_unique<ScreenMus>(this); - screen_->Init(connector); + screen_ = std::make_unique<ScreenMus>(this); + screen_->Init(connector); - std::unique_ptr<ClipboardMus> clipboard = std::make_unique<ClipboardMus>(); - clipboard->Init(connector); - ui::Clipboard::SetClipboardForCurrentThread(std::move(clipboard)); + std::unique_ptr<ClipboardMus> clipboard = std::make_unique<ClipboardMus>(); + clipboard->Init(connector); + ui::Clipboard::SetClipboardForCurrentThread(std::move(clipboard)); + } ViewsDelegate::GetInstance()->set_native_widget_factory( base::Bind(&MusClient::CreateNativeWidget, base::Unretained(this))); @@ -130,7 +147,8 @@ MusClient::MusClient(service_manager::Connector* connector, MusClient::~MusClient() { // ~WindowTreeClient calls back to us (we're its delegate), destroy it while // we are still valid. - window_tree_client_.reset(); + owned_window_tree_client_.reset(); + window_tree_client_ = nullptr; ui::OSExchangeDataProviderFactory::SetFactory(nullptr); ui::Clipboard::DestroyClipboardForCurrentThread(); @@ -321,8 +339,10 @@ void MusClient::OnEmbedRootDestroyed( } void MusClient::OnPointerEventObserved(const ui::PointerEvent& event, + int64_t display_id, aura::Window* target) { - pointer_watcher_event_router_->OnPointerEventObserved(event, target); + pointer_watcher_event_router_->OnPointerEventObserved(event, display_id, + target); } void MusClient::OnWindowManagerFrameValuesChanged() { diff --git a/chromium/ui/views/mus/mus_client.h b/chromium/ui/views/mus/mus_client.h index 0543e03247a..60239f46548 100644 --- a/chromium/ui/views/mus/mus_client.h +++ b/chromium/ui/views/mus/mus_client.h @@ -16,6 +16,7 @@ #include "services/ui/public/interfaces/event_injector.mojom.h" #include "services/ui/public/interfaces/window_server_test.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" @@ -38,6 +39,7 @@ class Connector; namespace ui { class CursorDataFactoryOzone; +class InputDeviceClient; } namespace wm { @@ -56,26 +58,41 @@ namespace internal { class NativeWidgetDelegate; } -namespace test { -class MusClientTestApi; -} - -enum MusClientTestingState { NO_TESTING, CREATE_TESTING_STATE }; - // MusClient establishes a connection to mus and sets up necessary state so that // aura and views target mus. This class is useful for typical clients, not the // WindowManager. Most clients don't create this directly, rather use AuraInit. class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, public ScreenMusDelegate { public: + struct VIEWS_MUS_EXPORT InitParams { + 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). + 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::kMash; + + // Create a wm::WMState. Some processes (e.g. the browser) may already + // have one. + bool create_wm_state = true; + + // 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. + aura::WindowTreeClient* window_tree_client = nullptr; + }; + // Most clients should use AuraInit, which creates a MusClient. - // |create_wm_state| indicates whether MusClient should create a wm::WMState. - MusClient( - service_manager::Connector* connector, - const service_manager::Identity& identity, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr, - bool create_wm_state = true, - MusClientTestingState testing_state = MusClientTestingState::NO_TESTING); + explicit MusClient(const InitParams& params); ~MusClient() override; static MusClient* Get() { return instance_; } @@ -91,14 +108,15 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, static std::map<std::string, std::vector<uint8_t>> ConfigurePropertiesFromParams(const Widget::InitParams& init_params); - aura::WindowTreeClient* window_tree_client() { - return window_tree_client_.get(); - } + aura::WindowTreeClient* window_tree_client() { return window_tree_client_; } PointerWatcherEventRouter* pointer_watcher_event_router() { return pointer_watcher_event_router_.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. @@ -133,7 +151,6 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, private: friend class AuraInit; - friend class test::MusClientTestApi; // Creates a DesktopWindowTreeHostMus. This is set as the factory function // ViewsDelegate such that if DesktopNativeWidgetAura is created without a @@ -149,6 +166,7 @@ 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, aura::Window* target) override; aura::PropertyConverter* GetPropertyConverter() override; @@ -177,10 +195,17 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, std::unique_ptr<aura::PropertyConverter> property_converter_; std::unique_ptr<MusPropertyMirror> mus_property_mirror_; - std::unique_ptr<aura::WindowTreeClient> window_tree_client_; + // Non-null if MusClient created the WindowTreeClient. + std::unique_ptr<aura::WindowTreeClient> owned_window_tree_client_; + + // Never null. + aura::WindowTreeClient* window_tree_client_; std::unique_ptr<PointerWatcherEventRouter> pointer_watcher_event_router_; + // Gives services transparent remote access the InputDeviceManager. + std::unique_ptr<ui::InputDeviceClient> input_device_client_; + ui::mojom::WindowServerTestPtr server_test_ptr_; ui::mojom::EventInjectorPtr event_injector_; diff --git a/chromium/ui/views/mus/pointer_watcher_event_router.cc b/chromium/ui/views/mus/pointer_watcher_event_router.cc index 82bf200b766..beed6b538c9 100644 --- a/chromium/ui/views/mus/pointer_watcher_event_router.cc +++ b/chromium/ui/views/mus/pointer_watcher_event_router.cc @@ -7,12 +7,16 @@ #include "ui/aura/client/capture_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 { @@ -85,6 +89,7 @@ void PointerWatcherEventRouter::RemovePointerWatcher(PointerWatcher* watcher) { void PointerWatcherEventRouter::OnPointerEventObserved( const ui::PointerEvent& event, + int64_t display_id, aura::Window* target) { Widget* target_widget = nullptr; ui::PointerEvent updated_event(event); @@ -113,10 +118,13 @@ void PointerWatcherEventRouter::OnPointerEventObserved( } } - // The mojo input events type converter uses the event root_location field - // to store screen coordinates. Screen coordinates really should be returned - // separately. See http://crbug.com/608547 - gfx::Point location_in_screen = event.root_location(); + // 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, @@ -157,8 +165,7 @@ 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 = - display::Screen::GetScreen()->GetCursorScreenPoint(); + gfx::Point location_in_screen = 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 2487fbc8e54..2c9dd56e081 100644 --- a/chromium/ui/views/mus/pointer_watcher_event_router.h +++ b/chromium/ui/views/mus/pointer_watcher_event_router.h @@ -5,7 +5,8 @@ #ifndef UI_VIEWS_MUS_POINTER_WATCHER_EVENT_ROUTER_H_ #define UI_VIEWS_MUS_POINTER_WATCHER_EVENT_ROUTER_H_ -#include "base/compiler_specific.h" +#include <stdint.h> + #include "base/macros.h" #include "base/observer_list.h" #include "ui/aura/client/capture_client_observer.h" @@ -58,6 +59,7 @@ class VIEWS_MUS_EXPORT PointerWatcherEventRouter // Called by WindowTreeClientDelegate to notify PointerWatchers appropriately. void OnPointerEventObserved(const ui::PointerEvent& event, + int64_t display_id, aura::Window* target); // Called when the |capture_client| has been set or will be unset. 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 7634fff74bc..821cefdcf38 100644 --- a/chromium/ui/views/mus/pointer_watcher_event_router_unittest.cc +++ b/chromium/ui/views/mus/pointer_watcher_event_router_unittest.cc @@ -11,6 +11,7 @@ #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" @@ -23,18 +24,24 @@ class TestPointerWatcher : public PointerWatcher { ~TestPointerWatcher() override {} ui::PointerEvent* last_event_observed() { return last_event_observed_.get(); } + gfx::Point last_location_in_screen() { return last_location_in_screen_; } - void Reset() { last_event_observed_.reset(); } + void Reset() { + last_event_observed_.reset(); + last_location_in_screen_ = gfx::Point(); + } // PointerWatcher: void OnPointerEventObserved(const ui::PointerEvent& event, const gfx::Point& location_in_screen, gfx::NativeView target) override { last_event_observed_ = std::make_unique<ui::PointerEvent>(event); + last_location_in_screen_ = location_in_screen; } private: std::unique_ptr<ui::PointerEvent> last_event_observed_; + gfx::Point last_location_in_screen_; DISALLOW_COPY_AND_ASSIGN(TestPointerWatcher); }; @@ -43,12 +50,13 @@ class TestPointerWatcher : public PointerWatcher { class PointerWatcherEventRouterTest : public testing::Test { public: - PointerWatcherEventRouterTest() {} - ~PointerWatcherEventRouterTest() override {} + PointerWatcherEventRouterTest() = default; + ~PointerWatcherEventRouterTest() override = default; - void OnPointerEventObserved(const ui::PointerEvent& event) { + void OnPointerEventObserved(const ui::PointerEvent& event, + int64_t display_id = 0) { MusClient::Get()->pointer_watcher_event_router()->OnPointerEventObserved( - event, nullptr); + event, display_id, nullptr); } PointerWatcherEventRouter::EventTypes event_types() const { @@ -56,13 +64,14 @@ class PointerWatcherEventRouterTest : public testing::Test { } private: + base::test::ScopedTaskEnvironment scoped_task_environment_{ + base::test::ScopedTaskEnvironment::MainThreadType::UI}; + ScopedViewsTestHelper helper_; + DISALLOW_COPY_AND_ASSIGN(PointerWatcherEventRouterTest); }; TEST_F(PointerWatcherEventRouterTest, EventTypes) { - base::test::ScopedTaskEnvironment scoped_task_environment( - base::test::ScopedTaskEnvironment::MainThreadType::UI); - ScopedViewsTestHelper helper; TestPointerWatcher pointer_watcher1, pointer_watcher2; PointerWatcherEventRouter* pointer_watcher_event_router = MusClient::Get()->pointer_watcher_event_router(); @@ -114,9 +123,6 @@ TEST_F(PointerWatcherEventRouterTest, EventTypes) { } TEST_F(PointerWatcherEventRouterTest, PointerWatcherNoMove) { - base::test::ScopedTaskEnvironment scoped_task_environment( - base::test::ScopedTaskEnvironment::MainThreadType::UI); - ScopedViewsTestHelper helper; ASSERT_TRUE(MusClient::Get()); PointerWatcherEventRouter* pointer_watcher_event_router = MusClient::Get()->pointer_watcher_event_router(); @@ -188,9 +194,6 @@ TEST_F(PointerWatcherEventRouterTest, PointerWatcherNoMove) { } TEST_F(PointerWatcherEventRouterTest, PointerWatcherMove) { - base::test::ScopedTaskEnvironment scoped_task_environment( - base::test::ScopedTaskEnvironment::MainThreadType::UI); - ScopedViewsTestHelper helper; ASSERT_TRUE(MusClient::Get()); PointerWatcherEventRouter* pointer_watcher_event_router = MusClient::Get()->pointer_watcher_event_router(); @@ -241,4 +244,36 @@ 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/screen_mus.cc b/chromium/ui/views/mus/screen_mus.cc index 36f721bda31..8c8dd07f7e9 100644 --- a/chromium/ui/views/mus/screen_mus.cc +++ b/chromium/ui/views/mus/screen_mus.cc @@ -37,7 +37,7 @@ namespace views { using Type = display::DisplayList::Type; ScreenMus::ScreenMus(ScreenMusDelegate* delegate) - : delegate_(delegate), display_manager_observer_binding_(this) { + : delegate_(delegate), screen_provider_observer_binding_(this) { DCHECK(delegate); display::Screen::SetScreenInstance(this); } @@ -48,23 +48,23 @@ ScreenMus::~ScreenMus() { } void ScreenMus::Init(service_manager::Connector* connector) { - connector->BindInterface(ui::mojom::kServiceName, &display_manager_); + connector->BindInterface(ui::mojom::kServiceName, &screen_provider_); - ui::mojom::DisplayManagerObserverPtr observer; - display_manager_observer_binding_.Bind(mojo::MakeRequest(&observer)); - display_manager_->AddObserver(std::move(observer)); + 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 = display_manager_observer_binding_.WaitForIncomingMethodCall(); + 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(display_manager_.encountered_error() || !success); + 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( diff --git a/chromium/ui/views/mus/screen_mus.h b/chromium/ui/views/mus/screen_mus.h index 4987b07edaa..42c4d150573 100644 --- a/chromium/ui/views/mus/screen_mus.h +++ b/chromium/ui/views/mus/screen_mus.h @@ -6,7 +6,7 @@ #define UI_VIEWS_MUS_SCREEN_MUS_H_ #include "mojo/public/cpp/bindings/binding.h" -#include "services/ui/public/interfaces/display_manager.mojom.h" +#include "services/ui/public/interfaces/screen_provider.mojom.h" #include "ui/display/screen_base.h" #include "ui/views/mus/mus_export.h" @@ -18,9 +18,9 @@ namespace views { class ScreenMusDelegate; -// Screen implementation backed by ui::mojom::DisplayManager. +// Screen implementation backed by ui::mojom::ScreenProvider. class VIEWS_MUS_EXPORT ScreenMus : public display::ScreenBase, - public ui::mojom::DisplayManagerObserver { + public ui::mojom::ScreenProviderObserver { public: explicit ScreenMus(ScreenMusDelegate* delegate); ~ScreenMus() override; @@ -37,15 +37,15 @@ class VIEWS_MUS_EXPORT ScreenMus : public display::ScreenBase, bool IsWindowUnderCursor(gfx::NativeWindow window) override; aura::Window* GetWindowAtScreenPoint(const gfx::Point& point) override; - // ui::mojom::DisplayManager: + // ui::mojom::ScreenProvider: void OnDisplaysChanged(std::vector<ui::mojom::WsDisplayPtr> ws_displays, int64_t primary_display_id, int64_t internal_display_id) override; ScreenMusDelegate* delegate_; - ui::mojom::DisplayManagerPtr display_manager_; - mojo::Binding<ui::mojom::DisplayManagerObserver> - display_manager_observer_binding_; + 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_delegate.h b/chromium/ui/views/mus/screen_mus_delegate.h index 0f36fd59da1..0fe8f7118f2 100644 --- a/chromium/ui/views/mus/screen_mus_delegate.h +++ b/chromium/ui/views/mus/screen_mus_delegate.h @@ -17,7 +17,7 @@ class Point; namespace views { -// Screen implementation backed by ui::mojom::DisplayManager. +// Delegate for screen implementation backed by ui::mojom::ScreenProvider. class VIEWS_MUS_EXPORT ScreenMusDelegate { public: virtual void OnWindowManagerFrameValuesChanged() = 0; diff --git a/chromium/ui/views/mus/test_utils.h b/chromium/ui/views/mus/test_utils.h deleted file mode 100644 index da72dcda000..00000000000 --- a/chromium/ui/views/mus/test_utils.h +++ /dev/null @@ -1,31 +0,0 @@ -// 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_MUS_TEST_UTILS_H_ -#define UI_VIEWS_MUS_TEST_UTILS_H_ - -#include "base/memory/ptr_util.h" -#include "ui/views/mus/mus_client.h" - -namespace views { -namespace test { - -class MusClientTestApi { - public: - static std::unique_ptr<MusClient> Create( - service_manager::Connector* connector, - const service_manager::Identity& identity) { - return base::WrapUnique( - new MusClient(connector, identity, nullptr, true, - MusClientTestingState::CREATE_TESTING_STATE)); - } - - private: - DISALLOW_IMPLICIT_CONSTRUCTORS(MusClientTestApi); -}; - -} // namespace test -} // namespace views - -#endif // UI_VIEWS_MUS_TEST_UTILS_H_ diff --git a/chromium/ui/views/mus/views_mus_test_suite.cc b/chromium/ui/views/mus/views_mus_test_suite.cc index 3b2cfcd1c39..304d7625316 100644 --- a/chromium/ui/views/mus/views_mus_test_suite.cc +++ b/chromium/ui/views/mus/views_mus_test_suite.cc @@ -34,7 +34,6 @@ #include "ui/gl/gl_switches.h" #include "ui/views/mus/desktop_window_tree_host_mus.h" #include "ui/views/mus/mus_client.h" -#include "ui/views/mus/test_utils.h" #include "ui/views/test/platform_test_helper.h" #include "ui/views/test/views_test_helper_aura.h" #include "ui/views/views_delegate.h" @@ -101,8 +100,11 @@ class ServiceManagerConnection { } std::unique_ptr<MusClient> CreateMusClient() { - return test::MusClientTestApi::Create(GetConnector(), - service_manager_identity_); + MusClient::InitParams params; + params.connector = GetConnector(); + params.identity = service_manager_identity_; + params.bind_test_ws_interfaces = true; + return std::make_unique<MusClient>(params); } private: diff --git a/chromium/ui/views/style/platform_style.cc b/chromium/ui/views/style/platform_style.cc index 99793bc88bd..ef68fb58bbb 100644 --- a/chromium/ui/views/style/platform_style.cc +++ b/chromium/ui/views/style/platform_style.cc @@ -57,6 +57,10 @@ const bool PlatformStyle::kUseRipples = true; const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = false; const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = true; const bool PlatformStyle::kShouldElideBookmarksInBookmarksBar = false; +const float PlatformStyle::kFocusHaloThickness = 2.f; +const float PlatformStyle::kFocusHaloInset = + -PlatformStyle::kFocusHaloThickness; +const bool PlatformStyle::kPreferFocusRings = false; // static std::unique_ptr<ScrollBar> PlatformStyle::CreateScrollBar(bool is_horizontal) { diff --git a/chromium/ui/views/style/platform_style.h b/chromium/ui/views/style/platform_style.h index 25710b7968e..b8e74bedb0b 100644 --- a/chromium/ui/views/style/platform_style.h +++ b/chromium/ui/views/style/platform_style.h @@ -74,6 +74,15 @@ class VIEWS_EXPORT PlatformStyle { // tail] or fade out. static const bool kShouldElideBookmarksInBookmarksBar; + // The thickness and inset amount of focus ring halos. + static const float kFocusHaloThickness; + static const float kFocusHaloInset; + + // Whether "button-like" (for example, buttons in the top chrome or Omnibox + // decorations) UI elements should use a focus ring, rather than show + // hover state on focus. + static const bool kPreferFocusRings; + // Creates the default scrollbar for the given orientation. static std::unique_ptr<ScrollBar> CreateScrollBar(bool is_horizontal); diff --git a/chromium/ui/views/style/platform_style_mac.mm b/chromium/ui/views/style/platform_style_mac.mm index 36cdfff642d..2106dccefa2 100644 --- a/chromium/ui/views/style/platform_style_mac.mm +++ b/chromium/ui/views/style/platform_style_mac.mm @@ -42,6 +42,9 @@ const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = false; const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = true; const bool PlatformStyle::kShouldElideBookmarksInBookmarksBar = true; const bool PlatformStyle::kUseRipples = false; +const float PlatformStyle::kFocusHaloThickness = 4.f; +const float PlatformStyle::kFocusHaloInset = -2.f; +const bool PlatformStyle::kPreferFocusRings = true; const Button::NotifyAction PlatformStyle::kMenuNotifyActivationAction = Button::NOTIFY_ON_PRESS; diff --git a/chromium/ui/views/style/typography_provider.cc b/chromium/ui/views/style/typography_provider.cc index a66e387eb0a..e208380993d 100644 --- a/chromium/ui/views/style/typography_provider.cc +++ b/chromium/ui/views/style/typography_provider.cc @@ -37,12 +37,11 @@ gfx::Font::Weight GetValueBolderThan(gfx::Font::Weight weight) { gfx::Font::Weight TypographyProvider::MediumWeightForUI() { #if defined(OS_MACOSX) // System fonts are not user-configurable on Mac, so there's a simpler check. - // However, 10.9 and 10.11 do not ship with a MEDIUM weight system font. In - // that case, trying to use MEDIUM there will give a bold font, which will - // look worse with the surrounding NORMAL text than just using NORMAL. - return (base::mac::IsOS10_9() || base::mac::IsOS10_11()) - ? gfx::Font::Weight::NORMAL - : gfx::Font::Weight::MEDIUM; + // However, 10.11 do not ship with a MEDIUM weight system font. In that + // case, trying to use MEDIUM there will give a bold font, which will look + // worse with the surrounding NORMAL text than just using NORMAL. + return base::mac::IsOS10_11() ? gfx::Font::Weight::NORMAL + : gfx::Font::Weight::MEDIUM; #else // NORMAL may already have at least MEDIUM weight. Return NORMAL in that case // since trying to return MEDIUM would actually make the font lighter-weight diff --git a/chromium/ui/views/view.cc b/chromium/ui/views/view.cc index f544882a25c..655e745ee61 100644 --- a/chromium/ui/views/view.cc +++ b/chromium/ui/views/view.cc @@ -12,7 +12,6 @@ #include "base/containers/adapters.h" #include "base/logging.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -52,6 +51,7 @@ #include "ui/views/drag_controller.h" #include "ui/views/layout/layout_manager.h" #include "ui/views/view_observer.h" +#include "ui/views/view_tracker.h" #include "ui/views/views_delegate.h" #include "ui/views/views_switches.h" #include "ui/views/widget/native_widget_private.h" @@ -515,7 +515,10 @@ gfx::Transform View::GetTransform() const { gfx::Transform transform = layer()->transform(); gfx::ScrollOffset scroll_offset = layer()->CurrentScrollOffset(); - transform.Translate(-scroll_offset.x(), -scroll_offset.y()); + // Offsets for layer-based scrolling are never negative, but the horizontal + // scroll direction is reversed in RTL via canvas flipping. + transform.Translate((base::i18n::IsRTL() ? 1 : -1) * scroll_offset.x(), + -scroll_offset.y()); return transform; } @@ -1465,13 +1468,8 @@ void View::OnAccessibilityEvent(ax::mojom::Event event_type) {} // Scrolling ------------------------------------------------------------------- void View::ScrollRectToVisible(const gfx::Rect& rect) { - // We must take RTL UI mirroring into account when adjusting the position of - // the region. - if (parent_) { - gfx::Rect scroll_rect(rect); - scroll_rect.Offset(GetMirroredX(), y()); - parent_->ScrollRectToVisible(scroll_rect); - } + if (parent_) + parent_->ScrollRectToVisible(rect + bounds().OffsetFromOrigin()); } void View::ScrollViewToVisible() { @@ -1800,10 +1798,19 @@ void View::OnBlur() { void View::Focus() { OnFocus(); ScrollViewToVisible(); + + for (ViewObserver& observer : observers_) + observer.OnViewFocused(this); } void View::Blur() { + ViewTracker tracker(this); OnBlur(); + + if (tracker.view()) { + for (ViewObserver& observer : observers_) + observer.OnViewBlurred(this); + } } // Tooltips -------------------------------------------------------------------- @@ -1849,112 +1856,6 @@ PaintInfo::ScaleType View::GetPaintScaleType() const { return PaintInfo::ScaleType::kScaleWithEdgeSnapping; } -// Debugging ------------------------------------------------------------------- - -#if !defined(NDEBUG) - -std::string View::PrintViewGraph(bool first) { - return DoPrintViewGraph(first, this); -} - -std::string View::DoPrintViewGraph(bool first, View* view_with_children) { - // 64-bit pointer = 16 bytes of hex + "0x" + '\0' = 19. - const size_t kMaxPointerStringLength = 19; - - std::string result; - - if (first) - result.append("digraph {\n"); - - // Node characteristics. - char p[kMaxPointerStringLength]; - - const std::string class_name(GetClassName()); - size_t base_name_index = class_name.find_last_of('/'); - if (base_name_index == std::string::npos) - base_name_index = 0; - else - base_name_index++; - - char bounds_buffer[512]; - - // Information about current node. - base::snprintf(p, arraysize(bounds_buffer), "%p", view_with_children); - result.append(" N"); - result.append(p + 2); - result.append(" [label=\""); - - result.append(class_name.substr(base_name_index).c_str()); - - base::snprintf(bounds_buffer, - arraysize(bounds_buffer), - "\\n bounds: (%d, %d), (%dx%d)", - bounds().x(), - bounds().y(), - bounds().width(), - bounds().height()); - result.append(bounds_buffer); - - gfx::DecomposedTransform decomp; - if (!GetTransform().IsIdentity() && - gfx::DecomposeTransform(&decomp, GetTransform())) { - base::snprintf(bounds_buffer, - arraysize(bounds_buffer), - "\\n translation: (%f, %f)", - decomp.translate[0], - decomp.translate[1]); - result.append(bounds_buffer); - - base::snprintf(bounds_buffer, arraysize(bounds_buffer), - "\\n rotation: %3.2f", - gfx::RadToDeg(std::acos(decomp.quaternion.w()) * 2)); - result.append(bounds_buffer); - - base::snprintf(bounds_buffer, - arraysize(bounds_buffer), - "\\n scale: (%2.4f, %2.4f)", - decomp.scale[0], - decomp.scale[1]); - result.append(bounds_buffer); - } - - result.append("\""); - if (!parent_) - result.append(", shape=box"); - if (layer()) { - if (layer()->has_external_content()) - result.append(", color=green"); - else - result.append(", color=red"); - - if (layer()->fills_bounds_opaquely()) - result.append(", style=filled"); - } - result.append("]\n"); - - // Link to parent. - if (parent_) { - char pp[kMaxPointerStringLength]; - - base::snprintf(pp, kMaxPointerStringLength, "%p", parent_); - result.append(" N"); - result.append(pp + 2); - result.append(" -> N"); - result.append(p + 2); - result.append("\n"); - } - - // Children. - for (auto* child : view_with_children->children_) - result.append(child->PrintViewGraph(false)); - - if (first) - result.append("}\n"); - - return result; -} -#endif - //////////////////////////////////////////////////////////////////////////////// // View, private: diff --git a/chromium/ui/views/view.h b/chromium/ui/views/view.h index 8b874876b89..cbc9e1866b4 100644 --- a/chromium/ui/views/view.h +++ b/chromium/ui/views/view.h @@ -419,6 +419,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, // Views as |children_|. The default implementation returns |children_|, // subclass if the paint order should differ from that of |children_|. // This order is taken into account by painting and targeting implementations. + // NOTE: see SetPaintToLayer() for details on painting and views with layers. virtual View::Views GetChildrenInZOrder(); // Transformations ----------------------------------------------------------- @@ -439,6 +440,10 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, // . SetPaintToLayer(ui::LayerType) has been invoked. // View creates the Layer only when it exists in a Widget with a non-NULL // Compositor. + // Enabling a view to have a layer impacts painting of sibling views. + // Specifically views with layers effectively paint in a z-order that is + // always above any sibling views that do not have layers. This happens + // regardless of the ordering returned by GetChildrenInZOrder(). void SetPaintToLayer(ui::LayerType layer_type = ui::LAYER_TEXTURED); // Please refer to the comments above the DestroyLayerImpl() function for @@ -1391,22 +1396,6 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, // hierarchy). virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) {} - // Debugging ----------------------------------------------------------------- - -#if !defined(NDEBUG) - // Returns string containing a graph of the views hierarchy in graphViz DOT - // language (http://graphviz.org/). Can be called within debugger and save - // to a file to compile/view. - // Note: Assumes initial call made with first = true. - virtual std::string PrintViewGraph(bool first); - - // Some classes may own an object which contains the children to displayed in - // the views hierarchy. The above function gives the class the flexibility to - // decide which object should be used to obtain the children, but this - // function makes the decision explicit. - std::string DoPrintViewGraph(bool first, View* view_with_children); -#endif - private: friend class internal::PreEventDispatchHandler; friend class internal::PostEventDispatchHandler; diff --git a/chromium/ui/views/view_observer.h b/chromium/ui/views/view_observer.h index 10258fa4b0d..e2ed593e061 100644 --- a/chromium/ui/views/view_observer.h +++ b/chromium/ui/views/view_observer.h @@ -44,6 +44,12 @@ class VIEWS_EXPORT ViewObserver { // Called from ~View. virtual void OnViewIsDeleting(View* observed_view) {} + // Called immediately after |observed_view| has gained focus. + virtual void OnViewFocused(View* observed_view) {} + + // Called immediately after |observed_view| has lost focus. + virtual void OnViewBlurred(View* observed_view) {} + protected: virtual ~ViewObserver() {} }; diff --git a/chromium/ui/views/view_properties.cc b/chromium/ui/views/view_properties.cc index fae6a1fd789..d579a16c8cb 100644 --- a/chromium/ui/views/view_properties.cc +++ b/chromium/ui/views/view_properties.cc @@ -5,6 +5,7 @@ #include "ui/views/view_properties.h" #include "ui/gfx/geometry/insets.h" +#include "ui/views/bubble/bubble_dialog_delegate.h" #if !defined(USE_AURA) // aura_constants.cc also declared the bool ClassProperty type. @@ -13,8 +14,14 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, bool); DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*); +DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, + views::BubbleDialogDelegateView*); + namespace views { DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Insets, kMarginsKey, nullptr); +DEFINE_UI_CLASS_PROPERTY_KEY(views::BubbleDialogDelegateView*, + kAnchoredDialogKey, + nullptr); } // namespace views diff --git a/chromium/ui/views/view_properties.h b/chromium/ui/views/view_properties.h index 7f8a770b0d5..4e706ec7a02 100644 --- a/chromium/ui/views/view_properties.h +++ b/chromium/ui/views/view_properties.h @@ -14,11 +14,18 @@ class Insets; namespace views { +class BubbleDialogDelegateView; + // 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. VIEWS_EXPORT extern const ui::ClassProperty<gfx::Insets*>* const kMarginsKey; +// A property to store the bubble dialog anchored to this view, to +// enable the bubble's contents to be included in the focus order. +VIEWS_EXPORT extern const ui::ClassProperty<BubbleDialogDelegateView*>* const + kAnchoredDialogKey; + } // namespace views // Declaring the template specialization here to make sure that the @@ -27,5 +34,7 @@ VIEWS_EXPORT extern const ui::ClassProperty<gfx::Insets*>* const kMarginsKey; // template instance before its specialization is declared in a // translation unit is a C++ error. DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*); +DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, + views::BubbleDialogDelegateView*); #endif // UI_VIEWS_VIEW_PROPERTIES_H_ diff --git a/chromium/ui/views/views_delegate.cc b/chromium/ui/views/views_delegate.cc index a64b2cb2551..7b303dac473 100644 --- a/chromium/ui/views/views_delegate.cc +++ b/chromium/ui/views/views_delegate.cc @@ -100,12 +100,6 @@ void ViewsDelegate::AddRef() { void ViewsDelegate::ReleaseRef() { } -content::WebContents* ViewsDelegate::CreateWebContents( - content::BrowserContext* browser_context, - content::SiteInstance* site_instance) { - return nullptr; -} - base::TimeDelta ViewsDelegate::GetTextfieldPasswordRevealDuration() { return base::TimeDelta(); } diff --git a/chromium/ui/views/views_delegate.h b/chromium/ui/views/views_delegate.h index cfaf7b0dd8a..8f703482084 100644 --- a/chromium/ui/views/views_delegate.h +++ b/chromium/ui/views/views_delegate.h @@ -27,12 +27,6 @@ namespace base { class TimeDelta; } -namespace content { -class WebContents; -class BrowserContext; -class SiteInstance; -} - namespace gfx { class ImageSkia; class Rect; @@ -175,11 +169,6 @@ class VIEWS_EXPORT ViewsDelegate { virtual void AddRef(); virtual void ReleaseRef(); - // Creates a web contents. This will return NULL unless overriden. - virtual content::WebContents* CreateWebContents( - content::BrowserContext* browser_context, - content::SiteInstance* site_instance); - // Gives the platform a chance to modify the properties of a Widget. virtual void OnBeforeWidgetInit(Widget::InitParams* params, internal::NativeWidgetDelegate* delegate) = 0; diff --git a/chromium/ui/views/views_test_suite.cc b/chromium/ui/views/views_test_suite.cc index 44f34e279a4..bd46fca6a32 100644 --- a/chromium/ui/views/views_test_suite.cc +++ b/chromium/ui/views/views_test_suite.cc @@ -52,7 +52,7 @@ void ViewsTestSuite::Initialize() { ui::RegisterPathProvider(); base::FilePath ui_test_pak_path; - ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); + ASSERT_TRUE(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); #if defined(USE_AURA) InitializeEnv(); 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 ad0123acabc..f1e8c4eff5a 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 @@ -10,7 +10,6 @@ #include "base/lazy_instance.h" #include "base/macros.h" #include "base/memory/ptr_util.h" -#include "base/message_loop/message_loop.h" #include "base/metrics/histogram_macros.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/aura/client/capture_client.h" 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 bb391ec3b33..42c838cb065 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 @@ -16,6 +16,7 @@ #include "ui/aura/client/window_parenting_client.h" #include "ui/aura/window.h" #include "ui/aura/window_observer.h" +#include "ui/aura/window_occlusion_tracker.h" #include "ui/aura/window_tree_host.h" #include "ui/base/class_property.h" #include "ui/base/hit_test.h" @@ -51,6 +52,7 @@ #include "ui/wm/core/focus_controller.h" #include "ui/wm/core/native_cursor_manager.h" #include "ui/wm/core/shadow_controller.h" +#include "ui/wm/core/shadow_controller_delegate.h" #include "ui/wm/core/shadow_types.h" #include "ui/wm/core/visibility_controller.h" #include "ui/wm/core/window_animations.h" @@ -533,8 +535,8 @@ void DesktopNativeWidgetAura::InitNativeWidget( event_client_.reset(new DesktopEventClient); aura::client::SetEventClient(host_->window(), event_client_.get()); - shadow_controller_.reset( - new wm::ShadowController(wm::GetActivationClient(host_->window()))); + shadow_controller_.reset(new wm::ShadowController( + wm::GetActivationClient(host_->window()), nullptr)); OnSizeConstraintsChanged(); @@ -588,6 +590,11 @@ const ui::Layer* DesktopNativeWidgetAura::GetLayer() const { } void DesktopNativeWidgetAura::ReorderNativeViews() { + // Reordering native views causes multiple changes to the window tree. + // Instantiate a ScopedPauseOcclusionTracking to recompute occlusion once at + // the end of this scope rather than after each individual change. + // https://crbug.com/829918 + aura::WindowOcclusionTracker::ScopedPauseOcclusionTracking pause_occlusion; window_reorderer_->ReorderChildWindows(); } @@ -698,6 +705,12 @@ void DesktopNativeWidgetAura::SetBounds(const gfx::Rect& bounds) { bounds_in_pixels); } +void DesktopNativeWidgetAura::SetBoundsConstrained(const gfx::Rect& bounds) { + if (!content_window_) + return; + SetBounds(NativeWidgetPrivate::ConstrainBoundsToDisplayWorkArea(bounds)); +} + void DesktopNativeWidgetAura::SetSize(const gfx::Size& size) { if (content_window_) desktop_window_tree_host_->SetSize(size); 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 fc4d17f3285..be8fd0378c6 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 @@ -137,6 +137,7 @@ class VIEWS_EXPORT DesktopNativeWidgetAura gfx::Rect GetRestoredBounds() const override; std::string GetWorkspace() const override; void SetBounds(const gfx::Rect& bounds) override; + void SetBoundsConstrained(const gfx::Rect& bounds) override; void SetSize(const gfx::Size& size) override; void StackAbove(gfx::NativeView native_view) override; void StackAtTop() 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 e9ddabeeda6..1e7210e7601 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 @@ -15,7 +15,9 @@ #include "ui/aura/client/focus_client.h" #include "ui/aura/client/window_parenting_client.h" #include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/window_occlusion_tracker_test_api.h" #include "ui/aura/window.h" +#include "ui/aura/window_occlusion_tracker.h" #include "ui/aura/window_tree_host.h" #include "ui/display/screen.h" #include "ui/events/event_processor.h" @@ -26,6 +28,7 @@ #include "ui/views/test/test_views_delegate.h" #include "ui/views/test/views_test_base.h" #include "ui/views/test/widget_test.h" +#include "ui/views/view_constants_aura.h" #include "ui/views/widget/widget.h" #include "ui/views/window/dialog_delegate.h" @@ -279,6 +282,64 @@ TEST_F(DesktopNativeWidgetAuraTest, DontAccessContentWindowDuringDestruction) { } } +namespace { + +std::unique_ptr<Widget> CreateAndShowControlWidget(aura::Window* parent) { + Widget::InitParams params(Widget::InitParams::TYPE_CONTROL); + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.parent = parent; + auto widget = std::make_unique<Widget>(); + widget->Init(params); + widget->Show(); + return widget; +} + +} // namespace + +TEST_F(DesktopNativeWidgetAuraTest, ReorderDoesntRecomputeOcclusion) { + // Create the parent widget. + Widget parent; + Widget::InitParams init_params = + CreateParams(Widget::InitParams::TYPE_WINDOW); + init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + DesktopNativeWidgetAura* desktop_native_widget_aura = + new DesktopNativeWidgetAura(&parent); + init_params.native_widget = desktop_native_widget_aura; + parent.Init(init_params); + parent.Show(); + + aura::Window* parent_window = parent.GetNativeWindow(); + aura::WindowOcclusionTracker::Track(parent_window); + + View* contents_view = parent.GetContentsView(); + + // Create child widgets. + std::unique_ptr<Widget> w1(CreateAndShowControlWidget(parent_window)); + std::unique_ptr<Widget> w2(CreateAndShowControlWidget(parent_window)); + std::unique_ptr<Widget> w3(CreateAndShowControlWidget(parent_window)); + + // Create child views. + View* host_view1 = new View(); + w1->GetNativeView()->SetProperty(kHostViewKey, host_view1); + contents_view->AddChildView(host_view1); + + View* host_view2 = new View(); + w2->GetNativeView()->SetProperty(kHostViewKey, host_view2); + contents_view->AddChildView(host_view2); + + View* host_view3 = new View(); + w3->GetNativeView()->SetProperty(kHostViewKey, host_view3); + contents_view->AddChildView(host_view3); + + // Reorder child views. Expect occlusion to only be recomputed once. + aura::test::WindowOcclusionTrackerTestApi window_occlusion_tracker_test_api; + const int num_times_occlusion_recomputed = + window_occlusion_tracker_test_api.GetNumTimesOcclusionRecomputed(); + contents_view->ReorderChildView(host_view3, 0); + EXPECT_EQ(num_times_occlusion_recomputed + 1, + window_occlusion_tracker_test_api.GetNumTimesOcclusionRecomputed()); +} + void QuitNestedLoopAndCloseWidget(std::unique_ptr<Widget> widget, base::Closure* quit_runloop) { quit_runloop->Run(); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen.cc new file mode 100644 index 00000000000..b656c14e4f7 --- /dev/null +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen.cc @@ -0,0 +1,23 @@ +// 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_screen.h" + +#include "ui/display/screen.h" + +namespace views { + +void InstallDesktopScreenIfNecessary() { +#if defined(OS_CHROMEOS) + // ChromeOS ozone builds use another path instead, where display::Screen is + // properly set. Thus, do early return here. + return; +#endif + + // The screen may have already been set in test initialization. + if (!display::Screen::GetScreen()) + display::Screen::SetScreenInstance(CreateDesktopScreen()); +} + +} // namespace views diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen.h b/chromium/ui/views/widget/desktop_aura/desktop_screen.h index 88d2ffee2d7..1f1ff49fe04 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen.h @@ -17,6 +17,8 @@ namespace views { // a WindowTreeHost. Caller owns the result. VIEWS_EXPORT display::Screen* CreateDesktopScreen(); +VIEWS_EXPORT void InstallDesktopScreenIfNecessary(); + } // namespace views #endif // UI_VIEWS_WIDGET_DESKTOP_AURA_DESKTOP_SCREEN_H_ diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc new file mode 100644 index 00000000000..a5e871e8dae --- /dev/null +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc @@ -0,0 +1,61 @@ +// 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_screen_ozone.h" + +#include "ui/display/display.h" +#include "ui/display/types/display_constants.h" +#include "ui/display/types/display_snapshot.h" +#include "ui/display/types/native_display_delegate.h" +#include "ui/gfx/geometry/dip_util.h" +#include "ui/ozone/public/ozone_platform.h" +#include "ui/views/widget/desktop_aura/desktop_screen.h" + +namespace views { + +DesktopScreenOzone::DesktopScreenOzone() + : delegate_( + ui::OzonePlatform::GetInstance()->CreateNativeDisplayDelegate()) { + delegate_->AddObserver(this); + delegate_->Initialize(); +} + +DesktopScreenOzone::~DesktopScreenOzone() = default; + +void DesktopScreenOzone::OnHostDisplaysReady( + const std::vector<display::DisplaySnapshot*>& displays) { + DCHECK(!displays.empty()); + // TODO(msisov): Add support for multiple displays. + display::DisplaySnapshot* display_snapshot = displays.front(); + DCHECK(display_snapshot); + + float device_scale_factor = 1.f; + if (display::Display::HasForceDeviceScaleFactor()) + device_scale_factor = display::Display::GetForcedDeviceScaleFactor(); + + gfx::Size scaled_size = gfx::ConvertSizeToDIP( + device_scale_factor, display_snapshot->current_mode()->size()); + + display::Display display(display_snapshot->display_id()); + display.set_bounds(gfx::Rect(scaled_size)); + display.set_work_area(display.bounds()); + display.set_device_scale_factor(device_scale_factor); + + ProcessDisplayChanged(display, true /* is_primary */); +} + +void DesktopScreenOzone::OnConfigurationChanged() { + delegate_->GetDisplays(base::BindOnce( + &DesktopScreenOzone::OnHostDisplaysReady, base::Unretained(this))); +} + +void DesktopScreenOzone::OnDisplaySnapshotsInvalidated() {} + +////////////////////////////////////////////////////////////////////////////// + +display::Screen* CreateDesktopScreen() { + return new DesktopScreenOzone; +} + +} // namespace views diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.h b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.h new file mode 100644 index 00000000000..e742f769ebd --- /dev/null +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.h @@ -0,0 +1,39 @@ +// 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_WIDGET_DESKTOP_AURA_DESKTOP_SCREEN_OZONE_H_ +#define UI_VIEWS_WIDGET_DESKTOP_AURA_DESKTOP_SCREEN_OZONE_H_ + +#include "ui/display/screen_base.h" +#include "ui/display/types/native_display_observer.h" + +namespace display { +class NativeDisplayDelegate; +class DisplaySnapshot; +} // namespace display + +namespace views { + +class DesktopScreenOzone : public display::ScreenBase, + public display::NativeDisplayObserver { + public: + DesktopScreenOzone(); + ~DesktopScreenOzone() override; + + private: + void OnHostDisplaysReady( + const std::vector<display::DisplaySnapshot*>& displays); + + // display::NativeDisplayDelegate: + void OnConfigurationChanged() override; + void OnDisplaySnapshotsInvalidated() override; + + std::unique_ptr<display::NativeDisplayDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(DesktopScreenOzone); +}; + +} // namespace views + +#endif // UI_VIEWS_WIDGET_DESKTOP_AURA_DESKTOP_SCREEN_OZONE_H_ diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc index 2822c8e41ef..8fd5d379d34 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc @@ -362,6 +362,9 @@ std::vector<display::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() { primary_display_index_ = 0; RROutput primary_display_id = XRRGetOutputPrimary(xdisplay_, x_root_window_); + int explicit_primary_display_index = -1; + int monitor_order_primary_display_index = -1; + bool has_work_area = false; gfx::Rect work_area_in_pixels; std::vector<int> value; @@ -436,10 +439,13 @@ std::vector<display::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() { } if (is_primary_display) - primary_display_index_ = displays.size(); + explicit_primary_display_index = displays.size(); + + auto monitor_iter = output_to_monitor.find(output_id); + if (monitor_iter != output_to_monitor.end() && monitor_iter->second == 0) + monitor_order_primary_display_index = displays.size(); if (!display::Display::HasForceColorProfile()) { - auto monitor_iter = output_to_monitor.find(output_id); gfx::ICCProfile icc_profile = GetICCProfileForMonitor( monitor_iter == output_to_monitor.end() ? 0 : monitor_iter->second); icc_profile.HistogramDisplay(display.id()); @@ -450,6 +456,12 @@ std::vector<display::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() { } } + if (explicit_primary_display_index != -1) { + primary_display_index_ = explicit_primary_display_index; + } else if (monitor_order_primary_display_index != -1) { + primary_display_index_ = monitor_order_primary_display_index; + } + if (displays.empty()) return GetFallbackDisplayList(); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_ozone.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_ozone.cc deleted file mode 100644 index 5834cffc5a0..00000000000 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_ozone.cc +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 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.h" - -namespace views { - -DesktopWindowTreeHost* DesktopWindowTreeHost::Create( - internal::NativeWidgetDelegate* native_widget_delegate, - DesktopNativeWidgetAura* desktop_native_widget_aura) { - NOTREACHED() << "Ozone builds should use DesktopWindowTreeHostMus codepath."; - return nullptr; -} - -} // namespace views 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 97b54ace8b5..60d2c66cca0 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 @@ -17,6 +17,9 @@ namespace views { +//////////////////////////////////////////////////////////////////////////////// +// DesktopWindowTreeHostPlatform: + DesktopWindowTreeHostPlatform::DesktopWindowTreeHostPlatform( internal::NativeWidgetDelegate* native_widget_delegate, DesktopNativeWidgetAura* desktop_native_widget_aura) @@ -33,15 +36,13 @@ void DesktopWindowTreeHostPlatform::SetBoundsInDIP( const gfx::Rect& bounds_in_dip) { DCHECK_NE(0, device_scale_factor()); SetBoundsInPixels( - gfx::ConvertRectToPixel(device_scale_factor(), bounds_in_dip)); + gfx::ConvertRectToPixel(device_scale_factor(), bounds_in_dip), + viz::LocalSurfaceId()); } void DesktopWindowTreeHostPlatform::Init(const Widget::InitParams& params) { CreateAndSetDefaultPlatformWindow(); - // TODO(sky): this should be |params.force_software_compositing|, figure out - // why it has to be true now. - const bool use_software_compositing = true; - CreateCompositor(viz::FrameSinkId(), use_software_compositing); + CreateCompositor(viz::FrameSinkId(), params.force_software_compositing); aura::WindowTreeHost::OnAcceleratedWidgetAvailable(); InitHost(); if (!params.bounds.IsEmpty()) @@ -76,7 +77,9 @@ void DesktopWindowTreeHostPlatform::Close() { return; // Hide while waiting for the close. - platform_window()->Hide(); + // Please note that it's better to call WindowTreeHost::Hide, which also calls + // PlatformWindow::Hide and Compositor::SetVisible(false). + Hide(); waiting_for_close_now_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -257,15 +260,13 @@ void DesktopWindowTreeHostPlatform::Restore() { } bool DesktopWindowTreeHostPlatform::IsMaximized() const { - // TODO: needs PlatformWindow support. - NOTIMPLEMENTED_LOG_ONCE(); - return false; + return platform_window()->GetPlatformWindowState() == + ui::PlatformWindowState::PLATFORM_WINDOW_STATE_MAXIMIZED; } bool DesktopWindowTreeHostPlatform::IsMinimized() const { - // TODO: needs PlatformWindow support. - NOTIMPLEMENTED_LOG_ONCE(); - return false; + return platform_window()->GetPlatformWindowState() == + ui::PlatformWindowState::PLATFORM_WINDOW_STATE_MINIMIZED; } bool DesktopWindowTreeHostPlatform::HasCapture() const { @@ -340,14 +341,13 @@ bool DesktopWindowTreeHostPlatform::ShouldWindowContentsBeTransparent() const { void DesktopWindowTreeHostPlatform::FrameTypeChanged() {} void DesktopWindowTreeHostPlatform::SetFullscreen(bool fullscreen) { - // TODO: needs PlatformWindow support. - NOTIMPLEMENTED_LOG_ONCE(); + if (IsFullscreen() != fullscreen) + platform_window()->ToggleFullscreen(); } bool DesktopWindowTreeHostPlatform::IsFullscreen() const { - // TODO: needs PlatformWindow support. - NOTIMPLEMENTED_LOG_ONCE(); - return false; + return platform_window()->GetPlatformWindowState() == + ui::PlatformWindowState::PLATFORM_WINDOW_STATE_FULLSCREEN; } void DesktopWindowTreeHostPlatform::SetOpacity(float opacity) { @@ -409,18 +409,61 @@ void DesktopWindowTreeHostPlatform::OnClosed() { desktop_native_widget_aura_->OnHostClosed(); } +void DesktopWindowTreeHostPlatform::OnWindowStateChanged( + ui::PlatformWindowState new_state) { + // Propagate minimization/restore to compositor to avoid drawing 'blank' + // frames that could be treated as previews, which show content even if a + // window is minimized. + bool visible = + new_state != ui::PlatformWindowState::PLATFORM_WINDOW_STATE_MINIMIZED; + if (visible != compositor()->IsVisible()) { + compositor()->SetVisible(visible); + native_widget_delegate_->OnNativeWidgetVisibilityChanged(visible); + } + + // It might require relayouting when state property has been changed. + if (visible) + Relayout(); +} + void DesktopWindowTreeHostPlatform::OnCloseRequest() { GetWidget()->Close(); } +void DesktopWindowTreeHostPlatform::OnAcceleratedWidgetDestroying() { + native_widget_delegate_->OnNativeWidgetDestroying(); +} + void DesktopWindowTreeHostPlatform::OnActivationChanged(bool active) { is_active_ = active; aura::WindowTreeHostPlatform::OnActivationChanged(active); desktop_native_widget_aura_->HandleActivationChanged(active); } +void DesktopWindowTreeHostPlatform::Relayout() { + Widget* widget = native_widget_delegate_->AsWidget(); + NonClientView* non_client_view = widget->non_client_view(); + // non_client_view may be NULL, especially during creation. + if (non_client_view) { + non_client_view->client_view()->InvalidateLayout(); + non_client_view->InvalidateLayout(); + } + widget->GetRootView()->Layout(); +} + Widget* DesktopWindowTreeHostPlatform::GetWidget() { return native_widget_delegate_->AsWidget(); } +//////////////////////////////////////////////////////////////////////////////// +// DesktopWindowTreeHost: + +// static +DesktopWindowTreeHost* DesktopWindowTreeHost::Create( + internal::NativeWidgetDelegate* native_widget_delegate, + DesktopNativeWidgetAura* desktop_native_widget_aura) { + return new DesktopWindowTreeHostPlatform(native_widget_delegate, + desktop_native_widget_aura); +} + } // namespace views 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 e9bc864128e..551bcb598b8 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 @@ -90,10 +90,14 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform // WindowTreeHostPlatform: void OnClosed() override; + void OnWindowStateChanged(ui::PlatformWindowState new_state) override; void OnCloseRequest() override; + void OnAcceleratedWidgetDestroying() override; void OnActivationChanged(bool active) override; private: + void Relayout(); + Widget* GetWidget(); internal::NativeWidgetDelegate* const native_widget_delegate_; 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 c8f868bf416..288b92c3ead 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 @@ -20,6 +20,7 @@ #include "ui/display/win/dpi.h" #include "ui/display/win/screen_win.h" #include "ui/events/keyboard_hook.h" +#include "ui/events/keycodes/dom/dom_code.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/native_widget_types.h" @@ -521,7 +522,14 @@ gfx::Rect DesktopWindowTreeHostWin::GetBoundsInPixels() const { return without_expansion; } -void DesktopWindowTreeHostWin::SetBoundsInPixels(const gfx::Rect& bounds) { +void DesktopWindowTreeHostWin::SetBoundsInPixels( + const gfx::Rect& bounds, + const viz::LocalSurfaceId& local_surface_id) { + // On Windows, the callers of SetBoundsInPixels() shouldn't need to (or be + // able to) allocate LocalSurfaceId for the compositor. Aura itself should + // allocate the new ids as needed, instead. + DCHECK(!local_surface_id.is_valid()); + // If the window bounds have to be expanded we need to subtract the // window_expansion_top_left_delta_ from the origin and add the // window_expansion_bottom_right_delta_ to the width and height @@ -556,15 +564,16 @@ void DesktopWindowTreeHostWin::ReleaseCapture() { } bool DesktopWindowTreeHostWin::CaptureSystemKeyEventsImpl( - base::Optional<base::flat_set<int>> key_codes) { + base::Optional<base::flat_set<ui::DomCode>> dom_codes) { // Only one KeyboardHook should be active at a time, otherwise there will be // problems with event routing (i.e. which Hook takes precedence) and // destruction ordering. DCHECK(!keyboard_hook_); keyboard_hook_ = ui::KeyboardHook::Create( - std::move(key_codes), + std::move(dom_codes), GetAcceleratedWidget(), base::BindRepeating(&DesktopWindowTreeHostWin::HandleKeyEvent, base::Unretained(this))); + return keyboard_hook_ != nullptr; } @@ -572,8 +581,14 @@ void DesktopWindowTreeHostWin::ReleaseSystemKeyEventCapture() { keyboard_hook_.reset(); } -bool DesktopWindowTreeHostWin::IsKeyLocked(int native_key_code) { - return keyboard_hook_ && keyboard_hook_->IsKeyLocked(native_key_code); +bool DesktopWindowTreeHostWin::IsKeyLocked(ui::DomCode dom_code) { + return keyboard_hook_ && keyboard_hook_->IsKeyLocked(dom_code); +} + +base::flat_map<std::string, std::string> +DesktopWindowTreeHostWin::GetKeyboardLayoutMap() { + NOTIMPLEMENTED(); + return {}; } void DesktopWindowTreeHostWin::SetCursorNative(gfx::NativeCursor cursor) { 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 4120d3ae63f..327e4fb5df8 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 @@ -23,6 +23,7 @@ class FocusClient; } namespace ui { +enum class DomCode; class InputMethod; class KeyboardHook; } // namespace ui @@ -126,14 +127,17 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin void ShowImpl() override; void HideImpl() override; gfx::Rect GetBoundsInPixels() const override; - void SetBoundsInPixels(const gfx::Rect& bounds) override; + void SetBoundsInPixels(const gfx::Rect& bounds, + const viz::LocalSurfaceId& local_surface_id = + viz::LocalSurfaceId()) override; gfx::Point GetLocationOnScreenInPixels() const override; void SetCapture() override; void ReleaseCapture() override; bool CaptureSystemKeyEventsImpl( - base::Optional<base::flat_set<int>> keys_codes) override; + base::Optional<base::flat_set<ui::DomCode>> dom_codes) override; void ReleaseSystemKeyEventCapture() override; - bool IsKeyLocked(int native_key_code) override; + bool IsKeyLocked(ui::DomCode dom_code) override; + base::flat_map<std::string, std::string> GetKeyboardLayoutMap() override; void SetCursorNative(gfx::NativeCursor cursor) override; void OnCursorVisibilityChangedNative(bool show) override; void MoveCursorToScreenLocationInPixels( 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 806360dbc69..cbfbfcbdcdb 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 @@ -37,6 +37,7 @@ #include "ui/events/devices/x11/touch_factory_x11.h" #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/events/platform/x11/x11_event_source.h" @@ -598,7 +599,7 @@ void DesktopWindowTreeHostX11::StackAtTop() { void DesktopWindowTreeHostX11::CenterWindow(const gfx::Size& size) { gfx::Size size_in_pixels = ToPixelRect(gfx::Rect(size)).size(); - gfx::Rect parent_bounds_in_pixels = GetWorkAreaBoundsInPixels(); + gfx::Rect parent_bounds_in_pixels = ToPixelRect(GetWorkAreaBoundsInScreen()); // If |window_|'s transient parent bounds are big enough to contain |size|, // use them instead. @@ -670,24 +671,21 @@ gfx::Rect DesktopWindowTreeHostX11::GetRestoredBounds() const { } std::string DesktopWindowTreeHostX11::GetWorkspace() const { - if (workspace_.empty()) - const_cast<DesktopWindowTreeHostX11*>(this)->UpdateWorkspace(); - return workspace_; + return workspace_ ? base::IntToString(workspace_.value()) : std::string(); } -bool DesktopWindowTreeHostX11::UpdateWorkspace() { - int workspace_int; - if (!ui::GetWindowDesktop(xwindow_, &workspace_int)) - return false; - std::string workspace_str = base::IntToString(workspace_int); - if (workspace_ == workspace_str) - return false; - workspace_ = workspace_str; - return true; +void DesktopWindowTreeHostX11::UpdateWorkspace() { + int workspace; + if (ui::GetWindowDesktop(xwindow_, &workspace)) + workspace_ = workspace; + else + workspace_ = base::nullopt; } gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInScreen() const { - return ToDIPRect(GetWorkAreaBoundsInPixels()); + return display::Screen::GetScreen() + ->GetDisplayNearestWindow(const_cast<aura::Window*>(window())) + .work_area(); } void DesktopWindowTreeHostX11::SetShape( @@ -891,7 +889,7 @@ void DesktopWindowTreeHostX11::SetVisibleOnAllWorkspaces(bool always_visible) { return; } - workspace_ = base::IntToString(kAllDesktops); + workspace_ = kAllDesktops; XEvent xevent; memset (&xevent, 0, sizeof (xevent)); xevent.type = ClientMessage; @@ -1199,7 +1197,13 @@ gfx::Rect DesktopWindowTreeHostX11::GetBoundsInPixels() const { } void DesktopWindowTreeHostX11::SetBoundsInPixels( - const gfx::Rect& requested_bounds_in_pixel) { + const gfx::Rect& requested_bounds_in_pixel, + const viz::LocalSurfaceId& local_surface_id) { + // On desktop-x11, the callers of SetBoundsInPixels() shouldn't need to (or be + // able to) allocate LocalSurfaceId for the compositor. Aura itself should + // allocate the new ids as needed, instead. + DCHECK(!local_surface_id.is_valid()); + gfx::Rect bounds_in_pixels(requested_bounds_in_pixel.origin(), AdjustSize(requested_bounds_in_pixel.size())); bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin(); @@ -1251,7 +1255,7 @@ void DesktopWindowTreeHostX11::SetBoundsInPixels( if (origin_changed) native_widget_delegate_->AsWidget()->OnNativeWidgetMove(); if (size_changed) { - OnHostResizedInPixels(bounds_in_pixels.size()); + OnHostResizedInPixels(bounds_in_pixels.size(), local_surface_id); ResetWindowRegion(); } } @@ -1298,15 +1302,16 @@ void DesktopWindowTreeHostX11::ReleaseCapture() { } bool DesktopWindowTreeHostX11::CaptureSystemKeyEventsImpl( - base::Optional<base::flat_set<int>> key_codes) { + base::Optional<base::flat_set<ui::DomCode>> dom_codes) { // Only one KeyboardHook should be active at a time, otherwise there will be // problems with event routing (i.e. which Hook takes precedence) and // destruction ordering. DCHECK(!keyboard_hook_); keyboard_hook_ = ui::KeyboardHook::Create( - std::move(key_codes), + std::move(dom_codes), GetAcceleratedWidget(), base::BindRepeating(&DesktopWindowTreeHostX11::DispatchKeyEvent, base::Unretained(this))); + return keyboard_hook_ != nullptr; } @@ -1314,8 +1319,8 @@ void DesktopWindowTreeHostX11::ReleaseSystemKeyEventCapture() { keyboard_hook_.reset(); } -bool DesktopWindowTreeHostX11::IsKeyLocked(int native_key_code) { - return keyboard_hook_ && keyboard_hook_->IsKeyLocked(native_key_code); +bool DesktopWindowTreeHostX11::IsKeyLocked(ui::DomCode dom_code) { + return keyboard_hook_ && keyboard_hook_->IsKeyLocked(dom_code); } void DesktopWindowTreeHostX11::SetCursorNative(gfx::NativeCursor cursor) { @@ -1363,12 +1368,40 @@ void DesktopWindowTreeHostX11::OnFullscreenStateChanged() {} void DesktopWindowTreeHostX11::InitX11Window( const Widget::InitParams& params) { - unsigned long attribute_mask = CWBackPixmap | CWBitGravity; + unsigned long attribute_mask = CWBackPixel | CWBitGravity; XSetWindowAttributes swa; memset(&swa, 0, sizeof(swa)); swa.background_pixmap = x11::None; swa.bit_gravity = NorthWestGravity; + // Set the background color on startup to make the initial flickering + // happening between the XWindow is mapped and the first expose event + // is completely handled less annoying. If possible, we use the content + // window's background color, otherwise we fallback to white. + int background_color; + + const views::LinuxUI* linux_ui = views::LinuxUI::instance(); + if (linux_ui && content_window()) { + ui::NativeTheme::ColorId target_color; + switch (params.type) { + case Widget::InitParams::TYPE_BUBBLE: + target_color = ui::NativeTheme::kColorId_BubbleBackground; + break; + case Widget::InitParams::TYPE_TOOLTIP: + target_color = ui::NativeTheme::kColorId_TooltipBackground; + break; + default: + target_color = ui::NativeTheme::kColorId_WindowBackground; + break; + } + + ui::NativeTheme* theme = linux_ui->GetNativeTheme(content_window()); + background_color = theme->GetSystemColor(target_color); + } else { + background_color = WhitePixel(xdisplay_, DefaultScreen(xdisplay_)); + } + swa.background_pixel = background_color; + ::Atom window_type; switch (params.type) { case Widget::InitParams::TYPE_MENU: @@ -1495,7 +1528,7 @@ void DesktopWindowTreeHostX11::InitX11Window( if (is_always_on_top_) state_atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_ABOVE")); - workspace_.clear(); + workspace_ = base::nullopt; if (params.visible_on_all_workspaces) { state_atom_list.push_back(gfx::GetAtom("_NET_WM_STATE_STICKY")); ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllDesktops); @@ -2134,7 +2167,12 @@ uint32_t DesktopWindowTreeHostX11::DispatchEvent( case ui::ET_SCROLL_FLING_CANCEL: case ui::ET_SCROLL: { ui::ScrollEvent scrollev(xev); - SendEventToSink(&scrollev); + // We need to filter zero scroll offset here. Because + // MouseWheelEventQueue assumes we'll never get a zero scroll offset + // event and we need delta to determine which element to scroll on + // phaseBegan. + if (scrollev.x_offset() != 0.0 || scrollev.y_offset() != 0.0) + SendEventToSink(&scrollev); break; } case ui::ET_KEY_PRESSED: @@ -2253,7 +2291,9 @@ uint32_t DesktopWindowTreeHostX11::DispatchEvent( } else if (changed_atom == gfx::GetAtom("_NET_FRAME_EXTENTS")) { OnFrameExtentsUpdated(); } else if (changed_atom == gfx::GetAtom("_NET_WM_DESKTOP")) { - if (UpdateWorkspace()) + base::Optional<int> old_workspace = workspace_; + UpdateWorkspace(); + if (workspace_ != old_workspace) OnHostWorkspaceChanged(); } break; @@ -2280,27 +2320,6 @@ void DesktopWindowTreeHostX11::DelayedChangeFrameType(Widget::FrameType type) { native_widget_delegate_->AsWidget()->non_client_view()->UpdateFrame(); } -gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInPixels() const { - std::vector<int> value; - if (ui::GetIntArrayProperty(x_root_window_, "_NET_WORKAREA", &value) && - value.size() >= 4) { - return gfx::Rect(value[0], value[1], value[2], value[3]); - } - - // Fetch the geometry of the root window. - Window root; - int x, y; - unsigned int width, height; - unsigned int border_width, depth; - if (!XGetGeometry(xdisplay_, x_root_window_, &root, &x, &y, &width, &height, - &border_width, &depth)) { - NOTIMPLEMENTED(); - return gfx::Rect(0, 0, 10, 10); - } - - return gfx::Rect(x, y, width, height); -} - gfx::Rect DesktopWindowTreeHostX11::ToDIPRect( const gfx::Rect& rect_in_pixels) const { gfx::RectF rect_in_dip = gfx::RectF(rect_in_pixels); @@ -2350,6 +2369,13 @@ aura::Window* DesktopWindowTreeHostX11::content_window() { return desktop_native_widget_aura_->content_window(); } +base::flat_map<std::string, std::string> +DesktopWindowTreeHostX11::GetKeyboardLayoutMap() { + if (views::LinuxUI::instance()) + return views::LinuxUI::instance()->GetKeyboardLayoutMap(); + return {}; +} + //////////////////////////////////////////////////////////////////////////////// // DesktopWindowTreeHost, public: 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 79d677baa9f..f424728b967 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 @@ -30,6 +30,7 @@ class ImageSkiaRep; } namespace ui { +enum class DomCode; class EventHandler; class KeyboardHook; class XScopedEventSelector; @@ -87,6 +88,9 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 // Disables event listening to make |dialog| modal. std::unique_ptr<base::Closure> DisableEventListening(); + // Returns a map of KeyboardEvent code to KeyboardEvent key values. + base::flat_map<std::string, std::string> GetKeyboardLayoutMap() override; + protected: // Overridden from DesktopWindowTreeHost: void Init(const Widget::InitParams& params) override; @@ -160,14 +164,16 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 void ShowImpl() override; void HideImpl() override; gfx::Rect GetBoundsInPixels() const override; - void SetBoundsInPixels(const gfx::Rect& requested_bounds_in_pixels) override; + void SetBoundsInPixels(const gfx::Rect& requested_bounds_in_pixels, + const viz::LocalSurfaceId& local_surface_id = + viz::LocalSurfaceId()) override; gfx::Point GetLocationOnScreenInPixels() const override; void SetCapture() override; void ReleaseCapture() override; bool CaptureSystemKeyEventsImpl( - base::Optional<base::flat_set<int>> keys_codes) override; + base::Optional<base::flat_set<ui::DomCode>> dom_codes) override; void ReleaseSystemKeyEventCapture() override; - bool IsKeyLocked(int native_key_code) override; + bool IsKeyLocked(ui::DomCode dom_code) override; void SetCursorNative(gfx::NativeCursor cursor) override; void MoveCursorToScreenLocationInPixels( const gfx::Point& location_in_pixels) override; @@ -222,8 +228,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 void OnFocusEvent(bool focus_in, int mode, int detail); // Makes a round trip to the X server to get the enclosing workspace for this - // window. Returns true iff |workspace_| was changed. - bool UpdateWorkspace(); + // window. + void UpdateWorkspace(); // Updates |xwindow_|'s minimum and maximum size. void UpdateMinAndMaxSize(); @@ -272,7 +278,6 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 void DelayedResize(const gfx::Size& size_in_pixels); void DelayedChangeFrameType(Widget::FrameType new_type); - gfx::Rect GetWorkAreaBoundsInPixels() const; gfx::Rect ToDIPRect(const gfx::Rect& rect_in_pixels) const; gfx::Rect ToPixelRect(const gfx::Rect& rect_in_dip) const; @@ -327,8 +332,9 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 // |xwindow_|'s maximum size. gfx::Size max_size_in_pixels_; - // The workspace containing |xwindow_|. - std::string workspace_; + // The workspace containing |xwindow_|. This will be base::nullopt when + // _NET_WM_DESKTOP is unset. + base::Optional<int> workspace_; // The window manager state bits. base::flat_set<::Atom> window_properties_; diff --git a/chromium/ui/views/widget/desktop_aura/window_event_filter.h b/chromium/ui/views/widget/desktop_aura/window_event_filter.h index 9d765cf73dd..a9bc0387c25 100644 --- a/chromium/ui/views/widget/desktop_aura/window_event_filter.h +++ b/chromium/ui/views/widget/desktop_aura/window_event_filter.h @@ -7,7 +7,6 @@ #include "base/compiler_specific.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "ui/events/event_handler.h" #include "ui/views/views_export.h" diff --git a/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.cc b/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.cc index 1ed79cf0122..9ac94ba132f 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.cc +++ b/chromium/ui/views/widget/desktop_aura/x11_desktop_handler.cc @@ -4,7 +4,6 @@ #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" -#include "base/message_loop/message_loop.h" #include "base/strings/string_number_conversions.h" #include "ui/aura/env.h" #include "ui/aura/window_event_dispatcher.h" diff --git a/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.cc b/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.cc index 6e4dddebbee..52b2985e7ad 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.cc +++ b/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.cc @@ -5,7 +5,6 @@ #include "ui/views/widget/desktop_aura/x11_desktop_window_move_client.h" #include "base/debug/stack_trace.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "ui/aura/env.h" #include "ui/aura/window.h" diff --git a/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.h b/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.h index 6d074456878..a2168023f3c 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.h +++ b/chromium/ui/views/widget/desktop_aura/x11_desktop_window_move_client.h @@ -7,7 +7,6 @@ #include "base/callback.h" #include "base/compiler_specific.h" -#include "base/message_loop/message_loop.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/x/x11.h" #include "ui/views/views_export.h" diff --git a/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.h b/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.h index 54a27a6afa6..f1cddd4baac 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.h +++ b/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.h @@ -7,7 +7,6 @@ #include "base/compiler_specific.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "ui/events/event_handler.h" #include "ui/gfx/x/x11.h" #include "ui/gfx/x/x11_types.h" diff --git a/chromium/ui/views/widget/desktop_widget_unittest.cc b/chromium/ui/views/widget/desktop_widget_unittest.cc index 0004ccab265..0ab25f179c2 100644 --- a/chromium/ui/views/widget/desktop_widget_unittest.cc +++ b/chromium/ui/views/widget/desktop_widget_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "ui/display/display.h" +#include "ui/display/screen.h" #include "ui/views/test/native_widget_factory.h" #include "ui/views/test/views_test_base.h" #include "ui/views/test/widget_test.h" @@ -29,7 +31,7 @@ TEST_F(DesktopScreenPositionClientTest, PositionDialog) { Widget* dialog = DialogDelegate::CreateDialogWidget( dialog_delegate_view, NULL, parent_widget.GetNativeView()); dialog->SetBounds(gfx::Rect(11, 12, 200, 200)); - EXPECT_EQ("11,12", dialog->GetWindowBoundsInScreen().origin().ToString()); + EXPECT_EQ(gfx::Point(11, 12), dialog->GetWindowBoundsInScreen().origin()); } // Verifies that setting the bounds of a control parented to something other @@ -39,6 +41,8 @@ TEST_F(DesktopScreenPositionClientTest, PositionControlWithNonRootParent) { Widget widget2; Widget widget3; gfx::Point origin = gfx::Point(16, 16); + gfx::Rect work_area = + display::Screen::GetScreen()->GetDisplayNearestPoint(origin).work_area(); // Use a custom frame type. By default we will choose a native frame when // aero glass is enabled, and this complicates the logic surrounding origin @@ -52,7 +56,9 @@ TEST_F(DesktopScreenPositionClientTest, PositionControlWithNonRootParent) { // parented to the second, also not positioned at (0,0). Widget::InitParams params1 = CreateParams(Widget::InitParams::TYPE_WINDOW); - params1.bounds = gfx::Rect(origin, gfx::Size(700, 600)); + params1.bounds = gfx::Rect( + origin + work_area.OffsetFromOrigin(), + gfx::Size(700, work_area.height() - origin.y() - work_area.y())); params1.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params1.native_widget = test::CreatePlatformDesktopNativeWidgetImpl(params1, &widget1, nullptr); @@ -60,7 +66,7 @@ TEST_F(DesktopScreenPositionClientTest, PositionControlWithNonRootParent) { Widget::InitParams params2 = CreateParams(Widget::InitParams::TYPE_WINDOW); - params2.bounds = gfx::Rect(origin, gfx::Size(600, 500)); + params2.bounds = gfx::Rect(origin, gfx::Size(600, work_area.height() - 100)); params2.parent = widget1.GetNativeView(); params2.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params2.child = true; @@ -71,14 +77,94 @@ TEST_F(DesktopScreenPositionClientTest, PositionControlWithNonRootParent) { params3.parent = widget2.GetNativeView(); params3.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params3.child = true; - params3.bounds = gfx::Rect(origin, gfx::Size(500, 400)); + params3.bounds = gfx::Rect(origin, gfx::Size(500, work_area.height() - 200)); widget3.Init(params3); // The origin of the 3rd window should be the sum of all parent origins. - gfx::Point expected_origin(origin.x() * 3, origin.y() * 3); - gfx::Rect expected_bounds(expected_origin, gfx::Size(500, 400)); + gfx::Point expected_origin(origin.x() * 3 + work_area.x(), + origin.y() * 3 + work_area.y()); + gfx::Rect expected_bounds(expected_origin, + gfx::Size(500, work_area.height() - 200)); gfx::Rect actual_bounds(widget3.GetWindowBoundsInScreen()); - EXPECT_EQ(expected_bounds.ToString(), actual_bounds.ToString()); + EXPECT_EQ(expected_bounds, actual_bounds); +} + +// Verifies that the initial bounds of the widget is fully on the screen. +TEST_F(DesktopScreenPositionClientTest, InitialBoundsConstrainedToDesktop) { + Widget widget; + // Use the primary display for this test. + gfx::Rect work_area = + display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); + // Make the origin start at 75% of the width and height. + gfx::Point origin = + gfx::Point(work_area.width() * 3 / 4, work_area.height() * 3 / 4); + + // Use a custom frame type. See above for further explanation. + widget.set_frame_type(Widget::FRAME_TYPE_FORCE_CUSTOM); + + // Create a window that is intentionally positioned so that it is off screen. + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); + params.bounds = gfx::Rect( + origin, gfx::Size(work_area.width() / 2, work_area.height() / 2)); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.native_widget = + test::CreatePlatformDesktopNativeWidgetImpl(params, &widget, nullptr); + widget.Init(params); + + // The bounds of the window should be fully on the primary display. + gfx::Point expected_origin(work_area.right() - work_area.width() / 2, + work_area.bottom() - work_area.height() / 2); + gfx::Rect expected_bounds(expected_origin, gfx::Size(work_area.width() / 2, + work_area.height() / 2)); + gfx::Rect actual_bounds(widget.GetWindowBoundsInScreen()); + EXPECT_EQ(expected_bounds, actual_bounds); +} + +// Verifies that the initial bounds of the widget is fully within the bounds of +// the parent. +TEST_F(DesktopScreenPositionClientTest, InitialBoundsConstrainedToParent) { + Widget widget1; + Widget widget2; + // Use the primary display for this test. + gfx::Rect work_area = + display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); + gfx::Point origin = gfx::Point(work_area.x() + work_area.width() / 4, + work_area.y() + work_area.height() / 4); + + // Use a custom frame type. See above for further explanation + widget1.set_frame_type(Widget::FRAME_TYPE_FORCE_CUSTOM); + widget2.set_frame_type(Widget::FRAME_TYPE_FORCE_CUSTOM); + + // Create 2 windows. A root window, and an arbitrary window parented to the + // root and positioned such that it extends beyond the bounds of the root. + Widget::InitParams params1 = CreateParams(Widget::InitParams::TYPE_WINDOW); + params1.bounds = gfx::Rect( + origin, gfx::Size(work_area.width() / 2, work_area.height() / 2)); + params1.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params1.native_widget = + test::CreatePlatformDesktopNativeWidgetImpl(params1, &widget1, nullptr); + widget1.Init(params1); + + gfx::Rect widget_bounds(widget1.GetWindowBoundsInScreen()); + + Widget::InitParams params2 = CreateParams(Widget::InitParams::TYPE_WINDOW); + params2.bounds = + gfx::Rect(widget_bounds.width() * 3 / 4, widget_bounds.height() * 3 / 4, + widget_bounds.width() / 2, widget_bounds.height() / 2); + params2.parent = widget1.GetNativeView(); + params2.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params2.child = true; + widget2.Init(params2); + + // The bounds of the child window should be fully in the parent. + gfx::Point expected_origin( + widget_bounds.right() - widget_bounds.width() / 2, + widget_bounds.bottom() - widget_bounds.height() / 2); + gfx::Rect expected_bounds( + expected_origin, + gfx::Size(widget_bounds.width() / 2, widget_bounds.height() / 2)); + gfx::Rect actual_bounds(widget2.GetWindowBoundsInScreen()); + EXPECT_EQ(expected_bounds, actual_bounds); } } // namespace views diff --git a/chromium/ui/views/widget/native_widget_aura.cc b/chromium/ui/views/widget/native_widget_aura.cc index 76f19a26a0b..8c0bbd9db9b 100644 --- a/chromium/ui/views/widget/native_widget_aura.cc +++ b/chromium/ui/views/widget/native_widget_aura.cc @@ -47,9 +47,11 @@ #include "ui/views/widget/widget_aura_utils.h" #include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/window_reorderer.h" +#include "ui/wm/core/coordinate_conversion.h" #include "ui/wm/core/shadow_types.h" #include "ui/wm/core/transient_window_manager.h" #include "ui/wm/core/window_animations.h" +#include "ui/wm/core/window_properties.h" #include "ui/wm/core/window_util.h" #include "ui/wm/public/activation_client.h" #include "ui/wm/public/window_move_client.h" @@ -187,7 +189,7 @@ void NativeWidgetAura::InitNativeWidget(const Widget::InitParams& params) { wm::AddTransientChild(parent, window_); if (!context) context = parent; - parent = NULL; + parent = nullptr; // Generally transient bubbles are showing state associated to the parent // window. Make sure the transient bubble is only visible if the parent is @@ -483,6 +485,22 @@ void NativeWidgetAura::SetBounds(const gfx::Rect& bounds) { window_->SetBounds(bounds); } +void NativeWidgetAura::SetBoundsConstrained(const gfx::Rect& bounds) { + if (!window_) + return; + + gfx::Rect new_bounds(bounds); + if (window_->parent()) { + if (window_->parent()->GetProperty(wm::kUsesScreenCoordinatesKey)) { + new_bounds = + NativeWidgetPrivate::ConstrainBoundsToDisplayWorkArea(new_bounds); + } else { + new_bounds.AdjustToFit(gfx::Rect(window_->parent()->bounds().size())); + } + } + SetBounds(new_bounds); +} + void NativeWidgetAura::SetSize(const gfx::Size& size) { if (window_) window_->SetBounds(gfx::Rect(window_->bounds().origin(), size)); diff --git a/chromium/ui/views/widget/native_widget_aura.h b/chromium/ui/views/widget/native_widget_aura.h index 73ce9503ff9..74bd738bcf9 100644 --- a/chromium/ui/views/widget/native_widget_aura.h +++ b/chromium/ui/views/widget/native_widget_aura.h @@ -96,6 +96,7 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, gfx::Rect GetRestoredBounds() const override; std::string GetWorkspace() const override; void SetBounds(const gfx::Rect& bounds) override; + void SetBoundsConstrained(const gfx::Rect& bounds) override; void SetSize(const gfx::Size& size) override; void StackAbove(gfx::NativeView native_view) override; void StackAtTop() override; diff --git a/chromium/ui/views/widget/native_widget_mac.h b/chromium/ui/views/widget/native_widget_mac.h index 74e3f333e57..c1d97e3ac54 100644 --- a/chromium/ui/views/widget/native_widget_mac.h +++ b/chromium/ui/views/widget/native_widget_mac.h @@ -37,10 +37,15 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { // 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, + // invoking OnWindowDestroyed()). + void WindowDestroying(); + // Deletes |bridge_| and informs |delegate_| that the native widget is - // destroyed. BridgedNativeWidget::OnWindowWillClose() calls this when the - // NSWindowDelegate informs the bridge that the window is being closed. - void OnWindowDestroyed(); + // destroyed. + void WindowDestroyed(); // Returns the vertical position that sheets should be anchored, in pixels // from the bottom of the window. @@ -81,6 +86,7 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { gfx::Rect GetRestoredBounds() const override; std::string GetWorkspace() const override; void SetBounds(const gfx::Rect& bounds) override; + void SetBoundsConstrained(const gfx::Rect& bounds) override; void SetSize(const gfx::Size& size) override; void StackAbove(gfx::NativeView native_view) override; void StackAtTop() override; @@ -138,6 +144,9 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { virtual NativeWidgetMacNSWindow* CreateNSWindow( const Widget::InitParams& params); + // Optional hook for subclasses invoked by WindowDestroying(). + virtual void OnWindowDestroying(NSWindow* window) {} + internal::NativeWidgetDelegate* delegate() { return delegate_; } private: diff --git a/chromium/ui/views/widget/native_widget_mac.mm b/chromium/ui/views/widget/native_widget_mac.mm index 223eb1191d1..c0e28737b88 100644 --- a/chromium/ui/views/widget/native_widget_mac.mm +++ b/chromium/ui/views/widget/native_widget_mac.mm @@ -18,6 +18,8 @@ #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" #import "ui/base/cocoa/window_size_constants.h" #include "ui/base/ui_base_switches.h" +#include "ui/display/display.h" +#include "ui/display/screen.h" #include "ui/gfx/font_list.h" #import "ui/gfx/mac/coordinate_conversion.h" #import "ui/gfx/mac/nswindow_frame_controls.h" @@ -109,7 +111,12 @@ bool NativeWidgetMac::IsWindowModalSheet() const { ui::MODAL_TYPE_WINDOW; } -void NativeWidgetMac::OnWindowDestroyed() { +void NativeWidgetMac::WindowDestroying() { + OnWindowDestroying(GetNativeWindow()); + delegate_->OnNativeWidgetDestroying(); +} + +void NativeWidgetMac::WindowDestroyed() { DCHECK(bridge_); bridge_.reset(); delegate_->OnNativeWidgetDestroyed(); @@ -173,7 +180,10 @@ bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const { } void NativeWidgetMac::FrameTypeChanged() { - NOTIMPLEMENTED(); + // This is called when the Theme has changed; forward the event to the root + // widget. + GetWidget()->ThemeChanged(); + GetWidget()->GetRootView()->SchedulePaint(); } Widget* NativeWidgetMac::GetWidget() { @@ -195,7 +205,7 @@ gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const { Widget* NativeWidgetMac::GetTopLevelWidget() { NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); - return native_widget ? native_widget->GetWidget() : NULL; + return native_widget ? native_widget->GetWidget() : nullptr; } const ui::Compositor* NativeWidgetMac::GetCompositor() const { @@ -252,7 +262,7 @@ bool NativeWidgetMac::HasCapture() const { } ui::InputMethod* NativeWidgetMac::GetInputMethod() { - return bridge_ ? bridge_->GetInputMethod() : NULL; + return bridge_ ? bridge_->GetInputMethod() : nullptr; } void NativeWidgetMac::CenterWindow(const gfx::Size& size) { @@ -332,6 +342,24 @@ void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) { bridge_->SetBounds(bounds); } +void NativeWidgetMac::SetBoundsConstrained(const gfx::Rect& bounds) { + if (!bridge_) + return; + + gfx::Rect new_bounds(bounds); + NativeWidgetPrivate* ancestor = + bridge_ && bridge_->parent() + ? GetNativeWidgetForNativeWindow(bridge_->parent()->GetNSWindow()) + : nullptr; + if (!ancestor) { + new_bounds = ConstrainBoundsToDisplayWorkArea(new_bounds); + } else { + new_bounds.AdjustToFit( + gfx::Rect(ancestor->GetWindowBoundsInScreen().size())); + } + SetBounds(new_bounds); +} + void NativeWidgetMac::SetSize(const gfx::Size& size) { // Ensure the top-left corner stays in-place (rather than the bottom-left, // which -[NSWindow setContentSize:] would do). @@ -376,7 +404,7 @@ void NativeWidgetMac::Close() { } // Clear the view early to suppress repaints. - bridge_->SetRootView(NULL); + 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: @@ -702,7 +730,7 @@ NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate); return [delegate nativeWidgetMac]; } - return NULL; // Not created by NativeWidgetMac. + return nullptr; // Not created by NativeWidgetMac. } // static diff --git a/chromium/ui/views/widget/native_widget_mac_unittest.mm b/chromium/ui/views/widget/native_widget_mac_unittest.mm index 6555f7ddd0f..e8c6ebeeaa7 100644 --- a/chromium/ui/views/widget/native_widget_mac_unittest.mm +++ b/chromium/ui/views/widget/native_widget_mac_unittest.mm @@ -12,7 +12,6 @@ #import "base/mac/scoped_nsobject.h" #import "base/mac/scoped_objc_class_swizzler.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" @@ -109,7 +108,7 @@ class BridgedNativeWidgetTestApi { ca_layer_params.pixel_size = size; ca_layer_params.scale_factor = kScaleFactor; ca_layer_frame_sink->UpdateCALayerTree(ca_layer_params); - bridge_->AcceleratedWidgetSwapCompleted(); + bridge_->AcceleratedWidgetCALayerParamsUpdated(); } NSAnimation* show_animation() { @@ -455,7 +454,7 @@ TEST_F(NativeWidgetMacTest, DISABLED_OrderFrontAfterMiniaturize) { // Wait and check that child is really visible. // TODO(kirr): remove the fixed delay. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), + FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), base::TimeDelta::FromSeconds(2)); base::RunLoop().Run(); diff --git a/chromium/ui/views/widget/native_widget_private.cc b/chromium/ui/views/widget/native_widget_private.cc new file mode 100644 index 00000000000..e8ad491578d --- /dev/null +++ b/chromium/ui/views/widget/native_widget_private.cc @@ -0,0 +1,25 @@ +// 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/native_widget_private.h" + +#include "ui/display/display.h" +#include "ui/display/screen.h" + +namespace views { +namespace internal { + +// static +gfx::Rect NativeWidgetPrivate::ConstrainBoundsToDisplayWorkArea( + const gfx::Rect& bounds) { + gfx::Rect new_bounds(bounds); + gfx::Rect work_area = + display::Screen::GetScreen()->GetDisplayMatching(bounds).work_area(); + if (!work_area.IsEmpty()) + new_bounds.AdjustToFit(work_area); + return new_bounds; +} + +} // namespace internal +} // namespace views diff --git a/chromium/ui/views/widget/native_widget_private.h b/chromium/ui/views/widget/native_widget_private.h index ebeb4e377b9..226dcba1511 100644 --- a/chromium/ui/views/widget/native_widget_private.h +++ b/chromium/ui/views/widget/native_widget_private.h @@ -77,6 +77,10 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { // capture set, or if |native_view| has no root. static gfx::NativeView GetGlobalCapture(gfx::NativeView native_view); + // Adjusts the given bounds to fit onto the display implied by the position + // of the given bounds. + static gfx::Rect ConstrainBoundsToDisplayWorkArea(const gfx::Rect& bounds); + // Initializes the NativeWidget. virtual void InitNativeWidget(const Widget::InitParams& params) = 0; @@ -173,6 +177,7 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { virtual gfx::Rect GetRestoredBounds() const = 0; virtual std::string GetWorkspace() const = 0; virtual void SetBounds(const gfx::Rect& bounds) = 0; + virtual void SetBoundsConstrained(const gfx::Rect& bounds) = 0; virtual void SetSize(const gfx::Size& size) = 0; virtual void StackAbove(gfx::NativeView native_view) = 0; virtual void StackAtTop() = 0; diff --git a/chromium/ui/views/widget/root_view.cc b/chromium/ui/views/widget/root_view.cc index d07c9ba57f4..4d11e22af84 100644 --- a/chromium/ui/views/widget/root_view.cc +++ b/chromium/ui/views/widget/root_view.cc @@ -8,7 +8,6 @@ #include "base/logging.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "build/build_config.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/cursor/cursor.h" diff --git a/chromium/ui/views/widget/widget.cc b/chromium/ui/views/widget/widget.cc index 7bc25d68ab4..bf4b1d07f7f 100644 --- a/chromium/ui/views/widget/widget.cc +++ b/chromium/ui/views/widget/widget.cc @@ -7,7 +7,6 @@ #include "base/auto_reset.h" #include "base/logging.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/trace_event/trace_event.h" #include "ui/aura/window.h" @@ -525,19 +524,7 @@ void Widget::CenterWindow(const gfx::Size& size) { } void Widget::SetBoundsConstrained(const gfx::Rect& bounds) { - gfx::Rect work_area = display::Screen::GetScreen() - ->GetDisplayNearestPoint(bounds.origin()) - .work_area(); - if (work_area.IsEmpty()) { - SetBounds(bounds); - } else { - // TODO(https://crbug.com/806936): The following code doesn't actually do - // what the comment describing this function says it should. - // Inset the work area slightly. - work_area.Inset(10, 10, 10, 10); - work_area.AdjustToFit(bounds); - SetBounds(work_area); - } + native_widget_->SetBoundsConstrained(bounds); } void Widget::SetVisibilityChangedAnimationsEnabled(bool value) { @@ -1510,7 +1497,7 @@ void Widget::SetInitialBounds(const gfx::Rect& bounds) { if (bounds.origin().IsOrigin()) { // No initial bounds supplied, so size the window to its content and // center over its parent. - native_widget_->CenterWindow(non_client_view_->GetPreferredSize()); + CenterWindow(non_client_view_->GetPreferredSize()); } else { // Use the preferred size and the supplied origin. gfx::Rect preferred_bounds(bounds); diff --git a/chromium/ui/views/widget/widget.h b/chromium/ui/views/widget/widget.h index e2df1f67a8b..bc3b94fa4f3 100644 --- a/chromium/ui/views/widget/widget.h +++ b/chromium/ui/views/widget/widget.h @@ -449,9 +449,8 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // Sizes the window to the specified size and centerizes it. void CenterWindow(const gfx::Size& size); - // Like SetBounds(), but ensures the Widget is fully visible on screen, - // resizing and/or repositioning as necessary. This is only useful for - // non-child widgets. + // Like SetBounds(), but ensures the Widget is fully visible on screen or + // parent widget, resizing and/or repositioning as necessary. void SetBoundsConstrained(const gfx::Rect& bounds); // Sets whether animations that occur when visibility is changed are enabled. diff --git a/chromium/ui/views/widget/widget_interactive_uitest.cc b/chromium/ui/views/widget/widget_interactive_uitest.cc index db44bd41b88..15a9ad10398 100644 --- a/chromium/ui/views/widget/widget_interactive_uitest.cc +++ b/chromium/ui/views/widget/widget_interactive_uitest.cc @@ -286,7 +286,7 @@ class WidgetTestInteractive : public WidgetTest { gl::GLSurfaceTestSupport::InitializeOneOff(); ui::RegisterPathProvider(); base::FilePath ui_test_pak_path; - ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); + ASSERT_TRUE(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); } WidgetTest::SetUp(); diff --git a/chromium/ui/views/widget/widget_unittest.cc b/chromium/ui/views/widget/widget_unittest.cc index b978aabb83c..8e298d2f647 100644 --- a/chromium/ui/views/widget/widget_unittest.cc +++ b/chromium/ui/views/widget/widget_unittest.cc @@ -51,6 +51,7 @@ #include "ui/wm/core/base_focus_rules.h" #include "ui/wm/core/focus_controller.h" #include "ui/wm/core/shadow_controller.h" +#include "ui/wm/core/shadow_controller_delegate.h" #endif namespace views { @@ -3775,8 +3776,8 @@ class WidgetShadowTest : public WidgetTest { focus_controller_ = std::make_unique<wm::FocusController>(new TestFocusRules); - shadow_controller_ = - std::make_unique<wm::ShadowController>(focus_controller_.get()); + shadow_controller_ = std::make_unique<wm::ShadowController>( + focus_controller_.get(), nullptr); } std::unique_ptr<wm::FocusController> focus_controller_; diff --git a/chromium/ui/views/widget/widget_utils_mac.h b/chromium/ui/views/widget/widget_utils_mac.h new file mode 100644 index 00000000000..2ee5ec72185 --- /dev/null +++ b/chromium/ui/views/widget/widget_utils_mac.h @@ -0,0 +1,16 @@ +// 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_WIDGET_WIDGET_UTILS_MAC_H_ +#define UI_VIEWS_WIDGET_WIDGET_UTILS_MAC_H_ + +#include "ui/views/widget/widget.h" + +namespace views { + +gfx::Size GetWindowSizeForClientSize(Widget* widget, const gfx::Size& size); + +} // namespace views + +#endif // UI_VIEWS_WIDGET_WIDGET_UTILS_MAC_H_
\ No newline at end of file diff --git a/chromium/ui/views/widget/widget_utils_mac.mm b/chromium/ui/views/widget/widget_utils_mac.mm new file mode 100644 index 00000000000..58d0a437fb0 --- /dev/null +++ b/chromium/ui/views/widget/widget_utils_mac.mm @@ -0,0 +1,17 @@ +// 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/widget_utils_mac.h" + +#import "ui/views/cocoa/bridged_native_widget.h" + +namespace views { + +gfx::Size GetWindowSizeForClientSize(Widget* widget, const gfx::Size& size) { + DCHECK(widget); + return BridgedNativeWidget::GetWindowSizeForClientSize( + widget->GetNativeWindow(), size); +} + +} // namespace views diff --git a/chromium/ui/views/win/hwnd_message_handler.cc b/chromium/ui/views/win/hwnd_message_handler.cc index 6a468941cf4..cadeb232262 100644 --- a/chromium/ui/views/win/hwnd_message_handler.cc +++ b/chromium/ui/views/win/hwnd_message_handler.cc @@ -17,7 +17,7 @@ #include "base/debug/alias.h" #include "base/location.h" #include "base/macros.h" -#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_current.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" @@ -239,6 +239,12 @@ ui::EventType GetTouchEventType(POINTER_FLAGS pointer_flags) { return ui::ET_TOUCH_MOVED; } +bool IsHitTestOnResizeHandle(LRESULT hittest) { + return hittest == HTRIGHT || hittest == HTLEFT || hittest == HTTOP || + hittest == HTBOTTOM || hittest == HTTOPLEFT || hittest == HTTOPRIGHT || + hittest == HTBOTTOMLEFT || hittest == HTBOTTOMRIGHT; +} + const int kTouchDownContextResetTimeout = 500; // Windows does not flag synthesized mouse messages from touch or pen in all @@ -339,8 +345,7 @@ base::LazyInstance<HWNDMessageHandler::FullscreenWindowMonitorMap>:: long HWNDMessageHandler::last_touch_or_pen_message_time_ = 0; HWNDMessageHandler::HWNDMessageHandler(HWNDMessageHandlerDelegate* delegate) - : msg_handled_(FALSE), - delegate_(delegate), + : delegate_(delegate), fullscreen_handler_(new FullscreenHandler), waiting_for_close_now_(false), use_system_default_icon_(false), @@ -370,8 +375,7 @@ HWNDMessageHandler::HWNDMessageHandler(HWNDMessageHandlerDelegate* delegate) pointer_events_for_touch_(features::IsUsingWMPointerForTouch()), precision_touchpad_scroll_phase_enabled_(base::FeatureList::IsEnabled( features::kPrecisionTouchpadScrollPhase)), - autohide_factory_(this), - weak_factory_(this) {} + autohide_factory_(this) {} HWNDMessageHandler::~HWNDMessageHandler() { DCHECK(delegate_->GetHWNDMessageDelegateInputMethod()); @@ -451,7 +455,7 @@ void HWNDMessageHandler::Close() { waiting_for_close_now_ = true; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&HWNDMessageHandler::CloseNow, - weak_factory_.GetWeakPtr())); + msg_handler_weak_factory_.GetWeakPtr())); } } @@ -737,8 +741,7 @@ bool HWNDMessageHandler::RunMoveLoop(const gfx::Vector2d& drag_offset, MoveLoopMouseWatcher watcher(this, hide_on_escape); // In Aura, we handle touch events asynchronously. So we need to allow nested // tasks while in windows move loop. - base::MessageLoop::ScopedNestableTaskAllower allow_nested( - base::MessageLoop::current()); + base::MessageLoopCurrent::ScopedNestableTaskAllower allow_nested; SendMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | 0x0002, GetMessagePos()); // Windows doesn't appear to offer a way to determine whether the user @@ -927,7 +930,7 @@ LRESULT HWNDMessageHandler::OnWndProc(UINT message, // NOTE: We inline ProcessWindowMessage() as 'this' may be destroyed during // dispatch and ProcessWindowMessage() doesn't deal with that well. const BOOL old_msg_handled = msg_handled_; - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); const BOOL processed = _ProcessWindowMessage(window, message, w_param, l_param, result, 0); if (!ref) @@ -992,9 +995,9 @@ LRESULT HWNDMessageHandler::HandleMouseMessage(unsigned int message, bool* handled) { // Don't track forwarded mouse messages. We expect the caller to track the // mouse. - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT ret = HandleMouseEventInternal(message, w_param, l_param, false); - *handled = IsMsgHandled(); + *handled = !ref.get() || msg_handled_; return ret; } @@ -1002,13 +1005,13 @@ LRESULT HWNDMessageHandler::HandleKeyboardMessage(unsigned int message, WPARAM w_param, LPARAM l_param, bool* handled) { - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT ret = 0; if ((message == WM_CHAR) || (message == WM_SYSCHAR)) ret = OnImeMessages(message, w_param, l_param); else ret = OnKeyEvent(message, w_param, l_param); - *handled = IsMsgHandled(); + *handled = !ref.get() || msg_handled_; return ret; } @@ -1016,9 +1019,9 @@ LRESULT HWNDMessageHandler::HandleTouchMessage(unsigned int message, WPARAM w_param, LPARAM l_param, bool* handled) { - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT ret = OnTouchEvent(message, w_param, l_param); - *handled = IsMsgHandled(); + *handled = !ref.get() || msg_handled_; return ret; } @@ -1026,9 +1029,9 @@ LRESULT HWNDMessageHandler::HandlePointerMessage(unsigned int message, WPARAM w_param, LPARAM l_param, bool* handled) { - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT ret = OnPointerEvent(message, w_param, l_param); - *handled = IsMsgHandled(); + *handled = !ref.get() || msg_handled_; return ret; } @@ -1036,9 +1039,9 @@ LRESULT HWNDMessageHandler::HandleScrollMessage(unsigned int message, WPARAM w_param, LPARAM l_param, bool* handled) { - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT ret = OnScrollMessage(message, w_param, l_param); - *handled = IsMsgHandled(); + *handled = !ref.get() || msg_handled_; return ret; } @@ -1046,10 +1049,10 @@ LRESULT HWNDMessageHandler::HandleNcHitTestMessage(unsigned int message, WPARAM w_param, LPARAM l_param, bool* handled) { - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT ret = OnNCHitTest( gfx::Point(CR_GET_X_LPARAM(l_param), CR_GET_Y_LPARAM(l_param))); - *handled = IsMsgHandled(); + *handled = !ref.get() || msg_handled_; return ret; } @@ -1410,7 +1413,7 @@ LRESULT HWNDMessageHandler::DefWindowProcWithRedrawLock(UINT message, ScopedRedrawLock lock(this); // The Widget and HWND can be destroyed in the call to DefWindowProc, so use // the WeakPtrFactory to avoid unlocking (and crashing) after destruction. - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); LRESULT result = DefWindowProc(hwnd(), message, w_param, l_param); if (!ref) lock.CancelUnlockOperation(); @@ -1441,7 +1444,7 @@ void HWNDMessageHandler::ForceRedrawWindow(int attempts) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(&HWNDMessageHandler::ForceRedrawWindow, - weak_factory_.GetWeakPtr(), attempts), + msg_handler_weak_factory_.GetWeakPtr(), attempts), base::TimeDelta::FromMilliseconds(500)); return; } @@ -1708,7 +1711,7 @@ LRESULT HWNDMessageHandler::OnImeMessages(UINT message, WPARAM w_param, LPARAM l_param) { LRESULT result = 0; - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); const bool msg_handled = delegate_->HandleIMEMessage(message, w_param, l_param, &result); if (ref.get()) @@ -1749,7 +1752,7 @@ LRESULT HWNDMessageHandler::OnKeyEvent(UINT message, MSG msg = { hwnd(), message, w_param, l_param, static_cast<DWORD>(GetMessageTime())}; ui::KeyEvent key(msg); - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); delegate_->HandleKeyEvent(&key); if (!ref) return 0; @@ -2336,21 +2339,28 @@ void HWNDMessageHandler::OnSysCommand(UINT notification_code, ((notification_code & sc_mask) == SC_MOVE) || ((notification_code & sc_mask) == SC_MAXIMIZE))) return; + + const bool window_control_action = + (notification_code & sc_mask) == SC_MINIMIZE || + (notification_code & sc_mask) == SC_MAXIMIZE || + (notification_code & sc_mask) == SC_RESTORE; + const bool custom_controls_frame_mode = + delegate_->GetFrameMode() == FrameMode::SYSTEM_DRAWN_NO_CONTROLS || + delegate_->GetFrameMode() == FrameMode::CUSTOM_DRAWN; + if (custom_controls_frame_mode && window_control_action) + delegate_->ResetWindowControls(); + if (delegate_->GetFrameMode() == FrameMode::CUSTOM_DRAWN) { - if ((notification_code & sc_mask) == SC_MINIMIZE || - (notification_code & sc_mask) == SC_MAXIMIZE || - (notification_code & sc_mask) == SC_RESTORE) { - delegate_->ResetWindowControls(); - DestroyAXSystemCaret(); - } else if ((notification_code & sc_mask) == SC_MOVE || - (notification_code & sc_mask) == SC_SIZE) { - if (!IsVisible()) { - // Circumvent ScopedRedrawLocks and force visibility before entering a - // resize or move modal loop to get continuous sizing/moving feedback. - SetWindowLong(hwnd(), GWL_STYLE, - GetWindowLong(hwnd(), GWL_STYLE) | WS_VISIBLE); - } + const bool window_bounds_change = + (notification_code & sc_mask) == SC_MOVE || + (notification_code & sc_mask) == SC_SIZE; + if (window_bounds_change || window_control_action) DestroyAXSystemCaret(); + if (window_bounds_change && !IsVisible()) { + // Circumvent ScopedRedrawLocks and force visibility before entering a + // resize or move modal loop to get continuous sizing/moving feedback. + SetWindowLong(hwnd(), GWL_STYLE, + GetWindowLong(hwnd(), GWL_STYLE) | WS_VISIBLE); } } @@ -2376,7 +2386,8 @@ void HWNDMessageHandler::OnSysCommand(UINT notification_code, // with the mouse/touch/keyboard, we flag as being in a size loop. if ((notification_code & sc_mask) == SC_SIZE) in_size_loop_ = true; - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref( + msg_handler_weak_factory_.GetWeakPtr()); DefWindowProc(hwnd(), WM_SYSCOMMAND, notification_code, MAKELPARAM(point.x(), point.y())); @@ -2455,7 +2466,7 @@ LRESULT HWNDMessageHandler::OnTouchEvent(UINT message, base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(&HWNDMessageHandler::ResetTouchDownContext, - weak_factory_.GetWeakPtr()), + msg_handler_weak_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kTouchDownContextResetTimeout)); } else { if (input[i].dwFlags & TOUCHEVENTF_MOVE) { @@ -2488,8 +2499,9 @@ LRESULT HWNDMessageHandler::OnTouchEvent(UINT message, // events on windows don't fire if we enter a modal loop in the context of // a touch event. base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&HWNDMessageHandler::HandleTouchEvents, - weak_factory_.GetWeakPtr(), touch_events)); + FROM_HERE, + base::BindOnce(&HWNDMessageHandler::HandleTouchEvents, + msg_handler_weak_factory_.GetWeakPtr(), touch_events)); } CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(l_param)); SetMsgHandled(FALSE); @@ -2596,7 +2608,7 @@ void HWNDMessageHandler::OnWindowPosChanging(WINDOWPOS* window_pos) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&HWNDMessageHandler::StopIgnoringPosChanges, - weak_factory_.GetWeakPtr())); + msg_handler_weak_factory_.GetWeakPtr())); } last_monitor_ = monitor; last_monitor_rect_ = monitor_rect; @@ -2678,7 +2690,7 @@ void HWNDMessageHandler::OnSessionChange(WPARAM status_code) { } void HWNDMessageHandler::HandleTouchEvents(const TouchEvents& touch_events) { - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); for (size_t i = 0; i < touch_events.size() && ref; ++i) { ui::TouchEvent* touch_event = const_cast<ui::TouchEvent*>(&touch_events[i]); delegate_->HandleTouchEvent(touch_event); @@ -2693,25 +2705,28 @@ LRESULT HWNDMessageHandler::HandleMouseEventInternal(UINT message, WPARAM w_param, LPARAM l_param, bool track_mouse) { - // We handle touch events on Windows Aura. Windows generates synthesized - // mouse messages in response to touch which we should ignore. However touch - // messages are only received for the client area. We need to ignore the - // synthesized mouse messages for all points in the client area and places - // which return HTNOWHERE. - // TODO(ananta) - // Windows does not reliably set the touch flag on mouse messages. Look into - // a better way of identifying mouse messages originating from touch. - if ((message != WM_MOUSEWHEEL && message != WM_MOUSEHWHEEL) && - (ui::IsMouseEventFromTouch(message))) { - LPARAM l_param_ht = l_param; - // For mouse events (except wheel events), location is in window coordinates - // and should be converted to screen coordinates for WM_NCHITTEST. - POINT screen_point = CR_POINT_INITIALIZER_FROM_LPARAM(l_param_ht); - MapWindowPoints(hwnd(), HWND_DESKTOP, &screen_point, 1); - l_param_ht = MAKELPARAM(screen_point.x, screen_point.y); - - LRESULT hittest = SendMessage(hwnd(), WM_NCHITTEST, 0, l_param_ht); - if (hittest == HTCLIENT || hittest == HTNOWHERE) + // We handle touch events in Aura. Windows generates synthesized mouse + // messages whenever there's a touch, but it doesn't give us the actual touch + // messages if it thinks the touch point is in non-client space. + if (message != WM_MOUSEWHEEL && message != WM_MOUSEHWHEEL && + ui::IsMouseEventFromTouch(message)) { + LRESULT hittest = SendMessage(hwnd(), WM_NCHITTEST, 0, l_param); + // Always DefWindowProc on the titlebar. We could let the event fall through + // and the special handling in HandleMouseInputForCaption would take care of + // this, but in the touch case Windows does a better job. + if (hittest == HTCAPTION || hittest == HTSYSMENU) + SetMsgHandled(FALSE); + // We must let Windows handle the caption buttons if it's drawing them, or + // they won't work. + if (delegate_->GetFrameMode() == FrameMode::SYSTEM_DRAWN && + (hittest == HTCLOSE || hittest == HTMINBUTTON || + hittest == HTMAXBUTTON)) { + SetMsgHandled(FALSE); + } + // Let resize events fall through. Ignore everything else, as we're either + // letting Windows handle it above or we've already handled the equivalent + // touch message. + if (!IsHitTestOnResizeHandle(hittest)) return 0; } @@ -2802,7 +2817,7 @@ LRESULT HWNDMessageHandler::HandleMouseEventInternal(UINT message, // There are cases where the code handling the message destroys the window, // so use the weak ptr to check if destruction occured or not. - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); bool handled = delegate_->HandleMouseEvent(&event); if (!ref.get()) @@ -2861,7 +2876,7 @@ LRESULT HWNDMessageHandler::HandlePointerEventTypeTouch(UINT message, base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(&HWNDMessageHandler::ResetTouchDownContext, - weak_factory_.GetWeakPtr()), + msg_handler_weak_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kTouchDownContextResetTimeout)); } @@ -2897,11 +2912,11 @@ LRESULT HWNDMessageHandler::HandlePointerEventTypeTouch(UINT message, ui::GetModifiersFromKeyState(), rotation_angle); event.latency()->AddLatencyNumberWithTimestamp( - ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 0, event_time, 1); + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, event_time, 1); // There are cases where the code handling the message destroys the // window, so use the weak ptr to check if destruction occurred or not. - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); delegate_->HandleTouchEvent(&event); if (event_type == ui::ET_TOUCH_RELEASED) @@ -2935,7 +2950,7 @@ LRESULT HWNDMessageHandler::HandlePointerEventTypePen(UINT message, // There are cases where the code handling the message destroys the // window, so use the weak ptr to check if destruction occured or not. - base::WeakPtr<HWNDMessageHandler> ref(weak_factory_.GetWeakPtr()); + base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); if (event) { if (event->IsTouchEvent()) { delegate_->HandleTouchEvent(event->AsTouchEvent()); @@ -3018,7 +3033,6 @@ void HWNDMessageHandler::GenerateTouchEvent(ui::EventType event_type, event.latency()->AddLatencyNumberWithTimestamp( ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, - 0, time_stamp, 1); diff --git a/chromium/ui/views/win/hwnd_message_handler.h b/chromium/ui/views/win/hwnd_message_handler.h index 11c76b89e38..5afb32aa043 100644 --- a/chromium/ui/views/win/hwnd_message_handler.h +++ b/chromium/ui/views/win/hwnd_message_handler.h @@ -27,6 +27,7 @@ #include "ui/events/event.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/sequential_id_generator.h" +#include "ui/gfx/win/msg_util.h" #include "ui/gfx/win/window_impl.h" #include "ui/views/views_export.h" #include "ui/views/win/pen_event_processor.h" @@ -65,54 +66,6 @@ const int WM_NCUAHDRAWFRAME = 0xAF; // WM_WINDOWPOSCHANGED won't be received. const int WM_WINDOWSIZINGFINISHED = WM_USER; -// IsMsgHandled() and BEGIN_SAFE_MSG_MAP_EX are a modified version of -// BEGIN_MSG_MAP_EX. The main difference is it uses a WeakPtrFactory member -// (|weak_factory|) that is used in _ProcessWindowMessage() and changing -// IsMsgHandled() from a member function to a define that checks if the weak -// factory is still valid in addition to the member. Together these allow for -// |this| to be deleted during dispatch. -#define IsMsgHandled() !ref.get() || msg_handled_ - -#define BEGIN_SAFE_MSG_MAP_EX(weak_factory) \ - private: \ - BOOL msg_handled_; \ -\ - public: \ - /* "handled" management for cracked handlers */ \ - void SetMsgHandled(BOOL handled) { \ - msg_handled_ = handled; \ - } \ - BOOL ProcessWindowMessage(HWND hwnd, \ - UINT msg, \ - WPARAM w_param, \ - LPARAM l_param, \ - LRESULT& l_result, \ - DWORD msg_map_id = 0) override { \ - base::WeakPtr<HWNDMessageHandler> ref(weak_factory.GetWeakPtr()); \ - BOOL old_msg_handled = msg_handled_; \ - BOOL ret = _ProcessWindowMessage(hwnd, msg, w_param, l_param, l_result, \ - msg_map_id); \ - if (ref.get()) \ - msg_handled_ = old_msg_handled; \ - return ret; \ - } \ - BOOL _ProcessWindowMessage(HWND hWnd, \ - UINT uMsg, \ - WPARAM wParam, \ - LPARAM lParam, \ - LRESULT& lResult, \ - DWORD dwMsgMapID) { \ - base::WeakPtr<HWNDMessageHandler> ref(weak_factory.GetWeakPtr()); \ - BOOL bHandled = TRUE; \ - hWnd; \ - uMsg; \ - wParam; \ - lParam; \ - lResult; \ - bHandled; \ - switch(dwMsgMapID) { \ - case 0: - // An object that handles messages for a HWND that implements the views // "Custom Frame" look. The purpose of this class is to isolate the windows- // specific message handling from the code that wraps it. It is intended to be @@ -360,11 +313,10 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, // Message Handlers ---------------------------------------------------------- - BEGIN_SAFE_MSG_MAP_EX(weak_factory_) + CR_BEGIN_MSG_MAP_EX(HWNDMessageHandler) // Range handlers must go first! CR_MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) - CR_MESSAGE_RANGE_HANDLER_EX(WM_NCMOUSEMOVE, - WM_NCXBUTTONDBLCLK, + CR_MESSAGE_RANGE_HANDLER_EX(WM_NCMOUSEMOVE, WM_NCXBUTTONDBLCLK, OnMouseRange) // CustomFrameWindow hacks @@ -771,15 +723,15 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, static base::LazyInstance<FullscreenWindowMonitorMap>::DestructorAtExit fullscreen_monitor_map_; - // The WeakPtrFactories below must occur last in the class definition so they - // get destroyed last. + // The WeakPtrFactories below (one inside the + // CR_MSG_MAP_CLASS_DECLARATIONS macro and autohide_factory_) must + // occur last in the class definition so they get destroyed last. + + CR_MSG_MAP_CLASS_DECLARATIONS(HWNDMessageHandler) // The factory used to lookup appbar autohide edges. base::WeakPtrFactory<HWNDMessageHandler> autohide_factory_; - // The factory used with BEGIN_SAFE_MSG_MAP_EX. - base::WeakPtrFactory<HWNDMessageHandler> weak_factory_; - DISALLOW_COPY_AND_ASSIGN(HWNDMessageHandler); }; diff --git a/chromium/ui/views/win/pen_event_processor.cc b/chromium/ui/views/win/pen_event_processor.cc index 5de8173f343..95089ddb7fa 100644 --- a/chromium/ui/views/win/pen_event_processor.cc +++ b/chromium/ui/views/win/pen_event_processor.cc @@ -18,12 +18,6 @@ int GetFlagsFromPointerMessage(UINT message, const POINTER_INFO& pointer_info) { if (pointer_info.pointerFlags & POINTER_FLAG_SECONDBUTTON) flags |= ui::EF_RIGHT_MOUSE_BUTTON; - if (message == WM_POINTERUP) { - if (pointer_info.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_UP) - flags |= ui::EF_RIGHT_MOUSE_BUTTON; - else - flags |= ui::EF_LEFT_MOUSE_BUTTON; - } return flags; } @@ -121,10 +115,13 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateMouseEvent( break; case WM_POINTERUP: event_type = ui::ET_MOUSE_RELEASED; - if (pointer_info.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP) + if (pointer_info.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP) { + flag |= ui::EF_LEFT_MOUSE_BUTTON; changed_flag = ui::EF_LEFT_MOUSE_BUTTON; - else + } else { + flag |= ui::EF_RIGHT_MOUSE_BUTTON; changed_flag = ui::EF_RIGHT_MOUSE_BUTTON; + } id_generator_->ReleaseNumber(pointer_id); click_count = 1; if (!sent_mouse_down_) @@ -191,7 +188,7 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateTouchEvent( flags | ui::GetModifiersFromKeyState(), rotation_angle); event->set_hovering(event_type == ui::ET_TOUCH_RELEASED); event->latency()->AddLatencyNumberWithTimestamp( - ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 0, event_time, 1); + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, event_time, 1); return event; } diff --git a/chromium/ui/views/win/pen_event_processor_unittest.cc b/chromium/ui/views/win/pen_event_processor_unittest.cc index 614e2e78260..1fa9319f1c9 100644 --- a/chromium/ui/views/win/pen_event_processor_unittest.cc +++ b/chromium/ui/views/win/pen_event_processor_unittest.cc @@ -164,4 +164,67 @@ TEST(PenProcessorTest, UnpairedPointerDownMouseDMEnabled) { EXPECT_EQ(nullptr, event.get()); } +TEST(PenProcessorTest, TouchFlagDMEnabled) { + ui::SequentialIDGenerator id_generator(0); + PenEventProcessor processor(&id_generator, + /*direct_manipulation_enabled*/ true); + + POINTER_PEN_INFO pen_info; + memset(&pen_info, 0, sizeof(POINTER_PEN_INFO)); + gfx::Point point(100, 100); + + pen_info.pointerInfo.pointerFlags = + POINTER_FLAG_INCONTACT | POINTER_FLAG_FIRSTBUTTON; + pen_info.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_DOWN; + + std::unique_ptr<ui::Event> event = + processor.GenerateEvent(WM_POINTERDOWN, 0, pen_info, point); + ASSERT_TRUE(event); + ASSERT_TRUE(event->IsTouchEvent()); + EXPECT_EQ(ui::ET_TOUCH_PRESSED, event->AsTouchEvent()->type()); + EXPECT_TRUE(event->flags() & ui::EF_LEFT_MOUSE_BUTTON); + + pen_info.pointerInfo.pointerFlags = POINTER_FLAG_UP; + pen_info.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_UP; + + event = processor.GenerateEvent(WM_POINTERUP, 0, pen_info, point); + ASSERT_TRUE(event); + ASSERT_TRUE(event->IsTouchEvent()); + EXPECT_EQ(ui::ET_TOUCH_RELEASED, event->AsTouchEvent()->type()); + EXPECT_FALSE(event->flags() & ui::EF_LEFT_MOUSE_BUTTON); +} + +TEST(PenProcessorTest, MouseFlagDMEnabled) { + ui::SequentialIDGenerator id_generator(0); + PenEventProcessor processor(&id_generator, + /*direct_manipulation_enabled*/ true); + + POINTER_PEN_INFO pen_info; + memset(&pen_info, 0, sizeof(POINTER_PEN_INFO)); + gfx::Point point(100, 100); + + pen_info.pointerInfo.pointerFlags = POINTER_FLAG_FIRSTBUTTON; + pen_info.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_DOWN; + + std::unique_ptr<ui::Event> event = + processor.GenerateEvent(WM_POINTERDOWN, 0, pen_info, point); + ASSERT_TRUE(event); + ASSERT_TRUE(event->IsMouseEvent()); + EXPECT_EQ(ui::ET_MOUSE_PRESSED, event->AsMouseEvent()->type()); + EXPECT_TRUE(event->flags() & ui::EF_LEFT_MOUSE_BUTTON); + EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, + event->AsMouseEvent()->changed_button_flags()); + + pen_info.pointerInfo.pointerFlags = POINTER_FLAG_NONE; + pen_info.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_UP; + + event = processor.GenerateEvent(WM_POINTERUP, 0, pen_info, point); + ASSERT_TRUE(event); + ASSERT_TRUE(event->IsMouseEvent()); + EXPECT_EQ(ui::ET_MOUSE_RELEASED, event->AsMouseEvent()->type()); + EXPECT_TRUE(event->flags() & ui::EF_LEFT_MOUSE_BUTTON); + EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, + event->AsMouseEvent()->changed_button_flags()); +} + } // namespace views diff --git a/chromium/ui/views/window/dialog_delegate.cc b/chromium/ui/views/window/dialog_delegate.cc index 3e88b8db4c8..739e5e548b2 100644 --- a/chromium/ui/views/window/dialog_delegate.cc +++ b/chromium/ui/views/window/dialog_delegate.cc @@ -42,11 +42,6 @@ DialogDelegate::DialogDelegate() creation_time_ = base::TimeTicks::Now(); } -DialogDelegate::~DialogDelegate() { - UMA_HISTOGRAM_LONG_TIMES("Dialog.DialogDelegate.Duration", - base::TimeTicks::Now() - creation_time_); -} - // static Widget* DialogDelegate::CreateDialogWidget(WidgetDelegate* delegate, gfx::NativeWindow context, @@ -249,6 +244,11 @@ void DialogDelegate::DialogModelChanged() { observer.OnDialogModelChanged(); } +DialogDelegate::~DialogDelegate() { + UMA_HISTOGRAM_LONG_TIMES("Dialog.DialogDelegate.Duration", + base::TimeTicks::Now() - creation_time_); +} + ax::mojom::Role DialogDelegate::GetAccessibleWindowRole() const { return ax::mojom::Role::kDialog; } diff --git a/chromium/ui/views/window/dialog_delegate.h b/chromium/ui/views/window/dialog_delegate.h index 69add25e1a0..83019bb77d9 100644 --- a/chromium/ui/views/window/dialog_delegate.h +++ b/chromium/ui/views/window/dialog_delegate.h @@ -35,7 +35,6 @@ class VIEWS_EXPORT DialogDelegate : public ui::DialogModel, public WidgetDelegate { public: DialogDelegate(); - ~DialogDelegate() override; // Creates a widget at a default location. static Widget* CreateDialogWidget(WidgetDelegate* delegate, @@ -129,6 +128,8 @@ class VIEWS_EXPORT DialogDelegate : public ui::DialogModel, void DialogModelChanged(); protected: + ~DialogDelegate() override; + // Overridden from WidgetDelegate: ax::mojom::Role GetAccessibleWindowRole() const override; |