1 // Copyright 2013 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.
5 #include "base/base64.h"
6 #include "base/command_line.h"
7 #include "base/json/json_reader.h"
8 #include "base/json/json_writer.h"
9 #include "base/values.h"
10 #include "content/public/browser/devtools_agent_host.h"
11 #include "content/public/browser/render_view_host.h"
12 #include "content/public/browser/web_contents.h"
13 #include "content/public/common/url_constants.h"
14 #include "content/public/test/browser_test_utils.h"
15 #include "content/public/test/content_browser_test.h"
16 #include "content/public/test/content_browser_test_utils.h"
17 #include "content/public/test/test_navigation_observer.h"
18 #include "content/shell/browser/shell.h"
19 #include "net/dns/mock_host_resolver.h"
20 #include "net/test/embedded_test_server/embedded_test_server.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "ui/compositor/compositor_switches.h"
23 #include "ui/gfx/codec/png_codec.h"
29 const char kIdParam
[] = "id";
30 const char kMethodParam
[] = "method";
31 const char kParamsParam
[] = "params";
35 class DevToolsProtocolTest
: public ContentBrowserTest
,
36 public DevToolsAgentHostClient
{
38 DevToolsProtocolTest()
40 waiting_for_notifications_count_(0),
45 void SendCommand(const std::string
& method
,
46 scoped_ptr
<base::DictionaryValue
> params
) {
47 SendCommand(method
, params
.Pass(), true);
50 void SendCommand(const std::string
& method
,
51 scoped_ptr
<base::DictionaryValue
> params
,
54 base::DictionaryValue command
;
55 command
.SetInteger(kIdParam
, ++last_sent_id_
);
56 command
.SetString(kMethodParam
, method
);
58 command
.Set(kParamsParam
, params
.release());
60 std::string json_command
;
61 base::JSONWriter::Write(command
, &json_command
);
62 agent_host_
->DispatchProtocolMessage(json_command
);
63 // Some messages are dispatched synchronously.
64 // Only run loop if we are not finished yet.
65 if (in_dispatch_
&& wait
)
66 base::MessageLoop::current()->Run();
70 bool HasValue(const std::string
& path
) {
71 base::Value
* value
= 0;
72 return result_
->Get(path
, &value
);
75 bool HasListItem(const std::string
& path_to_list
,
76 const std::string
& name
,
77 const std::string
& value
) {
78 base::ListValue
* list
;
79 if (!result_
->GetList(path_to_list
, &list
))
82 for (size_t i
= 0; i
!= list
->GetSize(); i
++) {
83 base::DictionaryValue
* item
;
84 if (!list
->GetDictionary(i
, &item
))
87 if (!item
->GetString(name
, &id
))
96 agent_host_
= DevToolsAgentHost::GetOrCreateFor(shell()->web_contents());
97 agent_host_
->AttachClient(this);
100 void TearDownOnMainThread() override
{
102 agent_host_
->DetachClient();
103 agent_host_
= nullptr;
107 void WaitForNotifications(int count
) {
108 waiting_for_notifications_count_
= count
;
112 scoped_ptr
<base::DictionaryValue
> result_
;
113 scoped_refptr
<DevToolsAgentHost
> agent_host_
;
115 std::vector
<int> result_ids_
;
116 std::vector
<std::string
> notifications_
;
119 void DispatchProtocolMessage(DevToolsAgentHost
* agent_host
,
120 const std::string
& message
) override
{
121 scoped_ptr
<base::DictionaryValue
> root(static_cast<base::DictionaryValue
*>(
122 base::JSONReader::Read(message
).release()));
124 if (root
->GetInteger("id", &id
)) {
125 result_ids_
.push_back(id
);
126 base::DictionaryValue
* result
;
127 EXPECT_TRUE(root
->GetDictionary("result", &result
));
128 result_
.reset(result
->DeepCopy());
129 in_dispatch_
= false;
130 if (base::MessageLoop::current()->is_running())
131 base::MessageLoop::current()->QuitNow();
133 std::string notification
;
134 EXPECT_TRUE(root
->GetString("method", ¬ification
));
135 notifications_
.push_back(notification
);
136 if (waiting_for_notifications_count_
) {
137 waiting_for_notifications_count_
--;
138 if (!waiting_for_notifications_count_
)
139 base::MessageLoop::current()->QuitNow();
144 void AgentHostClosed(DevToolsAgentHost
* agent_host
, bool replaced
) override
{
148 int waiting_for_notifications_count_
;
152 class SyntheticKeyEventTest
: public DevToolsProtocolTest
{
154 void SendKeyEvent(const std::string
& type
,
158 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
159 params
->SetString("type", type
);
160 params
->SetInteger("modifiers", modifier
);
161 params
->SetInteger("windowsVirtualKeyCode", windowsKeyCode
);
162 params
->SetInteger("nativeVirtualKeyCode", nativeKeyCode
);
163 SendCommand("Input.dispatchKeyEvent", params
.Pass());
167 IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest
, KeyEventSynthesizeKeyIdentifier
) {
168 NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
170 ASSERT_TRUE(content::ExecuteScript(
171 shell()->web_contents()->GetRenderViewHost(),
172 "function handleKeyEvent(event) {"
173 "domAutomationController.setAutomationId(0);"
174 "domAutomationController.send(event.keyIdentifier);"
176 "document.body.addEventListener('keydown', handleKeyEvent);"
177 "document.body.addEventListener('keyup', handleKeyEvent);"));
179 DOMMessageQueue dom_message_queue
;
181 // Send enter (keycode 13).
182 SendKeyEvent("rawKeyDown", 0, 13, 13);
183 SendKeyEvent("keyUp", 0, 13, 13);
185 std::string key_identifier
;
186 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
187 EXPECT_EQ("\"Enter\"", key_identifier
);
188 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
189 EXPECT_EQ("\"Enter\"", key_identifier
);
191 // Send escape (keycode 27).
192 SendKeyEvent("rawKeyDown", 0, 27, 27);
193 SendKeyEvent("keyUp", 0, 27, 27);
195 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
196 EXPECT_EQ("\"U+001B\"", key_identifier
);
197 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
198 EXPECT_EQ("\"U+001B\"", key_identifier
);
201 class CaptureScreenshotTest
: public DevToolsProtocolTest
{
203 #if !defined(OS_ANDROID)
204 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
205 command_line
->AppendSwitch(switches::kEnablePixelOutputInTests
);
210 // Does not link on Android
211 #if defined(OS_ANDROID)
212 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
213 #elif defined(OS_MACOSX) // Fails on 10.9. http://crbug.com/430620
214 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
215 #elif defined(MEMORY_SANITIZER)
216 // Also fails under MSAN. http://crbug.com/423583
217 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
219 #define MAYBE_CaptureScreenshot CaptureScreenshot
221 IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest
, MAYBE_CaptureScreenshot
) {
222 shell()->LoadURL(GURL("about:blank"));
224 EXPECT_TRUE(content::ExecuteScript(
225 shell()->web_contents()->GetRenderViewHost(),
226 "document.body.style.background = '#123456'"));
227 SendCommand("Page.captureScreenshot", nullptr);
229 EXPECT_TRUE(result_
->GetString("data", &base64
));
231 EXPECT_TRUE(base::Base64Decode(base64
, &png
));
233 gfx::PNGCodec::Decode(reinterpret_cast<const unsigned char*>(png
.data()),
234 png
.size(), &bitmap
);
235 SkColor
color(bitmap
.getColor(0, 0));
236 EXPECT_TRUE(std::abs(0x12-(int)SkColorGetR(color
)) <= 1);
237 EXPECT_TRUE(std::abs(0x34-(int)SkColorGetG(color
)) <= 1);
238 EXPECT_TRUE(std::abs(0x56-(int)SkColorGetB(color
)) <= 1);
241 #if defined(OS_ANDROID)
242 // Disabled, see http://crbug.com/469947.
243 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, DISABLED_SynthesizePinchGesture
) {
244 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
245 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
249 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
250 shell()->web_contents(),
251 "domAutomationController.send(window.innerWidth)", &old_width
));
254 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
255 shell()->web_contents(),
256 "domAutomationController.send(window.innerHeight)", &old_height
));
258 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
259 params
->SetInteger("x", old_width
/ 2);
260 params
->SetInteger("y", old_height
/ 2);
261 params
->SetDouble("scaleFactor", 2.0);
262 SendCommand("Input.synthesizePinchGesture", params
.Pass());
265 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
266 shell()->web_contents(),
267 "domAutomationController.send(window.innerWidth)", &new_width
));
268 ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_width
) / new_width
);
271 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
272 shell()->web_contents(),
273 "domAutomationController.send(window.innerHeight)", &new_height
));
274 ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_height
) / new_height
);
277 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, SynthesizeScrollGesture
) {
278 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
279 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
283 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
284 shell()->web_contents(),
285 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
286 ASSERT_EQ(0, scroll_top
);
288 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
289 params
->SetInteger("x", 0);
290 params
->SetInteger("y", 0);
291 params
->SetInteger("xDistance", 0);
292 params
->SetInteger("yDistance", -100);
293 SendCommand("Input.synthesizeScrollGesture", params
.Pass());
295 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
296 shell()->web_contents(),
297 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
298 ASSERT_EQ(100, scroll_top
);
301 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, SynthesizeTapGesture
) {
302 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
303 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
307 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
308 shell()->web_contents(),
309 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
310 ASSERT_EQ(0, scroll_top
);
312 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
313 params
->SetInteger("x", 16);
314 params
->SetInteger("y", 16);
315 params
->SetString("gestureSourceType", "touch");
316 SendCommand("Input.synthesizeTapGesture", params
.Pass());
318 // The link that we just tapped should take us to the bottom of the page. The
319 // new value of |document.body.scrollTop| will depend on the screen dimensions
320 // of the device that we're testing on, but in any case it should be greater
322 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
323 shell()->web_contents(),
324 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
325 ASSERT_GT(scroll_top
, 0);
327 #endif // defined(OS_ANDROID)
329 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, NavigationPreservesMessages
) {
330 ASSERT_TRUE(test_server()->Start());
331 GURL test_url
= test_server()->GetURL("files/devtools/navigation.html");
332 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
334 SendCommand("Page.enable", nullptr, false);
336 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
337 test_url
= GetTestUrl("devtools", "navigation.html");
338 params
->SetString("url", test_url
.spec());
339 SendCommand("Page.navigate", params
.Pass(), true);
341 bool enough_results
= result_ids_
.size() >= 2u;
342 EXPECT_TRUE(enough_results
);
343 if (enough_results
) {
344 EXPECT_EQ(1, result_ids_
[0]); // Page.enable
345 EXPECT_EQ(2, result_ids_
[1]); // Page.navigate
348 enough_results
= notifications_
.size() >= 1u;
349 EXPECT_TRUE(enough_results
);
350 bool found_frame_notification
= false;
351 for (const std::string
& notification
: notifications_
) {
352 if (notification
== "Page.frameStartedLoading")
353 found_frame_notification
= true;
355 EXPECT_TRUE(found_frame_notification
);
358 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, CrossSiteNoDetach
) {
359 host_resolver()->AddRule("*", "127.0.0.1");
360 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
361 content::SetupCrossSiteRedirector(embedded_test_server());
363 GURL test_url1
= embedded_test_server()->GetURL(
364 "A.com", "/devtools/navigation.html");
365 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1
, 1);
368 GURL test_url2
= embedded_test_server()->GetURL(
369 "B.com", "/devtools/navigation.html");
370 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2
, 1);
372 EXPECT_EQ(0u, notifications_
.size());
375 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, ReconnectPreservesState
) {
376 ASSERT_TRUE(test_server()->Start());
377 GURL test_url
= test_server()->GetURL("files/devtools/navigation.html");
378 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
380 Shell
* second
= CreateBrowser();
381 NavigateToURLBlockUntilNavigationsComplete(second
, test_url
, 1);
384 SendCommand("Runtime.enable", nullptr);
386 size_t notification_count
= notifications_
.size();
387 agent_host_
->DisconnectWebContents();
388 agent_host_
->ConnectWebContents(second
->web_contents());
389 WaitForNotifications(1);
391 bool found_notification
= false;
392 for (size_t i
= notification_count
; i
< notifications_
.size(); ++i
) {
393 if (notifications_
[i
] == "Runtime.executionContextsCleared")
394 found_notification
= true;
396 EXPECT_TRUE(found_notification
);
399 } // namespace content