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/web_contents.h"
12 #include "content/public/test/browser_test_utils.h"
13 #include "content/public/test/content_browser_test.h"
14 #include "content/public/test/content_browser_test_utils.h"
15 #include "content/public/test/test_navigation_observer.h"
16 #include "content/shell/browser/shell.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 #include "ui/compositor/compositor_switches.h"
19 #include "ui/gfx/codec/png_codec.h"
25 const char kIdParam
[] = "id";
26 const char kMethodParam
[] = "method";
27 const char kParamsParam
[] = "params";
31 class DevToolsProtocolTest
: public ContentBrowserTest
,
32 public DevToolsAgentHostClient
{
34 DevToolsProtocolTest()
40 void SendCommand(const std::string
& method
,
41 scoped_ptr
<base::DictionaryValue
> params
) {
42 SendCommand(method
, params
.Pass(), true);
45 void SendCommand(const std::string
& method
,
46 scoped_ptr
<base::DictionaryValue
> params
,
49 base::DictionaryValue command
;
50 command
.SetInteger(kIdParam
, ++last_sent_id_
);
51 command
.SetString(kMethodParam
, method
);
53 command
.Set(kParamsParam
, params
.release());
55 std::string json_command
;
56 base::JSONWriter::Write(command
, &json_command
);
57 agent_host_
->DispatchProtocolMessage(json_command
);
58 // Some messages are dispatched synchronously.
59 // Only run loop if we are not finished yet.
60 if (in_dispatch_
&& wait
)
61 base::MessageLoop::current()->Run();
65 bool HasValue(const std::string
& path
) {
66 base::Value
* value
= 0;
67 return result_
->Get(path
, &value
);
70 bool HasListItem(const std::string
& path_to_list
,
71 const std::string
& name
,
72 const std::string
& value
) {
73 base::ListValue
* list
;
74 if (!result_
->GetList(path_to_list
, &list
))
77 for (size_t i
= 0; i
!= list
->GetSize(); i
++) {
78 base::DictionaryValue
* item
;
79 if (!list
->GetDictionary(i
, &item
))
82 if (!item
->GetString(name
, &id
))
91 agent_host_
= DevToolsAgentHost::GetOrCreateFor(shell()->web_contents());
92 agent_host_
->AttachClient(this);
95 void TearDownOnMainThread() override
{
97 agent_host_
->DetachClient();
98 agent_host_
= nullptr;
102 scoped_ptr
<base::DictionaryValue
> result_
;
103 scoped_refptr
<DevToolsAgentHost
> agent_host_
;
105 std::vector
<int> result_ids_
;
106 std::vector
<std::string
> notifications_
;
109 void DispatchProtocolMessage(DevToolsAgentHost
* agent_host
,
110 const std::string
& message
) override
{
111 scoped_ptr
<base::DictionaryValue
> root(static_cast<base::DictionaryValue
*>(
112 base::JSONReader::DeprecatedRead(message
)));
114 if (root
->GetInteger("id", &id
)) {
115 result_ids_
.push_back(id
);
116 base::DictionaryValue
* result
;
117 EXPECT_TRUE(root
->GetDictionary("result", &result
));
118 result_
.reset(result
->DeepCopy());
119 in_dispatch_
= false;
120 if (base::MessageLoop::current()->is_running())
121 base::MessageLoop::current()->QuitNow();
123 std::string notification
;
124 EXPECT_TRUE(root
->GetString("method", ¬ification
));
125 notifications_
.push_back(notification
);
129 void AgentHostClosed(DevToolsAgentHost
* agent_host
, bool replaced
) override
{
136 class SyntheticKeyEventTest
: public DevToolsProtocolTest
{
138 void SendKeyEvent(const std::string
& type
,
142 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
143 params
->SetString("type", type
);
144 params
->SetInteger("modifiers", modifier
);
145 params
->SetInteger("windowsVirtualKeyCode", windowsKeyCode
);
146 params
->SetInteger("nativeVirtualKeyCode", nativeKeyCode
);
147 SendCommand("Input.dispatchKeyEvent", params
.Pass());
151 IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest
, KeyEventSynthesizeKeyIdentifier
) {
152 NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
154 ASSERT_TRUE(content::ExecuteScript(
155 shell()->web_contents()->GetRenderViewHost(),
156 "function handleKeyEvent(event) {"
157 "domAutomationController.setAutomationId(0);"
158 "domAutomationController.send(event.keyIdentifier);"
160 "document.body.addEventListener('keydown', handleKeyEvent);"
161 "document.body.addEventListener('keyup', handleKeyEvent);"));
163 DOMMessageQueue dom_message_queue
;
165 // Send enter (keycode 13).
166 SendKeyEvent("rawKeyDown", 0, 13, 13);
167 SendKeyEvent("keyUp", 0, 13, 13);
169 std::string key_identifier
;
170 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
171 EXPECT_EQ("\"Enter\"", key_identifier
);
172 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
173 EXPECT_EQ("\"Enter\"", key_identifier
);
175 // Send escape (keycode 27).
176 SendKeyEvent("rawKeyDown", 0, 27, 27);
177 SendKeyEvent("keyUp", 0, 27, 27);
179 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
180 EXPECT_EQ("\"U+001B\"", key_identifier
);
181 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
182 EXPECT_EQ("\"U+001B\"", key_identifier
);
185 class CaptureScreenshotTest
: public DevToolsProtocolTest
{
187 #if !defined(OS_ANDROID)
188 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
189 command_line
->AppendSwitch(switches::kEnablePixelOutputInTests
);
194 // Does not link on Android
195 #if defined(OS_ANDROID)
196 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
197 #elif defined(OS_MACOSX) // Fails on 10.9. http://crbug.com/430620
198 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
199 #elif defined(MEMORY_SANITIZER)
200 // Also fails under MSAN. http://crbug.com/423583
201 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
203 #define MAYBE_CaptureScreenshot CaptureScreenshot
205 IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest
, MAYBE_CaptureScreenshot
) {
206 shell()->LoadURL(GURL("about:blank"));
208 EXPECT_TRUE(content::ExecuteScript(
209 shell()->web_contents()->GetRenderViewHost(),
210 "document.body.style.background = '#123456'"));
211 SendCommand("Page.captureScreenshot", nullptr);
213 EXPECT_TRUE(result_
->GetString("data", &base64
));
215 EXPECT_TRUE(base::Base64Decode(base64
, &png
));
217 gfx::PNGCodec::Decode(reinterpret_cast<const unsigned char*>(png
.data()),
218 png
.size(), &bitmap
);
219 SkColor
color(bitmap
.getColor(0, 0));
220 EXPECT_TRUE(std::abs(0x12-(int)SkColorGetR(color
)) <= 1);
221 EXPECT_TRUE(std::abs(0x34-(int)SkColorGetG(color
)) <= 1);
222 EXPECT_TRUE(std::abs(0x56-(int)SkColorGetB(color
)) <= 1);
225 #if defined(OS_ANDROID)
226 // Disabled, see http://crbug.com/469947.
227 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, DISABLED_SynthesizePinchGesture
) {
228 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
229 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
233 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
234 shell()->web_contents(),
235 "domAutomationController.send(window.innerWidth)", &old_width
));
238 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
239 shell()->web_contents(),
240 "domAutomationController.send(window.innerHeight)", &old_height
));
242 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
243 params
->SetInteger("x", old_width
/ 2);
244 params
->SetInteger("y", old_height
/ 2);
245 params
->SetDouble("scaleFactor", 2.0);
246 SendCommand("Input.synthesizePinchGesture", params
.Pass());
249 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
250 shell()->web_contents(),
251 "domAutomationController.send(window.innerWidth)", &new_width
));
252 ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_width
) / new_width
);
255 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
256 shell()->web_contents(),
257 "domAutomationController.send(window.innerHeight)", &new_height
));
258 ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_height
) / new_height
);
261 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, SynthesizeScrollGesture
) {
262 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
263 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
267 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
268 shell()->web_contents(),
269 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
270 ASSERT_EQ(0, scroll_top
);
272 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
273 params
->SetInteger("x", 0);
274 params
->SetInteger("y", 0);
275 params
->SetInteger("xDistance", 0);
276 params
->SetInteger("yDistance", -100);
277 SendCommand("Input.synthesizeScrollGesture", params
.Pass());
279 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
280 shell()->web_contents(),
281 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
282 ASSERT_EQ(100, scroll_top
);
285 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, SynthesizeTapGesture
) {
286 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
287 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
291 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
292 shell()->web_contents(),
293 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
294 ASSERT_EQ(0, scroll_top
);
296 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
297 params
->SetInteger("x", 16);
298 params
->SetInteger("y", 16);
299 params
->SetString("gestureSourceType", "touch");
300 SendCommand("Input.synthesizeTapGesture", params
.Pass());
302 // The link that we just tapped should take us to the bottom of the page. The
303 // new value of |document.body.scrollTop| will depend on the screen dimensions
304 // of the device that we're testing on, but in any case it should be greater
306 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
307 shell()->web_contents(),
308 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
309 ASSERT_GT(scroll_top
, 0);
311 #endif // defined(OS_ANDROID)
313 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, NavigationPreservesMessages
) {
314 ASSERT_TRUE(test_server()->Start());
315 GURL test_url
= test_server()->GetURL("files/devtools/navigation.html");
316 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
318 SendCommand("Page.enable", nullptr, false);
320 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
321 test_url
= GetTestUrl("devtools", "navigation.html");
322 params
->SetString("url", test_url
.spec());
323 SendCommand("Page.navigate", params
.Pass(), true);
325 bool enough_results
= result_ids_
.size() >= 2u;
326 EXPECT_TRUE(enough_results
);
327 if (enough_results
) {
328 EXPECT_EQ(1, result_ids_
[0]); // Page.enable
329 EXPECT_EQ(2, result_ids_
[1]); // Page.navigate
332 enough_results
= notifications_
.size() >= 1u;
333 EXPECT_TRUE(enough_results
);
334 bool found_frame_notification
= false;
335 for (const std::string
& notification
: notifications_
) {
336 if (notification
== "Page.frameStartedLoading")
337 found_frame_notification
= true;
339 EXPECT_TRUE(found_frame_notification
);
342 } // namespace content