1 // Copyright 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 "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h" 6 7 #include "ash/ash_switches.h" 8 #include "ash/root_window_controller.h" 9 #include "ash/shelf/shelf_layout_manager.h" 10 #include "ash/shelf/shelf_types.h" 11 #include "ash/shell.h" 12 #include "ash/wm/window_properties.h" 13 #include "ash/wm/window_util.h" 14 #include "base/command_line.h" 15 #include "chrome/app/chrome_command_ids.h" 16 #include "chrome/browser/ui/browser_commands.h" 17 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 18 #include "chrome/browser/ui/fullscreen/fullscreen_controller_test.h" 19 #include "chrome/browser/ui/immersive_fullscreen_configuration.h" 20 #include "chrome/browser/ui/views/frame/browser_view.h" 21 #include "chrome/browser/ui/views/frame/top_container_view.h" 22 #include "chrome/browser/ui/views/tabs/tab.h" 23 #include "chrome/browser/ui/views/tabs/tab_strip.h" 24 #include "chrome/test/base/in_process_browser_test.h" 25 #include "chrome/test/base/ui_test_utils.h" 26 #include "content/public/browser/web_contents.h" 27 #include "content/public/browser/web_contents_view.h" 28 #include "content/public/test/test_utils.h" 29 #include "ui/aura/client/screen_position_client.h" 30 #include "ui/aura/env.h" 31 #include "ui/aura/root_window.h" 32 #include "ui/base/events/event.h" 33 #include "ui/compositor/layer_animator.h" 34 #include "ui/compositor/scoped_animation_duration_scale_mode.h" 35 #include "ui/gfx/rect.h" 36 #include "ui/views/view.h" 37 38 using ui::ScopedAnimationDurationScaleMode; 39 40 namespace { 41 42 // Returns the bounds of |view| in widget coordinates. 43 gfx::Rect GetRectInWidget(views::View* view) { 44 return view->ConvertRectToWidget(view->GetLocalBounds()); 45 } 46 47 } // namespace 48 49 // TODO(jamescook): If immersive mode becomes popular on CrOS, consider porting 50 // it to other Aura platforms (win_aura, linux_aura). http://crbug.com/163931 51 #if defined(OS_CHROMEOS) 52 53 class ImmersiveModeControllerAshTest : public InProcessBrowserTest { 54 public: 55 ImmersiveModeControllerAshTest() : browser_view_(NULL), controller_(NULL) {} 56 virtual ~ImmersiveModeControllerAshTest() {} 57 58 BrowserView* browser_view() { return browser_view_; } 59 ImmersiveModeControllerAsh* controller() { return controller_; } 60 61 // content::BrowserTestBase overrides: 62 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 63 ImmersiveFullscreenConfiguration::EnableImmersiveFullscreenForTest(); 64 } 65 66 virtual void SetUpOnMainThread() OVERRIDE { 67 ASSERT_TRUE(ImmersiveFullscreenConfiguration::UseImmersiveFullscreen()); 68 browser_view_ = static_cast<BrowserView*>(browser()->window()); 69 controller_ = static_cast<ImmersiveModeControllerAsh*>( 70 browser_view_->immersive_mode_controller()); 71 controller_->DisableAnimationsForTest(); 72 zero_duration_mode_.reset(new ScopedAnimationDurationScaleMode( 73 ScopedAnimationDurationScaleMode::ZERO_DURATION)); 74 } 75 76 virtual void CleanUpOnMainThread() OVERRIDE { 77 zero_duration_mode_.reset(); 78 } 79 80 private: 81 BrowserView* browser_view_; 82 ImmersiveModeControllerAsh* controller_; 83 scoped_ptr<ScopedAnimationDurationScaleMode> zero_duration_mode_; 84 85 DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerAshTest); 86 }; 87 88 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshTest, ImmersiveMode) { 89 views::View* contents_view = browser_view()->GetTabContentsContainerView(); 90 91 // Immersive mode is not on by default. 92 EXPECT_FALSE(controller()->IsEnabled()); 93 EXPECT_FALSE(controller()->ShouldHideTopViews()); 94 95 // Top-of-window views are visible. 96 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 97 EXPECT_TRUE(browser_view()->IsToolbarVisible()); 98 99 // Usual commands are enabled. 100 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL)); 101 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT)); 102 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FULLSCREEN)); 103 104 // Turning immersive mode on sets the toolbar to immersive style and hides 105 // the top-of-window views while leaving the tab strip visible. 106 chrome::ToggleFullscreenMode(browser()); 107 ASSERT_TRUE(browser_view()->IsFullscreen()); 108 EXPECT_TRUE(controller()->IsEnabled()); 109 EXPECT_TRUE(controller()->ShouldHideTopViews()); 110 EXPECT_FALSE(controller()->IsRevealed()); 111 EXPECT_TRUE(browser_view()->tabstrip()->IsImmersiveStyle()); 112 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 113 EXPECT_FALSE(browser_view()->IsToolbarVisible()); 114 // Content area is immediately below the tab indicators. 115 EXPECT_EQ(GetRectInWidget(browser_view()).y() + Tab::GetImmersiveHeight(), 116 GetRectInWidget(contents_view).y()); 117 118 // Commands are still enabled (usually fullscreen disables these). 119 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL)); 120 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT)); 121 EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FULLSCREEN)); 122 123 // Trigger a reveal keeps us in immersive mode, but top-of-window views 124 // become visible. 125 controller()->StartRevealForTest(true); 126 EXPECT_TRUE(controller()->IsEnabled()); 127 EXPECT_FALSE(controller()->ShouldHideTopViews()); 128 EXPECT_TRUE(controller()->IsRevealed()); 129 EXPECT_FALSE(browser_view()->tabstrip()->IsImmersiveStyle()); 130 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 131 EXPECT_TRUE(browser_view()->IsToolbarVisible()); 132 // Shelf hide triggered by enabling immersive mode eventually changes the 133 // widget bounds and causes a Layout(). Force it to happen early for test. 134 browser_view()->parent()->Layout(); 135 // Content area is still immediately below the tab indicators. 136 EXPECT_EQ(GetRectInWidget(browser_view()).y() + Tab::GetImmersiveHeight(), 137 GetRectInWidget(contents_view).y()); 138 139 // End reveal by moving the mouse off the top-of-window views. We 140 // should stay in immersive mode, but the toolbar should go invisible. 141 controller()->SetMouseHoveredForTest(false); 142 EXPECT_TRUE(controller()->IsEnabled()); 143 EXPECT_TRUE(controller()->ShouldHideTopViews()); 144 EXPECT_FALSE(controller()->IsRevealed()); 145 EXPECT_TRUE(browser_view()->tabstrip()->IsImmersiveStyle()); 146 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 147 EXPECT_FALSE(browser_view()->IsToolbarVisible()); 148 149 // Disabling immersive mode puts us back to the beginning. 150 chrome::ToggleFullscreenMode(browser()); 151 ASSERT_FALSE(browser_view()->IsFullscreen()); 152 EXPECT_FALSE(controller()->IsEnabled()); 153 EXPECT_FALSE(controller()->ShouldHideTopViews()); 154 EXPECT_FALSE(controller()->IsRevealed()); 155 EXPECT_FALSE(browser_view()->tabstrip()->IsImmersiveStyle()); 156 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 157 EXPECT_TRUE(browser_view()->IsToolbarVisible()); 158 159 // Disabling immersive mode while we are revealed should take us back to 160 // the beginning. 161 chrome::ToggleFullscreenMode(browser()); 162 ASSERT_TRUE(browser_view()->IsFullscreen()); 163 EXPECT_TRUE(controller()->IsEnabled()); 164 controller()->StartRevealForTest(true); 165 166 chrome::ToggleFullscreenMode(browser()); 167 ASSERT_FALSE(browser_view()->IsFullscreen()); 168 EXPECT_FALSE(controller()->IsEnabled()); 169 EXPECT_FALSE(controller()->ShouldHideTopViews()); 170 EXPECT_FALSE(controller()->IsRevealed()); 171 EXPECT_FALSE(browser_view()->tabstrip()->IsImmersiveStyle()); 172 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 173 EXPECT_TRUE(browser_view()->IsToolbarVisible()); 174 175 // When hiding the tab indicators, content is at the top of the browser view 176 // both before and during reveal. 177 controller()->SetForceHideTabIndicatorsForTest(true); 178 chrome::ToggleFullscreenMode(browser()); 179 ASSERT_TRUE(browser_view()->IsFullscreen()); 180 EXPECT_FALSE(browser_view()->IsTabStripVisible()); 181 EXPECT_EQ(GetRectInWidget(browser_view()).y(), 182 GetRectInWidget(contents_view).y()); 183 controller()->StartRevealForTest(true); 184 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 185 // Shelf hide triggered by enabling immersive mode eventually changes the 186 // widget bounds and causes a Layout(). Force it to happen early for test. 187 browser_view()->parent()->Layout(); 188 EXPECT_EQ(GetRectInWidget(browser_view()).y(), 189 GetRectInWidget(contents_view).y()); 190 chrome::ToggleFullscreenMode(browser()); 191 ASSERT_FALSE(browser_view()->IsFullscreen()); 192 controller()->SetForceHideTabIndicatorsForTest(false); 193 194 // Reveal ends when the mouse moves out of the reveal view. 195 chrome::ToggleFullscreenMode(browser()); 196 ASSERT_TRUE(browser_view()->IsFullscreen()); 197 EXPECT_TRUE(controller()->IsEnabled()); 198 controller()->StartRevealForTest(true); 199 controller()->SetMouseHoveredForTest(false); 200 EXPECT_FALSE(controller()->IsRevealed()); 201 202 // Restoring the window exits immersive mode. 203 browser_view()->GetWidget()->Restore(); 204 // Exiting immersive fullscreen occurs as a result of a task posted to the 205 // message loop. 206 content::RunAllPendingInMessageLoop(); 207 ASSERT_FALSE(browser_view()->IsFullscreen()); 208 EXPECT_FALSE(controller()->IsEnabled()); 209 EXPECT_FALSE(controller()->ShouldHideTopViews()); 210 EXPECT_FALSE(controller()->IsRevealed()); 211 EXPECT_FALSE(browser_view()->tabstrip()->IsImmersiveStyle()); 212 EXPECT_TRUE(browser_view()->IsTabStripVisible()); 213 EXPECT_TRUE(browser_view()->IsToolbarVisible()); 214 215 // Don't crash if we exit the test during a reveal. 216 if (!browser_view()->IsFullscreen()) 217 chrome::ToggleFullscreenMode(browser()); 218 ASSERT_TRUE(browser_view()->IsFullscreen()); 219 ASSERT_TRUE(controller()->IsEnabled()); 220 controller()->StartRevealForTest(true); 221 } 222 223 // Test behavior when the mouse becomes hovered without moving. 224 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshTest, 225 MouseHoveredWithoutMoving) { 226 chrome::ToggleFullscreenMode(browser()); 227 ASSERT_TRUE(controller()->IsEnabled()); 228 229 scoped_ptr<ImmersiveRevealedLock> lock; 230 231 // 1) Test that if the mouse becomes hovered without the mouse moving due to a 232 // lock causing the top-of-window views to be revealed (and the mouse 233 // happening to be near the top of the screen), the top-of-window views do not 234 // hide till the mouse moves off of the top-of-window views. 235 controller()->SetMouseHoveredForTest(true); 236 EXPECT_FALSE(controller()->IsRevealed()); 237 lock.reset(controller()->GetRevealedLock( 238 ImmersiveModeController::ANIMATE_REVEAL_NO)); 239 EXPECT_TRUE(controller()->IsRevealed()); 240 lock.reset(); 241 EXPECT_TRUE(controller()->IsRevealed()); 242 controller()->SetMouseHoveredForTest(false); 243 EXPECT_FALSE(controller()->IsRevealed()); 244 245 // 2) Test that if the mouse becomes hovered without moving because of a 246 // reveal in ImmersiveModeController::SetEnabled(true) and there are no locks 247 // keeping the top-of-window views revealed, that mouse hover does not prevent 248 // the top-of-window views from closing. 249 chrome::ToggleFullscreenMode(browser()); 250 controller()->SetMouseHoveredForTest(true); 251 EXPECT_FALSE(controller()->IsRevealed()); 252 chrome::ToggleFullscreenMode(browser()); 253 EXPECT_FALSE(controller()->IsRevealed()); 254 255 // 3) Test that if the mouse becomes hovered without moving because of a 256 // reveal in ImmersiveModeController::SetEnabled(true) and there is a lock 257 // keeping the top-of-window views revealed, that the top-of-window views do 258 // not hide till the mouse moves off of the top-of-window views. 259 chrome::ToggleFullscreenMode(browser()); 260 controller()->SetMouseHoveredForTest(true); 261 lock.reset(controller()->GetRevealedLock( 262 ImmersiveModeController::ANIMATE_REVEAL_NO)); 263 EXPECT_FALSE(controller()->IsRevealed()); 264 chrome::ToggleFullscreenMode(browser()); 265 EXPECT_TRUE(controller()->IsRevealed()); 266 lock.reset(); 267 EXPECT_TRUE(controller()->IsRevealed()); 268 controller()->SetMouseHoveredForTest(false); 269 EXPECT_FALSE(controller()->IsRevealed()); 270 } 271 272 // GetRevealedLock() specific tests. 273 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshTest, RevealedLock) { 274 scoped_ptr<ImmersiveRevealedLock> lock1; 275 scoped_ptr<ImmersiveRevealedLock> lock2; 276 277 // Immersive mode is not on by default. 278 EXPECT_FALSE(controller()->IsEnabled()); 279 280 // Move the mouse out of the way. 281 controller()->SetMouseHoveredForTest(false); 282 283 // 1) Test acquiring and releasing a revealed state lock while immersive mode 284 // is disabled. Acquiring or releasing the lock should have no effect till 285 // immersive mode is enabled. 286 lock1.reset(controller()->GetRevealedLock( 287 ImmersiveModeController::ANIMATE_REVEAL_NO)); 288 EXPECT_FALSE(controller()->IsEnabled()); 289 EXPECT_FALSE(controller()->IsRevealed()); 290 EXPECT_FALSE(controller()->ShouldHideTopViews()); 291 292 // Immersive mode should start in the revealed state due to the lock. 293 chrome::ToggleFullscreenMode(browser()); 294 EXPECT_TRUE(controller()->IsEnabled()); 295 EXPECT_TRUE(controller()->IsRevealed()); 296 EXPECT_FALSE(controller()->ShouldHideTopViews()); 297 298 chrome::ToggleFullscreenMode(browser()); 299 EXPECT_FALSE(controller()->IsEnabled()); 300 EXPECT_FALSE(controller()->IsRevealed()); 301 EXPECT_FALSE(controller()->ShouldHideTopViews()); 302 303 lock1.reset(); 304 EXPECT_FALSE(controller()->IsEnabled()); 305 EXPECT_FALSE(controller()->IsRevealed()); 306 EXPECT_FALSE(controller()->ShouldHideTopViews()); 307 308 // Immersive mode should start in the closed state because the lock is no 309 // longer held. 310 chrome::ToggleFullscreenMode(browser()); 311 EXPECT_TRUE(controller()->IsEnabled()); 312 EXPECT_FALSE(controller()->IsRevealed()); 313 EXPECT_TRUE(controller()->ShouldHideTopViews()); 314 315 // 2) Test that acquiring a revealed state lock reveals the top-of-window 316 // views if they are hidden. 317 EXPECT_FALSE(controller()->IsRevealed()); 318 lock1.reset(controller()->GetRevealedLock( 319 ImmersiveModeController::ANIMATE_REVEAL_NO)); 320 EXPECT_TRUE(controller()->IsRevealed()); 321 322 // 3) Test that the top-of-window views are only hidden when all of the locks 323 // are released. 324 lock2.reset(controller()->GetRevealedLock( 325 ImmersiveModeController::ANIMATE_REVEAL_NO)); 326 lock1.reset(); 327 EXPECT_TRUE(controller()->IsRevealed()); 328 329 lock2.reset(); 330 EXPECT_FALSE(controller()->IsRevealed()); 331 } 332 333 // Shelf-specific immersive mode tests. 334 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshTest, ImmersiveShelf) { 335 // Shelf is visible when the test starts. 336 ash::internal::ShelfLayoutManager* shelf = 337 ash::Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager(); 338 ASSERT_EQ(ash::SHELF_VISIBLE, shelf->visibility_state()); 339 340 // Turning immersive mode on sets the shelf to auto-hide. 341 chrome::ToggleFullscreenMode(browser()); 342 ASSERT_TRUE(browser_view()->IsFullscreen()); 343 ASSERT_TRUE(controller()->IsEnabled()); 344 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 345 346 // Disabling immersive mode puts it back. 347 chrome::ToggleFullscreenMode(browser()); 348 ASSERT_FALSE(browser_view()->IsFullscreen()); 349 ASSERT_FALSE(controller()->IsEnabled()); 350 EXPECT_EQ(ash::SHELF_VISIBLE, shelf->visibility_state()); 351 352 // The user could toggle the launcher behavior. 353 shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); 354 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 355 356 // Enabling immersive mode keeps auto-hide. 357 chrome::ToggleFullscreenMode(browser()); 358 ASSERT_TRUE(browser_view()->IsFullscreen()); 359 ASSERT_TRUE(controller()->IsEnabled()); 360 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 361 362 // Disabling immersive mode maintains the user's auto-hide selection. 363 chrome::ToggleFullscreenMode(browser()); 364 ASSERT_FALSE(browser_view()->IsFullscreen()); 365 ASSERT_FALSE(controller()->IsEnabled()); 366 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 367 } 368 369 // Test how being simultaneously in tab fullscreen and immersive fullscreen 370 // affects the shelf visibility and whether the tab indicators are hidden. 371 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshTest, 372 TabAndBrowserFullscreen) { 373 ash::internal::ShelfLayoutManager* shelf = 374 ash::Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager(); 375 376 controller()->SetForceHideTabIndicatorsForTest(false); 377 378 // The shelf should start out as visible. 379 ASSERT_EQ(ash::SHELF_VISIBLE, shelf->visibility_state()); 380 381 // 1) Test that entering tab fullscreen from immersive mode hides the tab 382 // indicators and the shelf. 383 chrome::ToggleFullscreenMode(browser()); 384 ASSERT_TRUE(controller()->IsEnabled()); 385 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 386 EXPECT_FALSE(controller()->ShouldHideTabIndicators()); 387 388 // The shelf visibility and the tab indicator visibility are updated as a 389 // result of NOTIFICATION_FULLSCREEN_CHANGED which is asynchronous. Wait for 390 // the notification before testing visibility. 391 scoped_ptr<FullscreenNotificationObserver> waiter( 392 new FullscreenNotificationObserver()); 393 394 browser()->fullscreen_controller()->ToggleFullscreenModeForTab( 395 browser_view()->GetActiveWebContents(), true); 396 waiter->Wait(); 397 ASSERT_TRUE(controller()->IsEnabled()); 398 EXPECT_EQ(ash::SHELF_HIDDEN, shelf->visibility_state()); 399 EXPECT_TRUE(controller()->ShouldHideTabIndicators()); 400 401 // 2) Test that exiting tab fullscreen shows the tab indicators and autohides 402 // the shelf. 403 waiter.reset(new FullscreenNotificationObserver()); 404 browser()->fullscreen_controller()->ToggleFullscreenModeForTab( 405 browser_view()->GetActiveWebContents(), false); 406 waiter->Wait(); 407 ASSERT_TRUE(controller()->IsEnabled()); 408 EXPECT_EQ(ash::SHELF_AUTO_HIDE, shelf->visibility_state()); 409 EXPECT_FALSE(controller()->ShouldHideTabIndicators()); 410 411 // 3) Test that exiting tab fullscreen and immersive fullscreen 412 // simultaneously correctly updates the shelf visibility and whether the tab 413 // indicators should be hidden. 414 waiter.reset(new FullscreenNotificationObserver()); 415 browser()->fullscreen_controller()->ToggleFullscreenModeForTab( 416 browser_view()->GetActiveWebContents(), true); 417 waiter->Wait(); 418 waiter.reset(new FullscreenNotificationObserver()); 419 chrome::ToggleFullscreenMode(browser()); 420 waiter->Wait(); 421 422 ASSERT_FALSE(controller()->IsEnabled()); 423 EXPECT_EQ(ash::SHELF_VISIBLE, shelf->visibility_state()); 424 EXPECT_TRUE(controller()->ShouldHideTabIndicators()); 425 } 426 427 // Validate top container touch insets are being updated at the correct time in 428 // immersive mode. 429 IN_PROC_BROWSER_TEST_F(ImmersiveModeControllerAshTest, 430 ImmersiveTopContainerInsets) { 431 content::WebContents* contents = browser_view()->GetActiveWebContents(); 432 aura::Window* window = contents->GetView()->GetContentNativeView(); 433 434 // Turning immersive mode on sets positive top touch insets on the render view 435 // window. 436 chrome::ToggleFullscreenMode(browser()); 437 ASSERT_TRUE(browser_view()->IsFullscreen()); 438 ASSERT_TRUE(controller()->IsEnabled()); 439 EXPECT_TRUE(window->hit_test_bounds_override_outer_touch().top() > 0); 440 441 // Trigger a reveal resets insets as now the touch target for the top 442 // container is large enough. 443 controller()->StartRevealForTest(true); 444 EXPECT_TRUE(window->hit_test_bounds_override_outer_touch().top() == 0); 445 446 // End reveal by moving the mouse off the top-of-window views. We 447 // should see the top insets being positive again to allow a bigger touch 448 // target. 449 controller()->SetMouseHoveredForTest(false); 450 EXPECT_TRUE(window->hit_test_bounds_override_outer_touch().top() > 0); 451 452 // Disabling immersive mode resets the top touch insets to 0. 453 chrome::ToggleFullscreenMode(browser()); 454 ASSERT_FALSE(browser_view()->IsFullscreen()); 455 ASSERT_FALSE(controller()->IsEnabled()); 456 EXPECT_TRUE(window->hit_test_bounds_override_outer_touch().top() == 0); 457 } 458 459 #endif // defined(OS_CHROMEOS) 460