blob: 0f2d94d93fe884175c88a17f4d0f5b185b267de6 [file] [log] [blame]
// 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<State>(state1_int);
for (int state2_int = 0; state2_int < NUM_STATES; ++state2_int) {
State state2 = static_cast<State>(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<content::WebContents> 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<content::WebContents> old_web_contents(
browser()->tab_strip_model()->ReplaceWebContentsAt(
0, new_web_contents));
ChangeWindowFullscreenState();
EXPECT_FALSE(browser()->window()->IsFullscreen());
}