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 "base/run_loop.h"
6 #include "ui/base/hit_test.h"
7 #include "ui/views/bubble/bubble_delegate.h"
8 #include "ui/views/bubble/bubble_frame_view.h"
9 #include "ui/views/test/test_widget_observer.h"
10 #include "ui/views/test/views_test_base.h"
11 #include "ui/views/widget/widget.h"
12 #include "ui/views/widget/widget_observer.h"
13
14 #if defined(USE_AURA)
15 #include "ui/aura/env.h"
16 #endif
17
18 namespace views {
19
20 namespace {
21
22 class TestBubbleDelegateView : public BubbleDelegateView {
23 public:
TestBubbleDelegateView(View * anchor_view)24 TestBubbleDelegateView(View* anchor_view)
25 : BubbleDelegateView(anchor_view, BubbleBorder::TOP_LEFT),
26 view_(new View()) {
27 view_->SetFocusable(true);
28 AddChildView(view_);
29 }
~TestBubbleDelegateView()30 virtual ~TestBubbleDelegateView() {}
31
SetAnchorRectForTest(gfx::Rect rect)32 void SetAnchorRectForTest(gfx::Rect rect) {
33 SetAnchorRect(rect);
34 }
35
SetAnchorViewForTest(View * view)36 void SetAnchorViewForTest(View* view) {
37 SetAnchorView(view);
38 }
39
40 // BubbleDelegateView overrides:
GetInitiallyFocusedView()41 virtual View* GetInitiallyFocusedView() OVERRIDE { return view_; }
GetPreferredSize()42 virtual gfx::Size GetPreferredSize() OVERRIDE { return gfx::Size(200, 200); }
GetFadeDuration()43 virtual int GetFadeDuration() OVERRIDE { return 1; }
44
45 private:
46 View* view_;
47
48 DISALLOW_COPY_AND_ASSIGN(TestBubbleDelegateView);
49 };
50
51 class BubbleDelegateTest : public ViewsTestBase {
52 public:
BubbleDelegateTest()53 BubbleDelegateTest() {}
~BubbleDelegateTest()54 virtual ~BubbleDelegateTest() {}
55
56 // Creates a test widget that owns its native widget.
CreateTestWidget()57 Widget* CreateTestWidget() {
58 Widget* widget = new Widget();
59 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
60 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
61 widget->Init(params);
62 return widget;
63 }
64
65 private:
66 DISALLOW_COPY_AND_ASSIGN(BubbleDelegateTest);
67 };
68
69 } // namespace
70
TEST_F(BubbleDelegateTest,CreateDelegate)71 TEST_F(BubbleDelegateTest, CreateDelegate) {
72 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
73 BubbleDelegateView* bubble_delegate = new BubbleDelegateView(
74 anchor_widget->GetContentsView(), BubbleBorder::NONE);
75 bubble_delegate->set_color(SK_ColorGREEN);
76 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
77 EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
78 EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
79 test::TestWidgetObserver bubble_observer(bubble_widget);
80 bubble_widget->Show();
81
82 BubbleBorder* border = bubble_delegate->GetBubbleFrameView()->bubble_border();
83 EXPECT_EQ(bubble_delegate->arrow(), border->arrow());
84 EXPECT_EQ(bubble_delegate->color(), border->background_color());
85
86 EXPECT_FALSE(bubble_observer.widget_closed());
87 bubble_widget->CloseNow();
88 EXPECT_TRUE(bubble_observer.widget_closed());
89 }
90
TEST_F(BubbleDelegateTest,CloseAnchorWidget)91 TEST_F(BubbleDelegateTest, CloseAnchorWidget) {
92 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
93 BubbleDelegateView* bubble_delegate = new BubbleDelegateView(
94 anchor_widget->GetContentsView(), BubbleBorder::NONE);
95 // Preventing close on deactivate should not prevent closing with the anchor.
96 bubble_delegate->set_close_on_deactivate(false);
97 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
98 EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
99 EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
100 EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget());
101 test::TestWidgetObserver bubble_observer(bubble_widget);
102 EXPECT_FALSE(bubble_observer.widget_closed());
103
104 bubble_widget->Show();
105 EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget());
106 EXPECT_FALSE(bubble_observer.widget_closed());
107
108 #if defined(USE_AURA)
109 // TODO(msw): Remove activation hack to prevent bookkeeping errors in:
110 // aura::test::TestActivationClient::OnWindowDestroyed().
111 scoped_ptr<Widget> smoke_and_mirrors_widget(CreateTestWidget());
112 EXPECT_FALSE(bubble_observer.widget_closed());
113 #endif
114
115 // Ensure that closing the anchor widget also closes the bubble itself.
116 anchor_widget->CloseNow();
117 EXPECT_TRUE(bubble_observer.widget_closed());
118 }
119
120 // This test checks that the bubble delegate is capable to handle an early
121 // destruction of the used anchor view. (Animations and delayed closure of the
122 // bubble will call upon the anchor view to get its location).
TEST_F(BubbleDelegateTest,CloseAnchorViewTest)123 TEST_F(BubbleDelegateTest, CloseAnchorViewTest) {
124 // Create an anchor widget and add a view to be used as an anchor view.
125 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
126 scoped_ptr<View> anchor_view(new View());
127 anchor_widget->GetContentsView()->AddChildView(anchor_view.get());
128 TestBubbleDelegateView* bubble_delegate = new TestBubbleDelegateView(
129 anchor_view.get());
130 // Prevent flakes by avoiding closing on activation changes.
131 bubble_delegate->set_close_on_deactivate(false);
132 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
133
134 // Check that the anchor view is correct and set up an anchor view rect.
135 // Make sure that this rect will get ignored (as long as the anchor view is
136 // attached).
137 EXPECT_EQ(anchor_view, bubble_delegate->GetAnchorView());
138 const gfx::Rect set_anchor_rect = gfx::Rect(10, 10, 100, 100);
139 bubble_delegate->SetAnchorRectForTest(set_anchor_rect);
140 const gfx::Rect view_rect = bubble_delegate->GetAnchorRect();
141 EXPECT_NE(view_rect.ToString(), set_anchor_rect.ToString());
142
143 // Create the bubble.
144 bubble_widget->Show();
145 EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget());
146
147 // Remove now the anchor view and make sure that the original found rect
148 // is still kept, so that the bubble does not jump when the view gets deleted.
149 anchor_widget->GetContentsView()->RemoveChildView(anchor_view.get());
150 anchor_view.reset();
151 EXPECT_EQ(NULL, bubble_delegate->GetAnchorView());
152 EXPECT_EQ(view_rect.ToString(), bubble_delegate->GetAnchorRect().ToString());
153 }
154
155 // Testing that a move of the anchor view will lead to new bubble locations.
TEST_F(BubbleDelegateTest,TestAnchorRectMovesWithViewTest)156 TEST_F(BubbleDelegateTest, TestAnchorRectMovesWithViewTest) {
157 // Create an anchor widget and add a view to be used as anchor view.
158 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
159 TestBubbleDelegateView* bubble_delegate = new TestBubbleDelegateView(
160 anchor_widget->GetContentsView());
161 BubbleDelegateView::CreateBubble(bubble_delegate);
162
163 anchor_widget->GetContentsView()->SetBounds(10, 10, 100, 100);
164 const gfx::Rect view_rect = bubble_delegate->GetAnchorRect();
165
166 anchor_widget->GetContentsView()->SetBounds(20, 10, 100, 100);
167 const gfx::Rect view_rect_2 = bubble_delegate->GetAnchorRect();
168 EXPECT_NE(view_rect.ToString(), view_rect_2.ToString());
169 }
170
TEST_F(BubbleDelegateTest,ResetAnchorWidget)171 TEST_F(BubbleDelegateTest, ResetAnchorWidget) {
172 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
173 BubbleDelegateView* bubble_delegate = new BubbleDelegateView(
174 anchor_widget->GetContentsView(), BubbleBorder::NONE);
175
176 // Make sure the bubble widget is parented to a widget other than the anchor
177 // widget so that closing the anchor widget does not close the bubble widget.
178 scoped_ptr<Widget> parent_widget(CreateTestWidget());
179 bubble_delegate->set_parent_window(parent_widget->GetNativeView());
180 // Preventing close on deactivate should not prevent closing with the parent.
181 bubble_delegate->set_close_on_deactivate(false);
182 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
183 EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
184 EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
185 EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget());
186 test::TestWidgetObserver bubble_observer(bubble_widget);
187 EXPECT_FALSE(bubble_observer.widget_closed());
188
189 // Showing and hiding the bubble widget should have no effect on its anchor.
190 bubble_widget->Show();
191 EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget());
192 bubble_widget->Hide();
193 EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget());
194
195 // Ensure that closing the anchor widget clears the bubble's reference to that
196 // anchor widget, but the bubble itself does not close.
197 anchor_widget->CloseNow();
198 EXPECT_NE(anchor_widget, bubble_delegate->anchor_widget());
199 EXPECT_FALSE(bubble_observer.widget_closed());
200
201 #if defined(USE_AURA)
202 // TODO(msw): Remove activation hack to prevent bookkeeping errors in:
203 // aura::test::TestActivationClient::OnWindowDestroyed().
204 scoped_ptr<Widget> smoke_and_mirrors_widget(CreateTestWidget());
205 EXPECT_FALSE(bubble_observer.widget_closed());
206 #endif
207
208 // Ensure that closing the parent widget also closes the bubble itself.
209 parent_widget->CloseNow();
210 EXPECT_TRUE(bubble_observer.widget_closed());
211 }
212
TEST_F(BubbleDelegateTest,InitiallyFocusedView)213 TEST_F(BubbleDelegateTest, InitiallyFocusedView) {
214 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
215 BubbleDelegateView* bubble_delegate = new BubbleDelegateView(
216 anchor_widget->GetContentsView(), BubbleBorder::NONE);
217 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
218 EXPECT_EQ(bubble_delegate->GetInitiallyFocusedView(),
219 bubble_widget->GetFocusManager()->GetFocusedView());
220 bubble_widget->CloseNow();
221 }
222
TEST_F(BubbleDelegateTest,NonClientHitTest)223 TEST_F(BubbleDelegateTest, NonClientHitTest) {
224 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
225 TestBubbleDelegateView* bubble_delegate =
226 new TestBubbleDelegateView(anchor_widget->GetContentsView());
227 BubbleDelegateView::CreateBubble(bubble_delegate);
228 BubbleFrameView* frame = bubble_delegate->GetBubbleFrameView();
229 const int border = frame->bubble_border()->GetBorderThickness();
230
231 struct {
232 const int point;
233 const int hit;
234 } cases[] = {
235 { border, HTNOWHERE },
236 { border + 50, HTCLIENT },
237 { 1000, HTNOWHERE },
238 };
239
240 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
241 gfx::Point point(cases[i].point, cases[i].point);
242 EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point))
243 << " with border: " << border << ", at point " << cases[i].point;
244 }
245 }
246
TEST_F(BubbleDelegateTest,CloseWhenAnchorWidgetBoundsChanged)247 TEST_F(BubbleDelegateTest, CloseWhenAnchorWidgetBoundsChanged) {
248 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
249 BubbleDelegateView* bubble_delegate = new BubbleDelegateView(
250 anchor_widget->GetContentsView(), BubbleBorder::NONE);
251 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
252 test::TestWidgetObserver bubble_observer(bubble_widget);
253 EXPECT_FALSE(bubble_observer.widget_closed());
254
255 bubble_widget->Show();
256 EXPECT_TRUE(bubble_widget->IsVisible());
257 anchor_widget->SetBounds(gfx::Rect(10, 10, 100, 100));
258 EXPECT_FALSE(bubble_widget->IsVisible());
259 }
260
261 // This class provides functionality to verify that the BubbleView shows up
262 // when we call BubbleDelegateView::StartFade(true) and is destroyed when we
263 // call BubbleDelegateView::StartFade(false).
264 class BubbleWidgetClosingTest : public BubbleDelegateTest,
265 public views::WidgetObserver {
266 public:
BubbleWidgetClosingTest()267 BubbleWidgetClosingTest() : bubble_destroyed_(false) {
268 #if defined(USE_AURA)
269 aura::Env::CreateInstance();
270 loop_.set_dispatcher(aura::Env::GetInstance()->GetDispatcher());
271 #endif
272 }
273
~BubbleWidgetClosingTest()274 virtual ~BubbleWidgetClosingTest() {}
275
Observe(views::Widget * widget)276 void Observe(views::Widget* widget) {
277 widget->AddObserver(this);
278 }
279
280 // views::WidgetObserver overrides.
OnWidgetDestroyed(Widget * widget)281 virtual void OnWidgetDestroyed(Widget* widget) OVERRIDE {
282 bubble_destroyed_ = true;
283 widget->RemoveObserver(this);
284 loop_.Quit();
285 }
286
bubble_destroyed() const287 bool bubble_destroyed() const { return bubble_destroyed_; }
288
RunNestedLoop()289 void RunNestedLoop() {
290 loop_.Run();
291 }
292
293 private:
294 bool bubble_destroyed_;
295 base::RunLoop loop_;
296
297 DISALLOW_COPY_AND_ASSIGN(BubbleWidgetClosingTest);
298 };
299
TEST_F(BubbleWidgetClosingTest,TestBubbleVisibilityAndClose)300 TEST_F(BubbleWidgetClosingTest, TestBubbleVisibilityAndClose) {
301 scoped_ptr<Widget> anchor_widget(CreateTestWidget());
302 TestBubbleDelegateView* bubble_delegate =
303 new TestBubbleDelegateView(anchor_widget->GetContentsView());
304 Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate);
305 EXPECT_FALSE(bubble_widget->IsVisible());
306
307 bubble_delegate->StartFade(true);
308 EXPECT_TRUE(bubble_widget->IsVisible());
309
310 EXPECT_EQ(bubble_delegate->GetInitiallyFocusedView(),
311 bubble_widget->GetFocusManager()->GetFocusedView());
312
313 Observe(bubble_widget);
314
315 bubble_delegate->StartFade(false);
316 RunNestedLoop();
317 EXPECT_TRUE(bubble_destroyed());
318 }
319
320 } // namespace views
321