blob: 2575b95a88a978ffc60dc4c58f7e1c231990165d [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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/feature_list.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.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/exclusive_access_test.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/blocked_content/popup_blocker_tab_helper.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/frame/user_activation_state.h"
#include "third_party/blink/public/common/switches.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
#include "ui/display/screen_base.h"
#include "ui/display/test/test_screen.h"
#include "ui/display/test/virtual_display_util.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/shell.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/test/display_manager_test_api.h" // nogncheck
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE)
#if BUILDFLAG(IS_MAC)
#include "ui/base/cocoa/nswindow_test_util.h"
#endif // BUILDFLAG(IS_MAC)
#if defined(USE_AURA)
#include "ui/aura/window.h"
#endif // USE_AURA
using content::WebContents;
namespace {
const base::FilePath::CharType* kSimpleFile = FILE_PATH_LITERAL("simple.html");
} // namespace
class FullscreenControllerInteractiveTest : public ExclusiveAccessTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
ExclusiveAccessTest::SetUpCommandLine(command_line);
// Slow bots are flaky due to slower loading interacting with
// deferred commits.
command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
}
// Tests that actually make the browser fullscreen have been flaky when
// run sharded, and so are restricted here to interactive ui tests.
void ToggleTabFullscreen(bool enter_fullscreen);
void ToggleTabFullscreenNoRetries(bool enter_fullscreen);
void ToggleBrowserFullscreen(bool enter_fullscreen);
// IsPointerLocked verifies that the FullscreenController state believes
// the pointer is locked. This is possible only for tests that initiate
// pointer lock from a renderer process, and uses logic that tests that the
// browser has focus. Thus, this can only be used in interactive ui tests
// and not on sharded tests.
bool IsPointerLocked() {
// Verify that IsPointerLocked is consistent between the
// Fullscreen Controller and the Render View Host View.
EXPECT_TRUE(browser()->IsPointerLocked() == browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetWidget()
->GetView()
->IsPointerLocked());
return browser()->IsPointerLocked();
}
void PressKeyAndWaitForPointerLockRequest(ui::KeyboardCode key_code) {
base::RunLoop run_loop;
browser()
->exclusive_access_manager()
->pointer_lock_controller()
->set_lock_state_callback_for_test(run_loop.QuitClosure());
ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), key_code, false,
false, false, false));
run_loop.Run();
}
void WaitForPointerLockBubbleToHide() {
PointerLockController* pointer_lock_controller =
browser()->exclusive_access_manager()->pointer_lock_controller();
base::RunLoop run_loop;
pointer_lock_controller->set_bubble_hide_callback_for_test(
base::BindRepeating(
[](base::RunLoop* run_loop,
ExclusiveAccessBubbleHideReason reason) {
ASSERT_EQ(reason, ExclusiveAccessBubbleHideReason::kTimeout);
run_loop->Quit();
},
&run_loop));
run_loop.Run();
pointer_lock_controller->set_bubble_hide_callback_for_test(
base::NullCallback());
}
private:
void ToggleTabFullscreen_Internal(bool enter_fullscreen,
bool retry_until_success);
};
void FullscreenControllerInteractiveTest::ToggleTabFullscreen(
bool enter_fullscreen) {
ToggleTabFullscreen_Internal(enter_fullscreen, true);
}
// |ToggleTabFullscreen| should not need to tolerate the transition failing.
// Most fullscreen tests run sharded in fullscreen_controller_browsertest.cc
// and some flakiness has occurred when calling |ToggleTabFullscreen|, so that
// method has been made robust by retrying if the transition fails.
// The root cause of that flakiness should still be tracked down, see
// http://crbug.com/133831. In the mean time, this method
// allows a fullscreen_controller_interactive_browsertest.cc test to verify
// that when running serially there is no flakiness in the transition.
void FullscreenControllerInteractiveTest::ToggleTabFullscreenNoRetries(
bool enter_fullscreen) {
ToggleTabFullscreen_Internal(enter_fullscreen, false);
}
void FullscreenControllerInteractiveTest::ToggleBrowserFullscreen(
bool enter_fullscreen) {
ASSERT_EQ(browser()->window()->IsFullscreen(), !enter_fullscreen);
ui_test_utils::ToggleFullscreenModeAndWait(browser());
ASSERT_EQ(browser()->window()->IsFullscreen(), enter_fullscreen);
ASSERT_EQ(IsFullscreenForBrowser(), enter_fullscreen);
}
void FullscreenControllerInteractiveTest::ToggleTabFullscreen_Internal(
bool enter_fullscreen, bool retry_until_success) {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
do {
ui_test_utils::FullscreenWaiter waiter(
browser(), {.tab_fullscreen = enter_fullscreen});
if (enter_fullscreen) {
browser()->EnterFullscreenModeForTab(tab->GetPrimaryMainFrame(), {});
} else {
browser()->ExitFullscreenModeForTab(tab);
}
waiter.Wait();
// Repeat ToggleFullscreenModeForTab until the correct state is entered.
// This addresses flakiness on test bots running many fullscreen
// tests in parallel.
} while (retry_until_success &&
!IsFullscreenForBrowser() &&
browser()->window()->IsFullscreen() != enter_fullscreen);
ASSERT_EQ(IsWindowFullscreenForTabOrPending(), enter_fullscreen);
if (!IsFullscreenForBrowser())
ASSERT_EQ(browser()->window()->IsFullscreen(), enter_fullscreen);
}
// Tests ///////////////////////////////////////////////////////////////////////
// Tests that while in fullscreen creating a new tab will exit fullscreen.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
TestNewTabExitsFullscreen) {
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE)
// Flaky in Linux interactive_ui_tests_wayland: crbug.com/1200036
if (ui::OzonePlatform::GetPlatformNameForTest() == "wayland")
GTEST_SKIP();
#endif
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(
AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
{
ui_test_utils::FullscreenWaiter waiter(browser(),
{.tab_fullscreen = false});
ASSERT_TRUE(
AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
waiter.Wait();
ASSERT_FALSE(browser()->window()->IsFullscreen());
}
}
// Tests a tab exiting fullscreen will bring the browser out of fullscreen.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
TestTabExitsItselfFromFullscreen) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(
AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(false));
}
// Tests that the closure provided to RunOrDeferUntilTransitionIsComplete is
// run. Some platforms may be synchronous (lambda is executed immediately) and
// others (e.g. Mac) will run it asynchronously (after the transition).
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
RunOrDeferClosureDuringTransition) {
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
GetFullscreenController()->EnterFullscreenModeForTab(
tab->GetPrimaryMainFrame(), {});
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
base::RunLoop run_loop;
bool lambda_called = false;
ASSERT_NO_FATAL_FAILURE(
GetFullscreenController()->RunOrDeferUntilTransitionIsComplete(
base::BindLambdaForTesting([&lambda_called, &run_loop]() {
lambda_called = true;
run_loop.Quit();
})));
// Lambda may run synchronously on some platforms. If it did not already run,
// block until it has.
if (!lambda_called)
run_loop.Run();
EXPECT_TRUE(lambda_called);
}
// Tests Fullscreen entered in Browser, then Tab mode, then exited via Browser.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
BrowserFullscreenExit) {
// Enter browser fullscreen.
ASSERT_NO_FATAL_FAILURE(ToggleBrowserFullscreen(true));
// Enter tab fullscreen.
ASSERT_TRUE(
AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
// Exit browser fullscreen.
ASSERT_NO_FATAL_FAILURE(ToggleBrowserFullscreen(false));
ASSERT_FALSE(browser()->window()->IsFullscreen());
}
// Tests Browser Fullscreen remains active after Tab mode entered and exited.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
BrowserFullscreenAfterTabFSExit) {
// Enter browser fullscreen.
ASSERT_NO_FATAL_FAILURE(ToggleBrowserFullscreen(true));
// Enter and then exit tab fullscreen.
ASSERT_TRUE(
AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(false));
// Verify browser fullscreen still active.
ASSERT_TRUE(IsFullscreenForBrowser());
}
// Tests fullscreen entered without permision prompt for file:// urls.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest, FullscreenFileURL) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), ui_test_utils::GetTestUrl(
base::FilePath(base::FilePath::kCurrentDirectory),
base::FilePath(kSimpleFile))));
// Validate that going fullscreen for a file does not ask permision.
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(false));
}
// Tests fullscreen is exited on page navigation.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
TestTabExitsFullscreenOnNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab")));
ASSERT_FALSE(browser()->window()->IsFullscreen());
}
// Test is flaky on all platforms: https://crbug.com/1234337
// Tests fullscreen is exited when navigating back.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
DISABLED_TestTabExitsFullscreenOnGoBack) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab")));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
GoBack();
ASSERT_FALSE(browser()->window()->IsFullscreen());
}
// Tests fullscreen is not exited on sub frame navigation.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
TestTabDoesntExitFullscreenOnSubFrameNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(ui_test_utils::GetTestUrl(base::FilePath(
base::FilePath::kCurrentDirectory), base::FilePath(kSimpleFile)));
GURL url_with_fragment(url.spec() + "#fragment");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_with_fragment));
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
}
// Test is flaky on all platforms: https://crbug.com/1234337
// Tests tab fullscreen exits, but browser fullscreen remains, on navigation.
IN_PROC_BROWSER_TEST_F(
FullscreenControllerInteractiveTest,
DISABLED_TestFullscreenFromTabWhenAlreadyInBrowserFullscreenWorks) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab")));
ASSERT_NO_FATAL_FAILURE(ToggleBrowserFullscreen(true));
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
GoBack();
ASSERT_TRUE(IsFullscreenForBrowser());
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
}
// TODO(crbug.com/40779265) Flaky on Linux-ozone, Lacros and MacOS.
#if (BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE)) || \
BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_MAC)
#define MAYBE_TabEntersPresentationModeFromWindowed \
DISABLED_TabEntersPresentationModeFromWindowed
#else
#define MAYBE_TabEntersPresentationModeFromWindowed \
TabEntersPresentationModeFromWindowed
#endif
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
MAYBE_TabEntersPresentationModeFromWindowed) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(
AddTabAtIndex(0, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
{
EXPECT_FALSE(browser()->window()->IsFullscreen());
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreenNoRetries(true));
EXPECT_TRUE(browser()->window()->IsFullscreen());
}
{
ui_test_utils::FullscreenWaiter waiter(browser(),
{.tab_fullscreen = false});
chrome::ToggleFullscreenMode(browser());
waiter.Wait();
EXPECT_FALSE(browser()->window()->IsFullscreen());
}
{
// Test that tab fullscreen mode doesn't make presentation mode the default
// on Lion.
ui_test_utils::ToggleFullscreenModeAndWait(browser());
EXPECT_TRUE(browser()->window()->IsFullscreen());
}
}
// Tests pointer lock can be escaped with ESC key.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
EscapingPointerLock) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Request to lock the pointer.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsPointerLocked());
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
// Escape, confirm we are out of pointer lock with no prompts.
SendEscapeToExclusiveAccessManager();
ASSERT_FALSE(IsPointerLocked());
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
}
// Tests pointer lock and fullscreen modes can be escaped with ESC key.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
EscapingPointerLockAndFullscreen) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Request to lock the pointer and enter fullscreen.
{
ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
PressKeyAndWaitForPointerLockRequest(ui::VKEY_B);
waiter.Wait();
}
// Escape, no prompts should remain.
{
ui_test_utils::FullscreenWaiter waiter(browser(),
{.tab_fullscreen = false});
SendEscapeToExclusiveAccessManager();
waiter.Wait();
}
ASSERT_FALSE(IsPointerLocked());
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
}
// Tests pointer lock then fullscreen.
// TODO(crbug.com/40835508): Re-enable this test
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_PointerLockThenFullscreen DISABLED_PointerLockThenFullscreen
#else
#define MAYBE_PointerLockThenFullscreen PointerLockThenFullscreen
#endif
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
MAYBE_PointerLockThenFullscreen) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
#if !defined(MEMORY_SANITIZER)
// Lock the pointer without a user gesture, expect no response.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_D);
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
ASSERT_FALSE(IsPointerLocked());
#else
// MSan builds change the timing of user gestures, which this part of the test
// depends upon. See `fullscreen_pointerlock.html` for more details, but the
// main idea is that it waits ~5 seconds after the keypress and assumes that
// the user gesture has expired.
#endif
// Lock the pointer with a user gesture.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
// Enter fullscreen mode, pointer should remain locked.
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
}
// Disabled on all due to issue with code under test: http://crbug.com/1255610.
//
// Was also disabled on platforms before:
// Times out sometimes on Linux. http://crbug.com/135115
// Mac: http://crbug.com/103912
// Tests pointer lock then fullscreen in same request.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
DISABLED_PointerLockAndFullscreen) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Request to lock the pointer and enter fullscreen.
{
ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
PressKeyAndWaitForPointerLockRequest(ui::VKEY_B);
waiter.Wait();
}
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
}
// Tests pointer lock can be exited and re-entered by an application silently
// with no UI distraction for users.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
PointerLockSilentAfterTargetUnlock) {
SetWebContentsGrantedSilentPointerLockPermission();
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Lock the pointer with a user gesture.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
// Wait for the bubble to be shown for its full duration. This allows
// the page to lock the pointer without showing the bubble later.
WaitForPointerLockBubbleToHide();
// Unlock the pointer from target, make sure it's unlocked.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_U);
ASSERT_FALSE(IsPointerLocked());
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Lock pointer again, make sure it works with no bubble.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsPointerLocked());
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Unlock the pointer again by target.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_U);
ASSERT_FALSE(IsPointerLocked());
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
}
// TODO: crbug.com/336428594 - Flaky on Lacros.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_SecondPointerLockShowsBubble DISABLED_SecondPointerLockShowsBubble
#else
#define MAYBE_SecondPointerLockShowsBubble SecondPointerLockShowsBubble
#endif
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
MAYBE_SecondPointerLockShowsBubble) {
SetWebContentsGrantedSilentPointerLockPermission();
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Lock the pointer with a user gesture.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
// Unlock the pointer from target, make sure it's unlocked.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_U);
ASSERT_FALSE(IsPointerLocked());
ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Lock the pointer again. The bubble wasn't shown for its full duration last
// time, so it gets shown again.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
}
// Tests pointer lock is exited on page navigation.
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && defined(USE_AURA)
// https://crbug.com/1191964
#define MAYBE_TestTabExitsPointerLockOnNavigation \
DISABLED_TestTabExitsPointerLockOnNavigation
#else
#define MAYBE_TestTabExitsPointerLockOnNavigation \
TestTabExitsPointerLockOnNavigation
#endif
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
MAYBE_TestTabExitsPointerLockOnNavigation) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
// Lock the pointer with a user gesture.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab")));
ASSERT_FALSE(IsPointerLocked());
}
// Tests pointer lock is exited when navigating back.
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && defined(USE_AURA)
// https://crbug.com/1192097
#define MAYBE_TestTabExitsPointerLockOnGoBack \
DISABLED_TestTabExitsPointerLockOnGoBack
#else
#define MAYBE_TestTabExitsPointerLockOnGoBack TestTabExitsPointerLockOnGoBack
#endif
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
MAYBE_TestTabExitsPointerLockOnGoBack) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
// Navigate twice to provide a place to go back to.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
// Lock the pointer with a user gesture.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
GoBack();
ASSERT_FALSE(IsPointerLocked());
}
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && \
defined(USE_AURA) || \
BUILDFLAG(IS_WIN) && defined(NDEBUG)
// TODO(erg): linux_aura bringup: http://crbug.com/163931
// Test is flaky on Windows: https://crbug.com/1124492
#define MAYBE_TestTabDoesntExitPointerLockOnSubFrameNavigation \
DISABLED_TestTabDoesntExitPointerLockOnSubFrameNavigation
#else
#define MAYBE_TestTabDoesntExitPointerLockOnSubFrameNavigation \
TestTabDoesntExitPointerLockOnSubFrameNavigation
#endif
// Tests pointer lock is not exited on sub frame navigation.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
MAYBE_TestTabDoesntExitPointerLockOnSubFrameNavigation) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
// Create URLs for test page and test page with #fragment.
GURL url(embedded_test_server()->GetURL(kFullscreenPointerLockHTML));
GURL url_with_fragment(url.spec() + "#fragment");
// Navigate to test page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Lock the pointer with a user gesture.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
ASSERT_TRUE(IsPointerLocked());
// Navigate to url with fragment. Pointer lock should persist.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_with_fragment));
ASSERT_TRUE(IsPointerLocked());
}
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
ReloadExitsPointerLockAndFullscreen) {
auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
ASSERT_TRUE(test_server_handle);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
// Request pointer lock.
PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
ASSERT_TRUE(IsPointerLocked());
ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
// Reload. Pointer lock request should be cleared.
{
base::RunLoop run_loop;
browser()
->exclusive_access_manager()
->pointer_lock_controller()
->set_lock_state_callback_for_test(run_loop.QuitClosure());
Reload();
run_loop.Run();
}
// Request to lock the pointer and enter fullscreen.
{
ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = true});
PressKeyAndWaitForPointerLockRequest(ui::VKEY_B);
waiter.Wait();
}
// We are fullscreen.
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
// Reload. Pointer should be unlocked and fullscreen exited.
{
ui_test_utils::FullscreenWaiter waiter(browser(),
{.tab_fullscreen = false});
Reload();
waiter.Wait();
ASSERT_FALSE(IsPointerLocked());
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
}
}
// Tests ToggleFullscreenModeForTab always causes window to change.
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
ToggleFullscreenModeForTab) {
// Most fullscreen tests run sharded in fullscreen_controller_browsertest.cc
// but flakiness required a while loop in
// ExclusiveAccessTest::ToggleTabFullscreen. This test verifies that
// when running serially there is no flakiness.
EXPECT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/simple.html");
ASSERT_TRUE(AddTabAtIndex(0, url, ui::PAGE_TRANSITION_TYPED));
// Validate that going fullscreen for a URL defaults to asking permision.
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreenNoRetries(true));
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreenNoRetries(false));
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
}
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
OpeningPopupExitsFullscreen) {
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
// Open a popup, which is activated. The opener exits fullscreen to mitigate
// usable security concerns. See WebContents::ForSecurityDropFullscreen().
BrowserList* browser_list = BrowserList::GetInstance();
EXPECT_EQ(1u, browser_list->size());
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::ExecuteScriptAsync(tab, "open('.', '', 'popup')");
Browser* popup = ui_test_utils::WaitForBrowserToOpen();
EXPECT_EQ(2u, browser_list->size());
ui_test_utils::BrowserActivationWaiter(popup).WaitForActivation();
EXPECT_TRUE(ui_test_utils::IsBrowserActive(popup));
ASSERT_FALSE(IsWindowFullscreenForTabOrPending());
}
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
BlockingContentsExitsFullscreen) {
ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(true));
ASSERT_TRUE(IsWindowFullscreenForTabOrPending());
// Blocking the tab for a modal dialog exits fullscreen.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::FullscreenWaiter waiter(browser(), {.tab_fullscreen = false});
static_cast<web_modal::WebContentsModalDialogManagerDelegate*>(browser())
->SetWebContentsBlocked(tab, true);
waiter.Wait();
EXPECT_FALSE(IsWindowFullscreenForTabOrPending());
}
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
CapturedContentEntersFullscreenWithinTab) {
// Simulate tab capture, as used by getDisplayMedia() content sharing.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
base::ScopedClosureRunner capture_closure =
tab->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/false, /*is_activity=*/true);
EXPECT_TRUE(tab->IsBeingVisiblyCaptured());
// The browser enters fullscreen-within-tab mode synchronously, but the window
// is not made fullscreen, and FullscreenWaiter is not notified.
content::WebContentsDelegate* delegate = tab->GetDelegate();
delegate->EnterFullscreenModeForTab(tab->GetPrimaryMainFrame(), {});
EXPECT_TRUE(delegate->IsFullscreenForTabOrPending(tab));
EXPECT_TRUE(tab->IsFullscreen());
EXPECT_FALSE(IsWindowFullscreenForTabOrPending());
EXPECT_EQ(tab->GetDelegate()->GetFullscreenState(tab).target_mode,
content::FullscreenMode::kPseudoContent);
delegate->ExitFullscreenModeForTab(tab);
EXPECT_FALSE(delegate->IsFullscreenForTabOrPending(tab));
EXPECT_FALSE(tab->IsFullscreen());
EXPECT_FALSE(IsWindowFullscreenForTabOrPending());
EXPECT_EQ(tab->GetDelegate()->GetFullscreenState(tab).target_mode,
content::FullscreenMode::kWindowed);
capture_closure.RunAndReset();
EXPECT_FALSE(tab->IsBeingVisiblyCaptured());
}
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
OpeningPopupDoesNotExitFullscreenWithinTab) {
// Simulate visible tab capture and enter fullscreen-within-tab.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
base::ScopedClosureRunner capture_closure =
tab->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/false, /*is_activity=*/true);
tab->GetDelegate()->EnterFullscreenModeForTab(tab->GetPrimaryMainFrame(), {});
EXPECT_EQ(tab->GetDelegate()->GetFullscreenState(tab).target_mode,
content::FullscreenMode::kPseudoContent);
EXPECT_TRUE(tab->IsFullscreen());
// Open a popup, which is activated. The opener remains fullscreen-within-tab.
BrowserList* browser_list = BrowserList::GetInstance();
EXPECT_EQ(1u, browser_list->size());
content::ExecuteScriptAsync(tab, "open('.', '', 'popup')");
Browser* popup = ui_test_utils::WaitForBrowserToOpen();
ASSERT_TRUE(popup);
ui_test_utils::WaitUntilBrowserBecomeActive(popup);
EXPECT_EQ(2u, browser_list->size());
EXPECT_EQ(tab->GetDelegate()->GetFullscreenState(tab).target_mode,
content::FullscreenMode::kPseudoContent);
}
IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
BlockingContentsDoesNotExitFullscreenWithinTab) {
// Simulate visible tab capture and enter fullscreen-within-tab.
WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
base::ScopedClosureRunner capture_closure =
tab->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/false, /*is_activity=*/true);
tab->GetDelegate()->EnterFullscreenModeForTab(tab->GetPrimaryMainFrame(), {});
EXPECT_EQ(tab->GetDelegate()->GetFullscreenState(tab).target_mode,
content::FullscreenMode::kPseudoContent);
EXPECT_TRUE(tab->IsFullscreen());
// Blocking the tab for a modal dialog does not exit fullscreen-within-tab.
static_cast<web_modal::WebContentsModalDialogManagerDelegate*>(browser())
->SetWebContentsBlocked(tab, true);
EXPECT_EQ(tab->GetDelegate()->GetFullscreenState(tab).target_mode,
content::FullscreenMode::kPseudoContent);
}
// Tests the automatic fullscreen content setting in IWA and non-IWA contexts.
class AutomaticFullscreenTest : public FullscreenControllerInteractiveTest,
public testing::WithParamInterface<bool> {
public:
AutomaticFullscreenTest() {
feature_list_.InitWithFeatures(
{features::kIsolatedWebApps, features::kIsolatedWebAppDevMode,
features::kAutomaticFullscreenContentSetting},
{});
}
void SetUpOnMainThread() override {
auto allow_automatic_fullscreen = [&](const GURL& url) {
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->SetContentSettingDefaultScope(
url, url, ContentSettingsType::AUTOMATIC_FULLSCREEN,
CONTENT_SETTING_ALLOW);
};
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
if (GetParam()) {
embedded_https_test_server().ServeFilesFromSourceDirectory(
GetChromeTestDataDir().AppendASCII("web_apps/simple_isolated_app"));
ASSERT_TRUE(embedded_https_test_server().Start());
auto url_info = web_app::InstallDevModeProxyIsolatedWebApp(
browser()->profile(), embedded_https_test_server().GetOrigin());
allow_automatic_fullscreen(url_info.origin().GetURL());
auto* frame =
web_app::OpenIsolatedWebApp(browser()->profile(), url_info.app_id());
web_contents_ = content::WebContents::FromRenderFrameHost(frame);
} else {
ASSERT_TRUE(embedded_https_test_server().Start());
GURL url = embedded_https_test_server().GetURL("a.com", "/simple.html");
allow_automatic_fullscreen(url);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
}
ASSERT_TRUE(WaitForRenderFrameReady(web_contents_->GetPrimaryMainFrame()));
}
void TearDownOnMainThread() override { web_contents_ = nullptr; }
bool RequestFullscreen(bool gesture = false,
content::RenderFrameHost* rfh = nullptr) {
static constexpr char kScript[] = R"JS(
(async () => {
try { await document.body.requestFullscreen(); } catch {}
return !!document.fullscreenElement;
})();
)JS";
auto options = gesture ? content::EXECUTE_SCRIPT_DEFAULT_OPTIONS
: content::EXECUTE_SCRIPT_NO_USER_GESTURE;
rfh = rfh ? rfh : web_contents_->GetPrimaryMainFrame();
content::WebContents* tab = content::WebContents::FromRenderFrameHost(rfh);
Browser* browser = chrome::FindBrowserWithTab(tab);
if (!gesture) {
// Ensure nothing inadvertently triggered user activation beforehand.
EXPECT_EQ(false, EvalJs(rfh, "navigator.userActivation.isActive",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
ui_test_utils::FullscreenWaiter waiter(browser, {.tab_fullscreen = true});
auto result = EvalJs(rfh, kScript, options);
if (result.error.empty() && result.ExtractBool()) {
waiter.Wait();
}
return browser->window()->IsFullscreen();
}
bool ExitFullscreen(WebContents* web_contents = nullptr) {
web_contents = web_contents ? web_contents : web_contents_.get();
Browser* browser = chrome::FindBrowserWithTab(web_contents);
ui_test_utils::FullscreenWaiter waiter(browser, {.tab_fullscreen = false});
const std::string script = R"((() => {
window.lastExit = Date.now();
return document.exitFullscreen();
})())";
// A user gesture is not needed and may break subsequent activation checks.
auto result =
EvalJs(web_contents, script, content::EXECUTE_SCRIPT_NO_USER_GESTURE);
waiter.Wait();
return result.error.empty() && !browser->window()->IsFullscreen();
}
std::pair<bool, Browser*> OpenPopupAndRequestFullscreenOnLoad() {
ui_test_utils::BrowserChangeObserver popup_observer(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
const std::string script = R"((() => {
let w = open(location.href, '', 'popup');
return new Promise(resolve => {
w.onload = async () => {
try { await w.document.body.requestFullscreen(); } catch {}
resolve(!!w.document.fullscreenElement);
};
});
})())";
Browser* browser = chrome::FindBrowserWithTab(web_contents_);
auto result = EvalJs(web_contents_, script);
Browser* popup = popup_observer.Wait();
if (!popup) {
return std::make_pair(false, nullptr);
}
EXPECT_NE(popup, browser);
ui_test_utils::WaitUntilBrowserBecomeActive(popup);
ui_test_utils::FullscreenWaiter waiter(popup, {.tab_fullscreen = true});
if (result.error.empty() && result.ExtractBool()) {
waiter.Wait();
}
return std::make_pair(popup->window()->IsFullscreen(), popup);
}
std::string QueryPermission(
const content::ToRenderFrameHost& target,
std::optional<bool> allow_without_gesture = true) {
const std::string options =
allow_without_gesture.has_value()
? content::JsReplace(", allowWithoutGesture: $1",
allow_without_gesture.value())
: "";
const std::string descriptor = "{name: 'fullscreen'" + options + "}";
const std::string script =
"navigator.permissions.query(" + descriptor +
").then(permission => permission.state).catch(e => e.name);";
return EvalJs(target, script, content::EXECUTE_SCRIPT_NO_USER_GESTURE)
.ExtractString();
}
protected:
raw_ptr<content::WebContents> web_contents_ = nullptr;
private:
base::test::ScopedFeatureList feature_list_;
web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
};
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, RequestFullscreenNoGesture) {
base::HistogramTester histograms;
EXPECT_TRUE(RequestFullscreen());
// Navigate away in order to flush use counters.
Browser* browser = chrome::FindBrowserWithTab(web_contents_);
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, GURL(url::kAboutBlankURL)));
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
if (!GetParam()) { // TODO(crbug.com/41497058): Test use counter in IWA too.
histograms.ExpectBucketCount(
"Blink.UseCounter.Features",
blink::mojom::WebFeature::kFullscreenAllowedByContentSetting, 1);
}
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, ImmediatelyAfterExit) {
EXPECT_TRUE(RequestFullscreen());
const base::TimeTicks exit = base::TimeTicks::Now();
EXPECT_TRUE(ExitFullscreen());
EXPECT_LT(base::TimeTicks::Now() - exit, base::Seconds(5));
EXPECT_FALSE(RequestFullscreen());
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, WithGestureAfterExit) {
EXPECT_TRUE(RequestFullscreen());
EXPECT_TRUE(ExitFullscreen());
EXPECT_TRUE(RequestFullscreen(/*gesture=*/true));
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, EventuallyAfterExit) {
EXPECT_TRUE(RequestFullscreen());
EXPECT_TRUE(ExitFullscreen());
base::RunLoop run_loop;
// TODO(crbug.com/333133285): Avoid waiting this long in wall-clock time.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(5300));
run_loop.Run();
EXPECT_TRUE(RequestFullscreen());
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, Popup) {
EXPECT_TRUE(OpenPopupAndRequestFullscreenOnLoad().first);
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, PopupImmediatelyAfterExit) {
EXPECT_TRUE(RequestFullscreen());
const base::TimeTicks exit = base::TimeTicks::Now();
EXPECT_TRUE(ExitFullscreen());
EXPECT_LT(base::TimeTicks::Now() - exit, base::Seconds(5));
EXPECT_FALSE(OpenPopupAndRequestFullscreenOnLoad().first);
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, PopupEventuallyAfterExit) {
EXPECT_TRUE(RequestFullscreen());
EXPECT_TRUE(ExitFullscreen());
base::RunLoop run_loop;
// TODO(crbug.com/333133285): Avoid waiting this long in wall-clock time.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(5300));
run_loop.Run();
EXPECT_TRUE(OpenPopupAndRequestFullscreenOnLoad().first);
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, ImmediatelyAfterPopupExit) {
auto [success, popup] = OpenPopupAndRequestFullscreenOnLoad();
EXPECT_TRUE(success);
ASSERT_TRUE(popup);
const base::TimeTicks exit = base::TimeTicks::Now();
ExitFullscreen(popup->tab_strip_model()->GetActiveWebContents());
EXPECT_LT(base::TimeTicks::Now() - exit, base::Seconds(5));
EXPECT_FALSE(RequestFullscreen());
popup->window()->Close();
ui_test_utils::WaitForBrowserToClose(popup);
EXPECT_LT(base::TimeTicks::Now() - exit, base::Seconds(5));
EXPECT_FALSE(RequestFullscreen());
EXPECT_TRUE(RequestFullscreen(/*gesture=*/true));
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, EventuallyAfterPopupExit) {
auto [success, popup] = OpenPopupAndRequestFullscreenOnLoad();
EXPECT_TRUE(success);
ASSERT_TRUE(popup);
ExitFullscreen(popup->tab_strip_model()->GetActiveWebContents());
base::RunLoop run_loop;
// TODO(crbug.com/333133285): Avoid waiting this long in wall-clock time.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(5300));
run_loop.Run();
EXPECT_TRUE(RequestFullscreen());
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, BlockingContentsDoesNotExit) {
EXPECT_TRUE(RequestFullscreen());
EXPECT_TRUE(web_contents_->IsFullscreen());
// Blocking the tab for a modal dialog does not exit fullscreen if the origin
// has been granted the automatic fullscreen content setting.
Browser* browser = chrome::FindBrowserWithTab(web_contents_);
static_cast<web_modal::WebContentsModalDialogManagerDelegate*>(browser)
->SetWebContentsBlocked(web_contents_, true);
EXPECT_TRUE(web_contents_->IsFullscreen());
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, QueryPermissionWithGesture) {
// Expect an API TypeError when allowWithoutGesture is false or unspecified.
EXPECT_EQ(
"TypeError",
QueryPermission(web_contents_, /*allow_without_gesture=*/std::nullopt));
EXPECT_EQ("TypeError",
QueryPermission(web_contents_, /*allow_without_gesture=*/false));
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, QueryPermissionWithoutGesture) {
// Permission is pre-granted on the initial test origin and denied elsewhere.
EXPECT_EQ("granted", QueryPermission(web_contents_));
const GURL url = embedded_https_test_server().GetURL("b.com", "/simple.html");
content::RenderFrameHost* rfh = ui_test_utils::NavigateToURL(browser(), url);
EXPECT_EQ("denied", QueryPermission(rfh));
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, CrossOriginIFrameDenied) {
// Append a cross-origin iframe without the permission policy.
const GURL src = embedded_https_test_server().GetURL("b.com", "/simple.html");
content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"");
content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
EXPECT_EQ("denied", QueryPermission(child));
EXPECT_FALSE(RequestFullscreen(/*gesture=*/false, child));
}
IN_PROC_BROWSER_TEST_P(AutomaticFullscreenTest, CrossOriginIFrameGranted) {
// Append a cross-origin iframe with the permission policy.
const GURL src = embedded_https_test_server().GetURL("b.com", "/simple.html");
content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
web_app::CreateIframe(rfh, "", src, /*permissions_policy=*/"fullscreen *");
content::RenderFrameHost* child = ChildFrameAt(rfh, 0);
EXPECT_EQ("granted", QueryPermission(child));
EXPECT_TRUE(RequestFullscreen(child));
EXPECT_TRUE(ExitFullscreen());
}
INSTANTIATE_TEST_SUITE_P(, AutomaticFullscreenTest, ::testing::Bool());
// Configures a two-display screen environment for testing of multi-screen
// fullscreen behavior.
class TestScreenEnvironment {
public:
#if BUILDFLAG(IS_CHROMEOS_ASH)
TestScreenEnvironment() = default;
~TestScreenEnvironment() = default;
#else
TestScreenEnvironment() {
#if BUILDFLAG(IS_MAC)
ns_window_faked_for_testing_ = ui::NSWindowFakedForTesting::IsEnabled();
// Disable `NSWindowFakedForTesting` to wait for actual async fullscreen on
// Mac via `FullscreenWaiter`.
ui::NSWindowFakedForTesting::SetEnabled(false);
#elif !BUILDFLAG(IS_WIN)
screen_.display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)},
display::DisplayList::Type::PRIMARY);
display::Screen::SetScreenInstance(&screen_);
#endif // BUILDFLAG(IS_MAC)
}
~TestScreenEnvironment() {
#if BUILDFLAG(IS_MAC)
ui::NSWindowFakedForTesting::SetEnabled(ns_window_faked_for_testing_);
#elif !BUILDFLAG(IS_WIN)
display::Screen::SetScreenInstance(nullptr);
#endif // BUILDFLAG(IS_MAC)
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
TestScreenEnvironment(const TestScreenEnvironment&) = delete;
TestScreenEnvironment& operator=(const TestScreenEnvironment&) = delete;
// Set up a test Screen environment with at least two displays after
// `display::Screen` has been initialized.
void SetUp() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
.UpdateDisplay("100+100-801x802,901+0-802x803");
secondary_display_id_ =
ash::Shell::Get()->display_manager()->GetConnectedDisplayIdList()[1];
#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
if ((virtual_display_util_ = display::test::VirtualDisplayUtil::TryCreate(
display::Screen::GetScreen()))) {
secondary_display_id_ = virtual_display_util_->AddDisplay(
display::test::VirtualDisplayUtil::k1024x768);
} else {
GTEST_SKIP() << "Skipping test; unavailable multi-screen support.";
}
#else
screen_.display_list().AddDisplay({2, gfx::Rect(901, 0, 802, 803)},
display::DisplayList::Type::NOT_PRIMARY);
secondary_display_id_ = 2;
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
ASSERT_GE(display::Screen::GetScreen()->GetNumDisplays(), 2);
}
// Tear down the test Screen environment before `display::Screen` has shut
// down.
void TearDown() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
virtual_display_util_.reset();
#endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
}
void RemoveSecondDisplay() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
.UpdateDisplay("100+100-801x802");
#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
virtual_display_util_->RemoveDisplay(secondary_display_id_);
#else
screen_.display_list().RemoveDisplay(2);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
int64_t secondary_display_id() const { return secondary_display_id_; }
private:
int64_t secondary_display_id_ = display::kInvalidDisplayId;
#if BUILDFLAG(IS_MAC)
bool ns_window_faked_for_testing_ = false;
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
std::unique_ptr<display::test::VirtualDisplayUtil> virtual_display_util_;
#elif !BUILDFLAG(IS_CHROMEOS_ASH)
display::ScreenBase screen_;
#endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
};
// Tests fullscreen with multi-screen features from the Window Management API.
// Sites with the Window Management permission can request fullscreen on a
// specific screen, move fullscreen windows to different displays, and more.
// Tests must run in series to manage virtual displays on supported platforms.
// Use 2+ physical displays to run locally with --gtest_also_run_disabled_tests.
// See: //docs/ui/display/multiscreen_testing.md
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#define MAYBE_MultiScreenFullscreenControllerInteractiveTest \
MultiScreenFullscreenControllerInteractiveTest
#else
#define MAYBE_MultiScreenFullscreenControllerInteractiveTest \
DISABLED_MultiScreenFullscreenControllerInteractiveTest
#endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
class MAYBE_MultiScreenFullscreenControllerInteractiveTest
: public FullscreenControllerInteractiveTest {
public:
void SetUp() override {
// Set a test Screen instance before the browser `SetUp`.
test_screen_environment_ = std::make_unique<TestScreenEnvironment>();
FullscreenControllerInteractiveTest::SetUp();
}
void TearDown() override {
FullscreenControllerInteractiveTest::TearDown();
// Unset the test Screen instance after the browser `TearDown`.
test_screen_environment_.reset();
}
void SetUpOnMainThread() override { test_screen_environment_->SetUp(); }
void TearDownOnMainThread() override { test_screen_environment_->TearDown(); }
void UpdateScreenEnvironment() {
test_screen_environment_->RemoveSecondDisplay();
}
// Get a new tab that observes the test screen environment and auto-accepts
// Window Management permission prompts.
content::WebContents* SetUpWindowManagementTab() {
// Open a new tab that observes the test screen environment.
EXPECT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/simple.html"));
EXPECT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_TYPED));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Auto-accept Window Management permission prompts.
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
permission_request_manager->set_auto_response_for_test(
permissions::PermissionRequestManager::ACCEPT_ALL);
return tab;
}
// Get the display matching the `browser`'s current window bounds.
display::Display GetCurrentDisplay(Browser* browser) const {
return display::Screen::GetScreen()->GetDisplayMatching(
browser->window()->GetBounds());
}
// Wait for a JS content fullscreen change with the given script and options.
// Returns the script result.
content::EvalJsResult RequestContentFullscreenFromScript(
const std::string& eval_js_script,
bool expect_fullscreen,
int eval_js_options = content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
bool expect_window_fullscreen = true,
std::optional<int64_t> display_id = std::nullopt) {
ui_test_utils::FullscreenWaiter waiter(
browser(),
{.tab_fullscreen = expect_fullscreen, .display_id = display_id});
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::EvalJsResult result = EvalJs(tab, eval_js_script, eval_js_options);
waiter.Wait();
EXPECT_EQ(expect_window_fullscreen, browser()->window()->IsFullscreen());
return result;
}
// Execute JS to request content fullscreen on the current screen.
void RequestContentFullscreen() {
const std::string script = R"JS(
(async () => {
await document.body.requestFullscreen();
return !!document.fullscreenElement;
})();
)JS";
EXPECT_EQ(true, RequestContentFullscreenFromScript(script, true));
}
// Execute JS to request content fullscreen on a different screen from where
// the window is currently located.
void RequestContentFullscreenOnAnotherScreen() {
const std::string script = R"JS(
(async () => {
if (!window.screenDetails)
window.screenDetails = await window.getScreenDetails();
const otherScreen = window.screenDetails.screens.find(
s => s !== window.screenDetails.currentScreen);
const options = { screen: otherScreen };
await document.body.requestFullscreen(options);
return !!document.fullscreenElement;
})();
)JS";
EXPECT_EQ(true,
RequestContentFullscreenFromScript(
script, true, content::EXECUTE_SCRIPT_DEFAULT_OPTIONS, true,
test_screen_environment_->secondary_display_id()));
}
// Execute JS to exit content fullscreen.
void ExitContentFullscreen(bool expect_window_fullscreen = false) {
const std::string script = R"JS(
(async () => {
await document.exitFullscreen();
return !!document.fullscreenElement;
})();
)JS";
// Exiting fullscreen does not require a user gesture; do not supply one.
EXPECT_EQ(false, RequestContentFullscreenFromScript(
script, false, content::EXECUTE_SCRIPT_NO_USER_GESTURE,
expect_window_fullscreen));
}
// Awaits expiry of the navigator.userActivation signal on the active tab.
void WaitForUserActivationExpiry() {
const std::string await_activation_expiry_script = R"(
(async () => {
while (navigator.userActivation.isActive)
await new Promise(resolve => setTimeout(resolve, 1000));
return navigator.userActivation.isActive;
})();
)";
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(false, EvalJs(tab, await_activation_expiry_script,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_FALSE(tab->HasRecentInteraction());
}
private:
std::unique_ptr<TestScreenEnvironment> test_screen_environment_;
};
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_SeparateDisplay SeparateDisplay
#else
#define MAYBE_SeparateDisplay DISABLED_SeparateDisplay
#endif
// Test requesting fullscreen on a separate display.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_SeparateDisplay) {
SetUpWindowManagementTab();
#if !BUILDFLAG(IS_MAC)
const gfx::Rect original_bounds = browser()->window()->GetBounds();
#endif
const display::Display original_display = GetCurrentDisplay(browser());
// Execute JS to request fullscreen on a different screen.
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(original_display.id(), GetCurrentDisplay(browser()).id());
ExitContentFullscreen();
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(original_bounds, browser()->window()->GetBounds());
#endif
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_SeparateDisplayMaximized SeparateDisplayMaximized
#else
#define MAYBE_SeparateDisplayMaximized DISABLED_SeparateDisplayMaximized
#endif
// Test requesting fullscreen on a separate display from a maximized window.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_SeparateDisplayMaximized) {
SetUpWindowManagementTab();
#if !BUILDFLAG(IS_MAC)
const gfx::Rect original_bounds = browser()->window()->GetBounds();
#endif
const display::Display original_display = GetCurrentDisplay(browser());
browser()->window()->Maximize();
EXPECT_TRUE(browser()->window()->IsMaximized());
#if !BUILDFLAG(IS_MAC)
const gfx::Rect maximized_bounds = browser()->window()->GetBounds();
#endif
// Execute JS to request fullscreen on a different screen.
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(original_display.id(), GetCurrentDisplay(browser()).id());
ExitContentFullscreen();
EXPECT_TRUE(browser()->window()->IsMaximized());
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(maximized_bounds, browser()->window()->GetBounds());
#endif
// Unmaximize the window and check that the original bounds are restored.
browser()->window()->Restore();
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(original_bounds, browser()->window()->GetBounds());
#endif
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_SameDisplayAndSwap SameDisplayAndSwap
#else
#define MAYBE_SameDisplayAndSwap DISABLED_SameDisplayAndSwap
#endif
// Test requesting fullscreen on the current display and then swapping displays.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_SameDisplayAndSwap) {
SetUpWindowManagementTab();
#if !BUILDFLAG(IS_MAC)
const gfx::Rect original_bounds = browser()->window()->GetBounds();
#endif
const display::Display original_display = GetCurrentDisplay(browser());
// Execute JS to request fullscreen on the current screen.
RequestContentFullscreen();
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// Execute JS to request fullscreen on a different screen.
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(original_display.id(), GetCurrentDisplay(browser()).id());
ExitContentFullscreen();
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(original_bounds, browser()->window()->GetBounds());
#endif
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_SameDisplayAndSwapMaximized SameDisplayAndSwapMaximized
#else
#define MAYBE_SameDisplayAndSwapMaximized DISABLED_SameDisplayAndSwapMaximized
#endif
// Test requesting fullscreen on the current display and then swapping displays
// from a maximized window.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_SameDisplayAndSwapMaximized) {
SetUpWindowManagementTab();
#if !BUILDFLAG(IS_MAC)
const gfx::Rect original_bounds = browser()->window()->GetBounds();
#endif
const display::Display original_display = GetCurrentDisplay(browser());
browser()->window()->Maximize();
EXPECT_TRUE(browser()->window()->IsMaximized());
#if !BUILDFLAG(IS_MAC)
const gfx::Rect maximized_bounds = browser()->window()->GetBounds();
#endif
// Execute JS to request fullscreen on the current screen.
RequestContentFullscreen();
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// Execute JS to request fullscreen on a different screen.
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(original_display.id(), GetCurrentDisplay(browser()).id());
ExitContentFullscreen();
EXPECT_TRUE(browser()->window()->IsMaximized());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(maximized_bounds, browser()->window()->GetBounds());
#endif
// Unmaximize the window and check that the original bounds are restored.
browser()->window()->Restore();
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(original_bounds, browser()->window()->GetBounds());
#endif
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_BrowserFullscreenContentFullscreenSwapDisplay \
BrowserFullscreenContentFullscreenSwapDisplay
#else
#define MAYBE_BrowserFullscreenContentFullscreenSwapDisplay \
DISABLED_BrowserFullscreenContentFullscreenSwapDisplay
#endif
// Test requesting browser fullscreen on current display, launching
// tab-fullscreen on a different display, and then closing tab-fullscreen to
// restore browser-fullscreen on the original display.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_BrowserFullscreenContentFullscreenSwapDisplay) {
SetUpWindowManagementTab();
ToggleBrowserFullscreen(true);
EXPECT_TRUE(IsFullscreenForBrowser());
EXPECT_FALSE(IsWindowFullscreenForTabOrPending());
const gfx::Rect fullscreen_bounds = browser()->window()->GetBounds();
const display::Display original_display = GetCurrentDisplay(browser());
// On the Mac, the available fullscreen space is not always the entire
// screen. In non-immersive, on machines with a notch, the menu bar is not
// visible, but there's a black bar at the top of the screen. In immersive
// fullscreen, the top chrome appears to be part of the browser window but
// is actually in a separate widget/window (the overlay widget) positioned
// just above. The fullscreen bounds rect is therefore reduced in height
// by the notch bar (maybe) and top chrome.
//
// What should always be true is the left, right, and bottom sides of the
// fullscreen bounds match the those portions of the display bounds. The
// top is trickier. By using the "work area," we should be able to take the
// menu bar area out of the equation. Ideally, we would just check that
// fullscreen_bounds.y - overlay_widget.height == display.work_area.bottom.
// However, at this location in the source tree, we are not allowed to know
// anything about Views or widgets, so we cannot access the overlay_widget
// to query its frame. The most we can, therefore, say, is that the top
// of the fullscreen bounds must be greater than or equal to the bottom of
// the display bounds.
#if BUILDFLAG(IS_MAC)
EXPECT_LE(original_display.work_area().y(), fullscreen_bounds.y());
EXPECT_EQ(original_display.work_area().x(), fullscreen_bounds.x());
EXPECT_EQ(original_display.work_area().right(), fullscreen_bounds.right());
EXPECT_EQ(original_display.work_area().bottom(), fullscreen_bounds.bottom());
#else
EXPECT_EQ(original_display.bounds(), fullscreen_bounds);
#endif // BUILDFLAG(IS_MAC)
// Execute JS to request fullscreen on a different screen.
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(original_display.id(), GetCurrentDisplay(browser()).id());
// Fullscreen was originally initiated by browser, this should still be true.
EXPECT_TRUE(IsFullscreenForBrowser());
EXPECT_TRUE(IsWindowFullscreenForTabOrPending());
ExitContentFullscreen(/*expect_window_fullscreen=*/true);
EXPECT_EQ(fullscreen_bounds, browser()->window()->GetBounds());
EXPECT_TRUE(IsFullscreenForBrowser());
EXPECT_FALSE(IsWindowFullscreenForTabOrPending());
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_SeparateDisplayAndSwap SeparateDisplayAndSwap
#else
#define MAYBE_SeparateDisplayAndSwap DISABLED_SeparateDisplayAndSwap
#endif
// Test requesting fullscreen on a separate display and then swapping displays.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_SeparateDisplayAndSwap) {
SetUpWindowManagementTab();
#if !BUILDFLAG(IS_MAC)
const gfx::Rect original_bounds = browser()->window()->GetBounds();
#endif
const display::Display original_display = GetCurrentDisplay(browser());
display::Display last_recorded_display = original_display;
// Execute JS to request fullscreen on a different screen a few times.
for (size_t i = 0; i < 4; ++i) {
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(last_recorded_display.id(), GetCurrentDisplay(browser()).id());
last_recorded_display = GetCurrentDisplay(browser());
}
ExitContentFullscreen();
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// TODO(crbug.com/40277425): Bounds are flaky on Mac.
#if !BUILDFLAG(IS_MAC)
EXPECT_EQ(original_bounds, browser()->window()->GetBounds());
#endif
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Linux, where the
// window server's async handling of the fullscreen window state may transition
// the window into fullscreen on the actual (non-mocked) display bounds before
// or after the window bounds checks, yielding flaky results.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_SwapShowsBubble SwapShowsBubble
#else
#define MAYBE_SwapShowsBubble DISABLED_SwapShowsBubble
#endif
// Test requesting fullscreen on the current display and then swapping displays.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_SwapShowsBubble) {
SetUpWindowManagementTab();
// Execute JS to request fullscreen on the current screen.
RequestContentFullscreen();
const display::Display original_display = GetCurrentDisplay(browser());
// Explicitly check for, and destroy, the exclusive access bubble.
EXPECT_TRUE(IsExclusiveAccessBubbleDisplayed());
base::RunLoop run_loop;
ExclusiveAccessBubbleHideCallback callback = base::BindLambdaForTesting(
[&run_loop](ExclusiveAccessBubbleHideReason) { run_loop.Quit(); });
browser()->exclusive_access_manager()->context()->UpdateExclusiveAccessBubble(
{}, std::move(callback));
run_loop.Run();
EXPECT_FALSE(IsExclusiveAccessBubbleDisplayed());
// Execute JS to request fullscreen on a different screen.
RequestContentFullscreenOnAnotherScreen();
EXPECT_NE(original_display.id(), GetCurrentDisplay(browser()).id());
// Ensure the exclusive access bubble is re-shown on fullscreen display swap.
EXPECT_TRUE(IsExclusiveAccessBubbleDisplayed());
}
// TODO(crbug.com/40723237): Disabled on Windows, where RenderWidgetHostViewAura
// blindly casts display::Screen::GetScreen() to display::win::ScreenWin*.
#if BUILDFLAG(IS_WIN)
#define MAYBE_FullscreenOnPermissionGrant DISABLED_FullscreenOnPermissionGrant
#else
#define MAYBE_FullscreenOnPermissionGrant FullscreenOnPermissionGrant
#endif
// Test requesting fullscreen using the permission grant's transient activation.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_FullscreenOnPermissionGrant) {
EXPECT_TRUE(embedded_test_server()->Start());
const GURL url(embedded_test_server()->GetURL("/simple.html"));
ASSERT_TRUE(AddTabAtIndex(1, url, ui::PAGE_TRANSITION_TYPED));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
// Request the Window Management permission and accept the prompt after user
// activation expires; accepting should grant a new transient activation
// signal that can be used to request fullscreen, without another gesture.
ExecuteScriptAsync(tab, "getScreenDetails()");
WaitForUserActivationExpiry();
ASSERT_TRUE(permission_request_manager->IsRequestInProgress());
permission_request_manager->Accept();
const std::string script = R"(
(async () => {
await document.body.requestFullscreen();
return !!document.fullscreenElement;
})();
)";
EXPECT_EQ(true, RequestContentFullscreenFromScript(
script, true, content::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Mac and Linux,
// where the window server's async handling of the fullscreen window state may
// transition the window into fullscreen on the actual (non-mocked) display
// bounds before or after the window bounds checks, yielding flaky results.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_OpenPopupWhileFullscreen DISABLED_OpenPopupWhileFullscreen
#else
#define MAYBE_OpenPopupWhileFullscreen OpenPopupWhileFullscreen
#endif
// Test opening a popup on a separate display while fullscreen.
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_OpenPopupWhileFullscreen) {
content::WebContents* tab = SetUpWindowManagementTab();
const display::Display original_display = GetCurrentDisplay(browser());
BrowserList* browser_list = BrowserList::GetInstance();
EXPECT_EQ(1u, browser_list->size());
blocked_content::PopupBlockerTabHelper* popup_blocker =
blocked_content::PopupBlockerTabHelper::FromWebContents(tab);
EXPECT_EQ(0u, popup_blocker->GetBlockedPopupsCount());
// Execute JS to request fullscreen on the current screen.
RequestContentFullscreen();
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
// Execute JS to open a popup on a different screen.
const std::string script = R"(
(async () => {
// Note: WindowManagementPermissionContext will send an activation signal.
window.screenDetails = await window.getScreenDetails();
const otherScreen = window.screenDetails.screens.find(
s => s !== window.screenDetails.currentScreen);
const l = otherScreen.availLeft + 100;
const t = otherScreen.availTop + 100;
const w = window.open('', '', `left=${l},top=${t},width=300,height=300`);
// Return true iff the opener is fullscreen and the popup is open.
return !!document.fullscreenElement && !!w && !w.closed;
})();
)";
content::ExecuteScriptAsync(tab, script);
Browser* popup = ui_test_utils::WaitForBrowserToOpen();
EXPECT_NE(popup, browser());
auto* popup_contents = popup->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(WaitForRenderFrameReady(popup_contents->GetPrimaryMainFrame()));
EXPECT_EQ(0u, popup_blocker->GetBlockedPopupsCount());
EXPECT_EQ(2u, browser_list->size());
EXPECT_EQ(original_display.id(), GetCurrentDisplay(browser()).id());
EXPECT_NE(original_display.id(), GetCurrentDisplay(popup).id());
// The opener should still be fullscreen.
EXPECT_TRUE(IsWindowFullscreenForTabOrPending());
// Popup window activation is delayed until its opener exits fullscreen.
EXPECT_FALSE(ui_test_utils::IsBrowserActive(popup));
ToggleTabFullscreen(/*enter_fullscreen=*/false);
ui_test_utils::BrowserActivationWaiter(popup).WaitForActivation();
EXPECT_TRUE(ui_test_utils::IsBrowserActive(popup));
}
// TODO(crbug.com/40111905): Disabled on Windows, where views::FullscreenHandler
// implements fullscreen by directly obtaining MONITORINFO, ignoring the mocked
// display::Screen configuration used in this test. Disabled on Mac and Linux,
// where the window server's async handling of the fullscreen window state may
// transition the window into fullscreen on the actual (non-mocked) display
// bounds before or after the window bounds checks, yielding flaky results.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_FullscreenCompanionWindow DISABLED_FullscreenCompanionWindow
#else
#define MAYBE_FullscreenCompanionWindow FullscreenCompanionWindow
#endif
// Test requesting fullscreen on a specific screen and opening a cross-screen
// popup window from one gesture. Check the expected window activation pattern.
// https://w3c.github.io/window-management/#usage-overview-initiate-multi-screen-experiences
IN_PROC_BROWSER_TEST_F(MAYBE_MultiScreenFullscreenControllerInteractiveTest,
MAYBE_FullscreenCompanionWindow) {
content::WebContents* tab = SetUpWindowManagementTab();
BrowserList* browser_list = BrowserList::GetInstance();
EXPECT_EQ(1u, browser_list->size());
blocked_content::PopupBlockerTabHelper* popup_blocker =
blocked_content::PopupBlockerTabHelper::FromWebContents(tab);
EXPECT_EQ(0u, popup_blocker->GetBlockedPopupsCount());
// Execute JS to request fullscreen and open a popup on separate screens.
const std::string script = R"(
(async () => {
// Note: WindowManagementPermissionContext will send an activation signal.
window.screenDetails = await window.getScreenDetails();
const fullscreen_change_promise = new Promise(resolve => {
function waitAndRemove(e) {
document.removeEventListener("fullscreenchange", waitAndRemove);
document.removeEventListener("fullscreenerror", waitAndRemove);
resolve(document.fullscreenElement);
}
document.addEventListener("fullscreenchange", waitAndRemove);
document.addEventListener("fullscreenerror", waitAndRemove);
});
// Request fullscreen and ensure that transient activation is consumed.
const options = { screen: window.screenDetails.screens[0] };
const fullscreen_promise = document.body.requestFullscreen(options);
if (navigator.userActivation.isActive) {
console.error("Transient activation unexpectedly not consumed");
return false;
}
// Attempt to open a fullscreen companion window.
const s = window.screenDetails.screens[1];
const f = `left=${s.availLeft},top=${s.availTop},width=300,height=200`;
const w = window.open('.', '', f);
// Now await the fullscreen promise and change (or error) event.
await fullscreen_promise;
if (!await fullscreen_change_promise) {
console.error("Unexpected fullscreen change or error");
return false;
}
// Return true iff the opener is fullscreen and the popup is open.
return !!document.fullscreenElement && !!w && !w.closed;
})();
)";
EXPECT_TRUE(RequestContentFullscreenFromScript(script, true).ExtractBool());
EXPECT_TRUE(IsWindowFullscreenForTabOrPending());
EXPECT_EQ(0u, popup_blocker->GetBlockedPopupsCount());
EXPECT_EQ(2u, browser_list->size());
Browser* popup = browser_list->get(1);
EXPECT_NE(browser(), popup);
EXPECT_NE(GetCurrentDisplay(browser()).id(), GetCurrentDisplay(popup).id());
// Popup window activation is delayed until its opener exits fullscreen.
EXPECT_FALSE(ui_test_utils::IsBrowserActive(popup));
ToggleTabFullscreen(/*enter_fullscreen=*/false);
ui_test_utils::BrowserActivationWaiter(popup).WaitForActivation();
EXPECT_TRUE(ui_test_utils::IsBrowserActive(popup));
}