immersive_mode_controller_ash_unittest.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright 2013 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 "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h" 6 7#include "ash/display/display_manager.h" 8#include "ash/shell.h" 9#include "ash/test/ash_test_base.h" 10#include "chrome/app/chrome_command_ids.h" 11#include "chrome/browser/ui/browser_commands.h" 12#include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 13#include "chrome/browser/ui/fullscreen/fullscreen_controller_test.h" 14#include "chrome/browser/ui/immersive_fullscreen_configuration.h" 15#include "chrome/browser/ui/views/frame/browser_view.h" 16#include "chrome/browser/ui/views/frame/test_with_browser_view.h" 17#include "chrome/browser/ui/views/frame/top_container_view.h" 18#include "chrome/browser/ui/views/tabs/tab_strip.h" 19#include "chrome/browser/ui/views/toolbar_view.h" 20#include "ui/aura/client/cursor_client.h" 21#include "ui/aura/env.h" 22#include "ui/aura/root_window.h" 23#include "ui/aura/test/event_generator.h" 24#include "ui/aura/window.h" 25#include "ui/gfx/animation/slide_animation.h" 26#include "ui/views/bubble/bubble_delegate.h" 27#include "ui/views/controls/webview/webview.h" 28 29// For now, immersive fullscreen is Chrome OS only. 30#if defined(OS_CHROMEOS) 31 32///////////////////////////////////////////////////////////////////////////// 33 34class MockImmersiveModeControllerDelegate 35 : public ImmersiveModeController::Delegate { 36 public: 37 MockImmersiveModeControllerDelegate() : immersive_style_(false) {} 38 virtual ~MockImmersiveModeControllerDelegate() {} 39 40 bool immersive_style() const { return immersive_style_; } 41 42 // ImmersiveModeController::Delegate overrides: 43 virtual BookmarkBarView* GetBookmarkBar() OVERRIDE { return NULL; } 44 virtual FullscreenController* GetFullscreenController() OVERRIDE { 45 return NULL; 46 } 47 virtual void FullscreenStateChanged() OVERRIDE {} 48 virtual void SetImmersiveStyle(bool immersive) OVERRIDE { 49 immersive_style_ = immersive; 50 } 51 virtual content::WebContents* GetWebContents() OVERRIDE { 52 return NULL; 53 } 54 55 private: 56 bool immersive_style_; 57 58 DISALLOW_COPY_AND_ASSIGN(MockImmersiveModeControllerDelegate); 59}; 60 61///////////////////////////////////////////////////////////////////////////// 62 63class ImmersiveModeControllerAshTest : public ash::test::AshTestBase { 64 public: 65 enum Modality { 66 MODALITY_MOUSE, 67 MODALITY_TOUCH, 68 MODALITY_GESTURE 69 }; 70 71 ImmersiveModeControllerAshTest() : widget_(NULL), top_container_(NULL) {} 72 virtual ~ImmersiveModeControllerAshTest() {} 73 74 ImmersiveModeControllerAsh* controller() { return controller_.get(); } 75 views::View* top_container() { return top_container_; } 76 MockImmersiveModeControllerDelegate* delegate() { return delegate_.get(); } 77 78 // Access to private data from the controller. 79 bool top_edge_hover_timer_running() const { 80 return controller_->top_edge_hover_timer_.IsRunning(); 81 } 82 int mouse_x_when_hit_top() const { 83 return controller_->mouse_x_when_hit_top_in_screen_; 84 } 85 86 // ash::test::AshTestBase overrides: 87 virtual void SetUp() OVERRIDE { 88 ash::test::AshTestBase::SetUp(); 89 90 ImmersiveFullscreenConfiguration::EnableImmersiveFullscreenForTest(); 91 ASSERT_TRUE(ImmersiveFullscreenConfiguration::UseImmersiveFullscreen()); 92 93 controller_.reset(new ImmersiveModeControllerAsh); 94 delegate_.reset(new MockImmersiveModeControllerDelegate); 95 96 widget_ = new views::Widget(); 97 views::Widget::InitParams params; 98 params.context = CurrentContext(); 99 params.bounds = gfx::Rect(0, 0, 500, 500); 100 widget_->Init(params); 101 widget_->Show(); 102 103 top_container_ = new views::View(); 104 top_container_->SetBounds(0, 0, 500, 100); 105 top_container_->set_focusable(true); 106 107 widget_->GetContentsView()->AddChildView(top_container_); 108 109 controller_->Init(delegate_.get(), widget_, top_container_); 110 SetAnimationsDisabled(true); 111 112 // The mouse is moved so that it is not over |top_container_| by 113 // AshTestBase. 114 } 115 116 // Enable or disable the immersive mode controller's animations. When the 117 // immersive mode controller's animations are disabled, some behavior is 118 // slightly different. In particular, the behavior is different when there 119 // is a transfer in which lock keeps the top-of-window views revealed (eg 120 // bubble keeps top-of-window views revealed -> mouse keeps top-of-window 121 // views revealed). It is necessary to temparily enable the immersive 122 // controller's animations to get the correct behavior in tests. 123 void SetAnimationsDisabled(bool disabled) { 124 controller_->animations_disabled_for_test_ = disabled; 125 // Force any in progress animations to finish. 126 if (disabled) 127 controller_->animation_->End(); 128 } 129 130 // Attempt to reveal the top-of-window views via |modality|. 131 // The top-of-window views can only be revealed via mouse hover or a gesture. 132 void AttemptReveal(Modality modality) { 133 ASSERT_NE(modality, MODALITY_TOUCH); 134 AttemptRevealStateChange(true, modality); 135 } 136 137 // Attempt to unreveal the top-of-window views via |modality|. The 138 // top-of-window views can be unrevealed via any modality. 139 void AttemptUnreveal(Modality modality) { 140 AttemptRevealStateChange(false, modality); 141 } 142 143 // Sets whether the mouse is hovered above |top_container_|. 144 // SetHovered(true) moves the mouse over the |top_container_| but does not 145 // move it to the top of the screen so will not initiate a reveal. 146 void SetHovered(bool is_mouse_hovered) { 147 MoveMouse(0, is_mouse_hovered ? 1 : top_container_->height() + 100); 148 } 149 150 // Move the mouse to the given coordinates. The coordinates should be in 151 // |top_container_| coordinates. 152 void MoveMouse(int x, int y) { 153 gfx::Point screen_position(x, y); 154 views::View::ConvertPointToScreen(top_container_, &screen_position); 155 GetEventGenerator().MoveMouseTo(screen_position.x(), screen_position.y()); 156 157 // If the top edge timer started running as a result of the mouse move, run 158 // the task which occurs after the timer delay. This reveals the 159 // top-of-window views synchronously if the mouse is hovered at the top of 160 // the screen. 161 if (controller()->top_edge_hover_timer_.IsRunning()) { 162 controller()->top_edge_hover_timer_.user_task().Run(); 163 controller()->top_edge_hover_timer_.Stop(); 164 } 165 } 166 167 private: 168 // Attempt to change the revealed state to |revealed| via |modality|. 169 void AttemptRevealStateChange(bool revealed, Modality modality) { 170 // Compute the event position in |top_container_| coordinates. 171 gfx::Point event_position(0, revealed ? 0 : top_container_->height() + 100); 172 switch (modality) { 173 case MODALITY_MOUSE: { 174 MoveMouse(event_position.x(), event_position.y()); 175 break; 176 } 177 case MODALITY_TOUCH: { 178 gfx::Point screen_position = event_position; 179 views::View::ConvertPointToScreen(top_container_, &screen_position); 180 181 aura::test::EventGenerator& event_generator(GetEventGenerator()); 182 event_generator.MoveTouch(event_position); 183 event_generator.PressTouch(); 184 event_generator.ReleaseTouch(); 185 break; 186 } 187 case MODALITY_GESTURE: { 188 aura::client::GetCursorClient(CurrentContext())->DisableMouseEvents(); 189 ImmersiveModeControllerAsh::SwipeType swipe_type = revealed ? 190 ImmersiveModeControllerAsh::SWIPE_OPEN : 191 ImmersiveModeControllerAsh::SWIPE_CLOSE; 192 controller_->UpdateRevealedLocksForSwipe(swipe_type); 193 break; 194 } 195 } 196 } 197 198 scoped_ptr<ImmersiveModeControllerAsh> controller_; 199 scoped_ptr<MockImmersiveModeControllerDelegate> delegate_; 200 views::Widget* widget_; // Owned by the native widget. 201 views::View* top_container_; // Owned by |root_view_|. 202 scoped_ptr<aura::test::EventGenerator> event_generator_; 203 204 DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerAshTest); 205}; 206 207// Test of initial state and basic functionality. 208TEST_F(ImmersiveModeControllerAshTest, ImmersiveModeControllerAsh) { 209 // Initial state. 210 EXPECT_FALSE(controller()->IsEnabled()); 211 EXPECT_FALSE(controller()->ShouldHideTopViews()); 212 EXPECT_FALSE(controller()->IsRevealed()); 213 EXPECT_FALSE(delegate()->immersive_style()); 214 215 // Enabling hides the top views. 216 controller()->SetEnabled(true); 217 EXPECT_TRUE(controller()->IsEnabled()); 218 EXPECT_FALSE(controller()->IsRevealed()); 219 EXPECT_TRUE(controller()->ShouldHideTopViews()); 220 EXPECT_FALSE(controller()->ShouldHideTabIndicators()); 221 EXPECT_TRUE(delegate()->immersive_style()); 222 223 // Revealing shows the top views. 224 AttemptReveal(MODALITY_MOUSE); 225 EXPECT_TRUE(controller()->IsRevealed()); 226 EXPECT_FALSE(controller()->ShouldHideTopViews()); 227 // Tabs are painting in the normal style during a reveal. 228 EXPECT_FALSE(delegate()->immersive_style()); 229 230 // Disabling immersive fullscreen keeps the top views shown. 231 controller()->SetEnabled(false); 232 EXPECT_FALSE(controller()->IsEnabled()); 233 EXPECT_FALSE(controller()->IsRevealed()); 234 EXPECT_FALSE(controller()->ShouldHideTopViews()); 235 EXPECT_FALSE(delegate()->immersive_style()); 236 237 // Test disabling immersive fullscreen when the top views are hidden. 238 controller()->SetEnabled(true); 239 EXPECT_TRUE(controller()->IsEnabled()); 240 EXPECT_FALSE(controller()->IsRevealed()); 241 EXPECT_TRUE(controller()->ShouldHideTopViews()); 242 EXPECT_TRUE(delegate()->immersive_style()); 243 244 controller()->SetEnabled(false); 245 EXPECT_FALSE(controller()->IsEnabled()); 246 EXPECT_FALSE(controller()->IsRevealed()); 247 EXPECT_FALSE(controller()->ShouldHideTopViews()); 248 EXPECT_FALSE(delegate()->immersive_style()); 249} 250 251// Test mouse event processing for top-of-screen reveal triggering. 252TEST_F(ImmersiveModeControllerAshTest, OnMouseEvent) { 253 // Set up initial state. 254 controller()->SetEnabled(true); 255 ASSERT_TRUE(controller()->IsEnabled()); 256 ASSERT_FALSE(controller()->IsRevealed()); 257 258 aura::test::EventGenerator& event_generator(GetEventGenerator()); 259 260 gfx::Rect top_container_bounds_in_screen = 261 top_container()->GetBoundsInScreen(); 262 // A position along the top edge of TopContainerView in screen coordinates. 263 gfx::Point top_edge_pos(top_container_bounds_in_screen.x() + 100, 264 top_container_bounds_in_screen.y()); 265 266 // Mouse wheel event does nothing. 267 ui::MouseEvent wheel( 268 ui::ET_MOUSEWHEEL, top_edge_pos, top_edge_pos, ui::EF_NONE); 269 event_generator.Dispatch(&wheel); 270 EXPECT_FALSE(top_edge_hover_timer_running()); 271 272 // Move to top edge of screen starts hover timer running. We cannot use 273 // MoveMouse() because MoveMouse() stops the timer if it started running. 274 event_generator.MoveMouseTo(top_edge_pos); 275 EXPECT_TRUE(top_edge_hover_timer_running()); 276 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 277 278 // Moving |ImmersiveModeControllerAsh::kMouseRevealBoundsHeight| down from 279 // the top edge stops it. 280 event_generator.MoveMouseBy(0, 3); 281 EXPECT_FALSE(top_edge_hover_timer_running()); 282 283 // Moving back to the top starts the timer again. 284 event_generator.MoveMouseTo(top_edge_pos); 285 EXPECT_TRUE(top_edge_hover_timer_running()); 286 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 287 288 // Slight move to the right keeps the timer running for the same hit point. 289 event_generator.MoveMouseBy(1, 0); 290 EXPECT_TRUE(top_edge_hover_timer_running()); 291 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 292 293 // Moving back to the left also keeps the timer running. 294 event_generator.MoveMouseBy(-1, 0); 295 EXPECT_TRUE(top_edge_hover_timer_running()); 296 EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top()); 297 298 // Large move right restarts the timer (so it is still running) and considers 299 // this a new hit at the top. 300 event_generator.MoveMouseTo(top_edge_pos.x() + 100, top_edge_pos.y()); 301 EXPECT_TRUE(top_edge_hover_timer_running()); 302 EXPECT_EQ(top_edge_pos.x() + 100, mouse_x_when_hit_top()); 303 304 // Moving off the top edge horizontally stops the timer. 305 EXPECT_GT(CurrentContext()->bounds().width(), top_container()->width()); 306 event_generator.MoveMouseTo(top_container_bounds_in_screen.right(), 307 top_container_bounds_in_screen.y()); 308 EXPECT_FALSE(top_edge_hover_timer_running()); 309 310 // Once revealed, a move just a little below the top container doesn't end a 311 // reveal. 312 AttemptReveal(MODALITY_MOUSE); 313 event_generator.MoveMouseTo(top_container_bounds_in_screen.x(), 314 top_container_bounds_in_screen.bottom() + 1); 315 EXPECT_TRUE(controller()->IsRevealed()); 316 317 // Once revealed, clicking just below the top container ends the reveal. 318 event_generator.ClickLeftButton(); 319 EXPECT_FALSE(controller()->IsRevealed()); 320 321 // Moving a lot below the top container ends a reveal. 322 AttemptReveal(MODALITY_MOUSE); 323 EXPECT_TRUE(controller()->IsRevealed()); 324 event_generator.MoveMouseTo(top_container_bounds_in_screen.x(), 325 top_container_bounds_in_screen.bottom() + 50); 326 EXPECT_FALSE(controller()->IsRevealed()); 327 328 // The mouse position cannot cause a reveal when TopContainerView's widget 329 // has capture. 330 views::Widget* widget = top_container()->GetWidget(); 331 widget->SetCapture(top_container()); 332 AttemptReveal(MODALITY_MOUSE); 333 EXPECT_FALSE(controller()->IsRevealed()); 334 widget->ReleaseCapture(); 335 336 // The mouse position cannot end the reveal while TopContainerView's widget 337 // has capture. 338 AttemptReveal(MODALITY_MOUSE); 339 EXPECT_TRUE(controller()->IsRevealed()); 340 widget->SetCapture(top_container()); 341 event_generator.MoveMouseTo(top_container_bounds_in_screen.x(), 342 top_container_bounds_in_screen.bottom() + 51); 343 EXPECT_TRUE(controller()->IsRevealed()); 344 345 // Releasing capture should end the reveal. 346 widget->ReleaseCapture(); 347 EXPECT_FALSE(controller()->IsRevealed()); 348} 349 350// Test mouse event processing for top-of-screen reveal triggering when the user 351// has a vertical display layout (primary display above/below secondary display) 352// and the immersive fullscreen window is on the bottom display. 353TEST_F(ImmersiveModeControllerAshTest, MouseEventsVerticalDisplayLayout) { 354 if (!SupportsMultipleDisplays()) 355 return; 356 357 // Set up initial state. 358 UpdateDisplay("800x600,800x600"); 359 ash::DisplayLayout display_layout(ash::DisplayLayout::TOP, 0); 360 ash::Shell::GetInstance()->display_manager()->SetLayoutForCurrentDisplays( 361 display_layout); 362 363 controller()->SetEnabled(true); 364 ASSERT_TRUE(controller()->IsEnabled()); 365 ASSERT_FALSE(controller()->IsRevealed()); 366 367 ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows(); 368 ASSERT_EQ(root_windows[0], 369 top_container()->GetWidget()->GetNativeWindow()->GetRootWindow()); 370 371 gfx::Rect primary_root_window_bounds_in_screen = 372 root_windows[0]->GetBoundsInScreen(); 373 // Do not set |x| to the root window's x position because the display's 374 // corners have special behavior. 375 int x = primary_root_window_bounds_in_screen.x() + 10; 376 // The y position of the top edge of the primary display. 377 int y_top_edge = primary_root_window_bounds_in_screen.y(); 378 379 aura::test::EventGenerator& event_generator(GetEventGenerator()); 380 381 // Moving right below the top edge starts the hover timer running. We 382 // cannot use MoveMouse() because MoveMouse() stops the timer if it started 383 // running. 384 event_generator.MoveMouseTo(x, y_top_edge + 1); 385 EXPECT_TRUE(top_edge_hover_timer_running()); 386 EXPECT_EQ(y_top_edge + 1, 387 aura::Env::GetInstance()->last_mouse_location().y()); 388 389 // The timer should continue running if the user moves the mouse to the top 390 // edge even though the mouse is warped to the secondary display. 391 event_generator.MoveMouseTo(x, y_top_edge); 392 EXPECT_TRUE(top_edge_hover_timer_running()); 393 EXPECT_NE(y_top_edge, 394 aura::Env::GetInstance()->last_mouse_location().y()); 395 396 // The timer should continue running if the user overshoots the top edge 397 // a bit. 398 event_generator.MoveMouseTo(x, y_top_edge - 2); 399 EXPECT_TRUE(top_edge_hover_timer_running()); 400 401 // The timer should stop running if the user overshoots the top edge by 402 // a lot. 403 event_generator.MoveMouseTo(x, y_top_edge - 20); 404 EXPECT_FALSE(top_edge_hover_timer_running()); 405 406 // The timer should not start if the user moves the mouse to the bottom of the 407 // secondary display without crossing the top edge first. 408 event_generator.MoveMouseTo(x, y_top_edge - 2); 409 410 // Reveal the top-of-window views by overshooting the top edge slightly. 411 event_generator.MoveMouseTo(x, y_top_edge + 1); 412 // MoveMouse() runs the timer task. 413 MoveMouse(x, y_top_edge - 2); 414 EXPECT_TRUE(controller()->IsRevealed()); 415 416 // The top-of-window views should stay revealed if the user moves the mouse 417 // around in the bottom region of the secondary display. 418 event_generator.MoveMouseTo(x + 10, y_top_edge - 3); 419 EXPECT_TRUE(controller()->IsRevealed()); 420 421 // The top-of-window views should hide if the user moves the mouse away from 422 // the bottom region of the secondary display. 423 event_generator.MoveMouseTo(x, y_top_edge - 20); 424 EXPECT_FALSE(controller()->IsRevealed()); 425} 426 427// Test that hovering the mouse over the find bar does not end a reveal. 428TEST_F(ImmersiveModeControllerAshTest, FindBar) { 429 // Set up initial state. 430 controller()->SetEnabled(true); 431 ASSERT_TRUE(controller()->IsEnabled()); 432 ASSERT_FALSE(controller()->IsRevealed()); 433 434 // Compute the find bar bounds relative to TopContainerView. The find 435 // bar is aligned with the bottom right of the TopContainerView. 436 gfx::Rect find_bar_bounds(top_container()->bounds().right() - 100, 437 top_container()->bounds().bottom(), 438 100, 439 50); 440 441 gfx::Point find_bar_position_in_screen = find_bar_bounds.origin(); 442 views::View::ConvertPointToScreen(top_container(), 443 &find_bar_position_in_screen); 444 gfx::Rect find_bar_bounds_in_screen(find_bar_position_in_screen, 445 find_bar_bounds.size()); 446 controller()->OnFindBarVisibleBoundsChanged(find_bar_bounds_in_screen); 447 448 // Moving the mouse over the find bar does not end the reveal. 449 gfx::Point over_find_bar(find_bar_bounds.x() + 25, find_bar_bounds.y() + 25); 450 AttemptReveal(MODALITY_MOUSE); 451 EXPECT_TRUE(controller()->IsRevealed()); 452 MoveMouse(over_find_bar.x(), over_find_bar.y()); 453 EXPECT_TRUE(controller()->IsRevealed()); 454 455 // Moving the mouse off of the find bar horizontally ends the reveal. 456 MoveMouse(find_bar_bounds.x() - 25, find_bar_bounds.y() + 25); 457 EXPECT_FALSE(controller()->IsRevealed()); 458 459 // Moving the mouse off of the find bar vertically ends the reveal. 460 AttemptReveal(MODALITY_MOUSE); 461 EXPECT_TRUE(controller()->IsRevealed()); 462 MoveMouse(find_bar_bounds.x() + 25, find_bar_bounds.bottom() + 25); 463 464 // Similar to the TopContainerView, moving the mouse slightly off vertically 465 // of the find bar does not end the reveal. 466 AttemptReveal(MODALITY_MOUSE); 467 MoveMouse(find_bar_bounds.x() + 25, find_bar_bounds.bottom() + 1); 468 EXPECT_TRUE(controller()->IsRevealed()); 469 470 // Similar to the TopContainerView, clicking the mouse even slightly off of 471 // the find bar ends the reveal. 472 GetEventGenerator().ClickLeftButton(); 473 EXPECT_FALSE(controller()->IsRevealed()); 474 475 // Set the find bar bounds to empty. Hovering over the position previously 476 // occupied by the find bar, |over_find_bar|, should end the reveal. 477 controller()->OnFindBarVisibleBoundsChanged(gfx::Rect()); 478 AttemptReveal(MODALITY_MOUSE); 479 MoveMouse(over_find_bar.x(), over_find_bar.y()); 480 EXPECT_FALSE(controller()->IsRevealed()); 481} 482 483// Test revealing the top-of-window views using one modality and ending 484// the reveal via another. For instance, initiating the reveal via a SWIPE_OPEN 485// edge gesture, switching to using the mouse and ending the reveal by moving 486// the mouse off of the top-of-window views. 487TEST_F(ImmersiveModeControllerAshTest, DifferentModalityEnterExit) { 488 controller()->SetEnabled(true); 489 EXPECT_TRUE(controller()->IsEnabled()); 490 EXPECT_FALSE(controller()->IsRevealed()); 491 492 // Initiate reveal via gesture, end reveal via mouse. 493 AttemptReveal(MODALITY_GESTURE); 494 EXPECT_TRUE(controller()->IsRevealed()); 495 MoveMouse(1, 1); 496 EXPECT_TRUE(controller()->IsRevealed()); 497 AttemptUnreveal(MODALITY_MOUSE); 498 EXPECT_FALSE(controller()->IsRevealed()); 499 500 // Initiate reveal via gesture, end reveal via touch. 501 AttemptReveal(MODALITY_GESTURE); 502 EXPECT_TRUE(controller()->IsRevealed()); 503 AttemptUnreveal(MODALITY_TOUCH); 504 EXPECT_FALSE(controller()->IsRevealed()); 505 506 // Initiate reveal via mouse, end reveal via gesture. 507 AttemptReveal(MODALITY_MOUSE); 508 EXPECT_TRUE(controller()->IsRevealed()); 509 AttemptUnreveal(MODALITY_GESTURE); 510 EXPECT_FALSE(controller()->IsRevealed()); 511 512 // Initiate reveal via mouse, end reveal via touch. 513 AttemptReveal(MODALITY_MOUSE); 514 EXPECT_TRUE(controller()->IsRevealed()); 515 AttemptUnreveal(MODALITY_TOUCH); 516 EXPECT_FALSE(controller()->IsRevealed()); 517} 518 519// Test when the SWIPE_CLOSE edge gesture closes the top-of-window views. 520TEST_F(ImmersiveModeControllerAshTest, EndRevealViaGesture) { 521 controller()->SetEnabled(true); 522 EXPECT_TRUE(controller()->IsEnabled()); 523 EXPECT_FALSE(controller()->IsRevealed()); 524 525 // A gesture should be able to close the top-of-window views when 526 // top-of-window views have focus. 527 AttemptReveal(MODALITY_MOUSE); 528 top_container()->RequestFocus(); 529 EXPECT_TRUE(controller()->IsRevealed()); 530 AttemptUnreveal(MODALITY_GESTURE); 531 EXPECT_FALSE(controller()->IsRevealed()); 532 top_container()->GetFocusManager()->ClearFocus(); 533 534 // If some other code is holding onto a lock, a gesture should not be able to 535 // end the reveal. 536 AttemptReveal(MODALITY_MOUSE); 537 scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock( 538 ImmersiveModeController::ANIMATE_REVEAL_NO)); 539 EXPECT_TRUE(controller()->IsRevealed()); 540 AttemptUnreveal(MODALITY_GESTURE); 541 EXPECT_TRUE(controller()->IsRevealed()); 542 lock.reset(); 543 EXPECT_FALSE(controller()->IsRevealed()); 544} 545 546// Do not test under windows because focus testing is not reliable on 547// Windows. (crbug.com/79493) 548#if !defined(OS_WIN) 549 550// Test how focus and activation affects whether the top-of-window views are 551// revealed. 552TEST_F(ImmersiveModeControllerAshTest, Focus) { 553 // Add views to the view hierarchy which we will focus and unfocus during the 554 // test. 555 views::View* child_view = new views::View(); 556 child_view->SetBounds(0, 0, 10, 10); 557 child_view->set_focusable(true); 558 top_container()->AddChildView(child_view); 559 views::View* unrelated_view = new views::View(); 560 unrelated_view->SetBounds(0, 100, 10, 10); 561 unrelated_view->set_focusable(true); 562 top_container()->parent()->AddChildView(unrelated_view); 563 views::FocusManager* focus_manager = 564 top_container()->GetWidget()->GetFocusManager(); 565 566 controller()->SetEnabled(true); 567 568 // 1) Test that the top-of-window views stay revealed as long as either a 569 // |child_view| has focus or the mouse is hovered above the top-of-window 570 // views. 571 AttemptReveal(MODALITY_MOUSE); 572 child_view->RequestFocus(); 573 focus_manager->ClearFocus(); 574 EXPECT_TRUE(controller()->IsRevealed()); 575 child_view->RequestFocus(); 576 SetHovered(false); 577 EXPECT_TRUE(controller()->IsRevealed()); 578 focus_manager->ClearFocus(); 579 EXPECT_FALSE(controller()->IsRevealed()); 580 581 // 2) Test that focusing |unrelated_view| hides the top-of-window views. 582 // Note: In this test we can cheat and trigger a reveal via focus because 583 // the top container does not hide when the top-of-window views are not 584 // revealed. 585 child_view->RequestFocus(); 586 EXPECT_TRUE(controller()->IsRevealed()); 587 unrelated_view->RequestFocus(); 588 EXPECT_FALSE(controller()->IsRevealed()); 589 590 // 3) Test that a loss of focus of |child_view| to |unrelated_view| 591 // while immersive mode is disabled is properly registered. 592 child_view->RequestFocus(); 593 EXPECT_TRUE(controller()->IsRevealed()); 594 controller()->SetEnabled(false); 595 EXPECT_FALSE(controller()->IsRevealed()); 596 unrelated_view->RequestFocus(); 597 controller()->SetEnabled(true); 598 EXPECT_FALSE(controller()->IsRevealed()); 599 600 // Repeat test but with a revealed lock acquired when immersive mode is 601 // disabled because the code path is different. 602 child_view->RequestFocus(); 603 EXPECT_TRUE(controller()->IsRevealed()); 604 controller()->SetEnabled(false); 605 scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock( 606 ImmersiveModeController::ANIMATE_REVEAL_NO)); 607 EXPECT_FALSE(controller()->IsRevealed()); 608 unrelated_view->RequestFocus(); 609 controller()->SetEnabled(true); 610 EXPECT_TRUE(controller()->IsRevealed()); 611 lock.reset(); 612 EXPECT_FALSE(controller()->IsRevealed()); 613} 614 615// Test how activation affects whether the top-of-window views are revealed. 616// The behavior when a bubble is activated is tested in 617// ImmersiveModeControllerAshTest.Bubbles. 618TEST_F(ImmersiveModeControllerAshTest, Activation) { 619 views::Widget* top_container_widget = top_container()->GetWidget(); 620 621 controller()->SetEnabled(true); 622 ASSERT_FALSE(controller()->IsRevealed()); 623 624 // 1) Test that a transient window which is not a bubble does not trigger a 625 // reveal but does keep the top-of-window views revealed if they are already 626 // revealed. 627 views::Widget::InitParams transient_params; 628 transient_params.ownership = 629 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 630 transient_params.parent = top_container_widget->GetNativeView(); 631 transient_params.bounds = gfx::Rect(0, 0, 100, 100); 632 scoped_ptr<views::Widget> transient_widget(new views::Widget()); 633 transient_widget->Init(transient_params); 634 transient_widget->Show(); 635 636 EXPECT_FALSE(controller()->IsRevealed()); 637 top_container_widget->Activate(); 638 AttemptReveal(MODALITY_MOUSE); 639 EXPECT_TRUE(controller()->IsRevealed()); 640 transient_widget->Activate(); 641 SetHovered(false); 642 EXPECT_TRUE(controller()->IsRevealed()); 643 transient_widget.reset(); 644 EXPECT_FALSE(controller()->IsRevealed()); 645 646 // 2) Test that activating a non-transient window ends the reveal if any. 647 views::Widget::InitParams non_transient_params; 648 non_transient_params.ownership = 649 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 650 non_transient_params.context = top_container_widget->GetNativeView(); 651 non_transient_params.bounds = gfx::Rect(0, 0, 100, 100); 652 scoped_ptr<views::Widget> non_transient_widget(new views::Widget()); 653 non_transient_widget->Init(non_transient_params); 654 non_transient_widget->Show(); 655 656 EXPECT_FALSE(controller()->IsRevealed()); 657 top_container_widget->Activate(); 658 AttemptReveal(MODALITY_MOUSE); 659 EXPECT_TRUE(controller()->IsRevealed()); 660 non_transient_widget->Activate(); 661 EXPECT_FALSE(controller()->IsRevealed()); 662} 663 664// Test how bubbles affect whether the top-of-window views are revealed. 665TEST_F(ImmersiveModeControllerAshTest, Bubbles) { 666 scoped_ptr<ImmersiveRevealedLock> revealed_lock; 667 views::Widget* top_container_widget = top_container()->GetWidget(); 668 669 // Add views to the view hierarchy to which we will anchor bubbles. 670 views::View* child_view = new views::View(); 671 child_view->SetBounds(0, 0, 10, 10); 672 top_container()->AddChildView(child_view); 673 views::View* unrelated_view = new views::View(); 674 unrelated_view->SetBounds(0, 100, 10, 10); 675 top_container()->parent()->AddChildView(unrelated_view); 676 677 controller()->SetEnabled(true); 678 ASSERT_FALSE(controller()->IsRevealed()); 679 680 // 1) Test that a bubble anchored to a child of the top container triggers 681 // a reveal and keeps the top-of-window views revealed for the duration of 682 // its visibility. 683 views::Widget* bubble_widget1(views::BubbleDelegateView::CreateBubble( 684 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE))); 685 bubble_widget1->Show(); 686 EXPECT_TRUE(controller()->IsRevealed()); 687 688 // Activating |top_container_widget| will close |bubble_widget1|. 689 top_container_widget->Activate(); 690 AttemptReveal(MODALITY_MOUSE); 691 revealed_lock.reset(controller()->GetRevealedLock( 692 ImmersiveModeController::ANIMATE_REVEAL_NO)); 693 EXPECT_TRUE(controller()->IsRevealed()); 694 695 views::Widget* bubble_widget2 = views::BubbleDelegateView::CreateBubble( 696 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)); 697 bubble_widget2->Show(); 698 EXPECT_TRUE(controller()->IsRevealed()); 699 revealed_lock.reset(); 700 SetHovered(false); 701 EXPECT_TRUE(controller()->IsRevealed()); 702 bubble_widget2->Close(); 703 EXPECT_FALSE(controller()->IsRevealed()); 704 705 // 2) Test that transitioning from keeping the top-of-window views revealed 706 // because of a bubble to keeping the top-of-window views revealed because of 707 // mouse hover by activating |top_container_widget| works. 708 views::Widget* bubble_widget3 = views::BubbleDelegateView::CreateBubble( 709 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)); 710 bubble_widget3->Show(); 711 SetHovered(true); 712 EXPECT_TRUE(controller()->IsRevealed()); 713 714 SetAnimationsDisabled(false); 715 // Activating |top_container_widget| will close |bubble_widget3|. 716 top_container_widget->Activate(); 717 SetAnimationsDisabled(true); 718 EXPECT_TRUE(controller()->IsRevealed()); 719 720 // 3) Test that the top-of-window views stay revealed as long as at least one 721 // bubble anchored to a child of the top container is visible. 722 SetHovered(false); 723 EXPECT_FALSE(controller()->IsRevealed()); 724 725 views::BubbleDelegateView* bubble_delegate4(new views::BubbleDelegateView( 726 child_view, views::BubbleBorder::NONE)); 727 bubble_delegate4->set_use_focusless(true); 728 views::Widget* bubble_widget4(views::BubbleDelegateView::CreateBubble( 729 bubble_delegate4)); 730 bubble_widget4->Show(); 731 732 views::BubbleDelegateView* bubble_delegate5(new views::BubbleDelegateView( 733 child_view, views::BubbleBorder::NONE)); 734 bubble_delegate5->set_use_focusless(true); 735 views::Widget* bubble_widget5(views::BubbleDelegateView::CreateBubble( 736 bubble_delegate5)); 737 bubble_widget5->Show(); 738 739 EXPECT_TRUE(controller()->IsRevealed()); 740 bubble_widget4->Hide(); 741 EXPECT_TRUE(controller()->IsRevealed()); 742 bubble_widget5->Hide(); 743 EXPECT_FALSE(controller()->IsRevealed()); 744 bubble_widget5->Show(); 745 EXPECT_TRUE(controller()->IsRevealed()); 746 747 // 4) Test that visibility changes which occur while immersive fullscreen is 748 // disabled are handled upon reenabling immersive fullscreen. 749 controller()->SetEnabled(false); 750 bubble_widget5->Hide(); 751 controller()->SetEnabled(true); 752 EXPECT_FALSE(controller()->IsRevealed()); 753 754 // We do not need |bubble_widget4| or |bubble_widget5| anymore, close them. 755 bubble_widget4->Close(); 756 bubble_widget5->Close(); 757 758 // 5) Test that a bubble added while immersive fullscreen is disabled is 759 // handled upon reenabling immersive fullscreen. 760 controller()->SetEnabled(false); 761 762 views::Widget* bubble_widget6 = views::BubbleDelegateView::CreateBubble( 763 new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)); 764 bubble_widget6->Show(); 765 766 controller()->SetEnabled(true); 767 EXPECT_TRUE(controller()->IsRevealed()); 768 769 bubble_widget6->Close(); 770 771 // 6) Test that a bubble which is not anchored to a child of the 772 // TopContainerView does not trigger a reveal or keep the 773 // top-of-window views revealed if they are already revealed. 774 views::Widget* bubble_widget7 = views::BubbleDelegateView::CreateBubble( 775 new views::BubbleDelegateView(unrelated_view, views::BubbleBorder::NONE)); 776 bubble_widget7->Show(); 777 EXPECT_FALSE(controller()->IsRevealed()); 778 779 // Activating |top_container_widget| will close |bubble_widget6|. 780 top_container_widget->Activate(); 781 AttemptReveal(MODALITY_MOUSE); 782 EXPECT_TRUE(controller()->IsRevealed()); 783 784 views::Widget* bubble_widget8 = views::BubbleDelegateView::CreateBubble( 785 new views::BubbleDelegateView(unrelated_view, views::BubbleBorder::NONE)); 786 bubble_widget8->Show(); 787 SetHovered(false); 788 EXPECT_FALSE(controller()->IsRevealed()); 789 bubble_widget8->Close(); 790} 791 792#endif // defined(OS_WIN) 793 794class ImmersiveModeControllerAshTestWithBrowserView 795 : public TestWithBrowserView { 796 public: 797 ImmersiveModeControllerAshTestWithBrowserView() {} 798 virtual ~ImmersiveModeControllerAshTestWithBrowserView() {} 799 800 // TestWithBrowserView override: 801 virtual void SetUp() OVERRIDE { 802 TestWithBrowserView::SetUp(); 803 804 browser()->window()->Show(); 805 806 ImmersiveFullscreenConfiguration::EnableImmersiveFullscreenForTest(); 807 ASSERT_TRUE(ImmersiveFullscreenConfiguration::UseImmersiveFullscreen()); 808 809 controller_ = static_cast<ImmersiveModeControllerAsh*>( 810 browser_view()->immersive_mode_controller()); 811 controller_->DisableAnimationsForTest(); 812 813 // Move the mouse so that it is not over the top-of-window views. The mouse 814 // position matters because entering immersive fullscreen causes synthesized 815 // mouse moves. (If the mouse is at the very top of the screen when entering 816 // immersive fullscreen, the top-of-window views will hide, then reveal as 817 // a result of the synthesized mouse moves). 818 controller()->SetMouseHoveredForTest(false); 819 } 820 821 // Returns the bounds of |view| in widget coordinates. 822 gfx::Rect GetBoundsInWidget(views::View* view) { 823 return view->ConvertRectToWidget(view->GetLocalBounds()); 824 } 825 826 ImmersiveModeControllerAsh* controller() { return controller_; } 827 828 private: 829 // Not owned. 830 ImmersiveModeControllerAsh* controller_; 831 832 DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerAshTestWithBrowserView); 833}; 834 835// Test the layout and visibility of the tabstrip, toolbar and TopContainerView 836// in immersive fullscreen. 837TEST_F(ImmersiveModeControllerAshTestWithBrowserView, Layout) { 838 AddTab(browser(), GURL("about:blank")); 839 840 TabStrip* tabstrip = browser_view()->tabstrip(); 841 ToolbarView* toolbar = browser_view()->toolbar(); 842 views::WebView* contents_web_view = 843 browser_view()->GetContentsWebViewForTest(); 844 845 // Immersive fullscreen starts out disabled. 846 ASSERT_FALSE(browser_view()->GetWidget()->IsFullscreen()); 847 ASSERT_FALSE(controller()->IsEnabled()); 848 849 // By default, the tabstrip and toolbar should be visible. 850 EXPECT_TRUE(tabstrip->visible()); 851 EXPECT_TRUE(toolbar->visible()); 852 853 // Enter immersive fullscreen. 854 { 855 // NOTIFICATION_FULLSCREEN_CHANGED is sent asynchronously. 856 scoped_ptr<FullscreenNotificationObserver> waiter( 857 new FullscreenNotificationObserver()); 858 chrome::ToggleFullscreenMode(browser()); 859 waiter->Wait(); 860 } 861 EXPECT_TRUE(browser_view()->GetWidget()->IsFullscreen()); 862 EXPECT_TRUE(controller()->IsEnabled()); 863 EXPECT_FALSE(controller()->IsRevealed()); 864 865 // Entering immersive fullscreen should make the tab strip use the immersive 866 // style and hide the toolbar. 867 EXPECT_TRUE(tabstrip->visible()); 868 EXPECT_TRUE(tabstrip->IsImmersiveStyle()); 869 EXPECT_FALSE(toolbar->visible()); 870 871 // The tab indicators should be flush with the top of the widget. 872 EXPECT_EQ(0, GetBoundsInWidget(tabstrip).y()); 873 874 // The web contents should be immediately below the tab indicators. 875 EXPECT_EQ(Tab::GetImmersiveHeight(), 876 GetBoundsInWidget(contents_web_view).y()); 877 878 // Revealing the top-of-window views should set the tab strip back to the 879 // normal style and show the toolbar. 880 controller()->StartRevealForTest(true); 881 EXPECT_TRUE(controller()->IsRevealed()); 882 EXPECT_TRUE(tabstrip->visible()); 883 EXPECT_FALSE(tabstrip->IsImmersiveStyle()); 884 EXPECT_TRUE(toolbar->visible()); 885 886 // The TopContainerView should be flush with the top edge of the widget. If 887 // it is not flush with the top edge the immersive reveal animation looks 888 // wonky. 889 EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).y()); 890 891 // The web contents should be at the same y position as they were when the 892 // top-of-window views were hidden. 893 EXPECT_EQ(Tab::GetImmersiveHeight(), 894 GetBoundsInWidget(contents_web_view).y()); 895 896 // Repeat the test for when in both immersive fullscreen and tab fullscreen. 897 { 898 scoped_ptr<FullscreenNotificationObserver> waiter( 899 new FullscreenNotificationObserver()); 900 browser()->fullscreen_controller()->ToggleFullscreenModeForTab( 901 contents_web_view->GetWebContents(), true); 902 waiter->Wait(); 903 } 904 // Hide and reveal the top-of-window views so that they get relain out. 905 controller()->SetMouseHoveredForTest(false); 906 controller()->StartRevealForTest(true); 907 908 // The tab strip and toolbar should still be visible and the TopContainerView 909 // should still be flush with the top edge of the widget. 910 EXPECT_TRUE(controller()->IsRevealed()); 911 EXPECT_TRUE(tabstrip->visible()); 912 EXPECT_FALSE(tabstrip->IsImmersiveStyle()); 913 EXPECT_TRUE(toolbar->visible()); 914 EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).y()); 915 916 // The web contents should be flush with the top edge of the widget when in 917 // both immersive and tab fullscreen. 918 EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y()); 919 920 // Hide the top-of-window views. Both the tab strip and the toolbar should 921 // hide when in both immersive and tab fullscreen. 922 controller()->SetMouseHoveredForTest(false); 923 EXPECT_FALSE(controller()->IsRevealed()); 924 EXPECT_FALSE(tabstrip->visible()); 925 EXPECT_FALSE(toolbar->visible()); 926 927 // The web contents should still be flush with the edge of the widget. 928 EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y()); 929 930 // Exiting both immersive and tab fullscreen should show the tab strip and 931 // toolbar. 932 { 933 scoped_ptr<FullscreenNotificationObserver> waiter( 934 new FullscreenNotificationObserver()); 935 chrome::ToggleFullscreenMode(browser()); 936 waiter->Wait(); 937 } 938 EXPECT_FALSE(browser_view()->GetWidget()->IsFullscreen()); 939 EXPECT_FALSE(controller()->IsEnabled()); 940 EXPECT_FALSE(controller()->IsRevealed()); 941 EXPECT_TRUE(tabstrip->visible()); 942 EXPECT_FALSE(tabstrip->IsImmersiveStyle()); 943 EXPECT_TRUE(toolbar->visible()); 944} 945 946// Test that the browser commands which are usually disabled in fullscreen are 947// are enabled in immersive fullscreen. 948TEST_F(ImmersiveModeControllerAshTestWithBrowserView, EnabledCommands) { 949 ASSERT_FALSE(controller()->IsEnabled()); 950 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL)); 951 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT)); 952 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FOCUS_LOCATION)); 953 954 chrome::ToggleFullscreenMode(browser()); 955 EXPECT_TRUE(controller()->IsEnabled()); 956 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL)); 957 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT)); 958 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FOCUS_LOCATION)); 959} 960 961// Test that restoring a window properly exits immersive fullscreen. 962TEST_F(ImmersiveModeControllerAshTestWithBrowserView, ExitUponRestore) { 963 ASSERT_FALSE(controller()->IsEnabled()); 964 chrome::ToggleFullscreenMode(browser()); 965 controller()->StartRevealForTest(true); 966 ASSERT_TRUE(controller()->IsEnabled()); 967 ASSERT_TRUE(controller()->IsRevealed()); 968 ASSERT_TRUE(browser_view()->GetWidget()->IsFullscreen()); 969 970 browser_view()->GetWidget()->Restore(); 971 // Exiting immersive fullscreen occurs as a result of a task posted to the 972 // message loop. 973 content::RunAllPendingInMessageLoop(); 974 975 EXPECT_FALSE(controller()->IsEnabled()); 976} 977 978#endif // defined(OS_CHROMEOS) 979