blob: a3665f72af98365316c2d9b646b6dfebd95eea2e [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 "base/command_line.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/exclusive_access/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/gmock/include/gmock/gmock.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.
namespace {
const char kFullscreenReshowHistogramName[] =
"ExclusiveAccess.BubbleReshowsPerSession.Fullscreen";
// FullscreenControllerTestWindow ----------------------------------------------
// A BrowserWindow used for testing FullscreenController. The behavior of this
// mock is verfied manually by running FullscreenControllerStateInteractiveTest.
class FullscreenControllerTestWindow : public TestBrowserWindow,
ExclusiveAccessContext {
public:
// Simulate the window state with an enumeration.
enum WindowState {
NORMAL,
FULLSCREEN,
TO_NORMAL,
TO_FULLSCREEN,
};
FullscreenControllerTestWindow();
~FullscreenControllerTestWindow() override {}
// BrowserWindow Interface:
bool ShouldHideUIForFullscreen() const override;
bool IsFullscreen() const override;
static const char* GetWindowStateString(WindowState state);
WindowState state() const { return state_; }
void set_browser(Browser* browser) { browser_ = browser; }
ExclusiveAccessContext* GetExclusiveAccessContext() override;
// ExclusiveAccessContext Interface:
Profile* GetProfile() override;
content::WebContents* GetActiveWebContents() override;
void HideDownloadShelf() override;
void UnhideDownloadShelf() override;
void EnterFullscreen(const GURL& url,
ExclusiveAccessBubbleType type) override;
void ExitFullscreen() override;
void UpdateExclusiveAccessExitBubbleContent(
const GURL& url,
ExclusiveAccessBubbleType bubble_type,
ExclusiveAccessBubbleHideCallback bubble_first_hide_callback,
bool force_update) override;
void OnExclusiveAccessUserInput() override;
bool CanUserExitFullscreen() const override;
// Simulates the window changing state.
void ChangeWindowFullscreenState();
private:
void EnterFullscreen();
// 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);
WindowState state_;
Browser* browser_;
};
FullscreenControllerTestWindow::FullscreenControllerTestWindow()
: state_(NORMAL),
browser_(NULL) {
}
void FullscreenControllerTestWindow::EnterFullscreen(
const GURL& url,
ExclusiveAccessBubbleType type) {
EnterFullscreen();
}
void FullscreenControllerTestWindow::ExitFullscreen() {
if (IsFullscreen()) {
state_ = TO_NORMAL;
if (IsTransitionReentrant(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
}
// static
const char* FullscreenControllerTestWindow::GetWindowStateString(
WindowState state) {
switch (state) {
ENUM_TO_STRING(NORMAL);
ENUM_TO_STRING(FULLSCREEN);
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 reentrant = IsTransitionReentrant(true);
if (!IsFullscreen())
state_ = TO_FULLSCREEN;
if (reentrant)
ChangeWindowFullscreenState();
}
bool FullscreenControllerTestWindow::IsTransitionReentrant(
bool new_fullscreen) {
bool fullscreen_changed = (new_fullscreen != IsFullscreen());
if (!fullscreen_changed)
return false;
if (FullscreenControllerStateTest::IsWindowFullscreenStateChangedReentrant())
return true;
// BrowserWindowCocoa::EnterFullscreen() and
// BrowserWindowCocoa::EnterFullscreenWithToolbar() are reentrant when
// switching between fullscreen with chrome and fullscreen without chrome.
return state_ == FULLSCREEN && !fullscreen_changed;
}
ExclusiveAccessContext*
FullscreenControllerTestWindow::GetExclusiveAccessContext() {
return this;
}
Profile* FullscreenControllerTestWindow::GetProfile() {
return browser_->profile();
}
content::WebContents* FullscreenControllerTestWindow::GetActiveWebContents() {
return browser_->tab_strip_model()->GetActiveWebContents();
}
void FullscreenControllerTestWindow::UnhideDownloadShelf() {
GetDownloadShelf()->Unhide();
}
void FullscreenControllerTestWindow::HideDownloadShelf() {
GetDownloadShelf()->Hide();
}
void FullscreenControllerTestWindow::UpdateExclusiveAccessExitBubbleContent(
const GURL& url,
ExclusiveAccessBubbleType bubble_type,
ExclusiveAccessBubbleHideCallback bubble_first_hide_callback,
bool force_update) {}
void FullscreenControllerTestWindow::OnExclusiveAccessUserInput() {}
bool FullscreenControllerTestWindow::CanUserExitFullscreen() const {
return true;
}
} // namespace
// 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:
void SetUp() override;
BrowserWindow* CreateBrowserWindow() override;
void ChangeWindowFullscreenState() override;
const char* GetWindowStateString() override;
void VerifyWindowState() override;
protected:
// FullscreenControllerStateTest:
bool ShouldSkipStateAndEventPair(State state, Event event) override;
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:
case STATE_TAB_FULLSCREEN:
case STATE_TAB_BROWSER_FULLSCREEN:
EXPECT_EQ(FullscreenControllerTestWindow::FULLSCREEN,
window_->state()) << GetAndClearDebugLog();
break;
case STATE_TO_NORMAL:
EXPECT_EQ(FullscreenControllerTestWindow::TO_NORMAL,
window_->state()) << GetAndClearDebugLog();
break;
case STATE_TO_BROWSER_FULLSCREEN:
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_BROWSER_FULLSCREEN ||
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(url::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(url::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/exclusive_access/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(url::kAboutBlankURL));
ASSERT_NO_FATAL_FAILURE(
TransitionToState(STATE_TO_BROWSER_FULLSCREEN))
<< 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(url::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 the
// ExclusiveAccessContext interface.
TEST_F(FullscreenControllerStateUnitTest,
ExitFullscreenViaExclusiveAccessContext) {
AddTab(browser(), GURL(url::kAboutBlankURL));
ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(browser()->window()->IsFullscreen());
// Exit fullscreen without going through fullscreen controller.
window_->ExitFullscreen();
ChangeWindowFullscreenState();
EXPECT_EQ(EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
browser()
->exclusive_access_manager()
->GetExclusiveAccessExitBubbleType());
}
// Test that switching tabs takes the browser out of tab fullscreen.
TEST_F(FullscreenControllerStateUnitTest, ExitTabFullscreenViaSwitchingTab) {
base::HistogramTester histogram_tester;
AddTab(browser(), GURL(url::kAboutBlankURL));
AddTab(browser(), GURL(url::kAboutBlankURL));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(browser()->window()->IsFullscreen());
histogram_tester.ExpectTotalCount(kFullscreenReshowHistogramName, 0);
browser()->tab_strip_model()->SelectNextTab();
ChangeWindowFullscreenState();
EXPECT_FALSE(browser()->window()->IsFullscreen());
// Do a simple test that histograms are being recorded upon exiting the
// fullscreen session (when simplified-fullscreen-ui is enabled).
histogram_tester.ExpectUniqueSample(kFullscreenReshowHistogramName, 0, 1);
}
// 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(url::kAboutBlankURL));
AddTab(browser(), GURL(url::kAboutBlankURL));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(browser()->window()->IsFullscreen());
std::unique_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(url::kAboutBlankURL));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(browser()->window()->IsFullscreen());
std::unique_ptr<content::WebContents> new_web_contents =
content::WebContents::Create(
content::WebContents::CreateParams(profile()));
std::unique_ptr<content::WebContents> old_web_contents =
browser()->tab_strip_model()->ReplaceWebContentsAt(
0, std::move(new_web_contents));
ChangeWindowFullscreenState();
EXPECT_FALSE(browser()->window()->IsFullscreen());
}
// Tests that, in a browser configured for Fullscreen-Within-Tab mode,
// fullscreening a screen-captured tab will NOT cause any fullscreen state
// change to the browser window. Furthermore, the test switches between tabs to
// confirm a captured tab will be resized by FullscreenController to the capture
// video resolution once the widget is detached from the UI.
//
// See 'FullscreenWithinTab Note' in fullscreen_controller.h.
TEST_F(FullscreenControllerStateUnitTest, OneCapturedFullscreenedTab) {
content::WebContentsDelegate* const wc_delegate =
static_cast<content::WebContentsDelegate*>(browser());
ASSERT_TRUE(wc_delegate->EmbedsFullscreenWidget());
AddTab(browser(), GURL(url::kAboutBlankURL));
AddTab(browser(), GURL(url::kAboutBlankURL));
content::WebContents* const first_tab =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::WebContents* const second_tab =
browser()->tab_strip_model()->GetWebContentsAt(1);
// Activate the first tab and tell its WebContents it is being captured.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
const gfx::Size kCaptureSize(1280, 720);
first_tab->IncrementCapturerCount(kCaptureSize);
ASSERT_FALSE(browser()->window()->IsFullscreen());
ASSERT_FALSE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
ASSERT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
ASSERT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Enter tab fullscreen. Since the tab is being captured, the browser window
// should not expand to fill the screen.
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Switch to the other tab. Check that the first tab was resized to the
// WebContents' preferred size.
browser()->tab_strip_model()->ActivateTabAt(
1, {TabStripModel::GestureType::kOther});
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// TODO(miu): Need to make an adjustment to content::WebContentsViewMac for
// the following to work:
#if !defined(OS_MACOSX)
EXPECT_EQ(kCaptureSize, first_tab->GetViewBounds().size());
#endif
// Switch back to the first tab and exit fullscreen.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
}
// Tests that, in a browser configured for Fullscreen-Within-Tab mode, more than
// one tab can be in fullscreen mode at the same time without interfering with
// each other. One tab is being screen-captured and is toggled into fullscreen
// mode, and then the user switches to another tab not being screen-captured and
// fullscreens it. The first tab's fullscreen toggle does not affect the
// browser window fullscreen, while the second one's does. Then, the order of
// operations is reversed.
//
// See 'FullscreenWithinTab Note' in fullscreen_controller.h.
TEST_F(FullscreenControllerStateUnitTest, TwoFullscreenedTabsOneCaptured) {
content::WebContentsDelegate* const wc_delegate =
static_cast<content::WebContentsDelegate*>(browser());
ASSERT_TRUE(wc_delegate->EmbedsFullscreenWidget());
AddTab(browser(), GURL(url::kAboutBlankURL));
AddTab(browser(), GURL(url::kAboutBlankURL));
content::WebContents* const first_tab =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::WebContents* const second_tab =
browser()->tab_strip_model()->GetWebContentsAt(1);
// Start capturing the first tab, fullscreen it, then switch to the second tab
// and fullscreen that. The second tab will cause the browser window to
// expand to fill the screen.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
const gfx::Size kCaptureSize(1280, 720);
first_tab->IncrementCapturerCount(kCaptureSize);
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
browser()->tab_strip_model()->ActivateTabAt(
1, {TabStripModel::GestureType::kOther});
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
EXPECT_TRUE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_TRUE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Now exit fullscreen while still in the second tab. The browser window
// should no longer be fullscreened.
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Finally, exit fullscreen on the captured tab.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
}
// Tests that, in a browser configured for Fullscreen-Within-Tab mode, more than
// one tab can be in fullscreen mode at the same time. This is like the
// TwoFullscreenedTabsOneCaptured test above, except that the screen-captured
// tab exits fullscreen mode while the second tab is still in the foreground.
// When the first tab exits fullscreen, the fullscreen state of the second tab
// and the browser window should remain unchanged.
//
// See 'FullscreenWithinTab Note' in fullscreen_controller.h.
TEST_F(FullscreenControllerStateUnitTest,
BackgroundCapturedTabExitsFullscreen) {
content::WebContentsDelegate* const wc_delegate =
static_cast<content::WebContentsDelegate*>(browser());
ASSERT_TRUE(wc_delegate->EmbedsFullscreenWidget());
AddTab(browser(), GURL(url::kAboutBlankURL));
AddTab(browser(), GURL(url::kAboutBlankURL));
content::WebContents* const first_tab =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::WebContents* const second_tab =
browser()->tab_strip_model()->GetWebContentsAt(1);
// Start capturing the first tab, fullscreen it, then switch to the second tab
// and fullscreen that. The second tab will cause the browser window to
// expand to fill the screen.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
const gfx::Size kCaptureSize(1280, 720);
first_tab->IncrementCapturerCount(kCaptureSize);
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
browser()->tab_strip_model()->ActivateTabAt(
1, {TabStripModel::GestureType::kOther});
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
EXPECT_TRUE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_TRUE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Now, the first tab (backgrounded) exits fullscreen. This should not affect
// the second tab's fullscreen, nor the state of the browser window.
GetFullscreenController()->ExitFullscreenModeForTab(first_tab);
EXPECT_TRUE(browser()->window()->IsFullscreen());
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_TRUE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Finally, exit fullscreen on the second tab.
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
}
// Tests that, in a browser configured for Fullscreen-Within-Tab mode,
// fullscreening a screen-captured tab will NOT cause any fullscreen state
// change to the browser window. Then, toggling Browser Fullscreen mode should
// fullscreen the browser window, but this should behave fully independently of
// the tab's fullscreen state.
//
// See 'FullscreenWithinTab Note' in fullscreen_controller.h.
TEST_F(FullscreenControllerStateUnitTest,
OneCapturedTabFullscreenedBeforeBrowserFullscreen) {
content::WebContentsDelegate* const wc_delegate =
static_cast<content::WebContentsDelegate*>(browser());
ASSERT_TRUE(wc_delegate->EmbedsFullscreenWidget());
AddTab(browser(), GURL(url::kAboutBlankURL));
content::WebContents* const tab =
browser()->tab_strip_model()->GetWebContentsAt(0);
// Start capturing the tab and fullscreen it. The state of the browser window
// should remain unchanged.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
const gfx::Size kCaptureSize(1280, 720);
tab->IncrementCapturerCount(kCaptureSize);
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_FALSE(GetFullscreenController()->IsFullscreenForBrowser());
// Now, toggle into Browser Fullscreen mode. The browser window should now be
// fullscreened.
ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_TRUE(GetFullscreenController()->IsFullscreenForBrowser());
// Now, toggle back out of Browser Fullscreen mode. The browser window exits
// fullscreen mode, but the tab stays in fullscreen mode.
ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_FALSE(GetFullscreenController()->IsFullscreenForBrowser());
// Finally, toggle back into Browser Fullscreen mode and then toggle out of
// tab fullscreen mode. The browser window should stay fullscreened, while
// the tab exits fullscreen mode.
ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_TRUE(GetFullscreenController()->IsFullscreenForBrowser());
}
class FullscreenChangeObserver : public content::WebContentsObserver {
public:
explicit FullscreenChangeObserver(content::WebContents* web_contents)
: WebContentsObserver(web_contents) {
}
MOCK_METHOD2(DidToggleFullscreenModeForTab, void(bool, bool));
private:
DISALLOW_COPY_AND_ASSIGN(FullscreenChangeObserver);
};
// Tests that going from tab fullscreen -> browser fullscreen causes an explicit
// WasResized to be called on ExitFullscreen while going from tab fullscreen ->
// Normal does not. This ensures that the Resize message we get in the renderer
// will have both the fullscreen change and size change in the same message.
// crbug.com/142427.
TEST_F(FullscreenControllerStateUnitTest, TabToBrowserFullscreenCausesResize) {
AddTab(browser(), GURL(url::kAboutBlankURL));
content::WebContents* const tab =
browser()->tab_strip_model()->GetWebContentsAt(0);
FullscreenChangeObserver fullscreenObserver(tab);
// Go into browser fullscreen, then tab fullscreen. Exiting tab fullscreen
// should call WasResized since the fullscreen change won't cause a size
// change itself.
ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(browser()->window()->IsFullscreen());
// The second parameter in DidToggleFullscreenModeForTab should be false,
// indicating that the fullscreen change will *not* cause a resize.
EXPECT_CALL(fullscreenObserver,
DidToggleFullscreenModeForTab(false, false));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
testing::Mock::VerifyAndClearExpectations(&fullscreenObserver);
ASSERT_TRUE(InvokeEvent(TOGGLE_FULLSCREEN));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_FALSE(browser()->window()->IsFullscreen());
// Go into tab fullscreen only. Exiting tab fullscreen should *not* cause
// a call to WasResized since the window will change size and we want the
// fullscreen change and size change to be in one Resize message.
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
ASSERT_TRUE(browser()->window()->IsFullscreen());
// The second parameter in DidToggleFullscreenModeForTab should now be true,
// indicating that the fullscreen change *will* cause a resize.
EXPECT_CALL(fullscreenObserver,
DidToggleFullscreenModeForTab(false, true));
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
ASSERT_FALSE(browser()->window()->IsFullscreen());
testing::Mock::VerifyAndClearExpectations(&fullscreenObserver);
}
// Tests that the state of a fullscreened, screen-captured tab is preserved if
// the tab is detached from one Browser window and attached to another.
//
// See 'FullscreenWithinTab Note' in fullscreen_controller.h.
TEST_F(FullscreenControllerStateUnitTest,
CapturedFullscreenedTabTransferredBetweenBrowserWindows) {
content::WebContentsDelegate* const wc_delegate =
static_cast<content::WebContentsDelegate*>(browser());
ASSERT_TRUE(wc_delegate->EmbedsFullscreenWidget());
AddTab(browser(), GURL(url::kAboutBlankURL));
AddTab(browser(), GURL(url::kAboutBlankURL));
content::WebContents* const tab =
browser()->tab_strip_model()->GetWebContentsAt(0);
// Activate the first tab and tell its WebContents it is being captured.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
const gfx::Size kCaptureSize(1280, 720);
tab->IncrementCapturerCount(kCaptureSize);
ASSERT_FALSE(browser()->window()->IsFullscreen());
ASSERT_FALSE(wc_delegate->IsFullscreenForTabOrPending(tab));
ASSERT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Enter tab fullscreen. Since the tab is being captured, the browser window
// should not expand to fill the screen.
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
// Create the second browser window.
const std::unique_ptr<BrowserWindow> second_browser_window(
CreateBrowserWindow());
const std::unique_ptr<Browser> second_browser(
CreateBrowser(browser()->profile(), browser()->type(), false,
second_browser_window.get()));
AddTab(second_browser.get(), GURL(url::kAboutBlankURL));
content::WebContentsDelegate* const second_wc_delegate =
static_cast<content::WebContentsDelegate*>(second_browser.get());
// Detach the tab from the first browser window and attach it to the second.
// The tab should remain in fullscreen mode and neither browser window should
// have expanded. It is correct for both FullscreenControllers to agree the
// tab is in fullscreen mode.
std::unique_ptr<content::WebContents> owned_wc =
browser()->tab_strip_model()->DetachWebContentsAt(0);
second_browser->tab_strip_model()->InsertWebContentsAt(
0, std::move(owned_wc), TabStripModel::ADD_ACTIVE);
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_FALSE(second_browser->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_TRUE(second_wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_FALSE(second_browser->exclusive_access_manager()
->fullscreen_controller()
->IsWindowFullscreenForTabOrPending());
// Now, detach and reattach it back to the first browser window. Again, the
// tab should remain in fullscreen mode and neither browser window should have
// expanded.
owned_wc = second_browser->tab_strip_model()->DetachWebContentsAt(0);
browser()->tab_strip_model()->InsertWebContentsAt(0, std::move(owned_wc),
TabStripModel::ADD_ACTIVE);
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_FALSE(second_browser->window()->IsFullscreen());
EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_TRUE(second_wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_FALSE(second_browser->exclusive_access_manager()
->fullscreen_controller()
->IsWindowFullscreenForTabOrPending());
// Exit fullscreen.
ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
EXPECT_FALSE(browser()->window()->IsFullscreen());
EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(second_wc_delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
EXPECT_FALSE(second_browser->exclusive_access_manager()
->fullscreen_controller()
->IsWindowFullscreenForTabOrPending());
// Required tear-down specific to this test.
second_browser->tab_strip_model()->CloseAllTabs();
}