// 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 #include #include #include #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 "build/build_config.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[] = "" " " " " " " ""; } 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(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 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(); std::unique_ptr 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(base::RandInt(0, 256)); expected->g = static_cast(base::RandInt(0, 256)); expected->b = static_cast(base::RandInt(0, 256)); } struct SerialSnapshot { SerialSnapshot() : host(nullptr) {} content::RenderWidgetHost* host; ExpectedColor color; }; std::vector 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(SkColorGetR(color)), static_cast(expected.color.r)) << "Red channels differed"; EXPECT_EQ(static_cast(SkColorGetG(color)), static_cast(expected.color.g)) << "Green channels differed"; EXPECT_EQ(static_cast(SkColorGetB(color)), static_cast(expected.color.b)) << "Blue channels differed"; expected_snapshots_.erase(iter); break; } } } std::map> 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& 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(SkColorGetR(color)) << ", " << static_cast(SkColorGetG(color)) << ", " << static_cast(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::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 #if (defined(OS_WIN) && !defined(NDEBUG)) || (defined(OS_CHROMEOS)) || \ ((defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(THREAD_SANITIZER)) #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::BindOnce(&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) { 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& 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::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 // !defined(OS_ANDROID) } // namespace content