1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/views/corewm/tooltip_controller.h"
6 
7 #include <memory>
8 
9 #include "ash/public/cpp/shell_window_ids.h"
10 #include "ash/shell.h"
11 #include "ash/test/ash_test_base.h"
12 #include "ash/wm/desks/desks_util.h"
13 #include "base/run_loop.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "ui/aura/env.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_event_dispatcher.h"
18 #include "ui/events/test/event_generator.h"
19 #include "ui/gfx/font.h"
20 #include "ui/gfx/geometry/point.h"
21 #include "ui/views/corewm/tooltip_controller_test_helper.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/wm/public/tooltip_client.h"
25 
26 using views::corewm::TooltipController;
27 using views::corewm::test::TooltipTestView;
28 using views::corewm::test::TooltipControllerTestHelper;
29 
30 // The tests in this file exercise bits of TooltipController that are hard to
31 // test outside of ash. Meaning these tests require the shell and related things
32 // to be installed.
33 
34 namespace ash {
35 
36 namespace {
37 
CreateNewWidgetWithBoundsOn(int display,const gfx::Rect & bounds)38 views::Widget* CreateNewWidgetWithBoundsOn(int display,
39                                            const gfx::Rect& bounds) {
40   views::Widget* widget = new views::Widget;
41   views::Widget::InitParams params;
42   params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
43   params.accept_events = true;
44   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
45   params.parent =
46       Shell::Get()->GetContainer(Shell::GetAllRootWindows().at(display),
47                                  desks_util::GetActiveDeskContainerId());
48   params.bounds = bounds;
49   widget->Init(std::move(params));
50   widget->Show();
51   return widget;
52 }
53 
CreateNewWidgetOn(int display)54 views::Widget* CreateNewWidgetOn(int display) {
55   return CreateNewWidgetWithBoundsOn(display, gfx::Rect());
56 }
57 
AddViewToWidgetAndResize(views::Widget * widget,views::View * view)58 void AddViewToWidgetAndResize(views::Widget* widget, views::View* view) {
59   if (!widget->GetContentsView())
60     widget->SetContentsView(std::make_unique<views::View>());
61 
62   views::View* contents_view = widget->GetContentsView();
63   contents_view->AddChildView(view);
64   view->SetBounds(contents_view->width(), 0, 100, 100);
65   gfx::Rect contents_view_bounds = contents_view->bounds();
66   contents_view_bounds.Union(view->bounds());
67   contents_view->SetBoundsRect(contents_view_bounds);
68   widget->SetBounds(gfx::Rect(widget->GetWindowBoundsInScreen().origin(),
69                               contents_view_bounds.size()));
70 }
71 
GetController()72 TooltipController* GetController() {
73   return static_cast<TooltipController*>(
74       ::wm::GetTooltipClient(Shell::GetPrimaryRootWindow()));
75 }
76 
77 }  // namespace
78 
79 class TooltipControllerTest : public AshTestBase {
80  public:
81   TooltipControllerTest() = default;
82   ~TooltipControllerTest() override = default;
83 
SetUp()84   void SetUp() override {
85     AshTestBase::SetUp();
86     helper_.reset(new TooltipControllerTestHelper(GetController()));
87   }
88 
89  protected:
90   std::unique_ptr<TooltipControllerTestHelper> helper_;
91 
92  private:
93   DISALLOW_COPY_AND_ASSIGN(TooltipControllerTest);
94 };
95 
TEST_F(TooltipControllerTest,NonNullTooltipClient)96 TEST_F(TooltipControllerTest, NonNullTooltipClient) {
97   EXPECT_TRUE(::wm::GetTooltipClient(Shell::GetPrimaryRootWindow()) != NULL);
98   EXPECT_EQ(base::string16(), helper_->GetTooltipText());
99   EXPECT_EQ(NULL, helper_->GetTooltipWindow());
100   EXPECT_FALSE(helper_->IsTooltipVisible());
101 }
102 
TEST_F(TooltipControllerTest,HideTooltipWhenCursorHidden)103 TEST_F(TooltipControllerTest, HideTooltipWhenCursorHidden) {
104   std::unique_ptr<views::Widget> widget(CreateNewWidgetOn(0));
105   TooltipTestView* view = new TooltipTestView;
106   AddViewToWidgetAndResize(widget.get(), view);
107   view->set_tooltip_text(base::ASCIIToUTF16("Tooltip Text"));
108   EXPECT_EQ(base::string16(), helper_->GetTooltipText());
109   EXPECT_EQ(NULL, helper_->GetTooltipWindow());
110 
111   ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
112   generator.MoveMouseRelativeTo(widget->GetNativeView(),
113                                 view->bounds().CenterPoint());
114   base::string16 expected_tooltip = base::ASCIIToUTF16("Tooltip Text");
115 
116   // Mouse event triggers tooltip update so it becomes visible.
117   EXPECT_TRUE(helper_->IsTooltipVisible());
118 
119   // Disable mouse event which hides the cursor and check again.
120   Shell::Get()->cursor_manager()->DisableMouseEvents();
121   base::RunLoop().RunUntilIdle();
122   EXPECT_FALSE(Shell::Get()->cursor_manager()->IsCursorVisible());
123   helper_->UpdateIfRequired();
124   EXPECT_FALSE(helper_->IsTooltipVisible());
125 
126   // Enable mouse event which shows the cursor and re-check.
127   Shell::Get()->cursor_manager()->EnableMouseEvents();
128   base::RunLoop().RunUntilIdle();
129   EXPECT_TRUE(Shell::Get()->cursor_manager()->IsCursorVisible());
130   helper_->UpdateIfRequired();
131   EXPECT_TRUE(helper_->IsTooltipVisible());
132 }
133 
TEST_F(TooltipControllerTest,TooltipsOnMultiDisplayShouldNotCrash)134 TEST_F(TooltipControllerTest, TooltipsOnMultiDisplayShouldNotCrash) {
135   UpdateDisplay("1000x600,600x400");
136   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
137   std::unique_ptr<views::Widget> widget1(
138       CreateNewWidgetWithBoundsOn(0, gfx::Rect(10, 10, 100, 100)));
139   TooltipTestView* view1 = new TooltipTestView;
140   AddViewToWidgetAndResize(widget1.get(), view1);
141   view1->set_tooltip_text(base::ASCIIToUTF16("Tooltip Text for view 1"));
142   EXPECT_EQ(widget1->GetNativeView()->GetRootWindow(), root_windows[0]);
143 
144   std::unique_ptr<views::Widget> widget2(
145       CreateNewWidgetWithBoundsOn(1, gfx::Rect(1200, 10, 100, 100)));
146   TooltipTestView* view2 = new TooltipTestView;
147   AddViewToWidgetAndResize(widget2.get(), view2);
148   view2->set_tooltip_text(base::ASCIIToUTF16("Tooltip Text for view 2"));
149   EXPECT_EQ(widget2->GetNativeView()->GetRootWindow(), root_windows[1]);
150 
151   // Show tooltip on second display.
152   ui::test::EventGenerator generator(root_windows[1]);
153   generator.MoveMouseRelativeTo(widget2->GetNativeView(),
154                                 view2->bounds().CenterPoint());
155   EXPECT_TRUE(helper_->IsTooltipVisible());
156 
157   // Get rid of secondary display. This destroy's the tooltip's aura window. If
158   // we have handled this case, we will not crash in the following statement.
159   UpdateDisplay("1000x600");
160   EXPECT_FALSE(helper_->IsTooltipVisible());
161   EXPECT_EQ(widget2->GetNativeView()->GetRootWindow(), root_windows[0]);
162 
163   // The tooltip should create a new aura window for itself, so we should still
164   // be able to show tooltips on the primary display.
165   ui::test::EventGenerator generator1(root_windows[0]);
166   generator1.MoveMouseRelativeTo(widget1->GetNativeView(),
167                                  view1->bounds().CenterPoint());
168   EXPECT_TRUE(helper_->IsTooltipVisible());
169 }
170 
171 }  // namespace ash
172