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 26 namespace views { 27 namespace { 28 29 NativeWidgetAura* 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 37 class 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 53 TEST_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 70 TEST_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. 88 TEST_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 103 class 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. 123 class 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. 140 class 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. 170 TEST_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 185 class 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 205 class 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. 237 TEST_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 252 TEST_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. 270 class 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. 306 TEST_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. 352 TEST_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. 416 TEST_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 434 TEST_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(). 447 class 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. 468 TEST_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