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
26namespace views {
27namespace {
28
29NativeWidgetAura* 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
37class NativeWidgetAuraTest : public aura::test::AuraTestBase {
38 public:
39  NativeWidgetAuraTest() {}
40  virtual ~NativeWidgetAuraTest() {}
41
42  // testing::Test overrides:
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
53TEST_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
70TEST_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.
88TEST_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
103class TestLayoutManagerBase : public aura::LayoutManager {
104 public:
105  TestLayoutManagerBase() {}
106  virtual ~TestLayoutManagerBase() {}
107
108  // aura::LayoutManager:
109  virtual void OnWindowResized() OVERRIDE {}
110  virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {}
111  virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {}
112  virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {}
113  virtual void OnChildWindowVisibilityChanged(aura::Window* child,
114                                              bool visible) OVERRIDE {}
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.
123class MaximizeLayoutManager : public TestLayoutManagerBase {
124 public:
125  MaximizeLayoutManager() {}
126  virtual ~MaximizeLayoutManager() {}
127
128 private:
129  // aura::LayoutManager:
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.
140class TestWidget : public views::Widget {
141 public:
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.
147  bool did_size_change_more_than_once() const {
148    return did_size_change_more_than_once_;
149  }
150
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.
170TEST_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
185class PropertyTestLayoutManager : public TestLayoutManagerBase {
186 public:
187  PropertyTestLayoutManager() : added_(false) {}
188  virtual ~PropertyTestLayoutManager() {}
189
190  bool added() const { return added_; }
191
192 private:
193  // aura::LayoutManager:
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
205class PropertyTestWidgetDelegate : public views::WidgetDelegate {
206 public:
207  explicit PropertyTestWidgetDelegate(Widget* widget) : widget_(widget) {}
208  virtual ~PropertyTestWidgetDelegate() {}
209
210 private:
211  // views::WidgetDelegate:
212  virtual bool CanMaximize() const OVERRIDE {
213    return true;
214  }
215  virtual bool CanMinimize() const OVERRIDE {
216    return true;
217  }
218  virtual bool CanResize() const OVERRIDE {
219    return true;
220  }
221  virtual void DeleteDelegate() OVERRIDE {
222    delete this;
223  }
224  virtual Widget* GetWidget() OVERRIDE {
225    return widget_;
226  }
227  virtual const Widget* GetWidget() const OVERRIDE {
228    return widget_;
229  }
230
231  Widget* widget_;
232  DISALLOW_COPY_AND_ASSIGN(PropertyTestWidgetDelegate);
233};
234
235// Verifies that the kCanMaximizeKey/kCanReizeKey have the correct
236// value when added to the layout manager.
237TEST_F(NativeWidgetAuraTest, TestPropertiesWhenAddedToLayout) {
238  root_window()->SetBounds(gfx::Rect(0, 0, 640, 480));
239  PropertyTestLayoutManager* layout_manager = new PropertyTestLayoutManager();
240  root_window()->SetLayoutManager(layout_manager);
241  scoped_ptr<TestWidget> widget(new TestWidget());
242  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
243  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
244  params.delegate = new PropertyTestWidgetDelegate(widget.get());
245  params.parent = NULL;
246  params.context = root_window();
247  widget->Init(params);
248  EXPECT_TRUE(layout_manager->added());
249  widget->CloseNow();
250}
251
252TEST_F(NativeWidgetAuraTest, GetClientAreaScreenBounds) {
253  // Create a widget.
254  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
255  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
256  params.context = root_window();
257  params.bounds.SetRect(10, 20, 300, 400);
258  scoped_ptr<Widget> widget(new Widget());
259  widget->Init(params);
260
261  // For Aura, client area bounds match window bounds.
262  gfx::Rect client_bounds = widget->GetClientAreaBoundsInScreen();
263  EXPECT_EQ(10, client_bounds.x());
264  EXPECT_EQ(20, client_bounds.y());
265  EXPECT_EQ(300, client_bounds.width());
266  EXPECT_EQ(400, client_bounds.height());
267}
268
269// View subclass that tracks whether it has gotten a gesture event.
270class GestureTrackingView : public views::View {
271 public:
272  GestureTrackingView()
273      : got_gesture_event_(false),
274        consume_gesture_event_(true) {}
275
276  void set_consume_gesture_event(bool value) {
277    consume_gesture_event_ = value;
278  }
279
280  void clear_got_gesture_event() {
281    got_gesture_event_ = false;
282  }
283  bool got_gesture_event() const {
284    return got_gesture_event_;
285  }
286
287  // View overrides:
288  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
289    got_gesture_event_ = true;
290    if (consume_gesture_event_)
291      event->StopPropagation();
292  }
293
294 private:
295  // Was OnGestureEvent() invoked?
296  bool got_gesture_event_;
297
298  // Dictates what OnGestureEvent() returns.
299  bool consume_gesture_event_;
300
301  DISALLOW_COPY_AND_ASSIGN(GestureTrackingView);
302};
303
304// Verifies a capture isn't set on touch press and that the view that gets
305// the press gets the release.
306TEST_F(NativeWidgetAuraTest, DontCaptureOnGesture) {
307  // Create two views (both sized the same). |child| is configured not to
308  // consume the gesture event.
309  GestureTrackingView* view = new GestureTrackingView();
310  GestureTrackingView* child = new GestureTrackingView();
311  child->set_consume_gesture_event(false);
312  view->SetLayoutManager(new FillLayout);
313  view->AddChildView(child);
314  scoped_ptr<TestWidget> widget(new TestWidget());
315  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
316  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
317  params.context = root_window();
318  params.bounds = gfx::Rect(0, 0, 100, 200);
319  widget->Init(params);
320  widget->SetContentsView(view);
321  widget->Show();
322
323  ui::TouchEvent press(
324      ui::ET_TOUCH_PRESSED, gfx::Point(41, 51), 1, ui::EventTimeForNow());
325  ui::EventDispatchDetails details =
326      event_processor()->OnEventFromSource(&press);
327  ASSERT_FALSE(details.dispatcher_destroyed);
328  // Both views should get the press.
329  EXPECT_TRUE(view->got_gesture_event());
330  EXPECT_TRUE(child->got_gesture_event());
331  view->clear_got_gesture_event();
332  child->clear_got_gesture_event();
333  // Touch events should not automatically grab capture.
334  EXPECT_FALSE(widget->HasCapture());
335
336  // Release touch. Only |view| should get the release since that it consumed
337  // the press.
338  ui::TouchEvent release(
339      ui::ET_TOUCH_RELEASED, gfx::Point(250, 251), 1, ui::EventTimeForNow());
340  details = event_processor()->OnEventFromSource(&release);
341  ASSERT_FALSE(details.dispatcher_destroyed);
342  EXPECT_TRUE(view->got_gesture_event());
343  EXPECT_FALSE(child->got_gesture_event());
344  view->clear_got_gesture_event();
345
346  // Work around for bug in NativeWidgetAura.
347  // TODO: fix bug and remove this.
348  widget->Close();
349}
350
351// Verifies views with layers are targeted for events properly.
352TEST_F(NativeWidgetAuraTest, PreferViewLayersToChildWindows) {
353  // Create two widgets: |parent| and |child|. |child| is a child of |parent|.
354  views::View* parent_root = new views::View;
355  scoped_ptr<Widget> parent(new Widget());
356  Widget::InitParams parent_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
357  parent_params.ownership =
358      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
359  parent_params.context = root_window();
360  parent->Init(parent_params);
361  parent->SetContentsView(parent_root);
362  parent->SetBounds(gfx::Rect(0, 0, 400, 400));
363  parent->Show();
364
365  scoped_ptr<Widget> child(new Widget());
366  Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL);
367  child_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
368  child_params.parent = parent->GetNativeWindow();
369  child->Init(child_params);
370  child->SetBounds(gfx::Rect(0, 0, 200, 200));
371  child->Show();
372
373  // Point is over |child|.
374  EXPECT_EQ(child->GetNativeWindow(),
375            parent->GetNativeWindow()->GetEventHandlerForPoint(
376                gfx::Point(50, 50)));
377
378  // Create a view with a layer and stack it at the bottom (below |child|).
379  views::View* view_with_layer = new views::View;
380  parent_root->AddChildView(view_with_layer);
381  view_with_layer->SetBounds(0, 0, 50, 50);
382  view_with_layer->SetPaintToLayer(true);
383
384  // Make sure that |child| still gets the event.
385  EXPECT_EQ(child->GetNativeWindow(),
386            parent->GetNativeWindow()->GetEventHandlerForPoint(
387                gfx::Point(20, 20)));
388
389  // Move |view_with_layer| to the top and make sure it gets the
390  // event when the point is within |view_with_layer|'s bounds.
391  view_with_layer->layer()->parent()->StackAtTop(
392      view_with_layer->layer());
393  EXPECT_EQ(parent->GetNativeWindow(),
394            parent->GetNativeWindow()->GetEventHandlerForPoint(
395                gfx::Point(20, 20)));
396
397  // Point is over |child|, it should get the event.
398  EXPECT_EQ(child->GetNativeWindow(),
399            parent->GetNativeWindow()->GetEventHandlerForPoint(
400                gfx::Point(70, 70)));
401
402  delete view_with_layer;
403  view_with_layer = NULL;
404
405  EXPECT_EQ(child->GetNativeWindow(),
406            parent->GetNativeWindow()->GetEventHandlerForPoint(
407                gfx::Point(20, 20)));
408
409  // Work around for bug in NativeWidgetAura.
410  // TODO: fix bug and remove this.
411  parent->Close();
412}
413
414// Verifies that widget->FlashFrame() sets aura::client::kDrawAttentionKey,
415// and activating the window clears it.
416TEST_F(NativeWidgetAuraTest, FlashFrame) {
417  scoped_ptr<Widget> widget(new Widget());
418  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
419  params.context = root_window();
420  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
421  widget->Init(params);
422  aura::Window* window = widget->GetNativeWindow();
423  EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
424  widget->FlashFrame(true);
425  EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
426  widget->FlashFrame(false);
427  EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
428  widget->FlashFrame(true);
429  EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
430  widget->Activate();
431  EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
432}
433
434TEST_F(NativeWidgetAuraTest, NoCrashOnThemeAfterClose) {
435  scoped_ptr<aura::Window> parent(new aura::Window(NULL));
436  parent->Init(aura::WINDOW_LAYER_NOT_DRAWN);
437  parent->SetBounds(gfx::Rect(0, 0, 480, 320));
438  scoped_ptr<Widget> widget(new Widget());
439  Init(parent.get(), widget.get());
440  widget->Show();
441  widget->Close();
442  base::MessageLoop::current()->RunUntilIdle();
443  widget->GetNativeTheme();  // Shouldn't crash.
444}
445
446// Used to track calls to WidgetDelegate::OnWidgetMove().
447class MoveTestWidgetDelegate : public WidgetDelegateView {
448 public:
449  MoveTestWidgetDelegate() : got_move_(false) {}
450  virtual ~MoveTestWidgetDelegate() {}
451
452  void ClearGotMove() { got_move_ = false; }
453  bool got_move() const { return got_move_; }
454
455  // WidgetDelegate overrides:
456  virtual void OnWidgetMove() OVERRIDE { got_move_ = true; }
457
458 private:
459  bool got_move_;
460
461  DISALLOW_COPY_AND_ASSIGN(MoveTestWidgetDelegate);
462};
463
464// This test simulates what happens when a window is normally maximized. That
465// is, it's layer is acquired for animation then the window is maximized.
466// Acquiring the layer resets the bounds of the window. This test verifies the
467// Widget is still notified correctly of a move in this case.
468TEST_F(NativeWidgetAuraTest, OnWidgetMovedInvokedAfterAcquireLayer) {
469  // |delegate| deletes itself when the widget is destroyed.
470  MoveTestWidgetDelegate* delegate = new MoveTestWidgetDelegate;
471  Widget* widget =
472      Widget::CreateWindowWithContextAndBounds(delegate,
473                                               root_window(),
474                                               gfx::Rect(10, 10, 100, 200));
475  widget->Show();
476  delegate->ClearGotMove();
477  // Simulate a maximize with animation.
478  delete widget->GetNativeView()->RecreateLayer().release();
479  widget->SetBounds(gfx::Rect(0, 0, 500, 500));
480  EXPECT_TRUE(delegate->got_move());
481  widget->CloseNow();
482}
483
484}  // namespace
485}  // namespace views
486