blob: cf9362686934410d1bd7165dd6c393b30c9933bf [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/command_line.h"
#include "base/test/run_until.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/test/fullscreen_test_util.h"
#include "chrome/browser/ui/test/popup_test_base.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/test/virtual_display_util.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"
namespace {
// Tests popups with multi-screen features from the Window Management API.
// Tests are run with and without the requisite Window Management permission.
// 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) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#define MAYBE_PopupMultiScreenTest PopupMultiScreenTest
#else
#define MAYBE_PopupMultiScreenTest DISABLED_PopupMultiScreenTest
#endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/371121282): Re-enable the test.
// TODO(crbug.com/365126887): Re-enable the test.
#undef MAYBE_PopupMultiScreenTest
#define MAYBE_PopupMultiScreenTest DISABLED_PopupMultiScreenTest
#endif // BUILDFLAG(IS_WIN)
class MAYBE_PopupMultiScreenTest : public PopupTestBase,
public ::testing::WithParamInterface<bool> {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
PopupTestBase::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
void SetUpOnMainThread() override {
if (!SetUpVirtualDisplays()) {
GTEST_SKIP() << "Skipping test; unavailable multi-screen support.";
}
ASSERT_GE(display::Screen::Get()->GetNumDisplays(), 2);
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(NavigateToURL(web_contents,
embedded_test_server()->GetURL("/simple.html")));
EXPECT_TRUE(WaitForRenderFrameReady(web_contents->GetPrimaryMainFrame()));
if (ShouldTestWindowManagement()) {
SetUpWindowManagement(browser());
}
}
void TearDownOnMainThread() override { virtual_display_util_.reset(); }
protected:
bool ShouldTestWindowManagement() { return GetParam(); }
// Create virtual displays as needed, ensuring 2 displays are available for
// testing multi-screen functionality. Not all platforms and OS versions are
// supported. Returns false if virtual displays could not be created.
bool SetUpVirtualDisplays() {
if (display::Screen::Get()->GetNumDisplays() > 1) {
return true;
}
if ((virtual_display_util_ = display::test::VirtualDisplayUtil::TryCreate(
display::Screen::Get()))) {
virtual_display_util_->AddDisplay(
display::test::VirtualDisplayUtil::k1024x768);
return true;
}
return false;
}
private:
std::unique_ptr<display::test::VirtualDisplayUtil> virtual_display_util_;
};
INSTANTIATE_TEST_SUITE_P(, MAYBE_PopupMultiScreenTest, ::testing::Bool());
// Tests opening a popup without explicit bounds.
IN_PROC_BROWSER_TEST_P(MAYBE_PopupMultiScreenTest, Basic) {
// Copy the display vector so references are not invalidated while looping.
std::vector<display::Display> displays =
display::Screen::Get()->GetAllDisplays();
for (const display::Display& opener_display : displays) {
browser()->window()->SetBounds(opener_display.work_area());
ASSERT_EQ(opener_display.id(), GetDisplayNearestBrowser(browser()).id());
for (const char* url : {"/simple.html", "about:blank"}) {
const std::string open_script =
content::JsReplace("open($1, '', 'popup');", url);
Browser* popup = OpenPopup(browser(), open_script);
display::Display popup_display = GetDisplayNearestBrowser(popup);
// The popup should open on the same screen as the opener.
EXPECT_EQ(opener_display.id(), popup_display.id())
<< " expected: " << opener_display.work_area().ToString()
<< " actual: " << popup_display.work_area().ToString()
<< " popup: " << popup->window()->GetBounds().ToString()
<< " script: " << open_script;
// The popup is constrained to the available bounds of its screen.
const gfx::Rect popup_bounds = popup->window()->GetBounds();
EXPECT_TRUE(popup_display.work_area().Contains(popup_bounds))
<< " work_area: " << popup_display.work_area().ToString()
<< " popup: " << popup_bounds.ToString();
}
}
}
// Tests opening a popup on another screen.
IN_PROC_BROWSER_TEST_P(MAYBE_PopupMultiScreenTest, OpenOnAnotherScreen) {
// Copy the display vector so references are not invalidated while looping.
std::vector<display::Display> displays =
display::Screen::Get()->GetAllDisplays();
for (const display::Display& opener_display : displays) {
browser()->window()->SetBounds(opener_display.work_area());
ASSERT_EQ(opener_display.id(), GetDisplayNearestBrowser(browser()).id());
for (const display::Display& target_display : displays) {
for (const char* url : {"/simple.html", "about:blank"}) {
const std::string open_script = content::JsReplace(
"open($1, '', 'left=$2,top=$3,width=200,height=200');", url,
target_display.work_area().x(), target_display.work_area().y());
Browser* popup = OpenPopup(browser(), open_script);
display::Display popup_display = GetDisplayNearestBrowser(popup);
// The popup only opens on another screen with permission.
const display::Display& expected_display =
ShouldTestWindowManagement() ? target_display : opener_display;
EXPECT_EQ(expected_display.id(), popup_display.id())
<< " expected: " << expected_display.work_area().ToString()
<< " actual: " << popup_display.work_area().ToString()
<< " opener: " << browser()->window()->GetBounds().ToString()
<< " popup: " << popup->window()->GetBounds().ToString()
<< " script: " << open_script;
// The popup is constrained to the available bounds of its screen.
const gfx::Rect popup_bounds = popup->window()->GetBounds();
EXPECT_TRUE(popup_display.work_area().Contains(popup_bounds))
<< " work_area: " << popup_display.work_area().ToString()
<< " popup: " << popup_bounds.ToString();
}
}
}
}
// Tests opening a popup on the same screen, then moving it to another screen.
// TODO(crbug.com/365057654): Test is failing on Mac bot.
IN_PROC_BROWSER_TEST_P(MAYBE_PopupMultiScreenTest,
DISABLED_MoveToAnotherScreen) {
content::WebContents* opener_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Copy the display vector so references are not invalidated while looping.
display::Screen* screen = display::Screen::Get();
std::vector<display::Display> displays = screen->GetAllDisplays();
for (const display::Display& opener_display : displays) {
browser()->window()->SetBounds(opener_display.work_area());
ASSERT_EQ(opener_display.id(), GetDisplayNearestBrowser(browser()).id());
gfx::Point opener_display_center = opener_display.work_area().CenterPoint();
for (const display::Display& target_display : displays) {
for (const char* url : {"/simple.html", "about:blank"}) {
const std::string open_script = content::JsReplace(
"w = open($1, '', 'left=$2,top=$3,width=200,height=200');", url,
opener_display_center.x() - 100, opener_display_center.y() - 100);
Browser* popup = OpenPopup(browser(), open_script);
EXPECT_EQ(opener_display, GetDisplayNearestBrowser(popup));
// Ensure the opener can access the popup window object.
ASSERT_NE("", EvalJs(opener_contents, "w.location.href"));
// Have the opener try to move the popup to the target screen.
const std::string move_script = content::JsReplace(
"w.moveTo($1, $2);", target_display.work_area().x(),
target_display.work_area().y());
{
SCOPED_TRACE(
testing::Message()
<< "\n"
<< "script: " << open_script << " " << move_script << "\n"
<< "opener: " << browser()->window()->GetBounds().ToString()
<< " popup: " << popup->window()->GetBounds().ToString());
content::ExecuteScriptAsync(opener_contents, move_script);
WaitForBoundsChange(popup, /*move_by=*/40, /*resize_by=*/0);
}
const display::Display popup_display = GetDisplayNearestBrowser(popup);
// The popup only moves to another screen with permission.
const display::Display& expected_display =
ShouldTestWindowManagement() ? target_display : opener_display;
EXPECT_EQ(expected_display.id(), popup_display.id())
<< " expected: " << expected_display.work_area().ToString()
<< " actual: " << popup_display.work_area().ToString()
<< " opener: " << browser()->window()->GetBounds().ToString()
<< " popup: " << popup->window()->GetBounds().ToString()
<< " script: " << open_script << " " << move_script;
// The popup is constrained to the available bounds of its screen.
const gfx::Rect popup_bounds = popup->window()->GetBounds();
EXPECT_TRUE(popup_display.work_area().Contains(popup_bounds))
<< " work_area: " << popup_display.work_area().ToString()
<< " popup: " << popup_bounds.ToString();
}
}
}
}
// Tests opening a popup on another screen from a cross-origin iframe.
IN_PROC_BROWSER_TEST_P(MAYBE_PopupMultiScreenTest, CrossOriginIFrame) {
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
content::SetupCrossSiteRedirector(&https_server);
ASSERT_TRUE(https_server.Start());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(NavigateToURL(web_contents,
https_server.GetURL("a.com", "/simple.html")));
EXPECT_TRUE(WaitForRenderFrameReady(web_contents->GetPrimaryMainFrame()));
// Grant permission to the new origin after navigation.
if (ShouldTestWindowManagement()) {
SetUpWindowManagement(browser());
}
// Append cross-origin iframes with and without the permission policy.
const GURL src = https_server.GetURL("b.com", "/simple.html");
const std::string script = R"JS(
new Promise(resolve => {
let f = document.createElement('iframe');
f.src = $1;
f.allow = $2 ? 'window-management' : '';
f.addEventListener('load', () => resolve(true));
document.body.appendChild(f);
});
)JS";
EXPECT_EQ(true, EvalJs(web_contents, content::JsReplace(script, src, false)));
EXPECT_EQ(true, EvalJs(web_contents, content::JsReplace(script, src, true)));
// Copy the display vector so references are not invalidated while looping.
std::vector<display::Display> displays =
display::Screen::Get()->GetAllDisplays();
for (const display::Display& opener_display : displays) {
browser()->window()->SetBounds(opener_display.work_area());
ASSERT_EQ(opener_display.id(), GetDisplayNearestBrowser(browser()).id());
for (const bool iframe_policy_granted : {true, false}) {
content::RenderFrameHost* cross_origin_iframe =
ChildFrameAt(web_contents, iframe_policy_granted ? 1 : 0);
ASSERT_TRUE(cross_origin_iframe);
ASSERT_NE(cross_origin_iframe->GetLastCommittedOrigin(),
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());
for (const display::Display& target_display : displays) {
for (const char* url : {"/simple.html", "about:blank"}) {
const std::string open_script = content::JsReplace(
"w = open($1, '', 'left=$2,top=$3,width=200,height=200');", url,
target_display.work_area().x(), target_display.work_area().y());
Browser* popup = OpenPopup(cross_origin_iframe, open_script);
display::Display popup_display = GetDisplayNearestBrowser(popup);
// The popup only opens on another screen with permission.
const display::Display& expected_display =
ShouldTestWindowManagement() && iframe_policy_granted
? target_display
: opener_display;
EXPECT_EQ(expected_display.id(), popup_display.id())
<< " expected: " << expected_display.work_area().ToString()
<< " actual: " << popup_display.work_area().ToString()
<< " opener: " << browser()->window()->GetBounds().ToString()
<< " popup: " << popup->window()->GetBounds().ToString()
<< " script: " << open_script;
}
}
}
}
}
} // namespace