blob: 534adbe140a48262672df1afb8b743de5eb08716 [file] [log] [blame]
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.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/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image.h"
namespace content {
namespace {
static const char kCanvasPageString[] =
"<body>"
" <canvas id=\"canvas\" width=\"64\" height=\"64\""
" style=\"position:absolute;top:0px;left:0px;width:100%;"
" height=100%;margin:0;padding:0;\">"
" </canvas>"
" <script>"
" window.ctx = document.getElementById(\"canvas\").getContext(\"2d\");"
" function fillWithColor(color) {"
" ctx.fillStyle = color;"
" ctx.fillRect(0, 0, 64, 64);"
" window.domAutomationController.send(color);"
" }"
" var offset = 150;"
" function openNewWindow() {"
" window.open(\"/test\", \"\", "
" \"top=\"+offset+\",left=\"+offset+\",width=200,height=200\");"
" offset += 50;"
" window.domAutomationController.send(true);"
" }"
" window.document.title = \"Ready\";"
" </script>"
"</body>";
}
class SnapshotBrowserTest : public ContentBrowserTest {
public:
SnapshotBrowserTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Use a smaller browser window to speed up the snapshots.
command_line->AppendSwitchASCII(::switches::kContentShellHostWindowSize,
"200x200");
}
void SetUp() override {
// These tests rely on the harness producing pixel output.
EnablePixelOutput();
ContentBrowserTest::SetUp();
}
content::WebContentsImpl* GetWebContents(Shell* browser) {
return static_cast<content::WebContentsImpl*>(browser->web_contents());
}
content::RenderWidgetHostImpl* GetRenderWidgetHostImpl(Shell* browser) {
return GetWebContents(browser)->GetRenderViewHost()->GetWidget();
}
void SetupTestServer() {
// Use an embedded test server so we can open multiple windows in
// the same renderer process, all referring to the same origin.
embedded_test_server()->RegisterRequestHandler(base::Bind(
&SnapshotBrowserTest::HandleRequest, base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_NO_FATAL_FAILURE(content::NavigateToURL(
shell(), embedded_test_server()->GetURL("/test")));
}
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
GURL absolute_url = embedded_test_server()->GetURL(request.relative_url);
if (absolute_url.path() != "/test")
return std::unique_ptr<net::test_server::HttpResponse>();
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_code(net::HTTP_OK);
http_response->set_content(kCanvasPageString);
http_response->set_content_type("text/html");
return http_response;
}
void WaitForAllWindowsToBeReady() {
const base::string16 expected_title = base::UTF8ToUTF16("Ready");
// The subordinate windows may load asynchronously. Wait for all of
// them to execute their script before proceeding.
auto browser_list = Shell::windows();
for (Shell* browser : browser_list) {
TitleWatcher watcher(GetWebContents(browser), expected_title);
const base::string16& actual_title = watcher.WaitAndGetTitle();
EXPECT_EQ(expected_title, actual_title);
}
}
struct ExpectedColor {
ExpectedColor() : r(0), g(0), b(0) {}
bool operator==(const ExpectedColor& other) const {
return (r == other.r && g == other.g && b == other.b);
}
uint8_t r;
uint8_t g;
uint8_t b;
};
void PickRandomColor(ExpectedColor* expected) {
expected->r = static_cast<uint8_t>(base::RandInt(0, 256));
expected->g = static_cast<uint8_t>(base::RandInt(0, 256));
expected->b = static_cast<uint8_t>(base::RandInt(0, 256));
}
struct SerialSnapshot {
SerialSnapshot() : host(nullptr) {}
content::RenderWidgetHost* host;
ExpectedColor color;
};
std::vector<SerialSnapshot> expected_snapshots_;
void SyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
const gfx::Image& image) {
bool found = false;
for (auto iter = expected_snapshots_.begin();
iter != expected_snapshots_.end(); ++iter) {
const SerialSnapshot& expected = *iter;
if (expected.host == rwhi) {
found = true;
const SkBitmap* bitmap = image.ToSkBitmap();
SkColor color = bitmap->getColor(1, 1);
EXPECT_EQ(static_cast<int>(SkColorGetR(color)),
static_cast<int>(expected.color.r))
<< "Red channels differed";
EXPECT_EQ(static_cast<int>(SkColorGetG(color)),
static_cast<int>(expected.color.g))
<< "Green channels differed";
EXPECT_EQ(static_cast<int>(SkColorGetB(color)),
static_cast<int>(expected.color.b))
<< "Blue channels differed";
expected_snapshots_.erase(iter);
break;
}
}
}
std::map<content::RenderWidgetHost*, std::vector<ExpectedColor>>
expected_async_snapshots_map_;
int num_remaining_async_snapshots_ = 0;
void AsyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
const gfx::Image& image) {
--num_remaining_async_snapshots_;
auto iterator = expected_async_snapshots_map_.find(rwhi);
ASSERT_NE(iterator, expected_async_snapshots_map_.end());
std::vector<ExpectedColor>& expected_snapshots = iterator->second;
const SkBitmap* bitmap = image.ToSkBitmap();
SkColor color = bitmap->getColor(1, 1);
bool found = false;
// Find first instance of this color in the list and clear out all
// of the entries before that point. If it's not found, report
// failure.
for (auto iter = expected_snapshots.begin();
iter != expected_snapshots.end(); ++iter) {
const ExpectedColor& expected = *iter;
if (SkColorGetR(color) == expected.r &&
SkColorGetG(color) == expected.g &&
SkColorGetB(color) == expected.b) {
// Erase everything up to this color, but not this color
// itself, since it might be returned again later on
// subsequent snapshot requests.
expected_snapshots.erase(expected_snapshots.begin(), iter);
found = true;
break;
}
}
EXPECT_TRUE(found) << "Did not find color ("
<< static_cast<int>(SkColorGetR(color)) << ", "
<< static_cast<int>(SkColorGetG(color)) << ", "
<< static_cast<int>(SkColorGetB(color))
<< ") in expected snapshots for RWH 0x" << rwhi;
}
};
// Even the single-window test doesn't work on Android yet. It's expected
// that the multi-window tests would never work on that platform.
#if !defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, SingleWindowTest) {
SetupTestServer();
content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(shell());
for (int i = 0; i < 40; ++i) {
SerialSnapshot expected;
expected.host = rwhi;
PickRandomColor(&expected.color);
std::string colorString = base::StringPrintf(
"#%02x%02x%02x", expected.color.r, expected.color.g, expected.color.b);
std::string script = std::string("fillWithColor(\"") + colorString + "\");";
std::string result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(GetWebContents(shell()),
script, &result));
EXPECT_EQ(result, colorString);
expected_snapshots_.push_back(expected);
// Get the snapshot from the surface rather than the window. The
// on-screen display path is verified by the GPU tests, and it
// seems difficult to figure out the colorspace transformation
// required to make these color comparisons.
rwhi->GetSnapshotFromBrowser(
base::Bind(&SnapshotBrowserTest::SyncSnapshotCallback,
base::Unretained(this), base::Unretained(rwhi)),
true);
while (expected_snapshots_.size() > 0) {
base::RunLoop().RunUntilIdle();
}
}
}
// Timing out either all the time, or infrequently, apparently because
// they're too slow, on the following configurations:
// Windows Debug
// Linux Chromium OS ASAN LSAN Tests (1)
// Linux TSAN Tests
// Linux FYI SkiaRenderer Vulkan (Intel HD 630)
// See crbug.com/771119
#if (defined(OS_WIN) && !defined(NDEBUG)) || (defined(OS_CHROMEOS))
#define MAYBE_SyncMultiWindowTest DISABLED_SyncMultiWindowTest
#define MAYBE_AsyncMultiWindowTest DISABLED_AsyncMultiWindowTest
#else
#define MAYBE_SyncMultiWindowTest SyncMultiWindowTest
#define MAYBE_AsyncMultiWindowTest AsyncMultiWindowTest
#endif
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_SyncMultiWindowTest) {
SetupTestServer();
for (int i = 0; i < 3; ++i) {
bool result = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(shell()), "openNewWindow()", &result));
EXPECT_TRUE(result);
}
base::RunLoop().RunUntilIdle();
WaitForAllWindowsToBeReady();
auto browser_list = Shell::windows();
EXPECT_EQ(4u, browser_list.size());
for (int i = 0; i < 20; ++i) {
for (int j = 0; j < 4; j++) {
// Start each iteration by taking a snapshot with a different
// browser instance.
int browser_index = (i + j) % 4;
Shell* browser = browser_list[browser_index];
content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(browser);
SerialSnapshot expected;
expected.host = rwhi;
PickRandomColor(&expected.color);
std::string colorString =
base::StringPrintf("#%02x%02x%02x", expected.color.r,
expected.color.g, expected.color.b);
std::string script =
std::string("fillWithColor(\"") + colorString + "\");";
std::string result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
GetWebContents(browser), script, &result));
EXPECT_EQ(result, colorString);
expected_snapshots_.push_back(expected);
// Get the snapshot from the surface rather than the window. The
// on-screen display path is verified by the GPU tests, and it
// seems difficult to figure out the colorspace transformation
// required to make these color comparisons.
rwhi->GetSnapshotFromBrowser(
base::Bind(&SnapshotBrowserTest::SyncSnapshotCallback,
base::Unretained(this), base::Unretained(rwhi)),
true);
}
while (expected_snapshots_.size() > 0) {
base::RunLoop().RunUntilIdle();
}
}
}
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_AsyncMultiWindowTest) {
// TODO(jonross): Re-enable this once the root cause of the failure has been
// fixed. https://crbug.com/1003375
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kUseVulkan))
return;
SetupTestServer();
for (int i = 0; i < 3; ++i) {
bool result = false;
EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
GetWebContents(shell()), "openNewWindow()", &result));
EXPECT_TRUE(result);
}
base::RunLoop().RunUntilIdle();
WaitForAllWindowsToBeReady();
auto browser_list = Shell::windows();
EXPECT_EQ(4u, browser_list.size());
// This many pending snapshots per window will be put on the queue
// before draining the requests. Anything more than 1 seems to catch
// bugs which might otherwise be introduced in LatencyInfo's
// propagation of the BROWSER_SNAPSHOT_FRAME_NUMBER_COMPONENT
// component type.
int divisor = 3;
for (int i = 0; i < 10 * divisor; ++i) {
for (int j = 0; j < 4; j++) {
// Start each iteration by taking a snapshot with a different
// browser instance.
int browser_index = (i + j) % 4;
Shell* browser = browser_list[browser_index];
content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(browser);
std::vector<ExpectedColor>& expected_snapshots =
expected_async_snapshots_map_[rwhi];
// Pick a unique random color.
ExpectedColor expected;
do {
PickRandomColor(&expected);
} while (base::Contains(expected_snapshots, expected));
expected_snapshots.push_back(expected);
std::string colorString = base::StringPrintf("#%02x%02x%02x", expected.r,
expected.g, expected.b);
std::string script =
std::string("fillWithColor(\"") + colorString + "\");";
std::string result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
GetWebContents(browser), script, &result));
EXPECT_EQ(result, colorString);
// Get the snapshot from the surface rather than the window. The
// on-screen display path is verified by the GPU tests, and it
// seems difficult to figure out the colorspace transformation
// required to make these color comparisons.
rwhi->GetSnapshotFromBrowser(
base::Bind(&SnapshotBrowserTest::AsyncSnapshotCallback,
base::Unretained(this), base::Unretained(rwhi)),
true);
++num_remaining_async_snapshots_;
}
// Periodically yield and drain the async snapshot requests.
if ((i % divisor) == 0) {
bool drained;
do {
drained = true;
for (auto iter = expected_async_snapshots_map_.begin();
iter != expected_async_snapshots_map_.end(); ++iter) {
if (iter->second.size() > 1) {
drained = false;
break;
}
}
if (!drained) {
base::RunLoop().RunUntilIdle();
}
} while (!drained);
}
}
// At the end of the test, cooperatively wait for all of the snapshot
// requests to be drained before exiting. This works around crashes
// seen when tearing down the compositor while these requests are in
// flight. Likely, user-visible APIs that use this facility are safe
// in this regard.
while (num_remaining_async_snapshots_ > 0) {
base::RunLoop().RunUntilIdle();
}
}
#endif // !defined(OS_ANDROID)
} // namespace content