Home | History | Annotate | Download | only in fullscreen
      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 "build/build_config.h"
      6 #include "chrome/browser/ui/browser.h"
      7 #include "chrome/browser/ui/browser_tabstrip.h"
      8 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
      9 #include "chrome/browser/ui/fullscreen/fullscreen_controller_state_test.h"
     10 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     11 #include "chrome/test/base/browser_with_test_window_test.h"
     12 #include "content/public/browser/web_contents.h"
     13 #include "content/public/common/url_constants.h"
     14 #include "testing/gtest/include/gtest/gtest.h"
     15 
     16 // The FullscreenControllerStateUnitTest unit test suite exhastively tests
     17 // the FullscreenController through all permutations of events. The behavior
     18 // of the BrowserWindow is mocked via FullscreenControllerTestWindow.
     19 
     20 
     21 // FullscreenControllerTestWindow ----------------------------------------------
     22 
     23 // A BrowserWindow used for testing FullscreenController. The behavior of this
     24 // mock is verfied manually by running FullscreenControllerStateInteractiveTest.
     25 class FullscreenControllerTestWindow : public TestBrowserWindow {
     26  public:
     27   // Simulate the window state with an enumeration.
     28   enum WindowState {
     29     NORMAL,
     30     FULLSCREEN,
     31     // No TO_ state for METRO_SNAP, the windows implementation is synchronous.
     32     METRO_SNAP,
     33     TO_NORMAL,
     34     TO_FULLSCREEN,
     35   };
     36 
     37   FullscreenControllerTestWindow();
     38   virtual ~FullscreenControllerTestWindow() {}
     39 
     40   // BrowserWindow Interface:
     41   virtual void EnterFullscreen(const GURL& url,
     42                                FullscreenExitBubbleType type) OVERRIDE;
     43   virtual void ExitFullscreen() OVERRIDE;
     44   virtual bool ShouldHideUIForFullscreen() const OVERRIDE;
     45   virtual bool IsFullscreen() const OVERRIDE;
     46 #if defined(OS_WIN)
     47   virtual void SetMetroSnapMode(bool enable) OVERRIDE;
     48   virtual bool IsInMetroSnapMode() const OVERRIDE;
     49 #endif
     50 #if defined(OS_MACOSX)
     51   virtual void EnterFullscreenWithChrome() OVERRIDE;
     52   virtual bool IsFullscreenWithChrome() OVERRIDE;
     53   virtual bool IsFullscreenWithoutChrome() OVERRIDE;
     54 #endif
     55 
     56   static const char* GetWindowStateString(WindowState state);
     57   WindowState state() const { return state_; }
     58   void set_browser(Browser* browser) { browser_ = browser; }
     59 
     60   // Simulates the window changing state.
     61   void ChangeWindowFullscreenState();
     62 
     63  private:
     64   // Enters fullscreen with |new_mac_with_chrome_mode|.
     65   void EnterFullscreen(bool new_mac_with_chrome_mode);
     66 
     67   // Returns true if ChangeWindowFullscreenState() should be called as a result
     68   // of updating the current fullscreen state to the passed in state.
     69   bool IsTransitionReentrant(bool new_fullscreen,
     70                              bool new_mac_with_chrome_mode);
     71 
     72   WindowState state_;
     73   bool mac_with_chrome_mode_;
     74   Browser* browser_;
     75 };
     76 
     77 FullscreenControllerTestWindow::FullscreenControllerTestWindow()
     78     : state_(NORMAL),
     79       mac_with_chrome_mode_(false),
     80       browser_(NULL) {
     81 }
     82 
     83 void FullscreenControllerTestWindow::EnterFullscreen(
     84     const GURL& url, FullscreenExitBubbleType type) {
     85   EnterFullscreen(false);
     86 }
     87 
     88 void FullscreenControllerTestWindow::ExitFullscreen() {
     89   if (IsFullscreen()) {
     90     state_ = TO_NORMAL;
     91     mac_with_chrome_mode_ = false;
     92 
     93     if (IsTransitionReentrant(false, false))
     94       ChangeWindowFullscreenState();
     95   }
     96 }
     97 
     98 bool FullscreenControllerTestWindow::ShouldHideUIForFullscreen() const {
     99   return IsFullscreen();
    100 }
    101 
    102 bool FullscreenControllerTestWindow::IsFullscreen() const {
    103 #if defined(OS_MACOSX)
    104   return state_ == FULLSCREEN || state_ == TO_FULLSCREEN;
    105 #else
    106   return state_ == FULLSCREEN || state_ == TO_NORMAL;
    107 #endif
    108 }
    109 
    110 #if defined(OS_WIN)
    111 void FullscreenControllerTestWindow::SetMetroSnapMode(bool enable) {
    112   if (enable != IsInMetroSnapMode())
    113     state_ = enable ? METRO_SNAP : NORMAL;
    114 
    115   if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant())
    116     ChangeWindowFullscreenState();
    117 }
    118 
    119 bool FullscreenControllerTestWindow::IsInMetroSnapMode() const {
    120   return state_ == METRO_SNAP;
    121 }
    122 #endif
    123 
    124 #if defined(OS_MACOSX)
    125 void FullscreenControllerTestWindow::EnterFullscreenWithChrome() {
    126   EnterFullscreen(true);
    127 }
    128 
    129 bool FullscreenControllerTestWindow::IsFullscreenWithChrome() {
    130   return IsFullscreen() && mac_with_chrome_mode_;
    131 }
    132 
    133 bool FullscreenControllerTestWindow::IsFullscreenWithoutChrome() {
    134   return IsFullscreen() && !mac_with_chrome_mode_;
    135 }
    136 #endif
    137 
    138 // static
    139 const char* FullscreenControllerTestWindow::GetWindowStateString(
    140     WindowState state) {
    141   switch (state) {
    142     ENUM_TO_STRING(NORMAL);
    143     ENUM_TO_STRING(FULLSCREEN);
    144     ENUM_TO_STRING(METRO_SNAP);
    145     ENUM_TO_STRING(TO_FULLSCREEN);
    146     ENUM_TO_STRING(TO_NORMAL);
    147     default:
    148       NOTREACHED() << "No string for state " << state;
    149       return "WindowState-Unknown";
    150   }
    151 }
    152 
    153 void FullscreenControllerTestWindow::ChangeWindowFullscreenState() {
    154   // Most states result in "no operation" intentionally. The tests
    155   // assume that all possible states and event pairs can be tested, even
    156   // though window managers will not generate all of these.
    157   if (state_ == TO_FULLSCREEN)
    158       state_ = FULLSCREEN;
    159   else if (state_ == TO_NORMAL)
    160       state_ = NORMAL;
    161 
    162   // Emit a change event from every state to ensure the Fullscreen Controller
    163   // handles it in all circumstances.
    164   browser_->WindowFullscreenStateChanged();
    165 }
    166 
    167 void FullscreenControllerTestWindow::EnterFullscreen(
    168     bool new_mac_with_chrome_mode) {
    169   bool reentrant = IsTransitionReentrant(true, new_mac_with_chrome_mode);
    170 
    171   mac_with_chrome_mode_ = new_mac_with_chrome_mode;
    172   if (!IsFullscreen())
    173     state_ = TO_FULLSCREEN;
    174 
    175   if (reentrant)
    176     ChangeWindowFullscreenState();
    177 }
    178 
    179 bool FullscreenControllerTestWindow::IsTransitionReentrant(
    180     bool new_fullscreen,
    181     bool new_mac_with_chrome_mode) {
    182 #if defined(OS_MACOSX)
    183   bool mac_with_chrome_mode_changed = new_mac_with_chrome_mode ?
    184       IsFullscreenWithoutChrome() : IsFullscreenWithChrome();
    185 #else
    186   bool mac_with_chrome_mode_changed = false;
    187 #endif
    188   bool fullscreen_changed = (new_fullscreen != IsFullscreen());
    189 
    190   if (!fullscreen_changed && !mac_with_chrome_mode_changed)
    191     return false;
    192 
    193   if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant())
    194     return true;
    195 
    196   // BrowserWindowCocoa::EnterFullscreen() and
    197   // BrowserWindowCocoa::EnterFullscreenWithChrome() are reentrant when
    198   // switching between fullscreen with chrome and fullscreen without chrome.
    199   return state_ == FULLSCREEN &&
    200       !fullscreen_changed &&
    201       mac_with_chrome_mode_changed;
    202 }
    203 
    204 
    205 // FullscreenControllerStateUnitTest -------------------------------------------
    206 
    207 // Unit test fixture testing Fullscreen Controller through its states. Most of
    208 // the test logic comes from FullscreenControllerStateTest.
    209 class FullscreenControllerStateUnitTest : public BrowserWithTestWindowTest,
    210                                           public FullscreenControllerStateTest {
    211  public:
    212   FullscreenControllerStateUnitTest();
    213 
    214   // FullscreenControllerStateTest:
    215   virtual void SetUp() OVERRIDE;
    216   virtual BrowserWindow* CreateBrowserWindow() OVERRIDE;
    217   virtual void ChangeWindowFullscreenState() OVERRIDE;
    218   virtual const char* GetWindowStateString() OVERRIDE;
    219   virtual void VerifyWindowState() OVERRIDE;
    220 
    221  protected:
    222   // FullscreenControllerStateTest:
    223   virtual bool ShouldSkipStateAndEventPair(State state, Event event) OVERRIDE;
    224   virtual Browser* GetBrowser() OVERRIDE;
    225   FullscreenControllerTestWindow* window_;
    226 };
    227 
    228 FullscreenControllerStateUnitTest::FullscreenControllerStateUnitTest ()
    229     : window_(NULL) {
    230 }
    231 
    232 void FullscreenControllerStateUnitTest::SetUp() {
    233   BrowserWithTestWindowTest::SetUp();
    234   window_->set_browser(browser());
    235 }
    236 
    237 BrowserWindow* FullscreenControllerStateUnitTest::CreateBrowserWindow() {
    238   window_ = new FullscreenControllerTestWindow();
    239   return window_;  // BrowserWithTestWindowTest takes ownership.
    240 }
    241 
    242 void FullscreenControllerStateUnitTest::ChangeWindowFullscreenState() {
    243   window_->ChangeWindowFullscreenState();
    244 }
    245 
    246 const char* FullscreenControllerStateUnitTest::GetWindowStateString() {
    247   return FullscreenControllerTestWindow::GetWindowStateString(window_->state());
    248 }
    249 
    250 void FullscreenControllerStateUnitTest::VerifyWindowState() {
    251   switch (state()) {
    252     case STATE_NORMAL:
    253       EXPECT_EQ(FullscreenControllerTestWindow::NORMAL,
    254                 window_->state()) << GetAndClearDebugLog();
    255       break;
    256 
    257     case STATE_BROWSER_FULLSCREEN_NO_CHROME:
    258     case STATE_BROWSER_FULLSCREEN_WITH_CHROME:
    259     case STATE_TAB_FULLSCREEN:
    260     case STATE_TAB_BROWSER_FULLSCREEN:
    261     case STATE_TAB_BROWSER_FULLSCREEN_CHROME:
    262       EXPECT_EQ(FullscreenControllerTestWindow::FULLSCREEN,
    263                 window_->state()) << GetAndClearDebugLog();
    264       break;
    265 
    266 #if defined(OS_WIN)
    267     case STATE_METRO_SNAP:
    268       EXPECT_EQ(FullscreenControllerTestWindow::METRO_SNAP,
    269                 window_->state()) << GetAndClearDebugLog();
    270       break;
    271 #endif
    272 
    273     case STATE_TO_NORMAL:
    274       EXPECT_EQ(FullscreenControllerTestWindow::TO_NORMAL,
    275                 window_->state()) << GetAndClearDebugLog();
    276       break;
    277 
    278     case STATE_TO_BROWSER_FULLSCREEN_NO_CHROME:
    279     case STATE_TO_BROWSER_FULLSCREEN_WITH_CHROME:
    280     case STATE_TO_TAB_FULLSCREEN:
    281       EXPECT_EQ(FullscreenControllerTestWindow::TO_FULLSCREEN,
    282                 window_->state()) << GetAndClearDebugLog();
    283       break;
    284 
    285     default:
    286       NOTREACHED() << GetAndClearDebugLog();
    287   }
    288 
    289   FullscreenControllerStateTest::VerifyWindowState();
    290 }
    291 
    292 bool FullscreenControllerStateUnitTest::ShouldSkipStateAndEventPair(
    293     State state, Event event) {
    294 #if defined(OS_MACOSX)
    295   // TODO(scheib) Toggle, Window Event, Toggle, Toggle on Mac as exposed by
    296   // test *.STATE_TO_NORMAL__TOGGLE_FULLSCREEN runs interactively and exits to
    297   // Normal. This doesn't appear to be the desired result, and would add
    298   // too much complexity to mimic in our simple FullscreenControllerTestWindow.
    299   // http://crbug.com/156968
    300   if ((state == STATE_TO_NORMAL ||
    301        state == STATE_TO_BROWSER_FULLSCREEN_NO_CHROME ||
    302        state == STATE_TO_TAB_FULLSCREEN) &&
    303       event == TOGGLE_FULLSCREEN)
    304     return true;
    305 #endif
    306 
    307   return FullscreenControllerStateTest::ShouldSkipStateAndEventPair(state,
    308                                                                     event);
    309 }
    310 
    311 Browser* FullscreenControllerStateUnitTest::GetBrowser() {
    312   return BrowserWithTestWindowTest::browser();
    313 }
    314 
    315 
    316 // Soak tests ------------------------------------------------------------------
    317 
    318 // Tests all states with all permutations of multiple events to detect lingering
    319 // state issues that would bleed over to other states.
    320 // I.E. for each state test all combinations of events E1, E2, E3.
    321 //
    322 // This produces coverage for event sequences that may happen normally but
    323 // would not be exposed by traversing to each state via TransitionToState().
    324 // TransitionToState() always takes the same path even when multiple paths
    325 // exist.
    326 TEST_F(FullscreenControllerStateUnitTest, TransitionsForEachState) {
    327   // A tab is needed for tab fullscreen.
    328   AddTab(browser(), GURL(content::kAboutBlankURL));
    329   TestTransitionsForEachState();
    330   // Progress of test can be examined via LOG(INFO) << GetAndClearDebugLog();
    331 }
    332 
    333 
    334 // Individual tests for each pair of state and event ---------------------------
    335 
    336 #define TEST_EVENT(state, event) \
    337     TEST_F(FullscreenControllerStateUnitTest, state##__##event) { \
    338       AddTab(browser(), GURL(content::kAboutBlankURL)); \
    339       ASSERT_NO_FATAL_FAILURE(TestStateAndEvent(state, event)) \
    340           << GetAndClearDebugLog(); \
    341     }
    342     // Progress of tests can be examined by inserting the following line:
    343     // LOG(INFO) << GetAndClearDebugLog(); }
    344 
    345 #include "chrome/browser/ui/fullscreen/fullscreen_controller_state_tests.h"
    346 
    347 
    348 // Specific one-off tests for known issues -------------------------------------
    349 
    350 // TODO(scheib) Toggling Tab fullscreen while pending Tab or
    351 // Browser fullscreen is broken currently http://crbug.com/154196
    352 TEST_F(FullscreenControllerStateUnitTest,
    353        DISABLED_ToggleTabWhenPendingBrowser) {
    354   // Only possible without reentrancy.
    355   if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant())
    356     return;
    357   AddTab(browser(), GURL(content::kAboutBlankURL));
    358   ASSERT_NO_FATAL_FAILURE(
    359       TransitionToState(STATE_TO_BROWSER_FULLSCREEN_NO_CHROME))
    360       << GetAndClearDebugLog();
    361 
    362   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)) << GetAndClearDebugLog();
    363   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE)) << GetAndClearDebugLog();
    364   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)) << GetAndClearDebugLog();
    365 }
    366 
    367 // TODO(scheib) Toggling Tab fullscreen while pending Tab or
    368 // Browser fullscreen is broken currently http://crbug.com/154196
    369 TEST_F(FullscreenControllerStateUnitTest, DISABLED_ToggleTabWhenPendingTab) {
    370   // Only possible without reentrancy.
    371   if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant())
    372     return;
    373   AddTab(browser(), GURL(content::kAboutBlankURL));
    374   ASSERT_NO_FATAL_FAILURE(
    375       TransitionToState(STATE_TO_TAB_FULLSCREEN))
    376       << GetAndClearDebugLog();
    377 
    378   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE)) << GetAndClearDebugLog();
    379   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE)) << GetAndClearDebugLog();
    380   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE)) << GetAndClearDebugLog();
    381 }
    382 
    383 // Debugging utility: Display the transition tables. Intentionally disabled
    384 TEST_F(FullscreenControllerStateUnitTest, DISABLED_DebugLogStateTables) {
    385   std::ostringstream output;
    386   output << "\n\nTransition Table:";
    387   output << GetTransitionTableAsString();
    388 
    389   output << "\n\nInitial transitions:";
    390   output << GetStateTransitionsAsString();
    391 
    392   // Calculate all transition pairs.
    393   for (int state1_int = 0; state1_int < NUM_STATES; ++state1_int) {
    394     State state1 = static_cast<State>(state1_int);
    395     for (int state2_int = 0; state2_int < NUM_STATES; ++state2_int) {
    396       State state2 = static_cast<State>(state2_int);
    397       if (ShouldSkipStateAndEventPair(state1, EVENT_INVALID) ||
    398           ShouldSkipStateAndEventPair(state2, EVENT_INVALID))
    399         continue;
    400       // Compute the transition
    401       if (NextTransitionInShortestPath(state1, state2, NUM_STATES).state ==
    402           STATE_INVALID) {
    403         LOG(ERROR) << "Should be skipping state transitions for: "
    404             << GetStateString(state1) << " " << GetStateString(state2);
    405       }
    406     }
    407   }
    408 
    409   output << "\n\nAll transitions:";
    410   output << GetStateTransitionsAsString();
    411   LOG(INFO) << output.str();
    412 }
    413 
    414 // Test that the fullscreen exit bubble is closed by
    415 // WindowFullscreenStateChanged() if fullscreen is exited via BrowserWindow.
    416 // This currently occurs when an extension exits fullscreen via changing the
    417 // browser bounds.
    418 TEST_F(FullscreenControllerStateUnitTest, ExitFullscreenViaBrowserWindow) {
    419   AddTab(browser(), GURL(content::kAboutBlankURL));
    420   ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
    421   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
    422   ASSERT_TRUE(browser()->window()->IsFullscreen());
    423   // Exit fullscreen without going through fullscreen controller.
    424   browser()->window()->ExitFullscreen();
    425   ChangeWindowFullscreenState();
    426   EXPECT_EQ(FEB_TYPE_NONE,
    427             browser()->fullscreen_controller()->GetFullscreenExitBubbleType());
    428 }
    429 
    430 // Test that switching tabs takes the browser out of tab fullscreen.
    431 TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaSwitchingTab) {
    432   AddTab(browser(), GURL(content::kAboutBlankURL));
    433   AddTab(browser(), GURL(content::kAboutBlankURL));
    434   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
    435   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
    436   ASSERT_TRUE(browser()->window()->IsFullscreen());
    437 
    438   browser()->tab_strip_model()->SelectNextTab();
    439   ChangeWindowFullscreenState();
    440   EXPECT_FALSE(browser()->window()->IsFullscreen());
    441 }
    442 
    443 // Test that switching tabs via detaching the active tab (which is in tab
    444 // fullscreen) takes the browser out of tab fullscreen. This case can
    445 // occur if the user is in both tab fullscreen and immersive browser fullscreen.
    446 TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaDetachingTab) {
    447   AddTab(browser(), GURL(content::kAboutBlankURL));
    448   AddTab(browser(), GURL(content::kAboutBlankURL));
    449   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
    450   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
    451   ASSERT_TRUE(browser()->window()->IsFullscreen());
    452 
    453   scoped_ptr<content::WebContents> web_contents(
    454       browser()->tab_strip_model()->DetachWebContentsAt(0));
    455   ChangeWindowFullscreenState();
    456   EXPECT_FALSE(browser()->window()->IsFullscreen());
    457 }
    458 
    459 // Test that replacing the web contents for a tab which is in tab fullscreen
    460 // takes the browser out of tab fullscreen. This can occur if the user
    461 // navigates to a prerendered page from a page which is tab fullscreen.
    462 TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaReplacingTab) {
    463   AddTab(browser(), GURL(content::kAboutBlankURL));
    464   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
    465   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
    466   ASSERT_TRUE(browser()->window()->IsFullscreen());
    467 
    468   content::WebContents* new_web_contents = content::WebContents::Create(
    469       content::WebContents::CreateParams(profile()));
    470   scoped_ptr<content::WebContents> old_web_contents(
    471       browser()->tab_strip_model()->ReplaceWebContentsAt(
    472           0, new_web_contents));
    473   ChangeWindowFullscreenState();
    474   EXPECT_FALSE(browser()->window()->IsFullscreen());
    475 }
    476