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.
5 #include "ui/views/widget/native_widget_aura.h"
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/root_window.h"
16 #include "ui/aura/window.h"
17 #include "ui/events/event.h"
18 #include "ui/gfx/screen.h"
19 #include "ui/views/layout/fill_layout.h"
20 #include "ui/views/test/views_test_base.h"
21 #include "ui/views/widget/root_view.h"
22 #include "ui/views/widget/widget_delegate.h"
27 NativeWidgetAura* Init(aura::Window* parent, Widget* widget) {
28 Widget::InitParams params(Widget::InitParams::TYPE_POPUP);
29 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
30 params.parent = parent;
32 return static_cast<NativeWidgetAura*>(widget->native_widget());
35 class NativeWidgetAuraTest : public ViewsTestBase {
37 NativeWidgetAuraTest() {}
38 virtual ~NativeWidgetAuraTest() {}
40 // testing::Test overrides:
41 virtual void SetUp() OVERRIDE {
42 ViewsTestBase::SetUp();
43 dispatcher()->host()->SetBounds(gfx::Rect(640, 480));
47 aura::Window* root_window() { return GetContext(); }
48 aura::RootWindow* dispatcher() { return root_window()->GetDispatcher(); }
51 DISALLOW_COPY_AND_ASSIGN(NativeWidgetAuraTest);
54 TEST_F(NativeWidgetAuraTest, CenterWindowLargeParent) {
55 // Make a parent window larger than the host represented by rootwindow.
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());
62 window->CenterWindow(gfx::Size(100, 100));
63 EXPECT_EQ(gfx::Rect( (640 - 100) / 2,
66 window->GetNativeWindow()->bounds());
70 TEST_F(NativeWidgetAuraTest, CenterWindowSmallParent) {
71 // Make a parent window smaller than the host represented by rootwindow.
72 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
73 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
74 parent->SetBounds(gfx::Rect(0, 0, 480, 320));
75 scoped_ptr<Widget> widget(new Widget());
76 NativeWidgetAura* window = Init(parent.get(), widget.get());
78 window->CenterWindow(gfx::Size(100, 100));
79 EXPECT_EQ(gfx::Rect( (480 - 100) / 2,
82 window->GetNativeWindow()->bounds());
86 // Verifies CenterWindow() constrains to parent size.
87 TEST_F(NativeWidgetAuraTest, CenterWindowSmallParentNotAtOrigin) {
88 // Make a parent window smaller than the host represented by rootwindow and
89 // offset it slightly from the origin.
90 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
91 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
92 parent->SetBounds(gfx::Rect(20, 40, 480, 320));
93 scoped_ptr<Widget> widget(new Widget());
94 NativeWidgetAura* window = Init(parent.get(), widget.get());
95 window->CenterWindow(gfx::Size(500, 600));
97 // |window| should be no bigger than |parent|.
98 EXPECT_EQ("20,40 480x320", window->GetNativeWindow()->bounds().ToString());
102 // Used by ShowMaximizedDoesntBounceAround. See it for details.
103 class TestLayoutManager : public aura::LayoutManager {
105 TestLayoutManager() {}
107 virtual void OnWindowResized() OVERRIDE {
109 virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {
110 // This simulates what happens when adding a maximized window.
111 SetChildBoundsDirect(child, gfx::Rect(0, 0, 300, 300));
113 virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {
115 virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {
117 virtual void OnChildWindowVisibilityChanged(aura::Window* child,
118 bool visible) OVERRIDE {
120 virtual void SetChildBounds(aura::Window* child,
121 const gfx::Rect& requested_bounds) OVERRIDE {
125 DISALLOW_COPY_AND_ASSIGN(TestLayoutManager);
128 // This simulates BrowserView, which creates a custom RootView so that
129 // OnNativeWidgetSizeChanged that is invoked during Init matters.
130 class TestWidget : public views::Widget {
132 TestWidget() : did_size_change_more_than_once_(false) {
135 // Returns true if the size changes to a non-empty size, and then to another
137 bool did_size_change_more_than_once() const {
138 return did_size_change_more_than_once_;
141 virtual void OnNativeWidgetSizeChanged(const gfx::Size& new_size) OVERRIDE {
142 if (last_size_.IsEmpty())
143 last_size_ = new_size;
144 else if (!did_size_change_more_than_once_ && new_size != last_size_)
145 did_size_change_more_than_once_ = true;
146 Widget::OnNativeWidgetSizeChanged(new_size);
150 bool did_size_change_more_than_once_;
151 gfx::Size last_size_;
153 DISALLOW_COPY_AND_ASSIGN(TestWidget);
156 // Verifies the size of the widget doesn't change more than once during Init if
157 // the window ends up maximized. This is important as otherwise
158 // RenderWidgetHostViewAura ends up getting resized during construction, which
159 // leads to noticable flashes.
160 TEST_F(NativeWidgetAuraTest, ShowMaximizedDoesntBounceAround) {
161 root_window()->SetBounds(gfx::Rect(0, 0, 640, 480));
162 root_window()->SetLayoutManager(new TestLayoutManager);
163 scoped_ptr<TestWidget> widget(new TestWidget());
164 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
165 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
166 params.parent = NULL;
167 params.context = root_window();
168 params.show_state = ui::SHOW_STATE_MAXIMIZED;
169 params.bounds = gfx::Rect(10, 10, 100, 200);
170 widget->Init(params);
171 EXPECT_FALSE(widget->did_size_change_more_than_once());
175 TEST_F(NativeWidgetAuraTest, GetClientAreaScreenBounds) {
177 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
178 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
179 params.context = root_window();
180 params.bounds.SetRect(10, 20, 300, 400);
181 scoped_ptr<Widget> widget(new Widget());
182 widget->Init(params);
184 // For Aura, client area bounds match window bounds.
185 gfx::Rect client_bounds = widget->GetClientAreaBoundsInScreen();
186 EXPECT_EQ(10, client_bounds.x());
187 EXPECT_EQ(20, client_bounds.y());
188 EXPECT_EQ(300, client_bounds.width());
189 EXPECT_EQ(400, client_bounds.height());
194 // View subclass that tracks whether it has gotten a gesture event.
195 class GestureTrackingView : public views::View {
197 GestureTrackingView()
198 : got_gesture_event_(false),
199 consume_gesture_event_(true) {}
201 void set_consume_gesture_event(bool value) {
202 consume_gesture_event_ = value;
205 void clear_got_gesture_event() {
206 got_gesture_event_ = false;
208 bool got_gesture_event() const {
209 return got_gesture_event_;
213 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
214 got_gesture_event_ = true;
215 if (consume_gesture_event_)
216 event->StopPropagation();
220 // Was OnGestureEvent() invoked?
221 bool got_gesture_event_;
223 // Dictates what OnGestureEvent() returns.
224 bool consume_gesture_event_;
226 DISALLOW_COPY_AND_ASSIGN(GestureTrackingView);
231 // Verifies a capture isn't set on touch press and that the view that gets
232 // the press gets the release.
233 TEST_F(NativeWidgetAuraTest, DontCaptureOnGesture) {
234 // Create two views (both sized the same). |child| is configured not to
235 // consume the gesture event.
236 GestureTrackingView* view = new GestureTrackingView();
237 GestureTrackingView* child = new GestureTrackingView();
238 child->set_consume_gesture_event(false);
239 view->SetLayoutManager(new FillLayout);
240 view->AddChildView(child);
241 scoped_ptr<TestWidget> widget(new TestWidget());
242 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
243 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
244 params.context = root_window();
245 params.bounds = gfx::Rect(0, 0, 100, 200);
246 widget->Init(params);
247 widget->SetContentsView(view);
250 ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(41, 51), 1,
252 dispatcher()->AsWindowTreeHostDelegate()->OnHostTouchEvent(&press);
253 // Both views should get the press.
254 EXPECT_TRUE(view->got_gesture_event());
255 EXPECT_TRUE(child->got_gesture_event());
256 view->clear_got_gesture_event();
257 child->clear_got_gesture_event();
258 // Touch events should not automatically grab capture.
259 EXPECT_FALSE(widget->HasCapture());
261 // Release touch. Only |view| should get the release since that it consumed
263 ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(250, 251), 1,
265 dispatcher()->AsWindowTreeHostDelegate()->OnHostTouchEvent(&release);
266 EXPECT_TRUE(view->got_gesture_event());
267 EXPECT_FALSE(child->got_gesture_event());
268 view->clear_got_gesture_event();
270 // Work around for bug in NativeWidgetAura.
271 // TODO: fix bug and remove this.
275 TEST_F(NativeWidgetAuraTest, ReleaseCaptureOnTouchRelease) {
276 GestureTrackingView* view = new GestureTrackingView();
277 scoped_ptr<TestWidget> widget(new TestWidget());
278 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
279 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
280 params.context = root_window();
281 params.bounds = gfx::Rect(0, 0, 100, 200);
282 widget->Init(params);
283 widget->SetContentsView(view);
286 ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(41, 51), 1,
288 dispatcher()->AsWindowTreeHostDelegate()->OnHostTouchEvent(&press);
289 EXPECT_TRUE(view->got_gesture_event());
290 view->clear_got_gesture_event();
292 widget->SetCapture(view);
293 EXPECT_TRUE(widget->HasCapture());
295 // Generate a release, this should trigger releasing capture.
296 ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(41, 51), 1,
298 dispatcher()->AsWindowTreeHostDelegate()->OnHostTouchEvent(&release);
299 EXPECT_TRUE(view->got_gesture_event());
300 view->clear_got_gesture_event();
301 EXPECT_FALSE(widget->HasCapture());
303 // Work around for bug in NativeWidgetAura.
304 // TODO: fix bug and remove this.
308 // Verifies views with layers are targeted for events properly.
309 TEST_F(NativeWidgetAuraTest, PreferViewLayersToChildWindows) {
310 // Create two widgets: |parent| and |child|. |child| is a child of |parent|.
311 views::View* parent_root = new views::View;
312 scoped_ptr<Widget> parent(new Widget());
313 Widget::InitParams parent_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
314 parent_params.ownership =
315 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
316 parent_params.context = root_window();
317 parent->Init(parent_params);
318 parent->SetContentsView(parent_root);
319 parent->SetBounds(gfx::Rect(0, 0, 400, 400));
322 scoped_ptr<Widget> child(new Widget());
323 Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL);
324 child_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
325 child_params.parent = parent->GetNativeWindow();
326 child->Init(child_params);
327 child->SetBounds(gfx::Rect(0, 0, 200, 200));
330 // Point is over |child|.
331 EXPECT_EQ(child->GetNativeWindow(),
332 parent->GetNativeWindow()->GetEventHandlerForPoint(
333 gfx::Point(50, 50)));
335 // Create a view with a layer and stack it at the bottom (below |child|).
336 views::View* view_with_layer = new views::View;
337 parent_root->AddChildView(view_with_layer);
338 view_with_layer->SetBounds(0, 0, 50, 50);
339 view_with_layer->SetPaintToLayer(true);
341 // Make sure that |child| still gets the event.
342 EXPECT_EQ(child->GetNativeWindow(),
343 parent->GetNativeWindow()->GetEventHandlerForPoint(
344 gfx::Point(20, 20)));
346 // Move |view_with_layer| to the top and make sure it gets the
347 // event when the point is within |view_with_layer|'s bounds.
348 view_with_layer->layer()->parent()->StackAtTop(
349 view_with_layer->layer());
350 EXPECT_EQ(parent->GetNativeWindow(),
351 parent->GetNativeWindow()->GetEventHandlerForPoint(
352 gfx::Point(20, 20)));
354 // Point is over |child|, it should get the event.
355 EXPECT_EQ(child->GetNativeWindow(),
356 parent->GetNativeWindow()->GetEventHandlerForPoint(
357 gfx::Point(70, 70)));
359 delete view_with_layer;
360 view_with_layer = NULL;
362 EXPECT_EQ(child->GetNativeWindow(),
363 parent->GetNativeWindow()->GetEventHandlerForPoint(
364 gfx::Point(20, 20)));
366 // Work around for bug in NativeWidgetAura.
367 // TODO: fix bug and remove this.
371 // Verifies that widget->FlashFrame() sets aura::client::kDrawAttentionKey,
372 // and activating the window clears it.
373 TEST_F(NativeWidgetAuraTest, FlashFrame) {
374 scoped_ptr<Widget> widget(new Widget());
375 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
376 params.context = root_window();
377 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
378 widget->Init(params);
379 aura::Window* window = widget->GetNativeWindow();
380 EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
381 widget->FlashFrame(true);
382 EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
383 widget->FlashFrame(false);
384 EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
385 widget->FlashFrame(true);
386 EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
388 EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
391 TEST_F(NativeWidgetAuraTest, NoCrashOnThemeAfterClose) {
392 scoped_ptr<aura::Window> parent(new aura::Window(NULL));
393 parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
394 parent->SetBounds(gfx::Rect(0, 0, 480, 320));
395 scoped_ptr<Widget> widget(new Widget());
396 NativeWidgetAura* window = Init(parent.get(), widget.get());
399 base::MessageLoop::current()->RunUntilIdle();
400 widget->GetNativeTheme(); // Shouldn't crash.