1 // Copyright 2013 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.
5 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
8 #include "ui/aura/client/aura_constants.h"
9 #include "ui/aura/client/cursor_client.h"
10 #include "ui/aura/client/window_tree_client.h"
11 #include "ui/aura/test/test_window_delegate.h"
12 #include "ui/aura/window.h"
13 #include "ui/aura/window_tree_host.h"
14 #include "ui/events/event_processor.h"
15 #include "ui/events/event_utils.h"
16 #include "ui/events/test/event_generator.h"
17 #include "ui/gfx/screen.h"
18 #include "ui/views/test/test_views.h"
19 #include "ui/views/test/views_test_base.h"
20 #include "ui/views/test/widget_test.h"
21 #include "ui/views/widget/widget.h"
22 #include "ui/wm/public/dispatcher_client.h"
27 typedef ViewsTestBase DesktopNativeWidgetAuraTest
;
29 // Verifies creating a Widget with a parent that is not in a RootWindow doesn't
31 TEST_F(DesktopNativeWidgetAuraTest
, CreateWithParentNotInRootWindow
) {
32 scoped_ptr
<aura::Window
> window(new aura::Window(NULL
));
33 window
->Init(ui::LAYER_NOT_DRAWN
);
35 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
36 params
.bounds
= gfx::Rect(0, 0, 200, 200);
37 params
.parent
= window
.get();
38 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
39 params
.native_widget
= new DesktopNativeWidgetAura(&widget
);
43 // Verifies that the Aura windows making up a widget instance have the correct
44 // bounds after the widget is resized.
45 TEST_F(DesktopNativeWidgetAuraTest
, DesktopAuraWindowSizeTest
) {
48 // On Linux we test this with popup windows because the WM may ignore the size
49 // suggestion for normal windows.
51 Widget::InitParams init_params
=
52 CreateParams(Widget::InitParams::TYPE_POPUP
);
54 Widget::InitParams init_params
=
55 CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS
);
58 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
59 init_params
.native_widget
= new DesktopNativeWidgetAura(&widget
);
60 widget
.Init(init_params
);
62 gfx::Rect
bounds(0, 0, 100, 100);
63 widget
.SetBounds(bounds
);
66 EXPECT_EQ(bounds
.ToString(),
67 widget
.GetNativeView()->GetRootWindow()->bounds().ToString());
68 EXPECT_EQ(bounds
.ToString(), widget
.GetNativeView()->bounds().ToString());
69 EXPECT_EQ(bounds
.ToString(),
70 widget
.GetNativeView()->parent()->bounds().ToString());
72 gfx::Rect
new_bounds(0, 0, 200, 200);
73 widget
.SetBounds(new_bounds
);
74 EXPECT_EQ(new_bounds
.ToString(),
75 widget
.GetNativeView()->GetRootWindow()->bounds().ToString());
76 EXPECT_EQ(new_bounds
.ToString(), widget
.GetNativeView()->bounds().ToString());
77 EXPECT_EQ(new_bounds
.ToString(),
78 widget
.GetNativeView()->parent()->bounds().ToString());
81 // Verifies GetNativeView() is initially hidden. If the native view is initially
82 // shown then animations can not be disabled.
83 TEST_F(DesktopNativeWidgetAuraTest
, NativeViewInitiallyHidden
) {
85 Widget::InitParams init_params
=
86 CreateParams(Widget::InitParams::TYPE_WINDOW
);
87 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
88 init_params
.native_widget
= new DesktopNativeWidgetAura(&widget
);
89 widget
.Init(init_params
);
90 EXPECT_FALSE(widget
.GetNativeView()->IsVisible());
93 // Verify that the cursor state is shared between two native widgets.
94 TEST_F(DesktopNativeWidgetAuraTest
, GlobalCursorState
) {
95 // Create two native widgets, each owning different root windows.
97 Widget::InitParams init_params_a
=
98 CreateParams(Widget::InitParams::TYPE_WINDOW
);
99 init_params_a
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
100 DesktopNativeWidgetAura
* desktop_native_widget_aura_a
=
101 new DesktopNativeWidgetAura(&widget_a
);
102 init_params_a
.native_widget
= desktop_native_widget_aura_a
;
103 widget_a
.Init(init_params_a
);
106 Widget::InitParams init_params_b
=
107 CreateParams(Widget::InitParams::TYPE_WINDOW
);
108 init_params_b
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
109 DesktopNativeWidgetAura
* desktop_native_widget_aura_b
=
110 new DesktopNativeWidgetAura(&widget_b
);
111 init_params_b
.native_widget
= desktop_native_widget_aura_b
;
112 widget_b
.Init(init_params_b
);
114 aura::client::CursorClient
* cursor_client_a
= aura::client::GetCursorClient(
115 desktop_native_widget_aura_a
->host()->window());
116 aura::client::CursorClient
* cursor_client_b
= aura::client::GetCursorClient(
117 desktop_native_widget_aura_b
->host()->window());
119 // Verify the cursor can be locked using one client and unlocked using
121 EXPECT_FALSE(cursor_client_a
->IsCursorLocked());
122 EXPECT_FALSE(cursor_client_b
->IsCursorLocked());
124 cursor_client_a
->LockCursor();
125 EXPECT_TRUE(cursor_client_a
->IsCursorLocked());
126 EXPECT_TRUE(cursor_client_b
->IsCursorLocked());
128 cursor_client_b
->UnlockCursor();
129 EXPECT_FALSE(cursor_client_a
->IsCursorLocked());
130 EXPECT_FALSE(cursor_client_b
->IsCursorLocked());
132 // Verify that mouse events can be disabled using one client and then
133 // re-enabled using another. Note that disabling mouse events should also
134 // have the side effect of making the cursor invisible.
135 EXPECT_TRUE(cursor_client_a
->IsCursorVisible());
136 EXPECT_TRUE(cursor_client_b
->IsCursorVisible());
137 EXPECT_TRUE(cursor_client_a
->IsMouseEventsEnabled());
138 EXPECT_TRUE(cursor_client_b
->IsMouseEventsEnabled());
140 cursor_client_b
->DisableMouseEvents();
141 EXPECT_FALSE(cursor_client_a
->IsCursorVisible());
142 EXPECT_FALSE(cursor_client_b
->IsCursorVisible());
143 EXPECT_FALSE(cursor_client_a
->IsMouseEventsEnabled());
144 EXPECT_FALSE(cursor_client_b
->IsMouseEventsEnabled());
146 cursor_client_a
->EnableMouseEvents();
147 EXPECT_TRUE(cursor_client_a
->IsCursorVisible());
148 EXPECT_TRUE(cursor_client_b
->IsCursorVisible());
149 EXPECT_TRUE(cursor_client_a
->IsMouseEventsEnabled());
150 EXPECT_TRUE(cursor_client_b
->IsMouseEventsEnabled());
152 // Verify that setting the cursor using one cursor client
153 // will set it for all root windows.
154 EXPECT_EQ(ui::kCursorNone
, cursor_client_a
->GetCursor().native_type());
155 EXPECT_EQ(ui::kCursorNone
, cursor_client_b
->GetCursor().native_type());
157 cursor_client_b
->SetCursor(ui::kCursorPointer
);
158 EXPECT_EQ(ui::kCursorPointer
, cursor_client_a
->GetCursor().native_type());
159 EXPECT_EQ(ui::kCursorPointer
, cursor_client_b
->GetCursor().native_type());
161 // Verify that hiding the cursor using one cursor client will
162 // hide it for all root windows. Note that hiding the cursor
163 // should not disable mouse events.
164 cursor_client_a
->HideCursor();
165 EXPECT_FALSE(cursor_client_a
->IsCursorVisible());
166 EXPECT_FALSE(cursor_client_b
->IsCursorVisible());
167 EXPECT_TRUE(cursor_client_a
->IsMouseEventsEnabled());
168 EXPECT_TRUE(cursor_client_b
->IsMouseEventsEnabled());
170 // Verify that the visibility state cannot be changed using one
171 // cursor client when the cursor was locked using another.
172 cursor_client_b
->LockCursor();
173 cursor_client_a
->ShowCursor();
174 EXPECT_FALSE(cursor_client_a
->IsCursorVisible());
175 EXPECT_FALSE(cursor_client_b
->IsCursorVisible());
177 // Verify the cursor becomes visible on unlock (since a request
178 // to make it visible was queued up while the cursor was locked).
179 cursor_client_b
->UnlockCursor();
180 EXPECT_TRUE(cursor_client_a
->IsCursorVisible());
181 EXPECT_TRUE(cursor_client_b
->IsCursorVisible());
184 // Verifies FocusController doesn't attempt to access |content_window_| during
185 // destruction. Previously the FocusController was destroyed after the window.
186 // This could be problematic as FocusController references |content_window_| and
187 // could attempt to use it after |content_window_| was destroyed. This test
188 // verifies this doesn't happen. Note that this test only failed under ASAN.
189 TEST_F(DesktopNativeWidgetAuraTest
, DontAccessContentWindowDuringDestruction
) {
190 aura::test::TestWindowDelegate delegate
;
193 Widget::InitParams init_params
=
194 CreateParams(Widget::InitParams::TYPE_WINDOW
);
195 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
196 DesktopNativeWidgetAura
* desktop_native_widget_aura
=
197 new DesktopNativeWidgetAura(&widget
);
198 init_params
.native_widget
= desktop_native_widget_aura
;
199 widget
.Init(init_params
);
201 // Owned by |widget|.
202 aura::Window
* window
= new aura::Window(&delegate
);
203 window
->Init(ui::LAYER_NOT_DRAWN
);
205 widget
.GetNativeWindow()->parent()->AddChild(window
);
211 void QuitNestedLoopAndCloseWidget(scoped_ptr
<Widget
> widget
,
212 base::Closure
* quit_runloop
) {
216 // Verifies that a widget can be destroyed when running a nested message-loop.
217 TEST_F(DesktopNativeWidgetAuraTest
, WidgetCanBeDestroyedFromNestedLoop
) {
218 scoped_ptr
<Widget
> widget(new Widget
);
219 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_WINDOW
);
220 params
.bounds
= gfx::Rect(0, 0, 200, 200);
221 params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
222 params
.native_widget
= new DesktopNativeWidgetAura(widget
.get());
223 widget
->Init(params
);
226 aura::Window
* window
= widget
->GetNativeView();
227 aura::Window
* root
= window
->GetRootWindow();
228 aura::client::DispatcherClient
* client
=
229 aura::client::GetDispatcherClient(root
);
231 // Post a task that terminates the nested loop and destroyes the widget. This
232 // task will be executed from the nested loop initiated with the call to
233 // |RunWithDispatcher()| below.
234 aura::client::DispatcherRunLoop
run_loop(client
, NULL
);
235 base::Closure quit_runloop
= run_loop
.QuitClosure();
236 message_loop()->PostTask(FROM_HERE
,
237 base::Bind(&QuitNestedLoopAndCloseWidget
,
238 base::Passed(&widget
),
239 base::Unretained(&quit_runloop
)));
243 // This class provides functionality to create fullscreen and top level popup
244 // windows. It additionally tests whether the destruction of these windows
245 // occurs correctly in desktop AURA without crashing.
246 // It provides facilities to test the following cases:-
247 // 1. Child window destroyed which should lead to the destruction of the
249 // 2. Parent window destroyed which should lead to the child being destroyed.
250 class DesktopAuraTopLevelWindowTest
251 : public views::TestViewsDelegate
,
252 public aura::WindowObserver
{
254 DesktopAuraTopLevelWindowTest()
255 : top_level_widget_(NULL
),
257 owner_destroyed_(false),
258 owned_window_destroyed_(false),
259 use_async_mode_(true) {}
261 ~DesktopAuraTopLevelWindowTest() override
{
262 EXPECT_TRUE(owner_destroyed_
);
263 EXPECT_TRUE(owned_window_destroyed_
);
264 top_level_widget_
= NULL
;
265 owned_window_
= NULL
;
268 // views::TestViewsDelegate overrides.
269 void OnBeforeWidgetInit(Widget::InitParams
* params
,
270 internal::NativeWidgetDelegate
* delegate
) override
{
271 if (!params
->native_widget
)
272 params
->native_widget
= new views::DesktopNativeWidgetAura(delegate
);
275 void CreateTopLevelWindow(const gfx::Rect
& bounds
, bool fullscreen
) {
276 Widget::InitParams init_params
;
277 init_params
.type
= Widget::InitParams::TYPE_WINDOW
;
278 init_params
.bounds
= bounds
;
279 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
280 init_params
.layer_type
= ui::LAYER_NOT_DRAWN
;
281 init_params
.accept_events
= fullscreen
;
283 widget_
.Init(init_params
);
285 owned_window_
= new aura::Window(&child_window_delegate_
);
286 owned_window_
->SetType(ui::wm::WINDOW_TYPE_NORMAL
);
287 owned_window_
->SetName("TestTopLevelWindow");
289 owned_window_
->SetProperty(aura::client::kShowStateKey
,
290 ui::SHOW_STATE_FULLSCREEN
);
292 owned_window_
->SetType(ui::wm::WINDOW_TYPE_MENU
);
294 owned_window_
->Init(ui::LAYER_TEXTURED
);
295 aura::client::ParentWindowWithContext(
297 widget_
.GetNativeView()->GetRootWindow(),
298 gfx::Rect(0, 0, 1900, 1600));
299 owned_window_
->Show();
300 owned_window_
->AddObserver(this);
302 ASSERT_TRUE(owned_window_
->parent() != NULL
);
303 owned_window_
->parent()->AddObserver(this);
306 views::Widget::GetWidgetForNativeView(owned_window_
->parent());
307 ASSERT_TRUE(top_level_widget_
!= NULL
);
310 void DestroyOwnedWindow() {
311 ASSERT_TRUE(owned_window_
!= NULL
);
312 // If async mode is off then clean up state here.
313 if (!use_async_mode_
) {
314 owned_window_
->RemoveObserver(this);
315 owned_window_
->parent()->RemoveObserver(this);
316 owner_destroyed_
= true;
317 owned_window_destroyed_
= true;
319 delete owned_window_
;
322 void DestroyOwnerWindow() {
323 ASSERT_TRUE(top_level_widget_
!= NULL
);
324 top_level_widget_
->CloseNow();
327 void OnWindowDestroying(aura::Window
* window
) override
{
328 window
->RemoveObserver(this);
329 if (window
== owned_window_
) {
330 owned_window_destroyed_
= true;
331 } else if (window
== top_level_widget_
->GetNativeView()) {
332 owner_destroyed_
= true;
334 ADD_FAILURE() << "Unexpected window destroyed callback: " << window
;
338 aura::Window
* owned_window() {
339 return owned_window_
;
342 views::Widget
* top_level_widget() {
343 return top_level_widget_
;
346 void set_use_async_mode(bool async_mode
) {
347 use_async_mode_
= async_mode
;
351 views::Widget widget_
;
352 views::Widget
* top_level_widget_
;
353 aura::Window
* owned_window_
;
354 bool owner_destroyed_
;
355 bool owned_window_destroyed_
;
356 aura::test::TestWindowDelegate child_window_delegate_
;
357 // This flag controls whether we need to wait for the destruction to complete
358 // before finishing the test. Defaults to true.
359 bool use_async_mode_
;
361 DISALLOW_COPY_AND_ASSIGN(DesktopAuraTopLevelWindowTest
);
364 typedef WidgetTest DesktopAuraWidgetTest
;
366 TEST_F(DesktopAuraWidgetTest
, FullscreenWindowDestroyedBeforeOwnerTest
) {
367 ViewsDelegate::views_delegate
= NULL
;
368 DesktopAuraTopLevelWindowTest fullscreen_window
;
369 ASSERT_NO_FATAL_FAILURE(fullscreen_window
.CreateTopLevelWindow(
370 gfx::Rect(0, 0, 200, 200), true));
372 RunPendingMessages();
373 ASSERT_NO_FATAL_FAILURE(fullscreen_window
.DestroyOwnedWindow());
374 RunPendingMessages();
377 TEST_F(DesktopAuraWidgetTest
, FullscreenWindowOwnerDestroyed
) {
378 ViewsDelegate::views_delegate
= NULL
;
380 DesktopAuraTopLevelWindowTest fullscreen_window
;
381 ASSERT_NO_FATAL_FAILURE(fullscreen_window
.CreateTopLevelWindow(
382 gfx::Rect(0, 0, 200, 200), true));
384 RunPendingMessages();
385 ASSERT_NO_FATAL_FAILURE(fullscreen_window
.DestroyOwnerWindow());
386 RunPendingMessages();
389 TEST_F(DesktopAuraWidgetTest
, TopLevelOwnedPopupTest
) {
390 ViewsDelegate::views_delegate
= NULL
;
391 DesktopAuraTopLevelWindowTest popup_window
;
392 ASSERT_NO_FATAL_FAILURE(popup_window
.CreateTopLevelWindow(
393 gfx::Rect(0, 0, 200, 200), false));
395 RunPendingMessages();
396 ASSERT_NO_FATAL_FAILURE(popup_window
.DestroyOwnedWindow());
397 RunPendingMessages();
400 // This test validates that when a top level owned popup Aura window is
401 // resized, the widget is resized as well.
402 TEST_F(DesktopAuraWidgetTest
, TopLevelOwnedPopupResizeTest
) {
403 ViewsDelegate::views_delegate
= NULL
;
404 DesktopAuraTopLevelWindowTest popup_window
;
406 popup_window
.set_use_async_mode(false);
408 ASSERT_NO_FATAL_FAILURE(popup_window
.CreateTopLevelWindow(
409 gfx::Rect(0, 0, 200, 200), false));
411 gfx::Rect
new_size(0, 0, 400, 400);
412 popup_window
.owned_window()->SetBounds(new_size
);
414 EXPECT_EQ(popup_window
.top_level_widget()->GetNativeView()->bounds().size(),
417 ASSERT_NO_FATAL_FAILURE(popup_window
.DestroyOwnedWindow());
420 // This test validates that when a top level owned popup Aura window is
421 // repositioned, the widget is repositioned as well.
422 TEST_F(DesktopAuraWidgetTest
, TopLevelOwnedPopupRepositionTest
) {
423 ViewsDelegate::views_delegate
= NULL
;
424 DesktopAuraTopLevelWindowTest popup_window
;
426 popup_window
.set_use_async_mode(false);
428 ASSERT_NO_FATAL_FAILURE(popup_window
.CreateTopLevelWindow(
429 gfx::Rect(0, 0, 200, 200), false));
431 gfx::Rect
new_pos(10, 10, 400, 400);
432 popup_window
.owned_window()->SetBoundsInScreen(
434 gfx::Screen::GetScreenFor(
435 popup_window
.owned_window())->GetDisplayNearestPoint(gfx::Point()));
438 popup_window
.top_level_widget()->GetWindowBoundsInScreen());
440 ASSERT_NO_FATAL_FAILURE(popup_window
.DestroyOwnedWindow());
443 // The following code verifies we can correctly destroy a Widget from a mouse
444 // enter/exit. We could test move/drag/enter/exit but in general we don't run
445 // nested message loops from such events, nor has the code ever really dealt
446 // with this situation.
448 // Generates two moves (first generates enter, second real move), a press, drag
449 // and release stopping at |last_event_type|.
450 void GenerateMouseEvents(Widget
* widget
, ui::EventType last_event_type
) {
451 const gfx::Rect
screen_bounds(widget
->GetWindowBoundsInScreen());
452 ui::MouseEvent
move_event(ui::ET_MOUSE_MOVED
, screen_bounds
.CenterPoint(),
453 screen_bounds
.CenterPoint(), ui::EventTimeForNow(),
455 ui::EventProcessor
* dispatcher
= WidgetTest::GetEventProcessor(widget
);
456 ui::EventDispatchDetails details
= dispatcher
->OnEventFromSource(&move_event
);
457 if (last_event_type
== ui::ET_MOUSE_ENTERED
|| details
.dispatcher_destroyed
)
459 details
= dispatcher
->OnEventFromSource(&move_event
);
460 if (last_event_type
== ui::ET_MOUSE_MOVED
|| details
.dispatcher_destroyed
)
463 ui::MouseEvent
press_event(ui::ET_MOUSE_PRESSED
, screen_bounds
.CenterPoint(),
464 screen_bounds
.CenterPoint(), ui::EventTimeForNow(),
466 details
= dispatcher
->OnEventFromSource(&press_event
);
467 if (last_event_type
== ui::ET_MOUSE_PRESSED
|| details
.dispatcher_destroyed
)
470 gfx::Point
end_point(screen_bounds
.CenterPoint());
471 end_point
.Offset(1, 1);
472 ui::MouseEvent
drag_event(ui::ET_MOUSE_DRAGGED
, end_point
, end_point
,
473 ui::EventTimeForNow(), 0, 0);
474 details
= dispatcher
->OnEventFromSource(&drag_event
);
475 if (last_event_type
== ui::ET_MOUSE_DRAGGED
|| details
.dispatcher_destroyed
)
478 ui::MouseEvent
release_event(ui::ET_MOUSE_RELEASED
, end_point
, end_point
,
479 ui::EventTimeForNow(), 0, 0);
480 details
= dispatcher
->OnEventFromSource(&release_event
);
481 if (details
.dispatcher_destroyed
)
485 // Creates a widget and invokes GenerateMouseEvents() with |last_event_type|.
486 void RunCloseWidgetDuringDispatchTest(WidgetTest
* test
,
487 ui::EventType last_event_type
) {
488 // |widget| is deleted by CloseWidgetView.
489 Widget
* widget
= new Widget
;
490 Widget::InitParams params
=
491 test
->CreateParams(Widget::InitParams::TYPE_POPUP
);
492 params
.native_widget
= new PlatformDesktopNativeWidget(widget
);
493 params
.bounds
= gfx::Rect(0, 0, 50, 100);
494 widget
->Init(params
);
495 widget
->SetContentsView(new CloseWidgetView(last_event_type
));
497 GenerateMouseEvents(widget
, last_event_type
);
500 // Verifies deleting the widget from a mouse pressed event doesn't crash.
501 TEST_F(DesktopAuraWidgetTest
, CloseWidgetDuringMousePress
) {
502 RunCloseWidgetDuringDispatchTest(this, ui::ET_MOUSE_PRESSED
);
505 // Verifies deleting the widget from a mouse released event doesn't crash.
506 TEST_F(DesktopAuraWidgetTest
, CloseWidgetDuringMouseReleased
) {
507 RunCloseWidgetDuringDispatchTest(this, ui::ET_MOUSE_RELEASED
);