blob: d8952d427ac6a219ec323d58936717ec1e8d9b19 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdlib.h>
#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/screen_orientation/screen_orientation_provider.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/screen_orientation_delegate.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 "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/features.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/display/screen.h"
namespace content {
class ScreenOrientationBrowserTest : public ContentBrowserTest {
public:
ScreenOrientationBrowserTest() = default;
ScreenOrientationBrowserTest(const ScreenOrientationBrowserTest&) = delete;
ScreenOrientationBrowserTest& operator=(const ScreenOrientationBrowserTest&) =
delete;
// ContentBrowserTest:
void SetUpOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->Start());
}
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
protected:
void SendFakeScreenOrientation(unsigned angle, const std::string& str_type) {
RenderWidgetHostImpl* main_frame_rwh = static_cast<RenderWidgetHostImpl*>(
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
display::mojom::ScreenOrientation type =
display::mojom::ScreenOrientation::kUndefined;
if (str_type == "portrait-primary") {
type = display::mojom::ScreenOrientation::kPortraitPrimary;
} else if (str_type == "portrait-secondary") {
type = display::mojom::ScreenOrientation::kPortraitSecondary;
} else if (str_type == "landscape-primary") {
type = display::mojom::ScreenOrientation::kLandscapePrimary;
} else if (str_type == "landscape-secondary") {
type = display::mojom::ScreenOrientation::kLandscapeSecondary;
}
ASSERT_NE(display::mojom::ScreenOrientation::kUndefined, type);
// This simulates what the browser process does when the screen orientation
// is changed.
main_frame_rwh->SetScreenOrientationForTesting(angle, type);
}
int GetOrientationAngle() {
int angle =
ExecuteScriptAndGetValue(shell()->web_contents()->GetPrimaryMainFrame(),
"screen.orientation.angle")
.GetInt();
return angle;
}
std::string GetOrientationType() {
std::string type =
ExecuteScriptAndGetValue(shell()->web_contents()->GetPrimaryMainFrame(),
"screen.orientation.type")
.GetString();
return type;
}
bool ScreenOrientationSupported() {
bool support =
ExecuteScriptAndGetValue(shell()->web_contents()->GetPrimaryMainFrame(),
"'orientation' in screen")
.GetBool();
return support;
}
bool WindowOrientationSupported() {
bool support =
ExecuteScriptAndGetValue(shell()->web_contents()->GetPrimaryMainFrame(),
"'orientation' in window")
.GetBool();
return support;
}
int GetWindowOrientationAngle() {
int angle =
ExecuteScriptAndGetValue(shell()->web_contents()->GetPrimaryMainFrame(),
"window.orientation")
.GetInt();
return angle;
}
};
class ScreenOrientationOOPIFBrowserTest : public ScreenOrientationBrowserTest {
public:
ScreenOrientationOOPIFBrowserTest() {}
ScreenOrientationOOPIFBrowserTest(const ScreenOrientationOOPIFBrowserTest&) =
delete;
ScreenOrientationOOPIFBrowserTest& operator=(
const ScreenOrientationOOPIFBrowserTest&) = delete;
void SetUpCommandLine(base::CommandLine* command_line) override {
IsolateAllSitesForTesting(command_line);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
SetupCrossSiteRedirector(embedded_test_server());
ScreenOrientationBrowserTest::SetUpOnMainThread();
}
};
// This test doesn't work on MacOS X but the reason is mostly because it is not
// used Aura. It could be set as !BUILDFLAG(IS_MAC) but the rule below will
// actually support MacOS X if and when it switches to Aura.
#if defined(USE_AURA) || BUILDFLAG(IS_ANDROID)
// Flaky on Chrome OS: http://crbug.com/468259
#if BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_ScreenOrientationChange DISABLED_ScreenOrientationChange
#else
#define MAYBE_ScreenOrientationChange ScreenOrientationChange
#endif
IN_PROC_BROWSER_TEST_F(ScreenOrientationBrowserTest,
MAYBE_ScreenOrientationChange) {
std::string types[] = { "portrait-primary",
"portrait-secondary",
"landscape-primary",
"landscape-secondary" };
GURL test_url = GetTestUrl("screen_orientation",
"screen_orientation_screenorientationchange.html");
{
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
navigation_observer.Wait();
WaitForResizeComplete(shell()->web_contents());
}
int angle = GetOrientationAngle();
for (int i = 0; i < 4; ++i) {
angle = (angle + 90) % 360;
SendFakeScreenOrientation(angle, types[i]);
TestNavigationObserver navigation_observer(shell()->web_contents());
navigation_observer.Wait();
EXPECT_EQ(angle, GetOrientationAngle());
EXPECT_EQ(types[i], GetOrientationType());
}
}
#endif // defined(USE_AURA) || BUILDFLAG(IS_ANDROID)
// Flaky on Chrome OS: http://crbug.com/468259
#if BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_WindowOrientationChange DISABLED_WindowOrientationChange
#else
#define MAYBE_WindowOrientationChange WindowOrientationChange
#endif
IN_PROC_BROWSER_TEST_F(ScreenOrientationBrowserTest,
MAYBE_WindowOrientationChange) {
GURL test_url = GetTestUrl("screen_orientation",
"screen_orientation_windoworientationchange.html");
{
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
navigation_observer.Wait();
#if USE_AURA || BUILDFLAG(IS_ANDROID)
WaitForResizeComplete(shell()->web_contents());
#endif // USE_AURA || BUILDFLAG(IS_ANDROID)
}
if (!WindowOrientationSupported())
return;
int angle = GetWindowOrientationAngle();
for (int i = 0; i < 4; ++i) {
angle = (angle + 90) % 360;
SendFakeScreenOrientation(angle, "portrait-primary");
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
navigation_observer.Wait();
EXPECT_EQ(angle == 270 ? -90 : angle, GetWindowOrientationAngle());
}
}
// LockSmoke test seems to have become flaky on all non-ChromeOS platforms.
// The cause is unfortunately unknown. See https://crbug.com/448876
// Chromium Android does not support fullscreen
IN_PROC_BROWSER_TEST_F(ScreenOrientationBrowserTest, DISABLED_LockSmoke) {
GURL test_url = GetTestUrl("screen_orientation",
"screen_orientation_lock_smoke.html");
TestNavigationObserver navigation_observer(shell()->web_contents(), 2);
shell()->LoadURL(test_url);
navigation_observer.Wait();
#if USE_AURA || BUILDFLAG(IS_ANDROID)
WaitForResizeComplete(shell()->web_contents());
#endif // USE_AURA || BUILDFLAG(IS_ANDROID)
std::string expected =
#if BUILDFLAG(IS_ANDROID)
"SecurityError"; // WebContents need to be fullscreen.
#else
"NotSupportedError"; // Locking isn't supported.
#endif
EXPECT_EQ(expected, shell()->web_contents()->GetLastCommittedURL().ref());
}
// Check that using screen orientation after a frame is detached doesn't crash
// the renderer process.
// This could be a web test if they were not using a mock screen orientation
// controller.
IN_PROC_BROWSER_TEST_F(ScreenOrientationBrowserTest, CrashTest_UseAfterDetach) {
GURL test_url(embedded_test_server()->GetURL(
"/screen_orientation/screen_orientation_use_after_detach.html"));
TestNavigationObserver navigation_observer(shell()->web_contents(), 2);
shell()->LoadURL(test_url);
navigation_observer.Wait();
// This is a success if the renderer process did not crash, thus, we end up
// here.
}
#if BUILDFLAG(IS_ANDROID)
class ScreenOrientationLockDisabledBrowserTest : public ContentBrowserTest {
public:
ScreenOrientationLockDisabledBrowserTest() {}
~ScreenOrientationLockDisabledBrowserTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kDisableScreenOrientationLock);
}
};
// Check that when --disable-screen-orientation-lock is passed to the command
// line, screen.orientation.lock() correctly reports to not be supported.
IN_PROC_BROWSER_TEST_F(ScreenOrientationLockDisabledBrowserTest,
DISABLED_NotSupported) {
GURL test_url = GetTestUrl("screen_orientation",
"screen_orientation_lock_disabled.html");
{
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
shell()->LoadURL(test_url);
navigation_observer.Wait();
}
{
ASSERT_TRUE(ExecJs(shell(), "run();"));
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
navigation_observer.Wait();
EXPECT_EQ("NotSupportedError",
shell()->web_contents()->GetLastCommittedURL().ref());
}
}
#endif // BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(ScreenOrientationOOPIFBrowserTest, ScreenOrientation) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
#if USE_AURA || BUILDFLAG(IS_ANDROID)
WaitForResizeComplete(shell()->web_contents());
#endif // USE_AURA || BUILDFLAG(IS_ANDROID)
std::string types[] = {"portrait-primary", "portrait-secondary",
"landscape-primary", "landscape-secondary"};
int angle = GetOrientationAngle();
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
FrameTreeNode* child = root->child_at(0);
MainThreadFrameObserver root_observer(
root->current_frame_host()->GetRenderWidgetHost());
MainThreadFrameObserver child_observer(
child->current_frame_host()->GetRenderWidgetHost());
for (int i = 0; i < 4; ++i) {
angle = (angle + 90) % 360;
SendFakeScreenOrientation(angle, types[i]);
root_observer.Wait();
child_observer.Wait();
EXPECT_EQ(angle,
EvalJs(root->current_frame_host(), "screen.orientation.angle"));
EXPECT_EQ(angle,
EvalJs(child->current_frame_host(), "screen.orientation.angle"));
EXPECT_EQ(types[i],
EvalJs(root->current_frame_host(), "screen.orientation.type"));
EXPECT_EQ(types[i],
EvalJs(child->current_frame_host(), "screen.orientation.type"));
}
}
// Regression test for triggering a screen orientation change for a pending
// main frame RenderFrameHost. See https://crbug.com/764202. In the bug, this
// was triggered via the DevTools audit panel and
// blink::mojom::FrameWidget::EnableDeviceEmulation, which calls
// RenderWidget::Resize on the renderer side. The test fakes this by directly
// sending the resize message to the widget.
#if BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_ScreenOrientationInPendingMainFrame \
DISABLED_ScreenOrientationInPendingMainFrame
#else
#define MAYBE_ScreenOrientationInPendingMainFrame \
ScreenOrientationInPendingMainFrame
#endif
IN_PROC_BROWSER_TEST_F(ScreenOrientationOOPIFBrowserTest,
MAYBE_ScreenOrientationInPendingMainFrame) {
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
#if USE_AURA || BUILDFLAG(IS_ANDROID)
WaitForResizeComplete(shell()->web_contents());
#endif // USE_AURA || BUILDFLAG(IS_ANDROID)
// Set up a fake Resize message with a screen orientation change.
RenderWidgetHost* main_frame_rwh =
web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost();
display::ScreenInfo screen_info = main_frame_rwh->GetScreenInfo();
int expected_angle = (screen_info.orientation_angle + 90) % 360;
// Start a cross-site navigation, but don't commit yet.
GURL second_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
TestNavigationManager delayer(shell()->web_contents(), second_url);
shell()->LoadURL(second_url);
EXPECT_TRUE(delayer.WaitForRequestStart());
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
RenderFrameHostImpl* pending_rfh =
root->render_manager()->speculative_frame_host();
// Send the orientation change to the pending RFH's widget.
static_cast<RenderWidgetHostImpl*>(pending_rfh->GetRenderWidgetHost())
->SetScreenOrientationForTesting(expected_angle,
screen_info.orientation_type);
// Let the navigation finish and make sure it succeeded.
delayer.WaitForNavigationFinished();
EXPECT_EQ(second_url,
web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
#if USE_AURA || BUILDFLAG(IS_ANDROID)
WaitForResizeComplete(shell()->web_contents());
#endif // USE_AURA || BUILDFLAG(IS_ANDROID)
EXPECT_EQ(expected_angle,
EvalJs(root->current_frame_host(), "screen.orientation.angle"));
}
#if BUILDFLAG(IS_ANDROID)
// This test is disabled because th trybots run in system portrait lock, which
// prevents the test from changing the screen orientation.
IN_PROC_BROWSER_TEST_F(ScreenOrientationOOPIFBrowserTest,
DISABLED_ScreenOrientationLock) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WaitForResizeComplete(shell()->web_contents());
const char* types[] = {"portrait-primary", "portrait-secondary",
"landscape-primary", "landscape-secondary"};
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
FrameTreeNode* child = root->child_at(0);
RenderFrameHostImpl* frames[] = {root->current_frame_host(),
child->current_frame_host()};
EXPECT_TRUE(ExecJs(root->current_frame_host(),
"document.body.webkitRequestFullscreen()"));
for (const char* type : types) {
std::string script =
base::StringPrintf("screen.orientation.lock('%s')", type);
EXPECT_TRUE(ExecJs(child->current_frame_host(), script));
for (auto* frame : frames) {
std::string orientation_type;
while (type != orientation_type) {
orientation_type =
EvalJs(frame, "screen.orientation.type").ExtractString();
}
}
EXPECT_TRUE(
ExecJs(child->current_frame_host(), "screen.orientation.unlock()"));
}
}
#endif // BUILDFLAG(IS_ANDROID)
class ScreenOrientationLockForPrerenderBrowserTest
: public ScreenOrientationBrowserTest {
public:
ScreenOrientationLockForPrerenderBrowserTest()
: prerender_helper_(base::BindRepeating(
&ScreenOrientationLockForPrerenderBrowserTest::web_contents,
base::Unretained(this))) {
}
// ScreenOrientationBrowserTest:
void SetUp() override {
prerender_helper_.SetUp(embedded_test_server());
ScreenOrientationBrowserTest::SetUp();
}
content::WebContents* web_contents() { return shell()->web_contents(); }
protected:
test::PrerenderTestHelper prerender_helper_;
};
class FakeScreenOrientationDelegate : public ScreenOrientationDelegate {
public:
FakeScreenOrientationDelegate() {
ScreenOrientationProvider::SetDelegate(this);
}
~FakeScreenOrientationDelegate() override {
ScreenOrientationProvider::SetDelegate(nullptr);
}
// ScreenOrientationDelegate:
bool FullScreenRequired(WebContents* web_contents) override {
return full_screen_required_;
}
bool ScreenOrientationProviderSupported(WebContents* web_contents) override {
return true;
}
void Lock(
WebContents* web_contents,
device::mojom::ScreenOrientationLockType lock_orientation) override {
lock_count_++;
}
void Unlock(WebContents* web_contents) override { unlock_count_++; }
void set_full_screen_required(bool is_required) {
full_screen_required_ = is_required;
}
int lock_count() const { return lock_count_; }
int unlock_count() const { return unlock_count_; }
private:
int lock_count_ = 0;
int unlock_count_ = 0;
bool full_screen_required_ = false;
};
// Unlock should not triggered the orientation upon the completion of a
// non-primary navigation.
IN_PROC_BROWSER_TEST_F(ScreenOrientationLockForPrerenderBrowserTest,
ShouldNotUnlockWhenPrerenderNavigation) {
FakeScreenOrientationDelegate delegate;
// Navigate to a site.
GURL initial_url = embedded_test_server()->GetURL("/empty.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), initial_url, 1);
EXPECT_TRUE(ExecuteScript(web_contents()->GetPrimaryMainFrame(),
"screen.orientation.lock('portrait')"));
// Delegate did apply lock once.
EXPECT_EQ(1, delegate.lock_count());
EXPECT_EQ(0, delegate.unlock_count());
GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
// Prerender to another site.
prerender_helper_.AddPrerender(prerender_url);
// Delegate should not apply unlock.
EXPECT_EQ(0, delegate.unlock_count());
// Navigate to the prerendered site.
prerender_helper_.NavigatePrimaryPage(prerender_url);
// Delegate did apply unlock once.
EXPECT_EQ(1, delegate.unlock_count());
}
// Test for ScreenOrientationProvider::DidToggleFullscreenModeForTab which
// overrides from WebContentsObserver. The prerendered page shouldn't trigger
// unlock the screen orientation in fullscreen mode.
IN_PROC_BROWSER_TEST_F(ScreenOrientationLockForPrerenderBrowserTest,
KeepFullscreenLockWhilePrerendering) {
FakeScreenOrientationDelegate delegate;
delegate.set_full_screen_required(true);
// Enter the full screen and request lock from the primary page.
const GURL initial_url = embedded_test_server()->GetURL("/simple_page.html");
ASSERT_TRUE(NavigateToURL(shell(), initial_url));
EXPECT_TRUE(ExecJs(shell(),
"document.body.requestFullscreen();"
"screen.orientation.lock('any');"));
EXPECT_EQ(1, delegate.lock_count());
// Start a prerender.
const GURL prerender_url = embedded_test_server()->GetURL("/title1.html");
int host_id = prerender_helper_.AddPrerender(prerender_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
// Shut down the prerendered page. It shouldn't trigger orientation unlock.
test::PrerenderHostObserver prerender_observer(*web_contents(), host_id);
PrerenderHostRegistry* registry =
static_cast<WebContentsImpl*>(web_contents())->GetPrerenderHostRegistry();
registry->CancelHost(host_id,
PrerenderHost::FinalStatus::kRendererProcessKilled);
prerender_observer.WaitForDestroyed();
// Delegate should not apply unlock.
EXPECT_EQ(0, delegate.unlock_count());
}
} // namespace content