1 // Copyright (c) 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "build/build_config.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <map>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/command_line.h"
15 #include "base/rand_util.h"
16 #include "base/run_loop.h"
17 #include "base/stl_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "content/browser/renderer_host/render_view_host_impl.h"
20 #include "content/browser/renderer_host/render_widget_host_impl.h"
21 #include "content/browser/web_contents/web_contents_impl.h"
22 #include "content/public/test/browser_test_utils.h"
23 #include "content/public/test/content_browser_test.h"
24 #include "content/public/test/content_browser_test_utils.h"
25 #include "content/shell/browser/shell.h"
26 #include "content/shell/common/shell_switches.h"
27 #include "gpu/command_buffer/service/gpu_switches.h"
28 #include "net/test/embedded_test_server/http_request.h"
29 #include "net/test/embedded_test_server/http_response.h"
30 #include "third_party/skia/include/core/SkBitmap.h"
31 #include "ui/gfx/image/image.h"
32
33 namespace content {
34
35 namespace {
36
37 static const char kCanvasPageString[] =
38 "<body>"
39 " <canvas id=\"canvas\" width=\"64\" height=\"64\""
40 " style=\"position:absolute;top:0px;left:0px;width:100%;"
41 " height=100%;margin:0;padding:0;\">"
42 " </canvas>"
43 " <script>"
44 " window.ctx = document.getElementById(\"canvas\").getContext(\"2d\");"
45 " function fillWithColor(color) {"
46 " ctx.fillStyle = color;"
47 " ctx.fillRect(0, 0, 64, 64);"
48 " window.domAutomationController.send(color);"
49 " }"
50 " var offset = 150;"
51 " function openNewWindow() {"
52 " window.open(\"/test\", \"\", "
53 " \"top=\"+offset+\",left=\"+offset+\",width=200,height=200\");"
54 " offset += 50;"
55 " window.domAutomationController.send(true);"
56 " }"
57 " window.document.title = \"Ready\";"
58 " </script>"
59 "</body>";
60 }
61
62 class SnapshotBrowserTest : public ContentBrowserTest {
63 public:
SnapshotBrowserTest()64 SnapshotBrowserTest() {}
65
SetUpCommandLine(base::CommandLine * command_line)66 void SetUpCommandLine(base::CommandLine* command_line) override {
67 // Use a smaller browser window to speed up the snapshots.
68 command_line->AppendSwitchASCII(::switches::kContentShellHostWindowSize,
69 "200x200");
70 }
71
SetUp()72 void SetUp() override {
73 // These tests rely on the harness producing pixel output.
74 EnablePixelOutput();
75 ContentBrowserTest::SetUp();
76 }
77
GetWebContents(Shell * browser)78 content::WebContentsImpl* GetWebContents(Shell* browser) {
79 return static_cast<content::WebContentsImpl*>(browser->web_contents());
80 }
81
GetRenderWidgetHostImpl(Shell * browser)82 content::RenderWidgetHostImpl* GetRenderWidgetHostImpl(Shell* browser) {
83 return GetWebContents(browser)->GetRenderViewHost()->GetWidget();
84 }
85
SetupTestServer()86 void SetupTestServer() {
87 // Use an embedded test server so we can open multiple windows in
88 // the same renderer process, all referring to the same origin.
89 embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
90 &SnapshotBrowserTest::HandleRequest, base::Unretained(this)));
91 ASSERT_TRUE(embedded_test_server()->Start());
92
93 ASSERT_TRUE(
94 NavigateToURL(shell(), embedded_test_server()->GetURL("/test")));
95 }
96
HandleRequest(const net::test_server::HttpRequest & request)97 std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
98 const net::test_server::HttpRequest& request) {
99 GURL absolute_url = embedded_test_server()->GetURL(request.relative_url);
100 if (absolute_url.path() != "/test")
101 return std::unique_ptr<net::test_server::HttpResponse>();
102
103 std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
104 new net::test_server::BasicHttpResponse());
105 http_response->set_code(net::HTTP_OK);
106 http_response->set_content(kCanvasPageString);
107 http_response->set_content_type("text/html");
108 return http_response;
109 }
110
WaitForAllWindowsToBeReady()111 void WaitForAllWindowsToBeReady() {
112 const base::string16 expected_title = base::UTF8ToUTF16("Ready");
113 // The subordinate windows may load asynchronously. Wait for all of
114 // them to execute their script before proceeding.
115 auto browser_list = Shell::windows();
116 for (Shell* browser : browser_list) {
117 TitleWatcher watcher(GetWebContents(browser), expected_title);
118 const base::string16& actual_title = watcher.WaitAndGetTitle();
119 EXPECT_EQ(expected_title, actual_title);
120 }
121 }
122
123 struct ExpectedColor {
ExpectedColorcontent::SnapshotBrowserTest::ExpectedColor124 ExpectedColor() : r(0), g(0), b(0) {}
operator ==content::SnapshotBrowserTest::ExpectedColor125 bool operator==(const ExpectedColor& other) const {
126 return (r == other.r && g == other.g && b == other.b);
127 }
128 uint8_t r;
129 uint8_t g;
130 uint8_t b;
131 };
132
PickRandomColor(ExpectedColor * expected)133 void PickRandomColor(ExpectedColor* expected) {
134 expected->r = static_cast<uint8_t>(base::RandInt(0, 256));
135 expected->g = static_cast<uint8_t>(base::RandInt(0, 256));
136 expected->b = static_cast<uint8_t>(base::RandInt(0, 256));
137 }
138
139 struct SerialSnapshot {
SerialSnapshotcontent::SnapshotBrowserTest::SerialSnapshot140 SerialSnapshot() : host(nullptr) {}
141
142 content::RenderWidgetHost* host;
143 ExpectedColor color;
144 };
145 std::vector<SerialSnapshot> expected_snapshots_;
146
SyncSnapshotCallback(content::RenderWidgetHostImpl * rwhi,const gfx::Image & image)147 void SyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
148 const gfx::Image& image) {
149 bool found = false;
150 for (auto iter = expected_snapshots_.begin();
151 iter != expected_snapshots_.end(); ++iter) {
152 const SerialSnapshot& expected = *iter;
153 if (expected.host == rwhi) {
154 found = true;
155
156 const SkBitmap* bitmap = image.ToSkBitmap();
157 SkColor color = bitmap->getColor(1, 1);
158
159 EXPECT_EQ(static_cast<int>(SkColorGetR(color)),
160 static_cast<int>(expected.color.r))
161 << "Red channels differed";
162 EXPECT_EQ(static_cast<int>(SkColorGetG(color)),
163 static_cast<int>(expected.color.g))
164 << "Green channels differed";
165 EXPECT_EQ(static_cast<int>(SkColorGetB(color)),
166 static_cast<int>(expected.color.b))
167 << "Blue channels differed";
168
169 expected_snapshots_.erase(iter);
170 break;
171 }
172 }
173 }
174
175 std::map<content::RenderWidgetHost*, std::vector<ExpectedColor>>
176 expected_async_snapshots_map_;
177 int num_remaining_async_snapshots_ = 0;
178
AsyncSnapshotCallback(content::RenderWidgetHostImpl * rwhi,const gfx::Image & image)179 void AsyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
180 const gfx::Image& image) {
181 --num_remaining_async_snapshots_;
182 auto iterator = expected_async_snapshots_map_.find(rwhi);
183 ASSERT_NE(iterator, expected_async_snapshots_map_.end());
184 std::vector<ExpectedColor>& expected_snapshots = iterator->second;
185 const SkBitmap* bitmap = image.ToSkBitmap();
186 SkColor color = bitmap->getColor(1, 1);
187 bool found = false;
188 // Find first instance of this color in the list and clear out all
189 // of the entries before that point. If it's not found, report
190 // failure.
191 for (auto iter = expected_snapshots.begin();
192 iter != expected_snapshots.end(); ++iter) {
193 const ExpectedColor& expected = *iter;
194 if (SkColorGetR(color) == expected.r &&
195 SkColorGetG(color) == expected.g &&
196 SkColorGetB(color) == expected.b) {
197 // Erase everything up to this color, but not this color
198 // itself, since it might be returned again later on
199 // subsequent snapshot requests.
200 expected_snapshots.erase(expected_snapshots.begin(), iter);
201 found = true;
202 break;
203 }
204 }
205
206 EXPECT_TRUE(found) << "Did not find color ("
207 << static_cast<int>(SkColorGetR(color)) << ", "
208 << static_cast<int>(SkColorGetG(color)) << ", "
209 << static_cast<int>(SkColorGetB(color))
210 << ") in expected snapshots for RWH 0x" << rwhi;
211 }
212 };
213
214 // Even the single-window test doesn't work on Android yet. It's expected
215 // that the multi-window tests would never work on that platform.
216 #if !defined(OS_ANDROID)
217
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest,SingleWindowTest)218 IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, SingleWindowTest) {
219 SetupTestServer();
220
221 content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(shell());
222
223 for (int i = 0; i < 40; ++i) {
224 SerialSnapshot expected;
225 expected.host = rwhi;
226 PickRandomColor(&expected.color);
227
228 std::string colorString = base::StringPrintf(
229 "#%02x%02x%02x", expected.color.r, expected.color.g, expected.color.b);
230 std::string script = std::string("fillWithColor(\"") + colorString + "\");";
231 std::string result;
232 EXPECT_TRUE(content::ExecuteScriptAndExtractString(GetWebContents(shell()),
233 script, &result));
234 EXPECT_EQ(result, colorString);
235
236 expected_snapshots_.push_back(expected);
237
238 // Get the snapshot from the surface rather than the window. The
239 // on-screen display path is verified by the GPU tests, and it
240 // seems difficult to figure out the colorspace transformation
241 // required to make these color comparisons.
242 rwhi->GetSnapshotFromBrowser(
243 base::BindOnce(&SnapshotBrowserTest::SyncSnapshotCallback,
244 base::Unretained(this), base::Unretained(rwhi)),
245 true);
246 while (expected_snapshots_.size() > 0) {
247 base::RunLoop().RunUntilIdle();
248 }
249 }
250 }
251
252 // Timing out either all the time, or infrequently, apparently because
253 // they're too slow, on the following configurations:
254 // Windows Debug
255 // Linux Chromium OS ASAN LSAN Tests (1)
256 // Linux TSAN Tests
257 // See crbug.com/771119
258 #if (defined(OS_WIN) && !defined(NDEBUG)) || (defined(OS_CHROMEOS)) || \
259 (defined(OS_LINUX) && defined(THREAD_SANITIZER))
260 #define MAYBE_SyncMultiWindowTest DISABLED_SyncMultiWindowTest
261 #define MAYBE_AsyncMultiWindowTest DISABLED_AsyncMultiWindowTest
262 #else
263 #define MAYBE_SyncMultiWindowTest SyncMultiWindowTest
264 #define MAYBE_AsyncMultiWindowTest AsyncMultiWindowTest
265 #endif
266
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest,MAYBE_SyncMultiWindowTest)267 IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_SyncMultiWindowTest) {
268 SetupTestServer();
269
270 for (int i = 0; i < 3; ++i) {
271 bool result = false;
272 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
273 GetWebContents(shell()), "openNewWindow()", &result));
274 EXPECT_TRUE(result);
275 }
276
277 base::RunLoop().RunUntilIdle();
278
279 WaitForAllWindowsToBeReady();
280
281 auto browser_list = Shell::windows();
282 EXPECT_EQ(4u, browser_list.size());
283
284 for (int i = 0; i < 20; ++i) {
285 for (int j = 0; j < 4; j++) {
286 // Start each iteration by taking a snapshot with a different
287 // browser instance.
288 int browser_index = (i + j) % 4;
289 Shell* browser = browser_list[browser_index];
290 content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(browser);
291
292 SerialSnapshot expected;
293 expected.host = rwhi;
294 PickRandomColor(&expected.color);
295
296 std::string colorString =
297 base::StringPrintf("#%02x%02x%02x", expected.color.r,
298 expected.color.g, expected.color.b);
299 std::string script =
300 std::string("fillWithColor(\"") + colorString + "\");";
301 std::string result;
302 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
303 GetWebContents(browser), script, &result));
304 EXPECT_EQ(result, colorString);
305 expected_snapshots_.push_back(expected);
306 // Get the snapshot from the surface rather than the window. The
307 // on-screen display path is verified by the GPU tests, and it
308 // seems difficult to figure out the colorspace transformation
309 // required to make these color comparisons.
310 rwhi->GetSnapshotFromBrowser(
311 base::BindOnce(&SnapshotBrowserTest::SyncSnapshotCallback,
312 base::Unretained(this), base::Unretained(rwhi)),
313 true);
314 }
315
316 while (expected_snapshots_.size() > 0) {
317 base::RunLoop().RunUntilIdle();
318 }
319 }
320 }
321
IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest,MAYBE_AsyncMultiWindowTest)322 IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_AsyncMultiWindowTest) {
323 SetupTestServer();
324
325 for (int i = 0; i < 3; ++i) {
326 bool result = false;
327 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
328 GetWebContents(shell()), "openNewWindow()", &result));
329 EXPECT_TRUE(result);
330 }
331
332 base::RunLoop().RunUntilIdle();
333
334 WaitForAllWindowsToBeReady();
335
336 auto browser_list = Shell::windows();
337 EXPECT_EQ(4u, browser_list.size());
338
339 // This many pending snapshots per window will be put on the queue
340 // before draining the requests. Anything more than 1 seems to catch
341 // bugs which might otherwise be introduced in LatencyInfo's
342 // propagation of the BROWSER_SNAPSHOT_FRAME_NUMBER_COMPONENT
343 // component type.
344 int divisor = 3;
345
346 for (int i = 0; i < 10 * divisor; ++i) {
347 for (int j = 0; j < 4; j++) {
348 // Start each iteration by taking a snapshot with a different
349 // browser instance.
350 int browser_index = (i + j) % 4;
351 Shell* browser = browser_list[browser_index];
352 content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(browser);
353
354 std::vector<ExpectedColor>& expected_snapshots =
355 expected_async_snapshots_map_[rwhi];
356
357 // Pick a unique random color.
358 ExpectedColor expected;
359 do {
360 PickRandomColor(&expected);
361 } while (base::Contains(expected_snapshots, expected));
362 expected_snapshots.push_back(expected);
363
364 std::string colorString = base::StringPrintf("#%02x%02x%02x", expected.r,
365 expected.g, expected.b);
366 std::string script =
367 std::string("fillWithColor(\"") + colorString + "\");";
368 std::string result;
369 EXPECT_TRUE(content::ExecuteScriptAndExtractString(
370 GetWebContents(browser), script, &result));
371 EXPECT_EQ(result, colorString);
372 // Get the snapshot from the surface rather than the window. The
373 // on-screen display path is verified by the GPU tests, and it
374 // seems difficult to figure out the colorspace transformation
375 // required to make these color comparisons.
376 rwhi->GetSnapshotFromBrowser(
377 base::BindOnce(&SnapshotBrowserTest::AsyncSnapshotCallback,
378 base::Unretained(this), base::Unretained(rwhi)),
379 true);
380 ++num_remaining_async_snapshots_;
381 }
382
383 // Periodically yield and drain the async snapshot requests.
384 if ((i % divisor) == 0) {
385 bool drained;
386 do {
387 drained = true;
388 for (auto iter = expected_async_snapshots_map_.begin();
389 iter != expected_async_snapshots_map_.end(); ++iter) {
390 if (iter->second.size() > 1) {
391 drained = false;
392 break;
393 }
394 }
395 if (!drained) {
396 base::RunLoop().RunUntilIdle();
397 }
398 } while (!drained);
399 }
400 }
401
402 // At the end of the test, cooperatively wait for all of the snapshot
403 // requests to be drained before exiting. This works around crashes
404 // seen when tearing down the compositor while these requests are in
405 // flight. Likely, user-visible APIs that use this facility are safe
406 // in this regard.
407 while (num_remaining_async_snapshots_ > 0) {
408 base::RunLoop().RunUntilIdle();
409 }
410 }
411
412 #endif // !defined(OS_ANDROID)
413
414 } // namespace content
415