Home | History | Annotate | Download | only in widget
      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/root_window.h"
     16 #include "ui/aura/test/aura_test_helper.h"
     17 #include "ui/aura/window.h"
     18 #include "ui/events/event.h"
     19 #include "ui/gfx/screen.h"
     20 #include "ui/views/layout/fill_layout.h"
     21 #include "ui/views/widget/root_view.h"
     22 #include "ui/views/widget/widget_delegate.h"
     23 
     24 namespace views {
     25 namespace {
     26 
     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;
     31   widget->Init(params);
     32   return static_cast<NativeWidgetAura*>(widget->native_widget());
     33 }
     34 
     35 class NativeWidgetAuraTest : public testing::Test {
     36  public:
     37   NativeWidgetAuraTest() {}
     38   virtual ~NativeWidgetAuraTest() {}
     39 
     40   // testing::Test overrides:
     41   virtual void SetUp() OVERRIDE {
     42     aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
     43     aura_test_helper_->SetUp();
     44     root_window()->SetBounds(gfx::Rect(0, 0, 640, 480));
     45     dispatcher()->SetHostSize(gfx::Size(640, 480));
     46   }
     47   virtual void TearDown() OVERRIDE {
     48     message_loop_.RunUntilIdle();
     49     aura_test_helper_->TearDown();
     50   }
     51 
     52  protected:
     53   aura::Window* root_window() { return aura_test_helper_->root_window(); }
     54   aura::RootWindow* dispatcher() { return aura_test_helper_->dispatcher(); }
     55 
     56  private:
     57   base::MessageLoopForUI message_loop_;
     58   scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
     59 
     60   DISALLOW_COPY_AND_ASSIGN(NativeWidgetAuraTest);
     61 };
     62 
     63 TEST_F(NativeWidgetAuraTest, CenterWindowLargeParent) {
     64   // Make a parent window larger than the host represented by rootwindow.
     65   scoped_ptr<aura::Window> parent(new aura::Window(NULL));
     66   parent->Init(ui::LAYER_NOT_DRAWN);
     67   parent->SetBounds(gfx::Rect(0, 0, 1024, 800));
     68   scoped_ptr<Widget>  widget(new Widget());
     69   NativeWidgetAura* window = Init(parent.get(), widget.get());
     70 
     71   window->CenterWindow(gfx::Size(100, 100));
     72   EXPECT_EQ(gfx::Rect( (640 - 100) / 2,
     73                        (480 - 100) / 2,
     74                        100, 100),
     75             window->GetNativeWindow()->bounds());
     76   widget->CloseNow();
     77 }
     78 
     79 TEST_F(NativeWidgetAuraTest, CenterWindowSmallParent) {
     80   // Make a parent window smaller than the host represented by rootwindow.
     81   scoped_ptr<aura::Window> parent(new aura::Window(NULL));
     82   parent->Init(ui::LAYER_NOT_DRAWN);
     83   parent->SetBounds(gfx::Rect(0, 0, 480, 320));
     84   scoped_ptr<Widget> widget(new Widget());
     85   NativeWidgetAura* window = Init(parent.get(), widget.get());
     86 
     87   window->CenterWindow(gfx::Size(100, 100));
     88   EXPECT_EQ(gfx::Rect( (480 - 100) / 2,
     89                        (320 - 100) / 2,
     90                        100, 100),
     91             window->GetNativeWindow()->bounds());
     92   widget->CloseNow();
     93 }
     94 
     95 // Verifies CenterWindow() constrains to parent size.
     96 TEST_F(NativeWidgetAuraTest, CenterWindowSmallParentNotAtOrigin) {
     97   // Make a parent window smaller than the host represented by rootwindow and
     98   // offset it slightly from the origin.
     99   scoped_ptr<aura::Window> parent(new aura::Window(NULL));
    100   parent->Init(ui::LAYER_NOT_DRAWN);
    101   parent->SetBounds(gfx::Rect(20, 40, 480, 320));
    102   scoped_ptr<Widget> widget(new Widget());
    103   NativeWidgetAura* window = Init(parent.get(), widget.get());
    104   window->CenterWindow(gfx::Size(500, 600));
    105 
    106   // |window| should be no bigger than |parent|.
    107   EXPECT_EQ("20,40 480x320", window->GetNativeWindow()->bounds().ToString());
    108   widget->CloseNow();
    109 }
    110 
    111 // Used by ShowMaximizedDoesntBounceAround. See it for details.
    112 class TestLayoutManager : public aura::LayoutManager {
    113  public:
    114   TestLayoutManager() {}
    115 
    116   virtual void OnWindowResized() OVERRIDE {
    117   }
    118   virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE {
    119     // This simulates what happens when adding a maximized window.
    120     SetChildBoundsDirect(child, gfx::Rect(0, 0, 300, 300));
    121   }
    122   virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {
    123   }
    124   virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE {
    125   }
    126   virtual void OnChildWindowVisibilityChanged(aura::Window* child,
    127                                               bool visible) OVERRIDE {
    128   }
    129   virtual void SetChildBounds(aura::Window* child,
    130                               const gfx::Rect& requested_bounds) OVERRIDE {
    131   }
    132 
    133  private:
    134   DISALLOW_COPY_AND_ASSIGN(TestLayoutManager);
    135 };
    136 
    137 // This simulates BrowserView, which creates a custom RootView so that
    138 // OnNativeWidgetSizeChanged that is invoked during Init matters.
    139 class TestWidget : public views::Widget {
    140  public:
    141   TestWidget() : did_size_change_more_than_once_(false) {
    142   }
    143 
    144   // Returns true if the size changes to a non-empty size, and then to another
    145   // size.
    146   bool did_size_change_more_than_once() const {
    147     return did_size_change_more_than_once_;
    148   }
    149 
    150   virtual void OnNativeWidgetSizeChanged(const gfx::Size& new_size) OVERRIDE {
    151     if (last_size_.IsEmpty())
    152       last_size_ = new_size;
    153     else if (!did_size_change_more_than_once_ && new_size != last_size_)
    154       did_size_change_more_than_once_ = true;
    155     Widget::OnNativeWidgetSizeChanged(new_size);
    156   }
    157 
    158  private:
    159   bool did_size_change_more_than_once_;
    160   gfx::Size last_size_;
    161 
    162   DISALLOW_COPY_AND_ASSIGN(TestWidget);
    163 };
    164 
    165 // Verifies the size of the widget doesn't change more than once during Init if
    166 // the window ends up maximized. This is important as otherwise
    167 // RenderWidgetHostViewAura ends up getting resized during construction, which
    168 // leads to noticable flashes.
    169 TEST_F(NativeWidgetAuraTest, ShowMaximizedDoesntBounceAround) {
    170   root_window()->SetBounds(gfx::Rect(0, 0, 640, 480));
    171   root_window()->SetLayoutManager(new TestLayoutManager);
    172   scoped_ptr<TestWidget> widget(new TestWidget());
    173   Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
    174   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    175   params.parent = NULL;
    176   params.context = root_window();
    177   params.show_state = ui::SHOW_STATE_MAXIMIZED;
    178   params.bounds = gfx::Rect(10, 10, 100, 200);
    179   widget->Init(params);
    180   EXPECT_FALSE(widget->did_size_change_more_than_once());
    181   widget->CloseNow();
    182 }
    183 
    184 TEST_F(NativeWidgetAuraTest, GetClientAreaScreenBounds) {
    185   // Create a widget.
    186   Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
    187   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    188   params.context = root_window();
    189   params.bounds.SetRect(10, 20, 300, 400);
    190   scoped_ptr<Widget> widget(new Widget());
    191   widget->Init(params);
    192 
    193   // For Aura, client area bounds match window bounds.
    194   gfx::Rect client_bounds = widget->GetClientAreaBoundsInScreen();
    195   EXPECT_EQ(10, client_bounds.x());
    196   EXPECT_EQ(20, client_bounds.y());
    197   EXPECT_EQ(300, client_bounds.width());
    198   EXPECT_EQ(400, client_bounds.height());
    199 }
    200 
    201 namespace {
    202 
    203 // View subclass that tracks whether it has gotten a gesture event.
    204 class GestureTrackingView : public views::View {
    205  public:
    206   GestureTrackingView()
    207       : got_gesture_event_(false),
    208         consume_gesture_event_(true) {}
    209 
    210   void set_consume_gesture_event(bool value) {
    211     consume_gesture_event_ = value;
    212   }
    213 
    214   void clear_got_gesture_event() {
    215     got_gesture_event_ = false;
    216   }
    217   bool got_gesture_event() const {
    218     return got_gesture_event_;
    219   }
    220 
    221   // View overrides:
    222   virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
    223     got_gesture_event_ = true;
    224     if (consume_gesture_event_)
    225       event->StopPropagation();
    226   }
    227 
    228  private:
    229   // Was OnGestureEvent() invoked?
    230   bool got_gesture_event_;
    231 
    232   // Dictates what OnGestureEvent() returns.
    233   bool consume_gesture_event_;
    234 
    235   DISALLOW_COPY_AND_ASSIGN(GestureTrackingView);
    236 };
    237 
    238 }  // namespace
    239 
    240 // Verifies a capture isn't set on touch press and that the view that gets
    241 // the press gets the release.
    242 TEST_F(NativeWidgetAuraTest, DontCaptureOnGesture) {
    243   // Create two views (both sized the same). |child| is configured not to
    244   // consume the gesture event.
    245   GestureTrackingView* view = new GestureTrackingView();
    246   GestureTrackingView* child = new GestureTrackingView();
    247   child->set_consume_gesture_event(false);
    248   view->SetLayoutManager(new FillLayout);
    249   view->AddChildView(child);
    250   scoped_ptr<TestWidget> widget(new TestWidget());
    251   Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
    252   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    253   params.context = root_window();
    254   params.bounds = gfx::Rect(0, 0, 100, 200);
    255   widget->Init(params);
    256   widget->SetContentsView(view);
    257   widget->Show();
    258 
    259   ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(41, 51), 1,
    260                            base::TimeDelta());
    261   dispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press);
    262   // Both views should get the press.
    263   EXPECT_TRUE(view->got_gesture_event());
    264   EXPECT_TRUE(child->got_gesture_event());
    265   view->clear_got_gesture_event();
    266   child->clear_got_gesture_event();
    267   // Touch events should not automatically grab capture.
    268   EXPECT_FALSE(widget->HasCapture());
    269 
    270   // Release touch. Only |view| should get the release since that it consumed
    271   // the press.
    272   ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(250, 251), 1,
    273                              base::TimeDelta());
    274   dispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release);
    275   EXPECT_TRUE(view->got_gesture_event());
    276   EXPECT_FALSE(child->got_gesture_event());
    277   view->clear_got_gesture_event();
    278 
    279   // Work around for bug in NativeWidgetAura.
    280   // TODO: fix bug and remove this.
    281   widget->Close();
    282 }
    283 
    284 TEST_F(NativeWidgetAuraTest, ReleaseCaptureOnTouchRelease) {
    285   GestureTrackingView* view = new GestureTrackingView();
    286   scoped_ptr<TestWidget> widget(new TestWidget());
    287   Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
    288   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    289   params.context = root_window();
    290   params.bounds = gfx::Rect(0, 0, 100, 200);
    291   widget->Init(params);
    292   widget->SetContentsView(view);
    293   widget->Show();
    294 
    295   ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(41, 51), 1,
    296                            base::TimeDelta());
    297   dispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press);
    298   EXPECT_TRUE(view->got_gesture_event());
    299   view->clear_got_gesture_event();
    300   // Set the capture.
    301   widget->SetCapture(view);
    302   EXPECT_TRUE(widget->HasCapture());
    303 
    304   // Generate a release, this should trigger releasing capture.
    305   ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(41, 51), 1,
    306                              base::TimeDelta());
    307   dispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release);
    308   EXPECT_TRUE(view->got_gesture_event());
    309   view->clear_got_gesture_event();
    310   EXPECT_FALSE(widget->HasCapture());
    311 
    312   // Work around for bug in NativeWidgetAura.
    313   // TODO: fix bug and remove this.
    314   widget->Close();
    315 }
    316 
    317 // Verifies views with layers are targeted for events properly.
    318 TEST_F(NativeWidgetAuraTest, PreferViewLayersToChildWindows) {
    319   // Create two widgets: |parent| and |child|. |child| is a child of |parent|.
    320   views::View* parent_root = new views::View;
    321   scoped_ptr<Widget> parent(new Widget());
    322   Widget::InitParams parent_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
    323   parent_params.ownership =
    324       views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    325   parent_params.context = root_window();
    326   parent->Init(parent_params);
    327   parent->SetContentsView(parent_root);
    328   parent->SetBounds(gfx::Rect(0, 0, 400, 400));
    329   parent->Show();
    330 
    331   scoped_ptr<Widget> child(new Widget());
    332   Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL);
    333   child_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    334   child_params.parent = parent->GetNativeWindow();
    335   child->Init(child_params);
    336   child->SetBounds(gfx::Rect(0, 0, 200, 200));
    337   child->Show();
    338 
    339   // Point is over |child|.
    340   EXPECT_EQ(child->GetNativeWindow(),
    341             parent->GetNativeWindow()->GetEventHandlerForPoint(
    342                 gfx::Point(50, 50)));
    343 
    344   // Create a view with a layer and stack it at the bottom (below |child|).
    345   views::View* view_with_layer = new views::View;
    346   parent_root->AddChildView(view_with_layer);
    347   view_with_layer->SetBounds(0, 0, 50, 50);
    348   view_with_layer->SetPaintToLayer(true);
    349 
    350   // Make sure that |child| still gets the event.
    351   EXPECT_EQ(child->GetNativeWindow(),
    352             parent->GetNativeWindow()->GetEventHandlerForPoint(
    353                 gfx::Point(20, 20)));
    354 
    355   // Move |view_with_layer| to the top and make sure it gets the
    356   // event when the point is within |view_with_layer|'s bounds.
    357   view_with_layer->layer()->parent()->StackAtTop(
    358       view_with_layer->layer());
    359   EXPECT_EQ(parent->GetNativeWindow(),
    360             parent->GetNativeWindow()->GetEventHandlerForPoint(
    361                 gfx::Point(20, 20)));
    362 
    363   // Point is over |child|, it should get the event.
    364   EXPECT_EQ(child->GetNativeWindow(),
    365             parent->GetNativeWindow()->GetEventHandlerForPoint(
    366                 gfx::Point(70, 70)));
    367 
    368   delete view_with_layer;
    369   view_with_layer = NULL;
    370 
    371   EXPECT_EQ(child->GetNativeWindow(),
    372             parent->GetNativeWindow()->GetEventHandlerForPoint(
    373                 gfx::Point(20, 20)));
    374 
    375   // Work around for bug in NativeWidgetAura.
    376   // TODO: fix bug and remove this.
    377   parent->Close();
    378 }
    379 
    380 // Verifies that widget->FlashFrame() sets aura::client::kDrawAttentionKey,
    381 // and activating the window clears it.
    382 TEST_F(NativeWidgetAuraTest, FlashFrame) {
    383   scoped_ptr<Widget> widget(new Widget());
    384   Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
    385   params.context = root_window();
    386   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    387   widget->Init(params);
    388   aura::Window* window = widget->GetNativeWindow();
    389   EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
    390   widget->FlashFrame(true);
    391   EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
    392   widget->FlashFrame(false);
    393   EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
    394   widget->FlashFrame(true);
    395   EXPECT_TRUE(window->GetProperty(aura::client::kDrawAttentionKey));
    396   widget->Activate();
    397   EXPECT_FALSE(window->GetProperty(aura::client::kDrawAttentionKey));
    398 }
    399 
    400 TEST_F(NativeWidgetAuraTest, NoCrashOnThemeAfterClose) {
    401   scoped_ptr<aura::Window> parent(new aura::Window(NULL));
    402   parent->Init(ui::LAYER_NOT_DRAWN);
    403   parent->SetBounds(gfx::Rect(0, 0, 480, 320));
    404   scoped_ptr<Widget> widget(new Widget());
    405   NativeWidgetAura* window = Init(parent.get(), widget.get());
    406   window->Show();
    407   window->Close();
    408   base::MessageLoop::current()->RunUntilIdle();
    409   widget->GetNativeTheme();  // Shouldn't crash.
    410 }
    411 
    412 }  // namespace
    413 }  // namespace views
    414