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