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