blob: e183ef09772deebf5ca1627de40fbe21b1d31e39 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_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 "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/display/screen_base.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/shell.h"
#include "ui/display/test/display_manager_test_api.h" // nogncheck
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
constexpr char kGetScreensScript[] = R"(
(async () => {
try {
const screenDetails = await self.getScreenDetails();
} catch {
return 'error';
}
try {
return (await navigator.permissions.query({name:'window-management'}))
.state;
} catch {
return "permission_error";
}
})();
)";
constexpr char kCheckPermissionScript[] = R"(
(async () => {
try {
return (await navigator.permissions.query({name:'window-management'}))
.state;
} catch {
return 'permission_error';
} })();
)";
// Tests of WindowManagementPermissionContext behavior.
class WindowManagementPermissionContextTest : public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
// Window management features are only available on secure contexts, and so
// we need to create an HTTPS test server here to serve those pages rather
// than using the default embedded_test_server().
https_test_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
// Support sites like a.test, b.test, c.test etc
https_test_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_test_server_->ServeFilesFromSourceDirectory("chrome/test/data");
net::test_server::RegisterDefaultHandlers(https_test_server_.get());
content::SetupCrossSiteRedirector(https_test_server_.get());
ASSERT_TRUE(https_test_server_->Start());
}
// 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());
}
net::EmbeddedTestServer* https_test_server() {
return https_test_server_.get();
}
protected:
std::unique_ptr<net::EmbeddedTestServer> https_test_server_;
};
class MultiscreenWindowManagementPermissionContextTest
: public WindowManagementPermissionContextTest {
public:
#if !BUILDFLAG(IS_CHROMEOS)
~MultiscreenWindowManagementPermissionContextTest() override {
display::Screen::SetScreenInstance(nullptr);
}
#endif
void SetScreenInstance() override {
#if BUILDFLAG(IS_CHROMEOS)
// Use the default, see SetUpOnMainThread.
WindowManagementPermissionContextTest::SetScreenInstance();
#else
display::Screen::SetScreenInstance(&screen_);
screen_.display_list().AddDisplay({1, gfx::Rect(100, 100, 801, 802)},
display::DisplayList::Type::PRIMARY);
screen_.display_list().AddDisplay({2, gfx::Rect(901, 100, 802, 803)},
display::DisplayList::Type::NOT_PRIMARY);
ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());
#endif // BUILDFLAG(IS_CHROMEOS)
}
void SetUpOnMainThread() override {
#if BUILDFLAG(IS_CHROMEOS)
// This has to happen later than SetScreenInstance as the ash shell
// does not exist yet.
display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
.UpdateDisplay("100+100-801x802,901+100-802x803");
ASSERT_EQ(2, display::Screen::Get()->GetNumDisplays());
#endif
WindowManagementPermissionContextTest::SetUpOnMainThread();
}
private:
display::ScreenBase screen_;
};
// Tests gesture requirements (a gesture is only needed to prompt the user).
IN_PROC_BROWSER_TEST_F(WindowManagementPermissionContextTest, GestureToPrompt) {
const GURL url(https_test_server()->GetURL("a.test", "/empty.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Auto-dismiss the permission request, iff the prompt is shown.
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
permission_request_manager->set_auto_response_for_test(
permissions::PermissionRequestManager::ACCEPT_ALL);
// Calling getScreenDetails() without a gesture or pre-existing permission
// will not prompt the user, and leaves the permission in the default "prompt"
// state.
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_EQ("error", EvalJs(tab, kGetScreensScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_EQ("prompt", EvalJs(tab, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
// Calling getScreenDetails() with a gesture will show the prompt, and
// auto-accept.
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_EQ("granted", EvalJs(tab, kGetScreensScript));
EXPECT_TRUE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
// Calling getScreenDetails() without a gesture, but with pre-existing
// permission, will succeed, since it does not need to prompt the user.
WaitForUserActivationExpiry();
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_EQ("granted", EvalJs(tab, kGetScreensScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
}
// TODO(crbug.com/40212482): Test failing on linux-chromeos-chrome.
// TODO(crbug.com/40212443): Test failing on linux.
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
#define MAYBE_DismissAndDeny DISABLED_DismissAndDeny
#else
#define MAYBE_DismissAndDeny DismissAndDeny
#endif
// Tests user activation after dimissing and denying the permission request.
IN_PROC_BROWSER_TEST_F(WindowManagementPermissionContextTest,
MAYBE_DismissAndDeny) {
const GURL url(https_test_server()->GetURL("a.test", "/empty.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
// Dismiss the prompt after activation expires, expect no activation.
ExecuteScriptAsync(tab, "getScreenDetails()");
WaitForUserActivationExpiry();
ASSERT_TRUE(permission_request_manager->IsRequestInProgress());
permission_request_manager->Dismiss();
EXPECT_EQ("prompt", EvalJs(tab, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
// Deny the prompt after activation expires, expect no activation.
ExecuteScriptAsync(tab, "getScreenDetails()");
WaitForUserActivationExpiry();
ASSERT_TRUE(permission_request_manager->IsRequestInProgress());
permission_request_manager->Deny();
EXPECT_EQ("denied", EvalJs(tab, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
}
// Tests user activation after accepting the permission request.
IN_PROC_BROWSER_TEST_F(WindowManagementPermissionContextTest, Accept) {
const GURL url(https_test_server()->GetURL("a.test", "/empty.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
// Accept the prompt after activation expires, expect an activation signal.
ExecuteScriptAsync(tab, "getScreenDetails()");
WaitForUserActivationExpiry();
ASSERT_TRUE(permission_request_manager->IsRequestInProgress());
permission_request_manager->Accept();
EXPECT_EQ("granted", EvalJs(tab, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_TRUE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
}
IN_PROC_BROWSER_TEST_F(WindowManagementPermissionContextTest,
IFrameSameOriginAllow) {
const GURL url(https_test_server()->GetURL("a.test", "/iframe.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* child = ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child);
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_FALSE(child->GetMainFrame()->HasTransientUserActivation());
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
// Accept the prompt after activation expires, expect an activation signal.
ExecuteScriptAsync(child, "getScreenDetails()");
WaitForUserActivationExpiry();
ASSERT_TRUE(permission_request_manager->IsRequestInProgress());
permission_request_manager->Accept();
EXPECT_EQ("granted", EvalJs(child, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_TRUE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_TRUE(child->GetMainFrame()->HasTransientUserActivation());
}
IN_PROC_BROWSER_TEST_F(WindowManagementPermissionContextTest,
IFrameCrossOriginDeny) {
const GURL url(https_test_server()->GetURL("a.test", "/iframe.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
GURL subframe_url(https_test_server()->GetURL("b.test", "/title1.html"));
content::NavigateIframeToURL(tab, /*iframe_id=*/"test", subframe_url);
content::RenderFrameHost* child = ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child);
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_FALSE(child->GetMainFrame()->HasTransientUserActivation());
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
// PermissionRequestManager will accept any window management permission
// dialogs that appear. However, the window-management permission is not
// explicitly allowed on the iframe, so requests made by the child frame will
// be automatically denied before a prompt might be issued.
permission_request_manager->set_auto_response_for_test(
permissions::PermissionRequestManager::ACCEPT_ALL);
EXPECT_EQ("error", EvalJs(child, kGetScreensScript));
EXPECT_EQ("denied", EvalJs(child, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
IN_PROC_BROWSER_TEST_F(WindowManagementPermissionContextTest,
IFrameCrossOriginExplicitAllow) {
const GURL url(https_test_server()->GetURL("a.test", "/iframe.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
// See https://w3c.github.io/webappsec-permissions-policy/ for more
// information on permissions policies and allowing cross-origin iframes
// to have particular permissions.
EXPECT_TRUE(ExecJs(tab,
R"(const frame = document.getElementById('test');
frame.setAttribute('allow', 'window-management');)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
GURL subframe_url(https_test_server()->GetURL("b.test", "/title1.html"));
content::NavigateIframeToURL(tab, /*iframe_id=*/"test", subframe_url);
content::RenderFrameHost* child = ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child);
EXPECT_FALSE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_FALSE(child->GetMainFrame()->HasTransientUserActivation());
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(tab);
// Accept the prompt after activation expires, expect an activation signal.
ExecuteScriptAsync(child, "getScreenDetails()");
WaitForUserActivationExpiry();
ASSERT_TRUE(permission_request_manager->IsRequestInProgress());
permission_request_manager->Accept();
EXPECT_EQ("granted", EvalJs(child, kCheckPermissionScript,
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_TRUE(tab->GetPrimaryMainFrame()->HasTransientUserActivation());
EXPECT_TRUE(child->GetMainFrame()->HasTransientUserActivation());
}
// TODO(enne): Windows assumes that display::GetScreen() is a ScreenWin
// which is not true here.
#if !BUILDFLAG(IS_WIN)
// Verify that window.screen.isExtended returns true in a same-origin
// iframe without the window management permission policy allowed.
IN_PROC_BROWSER_TEST_F(MultiscreenWindowManagementPermissionContextTest,
IsExtendedSameOriginAllow) {
const GURL url(https_test_server()->GetURL("a.test", "/iframe.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* child = ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child);
EXPECT_EQ(true, EvalJs(tab, R"(window.screen.isExtended)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_EQ(true, EvalJs(child, R"(window.screen.isExtended)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
// Verify that window.screen.isExtended returns false in a cross-origin
// iframe without the window management permission policy allowed.
IN_PROC_BROWSER_TEST_F(MultiscreenWindowManagementPermissionContextTest,
IsExtendedCrossOriginDeny) {
const GURL url(https_test_server()->GetURL("a.test", "/iframe.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
GURL subframe_url(https_test_server()->GetURL("b.test", "/title1.html"));
content::NavigateIframeToURL(tab, /*iframe_id=*/"test", subframe_url);
content::RenderFrameHost* child = ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child);
EXPECT_EQ(true, EvalJs(tab, R"(window.screen.isExtended)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_EQ(false, EvalJs(child, R"(window.screen.isExtended)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
// Verify that window.screen.isExtended returns true in a cross-origin
// iframe with the window management permission policy allowed.
IN_PROC_BROWSER_TEST_F(MultiscreenWindowManagementPermissionContextTest,
IsExtendedCrossOriginAllow) {
const GURL url(https_test_server()->GetURL("a.test", "/iframe.html"));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
// See https://w3c.github.io/webappsec-permissions-policy/ for more
// information on permissions policies and allowing cross-origin iframes
// to have particular permissions.
EXPECT_TRUE(ExecJs(tab, R"(const frame = document.getElementById('test');
frame.setAttribute('allow', 'window-management');)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
GURL subframe_url(https_test_server()->GetURL("b.test", "/title1.html"));
content::NavigateIframeToURL(tab, /*iframe_id=*/"test", subframe_url);
content::RenderFrameHost* child = ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child);
EXPECT_EQ(true, EvalJs(tab, R"(window.screen.isExtended)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_EQ(true, EvalJs(child, R"(window.screen.isExtended)",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
}
#endif // !BUILDFLAG(IS_WIN)
} // namespace