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/widget/native_widget_aura.h"
6
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/message_loop/message_loop.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "ui/aura/client/aura_constants.h"
13 #include "ui/aura/env.h"
14 #include "ui/aura/layout_manager.h"
15 #include "ui/aura/test/aura_test_base.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/events/event.h"
19 #include "ui/events/event_utils.h"
20 #include "ui/gfx/screen.h"
21 #include "ui/views/layout/fill_layout.h"
22 #include "ui/views/widget/root_view.h"
23 #include "ui/views/widget/widget_delegate.h"
24 #include "ui/wm/core/default_activation_client.h"
25
26 namespace views {
27 namespace {
28
Init(aura::Window * parent,Widget * widget)29 NativeWidgetAura* Init(aura::Window* parent, Widget* widget) {
30 Widget::InitParams params(Widget::InitParams::TYPE_POPUP);
31 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
32 params.parent = parent;
33 widget->Init(params);
34 return static_cast<NativeWidgetAura*>(widget->native_widget());
35 }
36
37 class NativeWidgetAuraTest : public aura::test::AuraTestBase {
38 public:
NativeWidgetAuraTest()39 NativeWidgetAuraTest() {}
~NativeWidgetAuraTest()40 virtual ~NativeWidgetAuraTest() {}
41
42 // testing::Test overrides:
SetUp()43 virtual void SetUp() OVERRIDE {
44 AuraTestBase::SetUp();
45 new wm::DefaultActivationClient(root_window());
46 host()->SetBounds(gfx::Rect(640, 480));
47 }
48
49 private:
50 DISALLOW_COPY_AND_ASSIGN(NativeWidgetAuraTest);
51 };
52
TEST_F(NativeWidgetAuraTest,CenterWindowLargeParent)53 TEST_F(NativeWidgetAuraTest, CenterWindowLargeParent) {
54 // Make a parent window larger than the host represented by
55 // WindowEventDispatcher.
56 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
57 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
58 parent->SetBounds(gfx::Rect(0, 0, 1024, 800));
59 scoped_ptr<Widget> widget(new Widget());
60 NativeWidgetAura* window = Init(parent.get(), widget.get());
61
62 window->CenterWindow(gfx::Size(100, 100));
63 EXPECT_EQ(gfx::Rect( (640 - 100) / 2,
64 (480 - 100) / 2,
65 100, 100),
66 window->GetNativeWindow()->bounds());
67 widget->CloseNow();
68 }
69
TEST_F(NativeWidgetAuraTest,CenterWindowSmallParent)70 TEST_F(NativeWidgetAuraTest, CenterWindowSmallParent) {
71 // Make a parent window smaller than the host represented by
72 // WindowEventDispatcher.
73 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
74 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
75 parent->SetBounds(gfx::Rect(0, 0, 480, 320));
76 scoped_ptr<Widget> widget(new Widget());
77 NativeWidgetAura* window = Init(parent.get(), widget.get());
78
79 window->CenterWindow(gfx::Size(100, 100));
80 EXPECT_EQ(gfx::Rect( (480 - 100) / 2,
81 (320 - 100) / 2,
82 100, 100),
83 window->GetNativeWindow()->bounds());
84 widget->CloseNow();
85 }
86
87 // Verifies CenterWindow() constrains to parent size.
TEST_F(NativeWidgetAuraTest,CenterWindowSmallParentNotAtOrigin)88 TEST_F(NativeWidgetAuraTest, CenterWindowSmallParentNotAtOrigin) {
89 // Make a parent window smaller than the host represented by
90 // WindowEventDispatcher and offset it slightly from the origin.
91 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
92 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
93 parent->SetBounds(gfx::Rect(20, 40, 480, 320));
94 scoped_ptr<Widget> widget(new Widget());
95 NativeWidgetAura* window = Init(parent.get(), widget.get());
96 window->CenterWindow(gfx::Size(500, 600));
97
98 // |window| should be no bigger than |parent|.
99 EXPECT_EQ("20,40 480x320", window->GetNativeWindow()->bounds().ToString());
100 widget->CloseNow();
101 }
102
103 class TestLayoutManagerBase : public aura::LayoutManager {
104 public:
TestLayoutManagerBase()105 TestLayoutManagerBase() {}
~TestLayoutManagerBase()106 virtual ~TestLayoutManagerBase() {}
107
108 // aura::LayoutManager:
OnWindowResized()109 virtual void OnWindowResized() OVERRIDE {}
OnWindowAddedToLayout(aura::Window * child)110 virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {}
OnWillRemoveWindowFromLayout(aura::Window * child)111 virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {}
OnWindowRemovedFromLayout(aura::Window * child)112 virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {}
OnChildWindowVisibilityChanged(aura::Window * child,bool visible)113 virtual void OnChildWindowVisibilityChanged(aura::Window* child,
114 bool visible) OVERRIDE {}
SetChildBounds(aura::Window * child,const gfx::Rect & requested_bounds)115 virtual void SetChildBounds(aura::Window* child,
116 const gfx::Rect& requested_bounds) OVERRIDE {}
117
118 private:
119 DISALLOW_COPY_AND_ASSIGN(TestLayoutManagerBase);
120 };
121
122 // Used by ShowMaximizedDoesntBounceAround. See it for details.
123 class MaximizeLayoutManager : public TestLayoutManagerBase {
124 public:
MaximizeLayoutManager()125 MaximizeLayoutManager() {}
~MaximizeLayoutManager()126 virtual ~MaximizeLayoutManager() {}
127
128 private:
129 // aura::LayoutManager:
OnWindowAddedToLayout(aura::Window * child)130 virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {
131 // This simulates what happens when adding a maximized window.
132 SetChildBoundsDirect(child, gfx::Rect(0, 0, 300, 300));
133 }
134
135 DISALLOW_COPY_AND_ASSIGN(MaximizeLayoutManager);
136 };
137
138 // This simulates BrowserView, which creates a custom RootView so that
139 // OnNativeWidgetSizeChanged that is invoked during Init matters.
140 class TestWidget : public views::Widget {
141 public:
TestWidget()142 TestWidget() : did_size_change_more_than_once_(false) {
143 }
144
145 // Returns true if the size changes to a non-empty size, and then to another
146 // size.
did_size_change_more_than_once() const147 bool did_size_change_more_than_once() const {
148 return did_size_change_more_than_once_;
149 }
150
OnNativeWidgetSizeChanged(const gfx::Size & new_size)151 virtual void OnNativeWidgetSizeChanged(const gfx::Size& new_size) OVERRIDE {
152 if (last_size_.IsEmpty())
153 last_size_ = new_size;
154 else if (!did_size_change_more_than_once_ && new_size != last_size_)
155 did_size_change_more_than_once_ = true;
156 Widget::OnNativeWidgetSizeChanged(new_size);
157 }
158
159 private:
160 bool did_size_change_more_than_once_;
161 gfx::Size last_size_;
162
163 DISALLOW_COPY_AND_ASSIGN(TestWidget);
164 };
165
166 // Verifies the size of the widget doesn't change more than once during Init if
167 // the window ends up maximized. This is important as otherwise
168 // RenderWidgetHostViewAura ends up getting resized during construction, which
169 // leads to noticable flashes.
TEST_F(NativeWidgetAuraTest,ShowMaximizedDoesntBounceAround)170 TEST_F(NativeWidgetAuraTest, ShowMaximizedDoesntBounceAround) {
171 root_window()->SetBounds(gfx::Rect(0, 0, 640, 480));
172 root_window()->SetLayoutManager(new MaximizeLayoutManager);
173 scoped_ptr<TestWidget> widget(new TestWidget());
174 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
175 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
176 params.parent = NULL;
177 params.context = root_window();
178 params.show_state = ui::SHOW_STATE_MAXIMIZED;
179 params.bounds = gfx::Rect(10, 10, 100, 200);
180 widget->Init(params);
181 EXPECT_FALSE(widget->did_size_change_more_than_once());
182 widget->CloseNow();
183 }
184
185 class PropertyTestLayoutManager : public TestLayoutManagerBase {
186 public:
PropertyTestLayoutManager()187 PropertyTestLayoutManager() : added_(false) {}
~PropertyTestLayoutManager()188 virtual ~PropertyTestLayoutManager() {}
189
added() const190 bool added() const { return added_; }
191
192 private:
193 // aura::LayoutManager:
OnWindowAddedToLayout(aura::Window * child)194 virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {
195 EXPECT_TRUE(child->GetProperty(aura::client::kCanMaximizeKey));
196 EXPECT_TRUE(child->GetProperty(aura::client::kCanResizeKey));
197 added_ = true;
198 }
199
200 bool added_;
201
202 DISALLOW_COPY_AND_ASSIGN(PropertyTestLayoutManager);
203 };
204
205 class PropertyTestWidgetDelegate : public views::WidgetDelegate {
206 public:
PropertyTestWidgetDelegate(Widget * widget)207 explicit PropertyTestWidgetDelegate(Widget* widget) : widget_(widget) {}
~PropertyTestWidgetDelegate()208 virtual ~PropertyTestWidgetDelegate() {}
209
210 private:
211 // views::WidgetDelegate:
CanMaximize() const212 virtual bool CanMaximize() const OVERRIDE {
213 return true;
214 }
CanResize() const215 virtual bool CanResize() const OVERRIDE {
216 return true;
217 }
DeleteDelegate()218 virtual void DeleteDelegate() OVERRIDE {
219 delete this;
220 }
GetWidget()221 virtual Widget* GetWidget() OVERRIDE {
222 return widget_;
223 }
GetWidget() const224 virtual const Widget* GetWidget() const OVERRIDE {
225 return widget_;
226 }
227
228 Widget* widget_;
229 DISALLOW_COPY_AND_ASSIGN(PropertyTestWidgetDelegate);
230 };
231
232 // Verifies that the kCanMaximizeKey/kCanReizeKey have the correct
233 // value when added to the layout manager.
TEST_F(NativeWidgetAuraTest,TestPropertiesWhenAddedToLayout)234 TEST_F(NativeWidgetAuraTest, TestPropertiesWhenAddedToLayout) {
235 root_window()->SetBounds(gfx::Rect(0, 0, 640, 480));
236 PropertyTestLayoutManager* layout_manager = new PropertyTestLayoutManager();
237 root_window()->SetLayoutManager(layout_manager);
238 scoped_ptr<TestWidget> widget(new TestWidget());
239 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
240 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
241 params.delegate = new PropertyTestWidgetDelegate(widget.get());
242 params.parent = NULL;
243 params.context = root_window();
244 widget->Init(params);
245 EXPECT_TRUE(layout_manager->added());
246 widget->CloseNow();
247 }
248
TEST_F(NativeWidgetAuraTest,GetClientAreaScreenBounds)249 TEST_F(NativeWidgetAuraTest, GetClientAreaScreenBounds) {
250 // Create a widget.
251 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
252 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
253 params.context = root_window();
254 params.bounds.SetRect(10, 20, 300, 400);
255 scoped_ptr<Widget> widget(new Widget());
256 widget->Init(params);
257
258 // For Aura, client area bounds match window bounds.
259 gfx::Rect client_bounds = widget->GetClientAreaBoundsInScreen();
260 EXPECT_EQ(10, client_bounds.x());
261 EXPECT_EQ(20, client_bounds.y());
262 EXPECT_EQ(300, client_bounds.width());
263 EXPECT_EQ(400, client_bounds.height());
264 }
265
266 // View subclass that tracks whether it has gotten a gesture event.
267 class GestureTrackingView : public views::View {
268 public:
GestureTrackingView()269 GestureTrackingView()
270 : got_gesture_event_(false),
271 consume_gesture_event_(true) {}
272
set_consume_gesture_event(bool value)273 void set_consume_gesture_event(bool value) {
274 consume_gesture_event_ = value;
275 }
276
clear_got_gesture_event()277 void clear_got_gesture_event() {
278 got_gesture_event_ = false;
279 }
got_gesture_event() const280 bool got_gesture_event() const {
281 return got_gesture_event_;
282 }
283
284 // View overrides:
OnGestureEvent(ui::GestureEvent * event)285 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
286 got_gesture_event_ = true;
287 if (consume_gesture_event_)
288 event->StopPropagation();
289 }
290
291 private:
292 // Was OnGestureEvent() invoked?
293 bool got_gesture_event_;
294
295 // Dictates what OnGestureEvent() returns.
296 bool consume_gesture_event_;
297
298 DISALLOW_COPY_AND_ASSIGN(GestureTrackingView);
299 };
300
301 // Verifies a capture isn't set on touch press and that the view that gets
302 // the press gets the release.
TEST_F(NativeWidgetAuraTest,DontCaptureOnGesture)303 TEST_F(NativeWidgetAuraTest, DontCaptureOnGesture) {
304 // Create two views (both sized the same). |child| is configured not to
305 // consume the gesture event.
306 GestureTrackingView* view = new GestureTrackingView();
307 GestureTrackingView* child = new GestureTrackingView();
308 child->set_consume_gesture_event(false);
309 view->SetLayoutManager(new FillLayout);
310 view->AddChildView(child);
311 scoped_ptr<TestWidget> widget(new TestWidget());
312 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
313 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
314 params.context = root_window();
315 params.bounds = gfx::Rect(0, 0, 100, 200);
316 widget->Init(params);
317 widget->SetContentsView(view);
318 widget->Show();
319
320 ui::TouchEvent press(
321 ui::ET_TOUCH_PRESSED, gfx::Point(41, 51), 1, ui::EventTimeForNow());
322 ui::EventDispatchDetails details =
323 event_processor()->OnEventFromSource(&press);
324 ASSERT_FALSE(details.dispatcher_destroyed);
325 // Both views should get the press.
326 EXPECT_TRUE(view->got_gesture_event());
327 EXPECT_TRUE(child->got_gesture_event());
328 view->clear_got_gesture_event();
329 child->clear_got_gesture_event();
330 // Touch events should not automatically grab capture.
331 EXPECT_FALSE(widget->HasCapture());
332
333 // Release touch. Only |view| should get the release since that it consumed
334 // the press.
335 ui::TouchEvent release(
336 ui::ET_TOUCH_RELEASED, gfx::Point(250, 251), 1, ui::EventTimeForNow());
337 details = event_processor()->OnEventFromSource(&release);
338 ASSERT_FALSE(details.dispatcher_destroyed);
339 EXPECT_TRUE(view->got_gesture_event());
340 EXPECT_FALSE(child->got_gesture_event());
341 view->clear_got_gesture_event();
342
343 // Work around for bug in NativeWidgetAura.
344 // TODO: fix bug and remove this.
345 widget->Close();
346 }
347
348 // Verifies views with layers are targeted for events properly.
TEST_F(NativeWidgetAuraTest,PreferViewLayersToChildWindows)349 TEST_F(NativeWidgetAuraTest, PreferViewLayersToChildWindows) {
350 // Create two widgets: |parent| and |child|. |child| is a child of |parent|.
351 views::View* parent_root = new views::View;
352 scoped_ptr<Widget> parent(new Widget());
353 Widget::InitParams parent_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
354 parent_params.ownership =
355 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
356 parent_params.context = root_window();
357 parent->Init(parent_params);
358 parent->SetContentsView(parent_root);
359 parent->SetBounds(gfx::Rect(0, 0, 400, 400));
360 parent->Show();
361
362 scoped_ptr<Widget> child(new Widget());
363 Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL);
364 child_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
365 child_params.parent = parent->GetNativeWindow();
366 child->Init(child_params);
367 child->SetBounds(gfx::Rect(0, 0, 200, 200));
368 child->Show();
369
370 // Point is over |child|.
371 EXPECT_EQ(child->GetNativeWindow(),
372 parent->GetNativeWindow()->GetEventHandlerForPoint(
373 gfx::Point(50, 50)));
374
375 // Create a view with a layer and stack it at the bottom (below |child|).
376 views::View* view_with_layer = new views::View;
377 parent_root->AddChildView(view_with_layer);
378 view_with_layer->SetBounds(0, 0, 50, 50);
379 view_with_layer->SetPaintToLayer(true);
380
381 // Make sure that |child| still gets the event.
382 EXPECT_EQ(child->GetNativeWindow(),
383 parent->GetNativeWindow()->GetEventHandlerForPoint(
384 gfx::Point(20, 20)));
385
386 // Move |view_with_layer| to the top and make sure it gets the
387 // event when the point is within |view_with_layer|'s bounds.
388 view_with_layer->layer()->parent()->StackAtTop(
389 view_with_layer->layer());
390 EXPECT_EQ(parent->GetNativeWindow(),
391 parent->GetNativeWindow()->GetEventHandlerForPoint(
392 gfx::Point(20, 20)));
393
394 // Point is over |child|, it should get the event.
395 EXPECT_EQ(child->GetNativeWindow(),
396 parent->GetNativeWindow()->GetEventHandlerForPoint(
397 gfx::Point(70, 70)));
398
399 delete view_with_layer;
400 view_with_layer = NULL;
401
402 EXPECT_EQ(child->GetNativeWindow(),
403 parent->GetNativeWindow()->GetEventHandlerForPoint(
404 gfx::Point(20, 20)));
405
406 // Work around for bug in NativeWidgetAura.
407 // TODO: fix bug and remove this.
408 parent->Close();
409 }
410
411 // Verifies that widget->FlashFrame() sets aura::client::kDrawAttentionKey,
412 // and activating the window clears it.
TEST_F(NativeWidgetAuraTest,FlashFrame)413 TEST_F(NativeWidgetAuraTest, FlashFrame) {
414 scoped_ptr<Widget> widget(new Widget());
415 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
416 params.context = root_window();
417 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
418 widget->Init(params);
419 aura::Window* window = widget->GetNativeWindow();
420 EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
421 widget->FlashFrame(true);
422 EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
423 widget->FlashFrame(false);
424 EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
425 widget->FlashFrame(true);
426 EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
427 widget->Activate();
428 EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
429 }
430
TEST_F(NativeWidgetAuraTest,NoCrashOnThemeAfterClose)431 TEST_F(NativeWidgetAuraTest, NoCrashOnThemeAfterClose) {
432 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
433 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
434 parent->SetBounds(gfx::Rect(0, 0, 480, 320));
435 scoped_ptr<Widget> widget(new Widget());
436 Init(parent.get(), widget.get());
437 widget->Show();
438 widget->Close();
439 base::MessageLoop::current()->RunUntilIdle();
440 widget->GetNativeTheme(); // Shouldn't crash.
441 }
442
443 // Used to track calls to WidgetDelegate::OnWidgetMove().
444 class MoveTestWidgetDelegate : public WidgetDelegateView {
445 public:
MoveTestWidgetDelegate()446 MoveTestWidgetDelegate() : got_move_(false) {}
~MoveTestWidgetDelegate()447 virtual ~MoveTestWidgetDelegate() {}
448
ClearGotMove()449 void ClearGotMove() { got_move_ = false; }
got_move() const450 bool got_move() const { return got_move_; }
451
452 // WidgetDelegate overrides:
OnWidgetMove()453 virtual void OnWidgetMove() OVERRIDE { got_move_ = true; }
454
455 private:
456 bool got_move_;
457
458 DISALLOW_COPY_AND_ASSIGN(MoveTestWidgetDelegate);
459 };
460
461 // This test simulates what happens when a window is normally maximized. That
462 // is, it's layer is acquired for animation then the window is maximized.
463 // Acquiring the layer resets the bounds of the window. This test verifies the
464 // Widget is still notified correctly of a move in this case.
TEST_F(NativeWidgetAuraTest,OnWidgetMovedInvokedAfterAcquireLayer)465 TEST_F(NativeWidgetAuraTest, OnWidgetMovedInvokedAfterAcquireLayer) {
466 // |delegate| deletes itself when the widget is destroyed.
467 MoveTestWidgetDelegate* delegate = new MoveTestWidgetDelegate;
468 Widget* widget =
469 Widget::CreateWindowWithContextAndBounds(delegate,
470 root_window(),
471 gfx::Rect(10, 10, 100, 200));
472 widget->Show();
473 delegate->ClearGotMove();
474 // Simulate a maximize with animation.
475 delete widget->GetNativeView()->RecreateLayer().release();
476 widget->SetBounds(gfx::Rect(0, 0, 500, 500));
477 EXPECT_TRUE(delegate->got_move());
478 widget->CloseNow();
479 }
480
481 } // namespace
482 } // namespace views
483