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