// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "build/build_config.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/fullscreen/fullscreen_controller.h" #include "chrome/browser/ui/fullscreen/fullscreen_controller_state_test.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "content/public/browser/web_contents.h" #include "content/public/common/url_constants.h" #include "testing/gtest/include/gtest/gtest.h" // The FullscreenControllerStateUnitTest unit test suite exhastively tests // the FullscreenController through all permutations of events. The behavior // of the BrowserWindow is mocked via FullscreenControllerTestWindow. // FullscreenControllerTestWindow ---------------------------------------------- // A BrowserWindow used for testing FullscreenController. The behavior of this // mock is verfied manually by running FullscreenControllerStateInteractiveTest. class FullscreenControllerTestWindow : public TestBrowserWindow { public: // Simulate the window state with an enumeration. enum WindowState { NORMAL, FULLSCREEN, // No TO_ state for METRO_SNAP, the windows implementation is synchronous. METRO_SNAP, TO_NORMAL, TO_FULLSCREEN, }; FullscreenControllerTestWindow(); virtual ~FullscreenControllerTestWindow() {} // BrowserWindow Interface: virtual void EnterFullscreen(const GURL& url, FullscreenExitBubbleType type) OVERRIDE; virtual void ExitFullscreen() OVERRIDE; virtual bool ShouldHideUIForFullscreen() const OVERRIDE; virtual bool IsFullscreen() const OVERRIDE; #if defined(OS_WIN) virtual void SetMetroSnapMode(bool enable) OVERRIDE; virtual bool IsInMetroSnapMode() const OVERRIDE; #endif #if defined(OS_MACOSX) virtual void EnterFullscreenWithChrome() OVERRIDE; virtual bool IsFullscreenWithChrome() OVERRIDE; virtual bool IsFullscreenWithoutChrome() OVERRIDE; #endif static const char* GetWindowStateString(WindowState state); WindowState state() const { return state_; } void set_browser(Browser* browser) { browser_ = browser; } // Simulates the window changing state. void ChangeWindowFullscreenState(); private: // Enters fullscreen with |new_mac_with_chrome_mode|. void EnterFullscreen(bool new_mac_with_chrome_mode); // Returns true if ChangeWindowFullscreenState() should be called as a result // of updating the current fullscreen state to the passed in state. bool IsTransitionReentrant(bool new_fullscreen, bool new_mac_with_chrome_mode); WindowState state_; bool mac_with_chrome_mode_; Browser* browser_; }; FullscreenControllerTestWindow::FullscreenControllerTestWindow() : state_(NORMAL), mac_with_chrome_mode_(false), browser_(NULL) { } void FullscreenControllerTestWindow::EnterFullscreen( const GURL& url, FullscreenExitBubbleType type) { EnterFullscreen(false); } void FullscreenControllerTestWindow::ExitFullscreen() { if (IsFullscreen()) { state_ = TO_NORMAL; mac_with_chrome_mode_ = false; if (IsTransitionReentrant(false, false)) ChangeWindowFullscreenState(); } } bool FullscreenControllerTestWindow::ShouldHideUIForFullscreen() const { return IsFullscreen(); } bool FullscreenControllerTestWindow::IsFullscreen() const { #if defined(OS_MACOSX) return state_ == FULLSCREEN || state_ == TO_FULLSCREEN; #else return state_ == FULLSCREEN || state_ == TO_NORMAL; #endif } #if defined(OS_WIN) void FullscreenControllerTestWindow::SetMetroSnapMode(bool enable) { if (enable != IsInMetroSnapMode()) state_ = enable ? METRO_SNAP : NORMAL; if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant()) ChangeWindowFullscreenState(); } bool FullscreenControllerTestWindow::IsInMetroSnapMode() const { return state_ == METRO_SNAP; } #endif #if defined(OS_MACOSX) void FullscreenControllerTestWindow::EnterFullscreenWithChrome() { EnterFullscreen(true); } bool FullscreenControllerTestWindow::IsFullscreenWithChrome() { return IsFullscreen() && mac_with_chrome_mode_; } bool FullscreenControllerTestWindow::IsFullscreenWithoutChrome() { return IsFullscreen() && !mac_with_chrome_mode_; } #endif // static const char* FullscreenControllerTestWindow::GetWindowStateString( WindowState state) { switch (state) { ENUM_TO_STRING(NORMAL); ENUM_TO_STRING(FULLSCREEN); ENUM_TO_STRING(METRO_SNAP); ENUM_TO_STRING(TO_FULLSCREEN); ENUM_TO_STRING(TO_NORMAL); default: NOTREACHED() << "No string for state " << state; return "WindowState-Unknown"; } } void FullscreenControllerTestWindow::ChangeWindowFullscreenState() { // Most states result in "no operation" intentionally. The tests // assume that all possible states and event pairs can be tested, even // though window managers will not generate all of these. if (state_ == TO_FULLSCREEN) state_ = FULLSCREEN; else if (state_ == TO_NORMAL) state_ = NORMAL; // Emit a change event from every state to ensure the Fullscreen Controller // handles it in all circumstances. browser_->WindowFullscreenStateChanged(); } void FullscreenControllerTestWindow::EnterFullscreen( bool new_mac_with_chrome_mode) { bool reentrant = IsTransitionReentrant(true, new_mac_with_chrome_mode); mac_with_chrome_mode_ = new_mac_with_chrome_mode; if (!IsFullscreen()) state_ = TO_FULLSCREEN; if (reentrant) ChangeWindowFullscreenState(); } bool FullscreenControllerTestWindow::IsTransitionReentrant( bool new_fullscreen, bool new_mac_with_chrome_mode) { #if defined(OS_MACOSX) bool mac_with_chrome_mode_changed = new_mac_with_chrome_mode ? IsFullscreenWithoutChrome() : IsFullscreenWithChrome(); #else bool mac_with_chrome_mode_changed = false; #endif bool fullscreen_changed = (new_fullscreen != IsFullscreen()); if (!fullscreen_changed && !mac_with_chrome_mode_changed) return false; if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant()) return true; // BrowserWindowCocoa::EnterFullscreen() and // BrowserWindowCocoa::EnterFullscreenWithChrome() are reentrant when // switching between fullscreen with chrome and fullscreen without chrome. return state_ == FULLSCREEN && !fullscreen_changed && mac_with_chrome_mode_changed; } // FullscreenControllerStateUnitTest ------------------------------------------- // Unit test fixture testing Fullscreen Controller through its states. Most of // the test logic comes from FullscreenControllerStateTest. class FullscreenControllerStateUnitTest : public BrowserWithTestWindowTest, public FullscreenControllerStateTest { public: FullscreenControllerStateUnitTest(); // FullscreenControllerStateTest: virtual void SetUp() OVERRIDE; virtual BrowserWindow* CreateBrowserWindow() OVERRIDE; virtual void ChangeWindowFullscreenState() OVERRIDE; virtual const char* GetWindowStateString() OVERRIDE; virtual void VerifyWindowState() OVERRIDE; protected: // FullscreenControllerStateTest: virtual bool ShouldSkipStateAndEventPair(State state, Event event) OVERRIDE; virtual Browser* GetBrowser() OVERRIDE; FullscreenControllerTestWindow* window_; }; FullscreenControllerStateUnitTest::FullscreenControllerStateUnitTest () : window_(NULL) { } void FullscreenControllerStateUnitTest::SetUp() { BrowserWithTestWindowTest::SetUp(); window_->set_browser(browser()); } BrowserWindow* FullscreenControllerStateUnitTest::CreateBrowserWindow() { window_ = new FullscreenControllerTestWindow(); return window_; // BrowserWithTestWindowTest takes ownership. } void FullscreenControllerStateUnitTest::ChangeWindowFullscreenState() { window_->ChangeWindowFullscreenState(); } const char* FullscreenControllerStateUnitTest::GetWindowStateString() { return FullscreenControllerTestWindow::GetWindowStateString(window_->state()); } void FullscreenControllerStateUnitTest::VerifyWindowState() { switch (state()) { case STATE_NORMAL: EXPECT_EQ(FullscreenControllerTestWindow::NORMAL, window_->state()) << GetAndClearDebugLog(); break; case STATE_BROWSER_FULLSCREEN_NO_CHROME: case STATE_BROWSER_FULLSCREEN_WITH_CHROME: case STATE_TAB_FULLSCREEN: case STATE_TAB_BROWSER_FULLSCREEN: case STATE_TAB_BROWSER_FULLSCREEN_CHROME: EXPECT_EQ(FullscreenControllerTestWindow::FULLSCREEN, window_->state()) << GetAndClearDebugLog(); break; #if defined(OS_WIN) case STATE_METRO_SNAP: EXPECT_EQ(FullscreenControllerTestWindow::METRO_SNAP, window_->state()) << GetAndClearDebugLog(); break; #endif case STATE_TO_NORMAL: EXPECT_EQ(FullscreenControllerTestWindow::TO_NORMAL, window_->state()) << GetAndClearDebugLog(); break; case STATE_TO_BROWSER_FULLSCREEN_NO_CHROME: case STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME: case STATE_TO_TAB_FULLSCREEN: EXPECT_EQ(FullscreenControllerTestWindow::TO_FULLSCREEN, window_->state()) << GetAndClearDebugLog(); break; default: NOTREACHED() << GetAndClearDebugLog(); } FullscreenControllerStateTest::VerifyWindowState(); } bool FullscreenControllerStateUnitTest::ShouldSkipStateAndEventPair( State state, Event event) { #if defined(OS_MACOSX) // TODO(scheib) Toggle, Window Event, Toggle, Toggle on Mac as exposed by // test *.STATE_TO_NORMAL__TOGGLE_FULLSCREEN runs interactively and exits to // Normal. This doesn't appear to be the desired result, and would add // too much complexity to mimic in our simple FullscreenControllerTestWindow. // http://crbug.com/156968 if ((state == STATE_TO_NORMAL || state == STATE_TO_BROWSER_FULLSCREEN_NO_CHROME || state == STATE_TO_TAB_FULLSCREEN) && event == TOGGLE_FULLSCREEN) return true; #endif return FullscreenControllerStateTest::ShouldSkipStateAndEventPair(state, event); } Browser* FullscreenControllerStateUnitTest::GetBrowser() { return BrowserWithTestWindowTest::browser(); } // Soak tests ------------------------------------------------------------------ // Tests all states with all permutations of multiple events to detect lingering // state issues that would bleed over to other states. // I.E. for each state test all combinations of events E1, E2, E3. // // This produces coverage for event sequences that may happen normally but // would not be exposed by traversing to each state via TransitionToState(). // TransitionToState() always takes the same path even when multiple paths // exist. TEST_F(FullscreenControllerStateUnitTest, TransitionsForEachState) { // A tab is needed for tab fullscreen. AddTab(browser(), GURL(content::kAboutBlankURL)); TestTransitionsForEachState(); // Progress of test can be examined via LOG(INFO) << GetAndClearDebugLog(); } // Individual tests for each pair of state and event --------------------------- #define TEST_EVENT(state, event) \ TEST_F(FullscreenControllerStateUnitTest, state##__##event) { \ AddTab(browser(), GURL(content::kAboutBlankURL)); \ ASSERT_NO_FATAL_FAILURE(TestStateAndEvent(state, event)) \ << GetAndClearDebugLog(); \ } // Progress of tests can be examined by inserting the following line: // LOG(INFO) << GetAndClearDebugLog(); } #include "chrome/browser/ui/fullscreen/fullscreen_controller_state_tests.h" // Specific one-off tests for known issues ------------------------------------- // TODO(scheib) Toggling Tab fullscreen while pending Tab or // Browser fullscreen is broken currently http://crbug.com/154196 TEST_F(FullscreenControllerStateUnitTest, DISABLED_ToggleTabWhenPendingBrowser) { // Only possible without reentrancy. if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant()) return; AddTab(browser(), GURL(content::kAboutBlankURL)); ASSERT_NO_FATAL_FAILURE( TransitionToState(STATE_TO_BROWSER_FULLSCREEN_NO_CHROME)) << GetAndClearDebugLog(); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)) << GetAndClearDebugLog(); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE)) << GetAndClearDebugLog(); ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)) << GetAndClearDebugLog(); } // TODO(scheib) Toggling Tab fullscreen while pending Tab or // Browser fullscreen is broken currently http://crbug.com/154196 TEST_F(FullscreenControllerStateUnitTest, DISABLED_ToggleTabWhenPendingTab) { // Only possible without reentrancy. if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant()) return; AddTab(browser(), GURL(content::kAboutBlankURL)); ASSERT_NO_FATAL_FAILURE( TransitionToState(STATE_TO_TAB_FULLSCREEN)) << GetAndClearDebugLog(); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)) << GetAndClearDebugLog(); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE)) << GetAndClearDebugLog(); ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)) << GetAndClearDebugLog(); } // Debugging utility: Display the transition tables. Intentionally disabled TEST_F(FullscreenControllerStateUnitTest, DISABLED_DebugLogStateTables) { std::ostringstream output; output << "\n\nTransition Table:"; output << GetTransitionTableAsString(); output << "\n\nInitial transitions:"; output << GetStateTransitionsAsString(); // Calculate all transition pairs. for (int state1_int = 0; state1_int < NUM_STATES; ++state1_int) { State state1 = static_cast(state1_int); for (int state2_int = 0; state2_int < NUM_STATES; ++state2_int) { State state2 = static_cast(state2_int); if (ShouldSkipStateAndEventPair(state1, EVENT_INVALID) || ShouldSkipStateAndEventPair(state2, EVENT_INVALID)) continue; // Compute the transition if (NextTransitionInShortestPath(state1, state2, NUM_STATES).state == STATE_INVALID) { LOG(ERROR) << "Should be skipping state transitions for: " << GetStateString(state1) << " " << GetStateString(state2); } } } output << "\n\nAll transitions:"; output << GetStateTransitionsAsString(); LOG(INFO) << output.str(); } // Test that the fullscreen exit bubble is closed by // WindowFullscreenStateChanged() if fullscreen is exited via BrowserWindow. // This currently occurs when an extension exits fullscreen via changing the // browser bounds. TEST_F(FullscreenControllerStateUnitTest, ExitFullscreenViaBrowserWindow) { AddTab(browser(), GURL(content::kAboutBlankURL)); ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN)); ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)); ASSERT_TRUE(browser()->window()->IsFullscreen()); // Exit fullscreen without going through fullscreen controller. browser()->window()->ExitFullscreen(); ChangeWindowFullscreenState(); EXPECT_EQ(FEB_TYPE_NONE, browser()->fullscreen_controller()->GetFullscreenExitBubbleType()); } // Test that switching tabs takes the browser out of tab fullscreen. TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaSwitchingTab) { AddTab(browser(), GURL(content::kAboutBlankURL)); AddTab(browser(), GURL(content::kAboutBlankURL)); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)); ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)); ASSERT_TRUE(browser()->window()->IsFullscreen()); browser()->tab_strip_model()->SelectNextTab(); ChangeWindowFullscreenState(); EXPECT_FALSE(browser()->window()->IsFullscreen()); } // Test that switching tabs via detaching the active tab (which is in tab // fullscreen) takes the browser out of tab fullscreen. This case can // occur if the user is in both tab fullscreen and immersive browser fullscreen. TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaDetachingTab) { AddTab(browser(), GURL(content::kAboutBlankURL)); AddTab(browser(), GURL(content::kAboutBlankURL)); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)); ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)); ASSERT_TRUE(browser()->window()->IsFullscreen()); scoped_ptr web_contents( browser()->tab_strip_model()->DetachWebContentsAt(0)); ChangeWindowFullscreenState(); EXPECT_FALSE(browser()->window()->IsFullscreen()); } // Test that replacing the web contents for a tab which is in tab fullscreen // takes the browser out of tab fullscreen. This can occur if the user // navigates to a prerendered page from a page which is tab fullscreen. TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaReplacingTab) { AddTab(browser(), GURL(content::kAboutBlankURL)); ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)); ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)); ASSERT_TRUE(browser()->window()->IsFullscreen()); content::WebContents* new_web_contents = content::WebContents::Create( content::WebContents::CreateParams(profile())); scoped_ptr old_web_contents( browser()->tab_strip_model()->ReplaceWebContentsAt( 0, new_web_contents)); ChangeWindowFullscreenState(); EXPECT_FALSE(browser()->window()->IsFullscreen()); }