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