
Removing reliance on EXECUTE_SCRIPT_USE_MANUAL_REPLY forces the JavaScript to be written in terms of promises, which makes the code less bug-prone since double-sends are systematically prevented (see https://crrev.com/c/4318742 for an example bug that would have been prevented by this). There are still more usages, especially under content/browser/webrtc, but those removals are not as mechanical and will be attempted in followups. Bug: 1423407, 1422660 Change-Id: Ib8f153da50f0bd3e2d09e00615973264dc3214d4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4332196 Reviewed-by: Dale Curtis <dalecurtis@chromium.org> Auto-Submit: Chris Fredrickson <cfredric@chromium.org> Commit-Queue: Chris Fredrickson <cfredric@chromium.org> Cr-Commit-Position: refs/heads/main@{#1122508}
422 lines
15 KiB
C++
422 lines
15 KiB
C++
// Copyright 2017 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/containers/contains.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/rand_util.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "build/build_config.h"
|
|
#include "build/chromeos_buildflags.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.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);"
|
|
" return color;"
|
|
" }"
|
|
" var offset = 150;"
|
|
" function openNewWindow() {"
|
|
" window.open(\"/test\", \"\", "
|
|
" \"top=\"+offset+\",left=\"+offset+\",width=200,height=200\");"
|
|
" offset += 50;"
|
|
" return 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::BindRepeating(
|
|
&SnapshotBrowserTest::HandleRequest, base::Unretained(this)));
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
ASSERT_TRUE(
|
|
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 nullptr;
|
|
|
|
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 std::u16string expected_title = u"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 std::u16string& 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) {}
|
|
|
|
raw_ptr<content::RenderWidgetHost> host;
|
|
ExpectedColor color;
|
|
};
|
|
std::vector<SerialSnapshot> expected_snapshots_;
|
|
|
|
void SyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
|
|
const gfx::Image& image) {
|
|
for (auto iter = expected_snapshots_.begin();
|
|
iter != expected_snapshots_.end(); ++iter) {
|
|
const SerialSnapshot& expected = *iter;
|
|
if (expected.host == rwhi) {
|
|
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 !BUILDFLAG(IS_ANDROID)
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
// TODO(crbug.com/1347296): This test is flakey on macOS.
|
|
#define MAYBE_SingleWindowTest DISABLED_SingleWindowTest
|
|
#else
|
|
#define MAYBE_SingleWindowTest SingleWindowTest
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_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 + "\");";
|
|
EXPECT_EQ(colorString, EvalJs(GetWebContents(shell()), script));
|
|
|
|
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::BindOnce(&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
|
|
// Windows Release (https://crbug.com/1376441)
|
|
// Linux Chromium OS ASAN LSAN Tests (1)
|
|
// Linux TSAN Tests
|
|
// See crbug.com/771119
|
|
// TODO(https://crbug.com/1317446): Fix and enable on Fuchsia.
|
|
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH) || \
|
|
((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && \
|
|
defined(THREAD_SANITIZER)) || \
|
|
BUILDFLAG(IS_FUCHSIA)
|
|
#define MAYBE_SyncMultiWindowTest DISABLED_SyncMultiWindowTest
|
|
#else
|
|
#define MAYBE_SyncMultiWindowTest SyncMultiWindowTest
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_SyncMultiWindowTest) {
|
|
SetupTestServer();
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_EQ(true, EvalJs(GetWebContents(shell()), "openNewWindow()"));
|
|
}
|
|
|
|
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 + "\");";
|
|
EXPECT_EQ(colorString, EvalJs(GetWebContents(browser), script));
|
|
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::BindOnce(&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
|
|
// See crbug.com/771119
|
|
// TODO(crbug.com/1164581): recently crashes flakily on
|
|
// linux_chromium_asan_rel_ng and linux-rel.
|
|
// TODO(https://crbug.com/1317446): Fix and enable on Fuchsia.
|
|
#if (BUILDFLAG(IS_WIN) && !defined(NDEBUG)) || BUILDFLAG(IS_CHROMEOS_ASH) || \
|
|
(BUILDFLAG(IS_CHROMEOS) && defined(THREAD_SANITIZER)) || \
|
|
BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_FUCHSIA)
|
|
#define MAYBE_AsyncMultiWindowTest DISABLED_AsyncMultiWindowTest
|
|
#else
|
|
#define MAYBE_AsyncMultiWindowTest AsyncMultiWindowTest
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_AsyncMultiWindowTest) {
|
|
SetupTestServer();
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
EXPECT_EQ(true, EvalJs(GetWebContents(shell()), "openNewWindow()"));
|
|
}
|
|
|
|
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 + "\");";
|
|
EXPECT_EQ(colorString, EvalJs(GetWebContents(browser), script));
|
|
// 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::BindOnce(&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 // !BUILDFLAG(IS_ANDROID)
|
|
|
|
} // namespace content
|