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()
44 void SendCommand(const std::string
& method
,
45 scoped_ptr
<base::DictionaryValue
> params
) {
46 SendCommand(method
, params
.Pass(), true);
49 void SendCommand(const std::string
& method
,
50 scoped_ptr
<base::DictionaryValue
> params
,
53 base::DictionaryValue command
;
54 command
.SetInteger(kIdParam
, ++last_sent_id_
);
55 command
.SetString(kMethodParam
, method
);
57 command
.Set(kParamsParam
, params
.release());
59 std::string json_command
;
60 base::JSONWriter::Write(command
, &json_command
);
61 agent_host_
->DispatchProtocolMessage(json_command
);
62 // Some messages are dispatched synchronously.
63 // Only run loop if we are not finished yet.
64 if (in_dispatch_
&& wait
)
65 base::MessageLoop::current()->Run();
69 bool HasValue(const std::string
& path
) {
70 base::Value
* value
= 0;
71 return result_
->Get(path
, &value
);
74 bool HasListItem(const std::string
& path_to_list
,
75 const std::string
& name
,
76 const std::string
& value
) {
77 base::ListValue
* list
;
78 if (!result_
->GetList(path_to_list
, &list
))
81 for (size_t i
= 0; i
!= list
->GetSize(); i
++) {
82 base::DictionaryValue
* item
;
83 if (!list
->GetDictionary(i
, &item
))
86 if (!item
->GetString(name
, &id
))
95 agent_host_
= DevToolsAgentHost::GetOrCreateFor(shell()->web_contents());
96 agent_host_
->AttachClient(this);
99 void TearDownOnMainThread() override
{
101 agent_host_
->DetachClient();
102 agent_host_
= nullptr;
106 scoped_ptr
<base::DictionaryValue
> result_
;
107 scoped_refptr
<DevToolsAgentHost
> agent_host_
;
109 std::vector
<int> result_ids_
;
110 std::vector
<std::string
> notifications_
;
113 void DispatchProtocolMessage(DevToolsAgentHost
* agent_host
,
114 const std::string
& message
) override
{
115 scoped_ptr
<base::DictionaryValue
> root(static_cast<base::DictionaryValue
*>(
116 base::JSONReader::Read(message
).release()));
118 if (root
->GetInteger("id", &id
)) {
119 result_ids_
.push_back(id
);
120 base::DictionaryValue
* result
;
121 EXPECT_TRUE(root
->GetDictionary("result", &result
));
122 result_
.reset(result
->DeepCopy());
123 in_dispatch_
= false;
124 if (base::MessageLoop::current()->is_running())
125 base::MessageLoop::current()->QuitNow();
127 std::string notification
;
128 EXPECT_TRUE(root
->GetString("method", ¬ification
));
129 notifications_
.push_back(notification
);
133 void AgentHostClosed(DevToolsAgentHost
* agent_host
, bool replaced
) override
{
140 class SyntheticKeyEventTest
: public DevToolsProtocolTest
{
142 void SendKeyEvent(const std::string
& type
,
146 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
147 params
->SetString("type", type
);
148 params
->SetInteger("modifiers", modifier
);
149 params
->SetInteger("windowsVirtualKeyCode", windowsKeyCode
);
150 params
->SetInteger("nativeVirtualKeyCode", nativeKeyCode
);
151 SendCommand("Input.dispatchKeyEvent", params
.Pass());
155 IN_PROC_BROWSER_TEST_F(SyntheticKeyEventTest
, KeyEventSynthesizeKeyIdentifier
) {
156 NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
158 ASSERT_TRUE(content::ExecuteScript(
159 shell()->web_contents()->GetRenderViewHost(),
160 "function handleKeyEvent(event) {"
161 "domAutomationController.setAutomationId(0);"
162 "domAutomationController.send(event.keyIdentifier);"
164 "document.body.addEventListener('keydown', handleKeyEvent);"
165 "document.body.addEventListener('keyup', handleKeyEvent);"));
167 DOMMessageQueue dom_message_queue
;
169 // Send enter (keycode 13).
170 SendKeyEvent("rawKeyDown", 0, 13, 13);
171 SendKeyEvent("keyUp", 0, 13, 13);
173 std::string key_identifier
;
174 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
175 EXPECT_EQ("\"Enter\"", key_identifier
);
176 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
177 EXPECT_EQ("\"Enter\"", key_identifier
);
179 // Send escape (keycode 27).
180 SendKeyEvent("rawKeyDown", 0, 27, 27);
181 SendKeyEvent("keyUp", 0, 27, 27);
183 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
184 EXPECT_EQ("\"U+001B\"", key_identifier
);
185 ASSERT_TRUE(dom_message_queue
.WaitForMessage(&key_identifier
));
186 EXPECT_EQ("\"U+001B\"", key_identifier
);
189 class CaptureScreenshotTest
: public DevToolsProtocolTest
{
191 #if !defined(OS_ANDROID)
192 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
193 command_line
->AppendSwitch(switches::kEnablePixelOutputInTests
);
198 // Does not link on Android
199 #if defined(OS_ANDROID)
200 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
201 #elif defined(OS_MACOSX) // Fails on 10.9. http://crbug.com/430620
202 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
203 #elif defined(MEMORY_SANITIZER)
204 // Also fails under MSAN. http://crbug.com/423583
205 #define MAYBE_CaptureScreenshot DISABLED_CaptureScreenshot
207 #define MAYBE_CaptureScreenshot CaptureScreenshot
209 IN_PROC_BROWSER_TEST_F(CaptureScreenshotTest
, MAYBE_CaptureScreenshot
) {
210 shell()->LoadURL(GURL("about:blank"));
212 EXPECT_TRUE(content::ExecuteScript(
213 shell()->web_contents()->GetRenderViewHost(),
214 "document.body.style.background = '#123456'"));
215 SendCommand("Page.captureScreenshot", nullptr);
217 EXPECT_TRUE(result_
->GetString("data", &base64
));
219 EXPECT_TRUE(base::Base64Decode(base64
, &png
));
221 gfx::PNGCodec::Decode(reinterpret_cast<const unsigned char*>(png
.data()),
222 png
.size(), &bitmap
);
223 SkColor
color(bitmap
.getColor(0, 0));
224 EXPECT_TRUE(std::abs(0x12-(int)SkColorGetR(color
)) <= 1);
225 EXPECT_TRUE(std::abs(0x34-(int)SkColorGetG(color
)) <= 1);
226 EXPECT_TRUE(std::abs(0x56-(int)SkColorGetB(color
)) <= 1);
229 #if defined(OS_ANDROID)
230 // Disabled, see http://crbug.com/469947.
231 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, DISABLED_SynthesizePinchGesture
) {
232 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
233 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
237 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
238 shell()->web_contents(),
239 "domAutomationController.send(window.innerWidth)", &old_width
));
242 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
243 shell()->web_contents(),
244 "domAutomationController.send(window.innerHeight)", &old_height
));
246 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
247 params
->SetInteger("x", old_width
/ 2);
248 params
->SetInteger("y", old_height
/ 2);
249 params
->SetDouble("scaleFactor", 2.0);
250 SendCommand("Input.synthesizePinchGesture", params
.Pass());
253 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
254 shell()->web_contents(),
255 "domAutomationController.send(window.innerWidth)", &new_width
));
256 ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_width
) / new_width
);
259 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
260 shell()->web_contents(),
261 "domAutomationController.send(window.innerHeight)", &new_height
));
262 ASSERT_DOUBLE_EQ(2.0, static_cast<double>(old_height
) / new_height
);
265 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, SynthesizeScrollGesture
) {
266 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
267 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
271 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
272 shell()->web_contents(),
273 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
274 ASSERT_EQ(0, scroll_top
);
276 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
277 params
->SetInteger("x", 0);
278 params
->SetInteger("y", 0);
279 params
->SetInteger("xDistance", 0);
280 params
->SetInteger("yDistance", -100);
281 SendCommand("Input.synthesizeScrollGesture", params
.Pass());
283 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
284 shell()->web_contents(),
285 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
286 ASSERT_EQ(100, scroll_top
);
289 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, SynthesizeTapGesture
) {
290 GURL test_url
= GetTestUrl("devtools", "synthetic_gesture_tests.html");
291 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
295 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
296 shell()->web_contents(),
297 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
298 ASSERT_EQ(0, scroll_top
);
300 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
301 params
->SetInteger("x", 16);
302 params
->SetInteger("y", 16);
303 params
->SetString("gestureSourceType", "touch");
304 SendCommand("Input.synthesizeTapGesture", params
.Pass());
306 // The link that we just tapped should take us to the bottom of the page. The
307 // new value of |document.body.scrollTop| will depend on the screen dimensions
308 // of the device that we're testing on, but in any case it should be greater
310 ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
311 shell()->web_contents(),
312 "domAutomationController.send(document.body.scrollTop)", &scroll_top
));
313 ASSERT_GT(scroll_top
, 0);
315 #endif // defined(OS_ANDROID)
317 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, NavigationPreservesMessages
) {
318 ASSERT_TRUE(test_server()->Start());
319 GURL test_url
= test_server()->GetURL("files/devtools/navigation.html");
320 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url
, 1);
322 SendCommand("Page.enable", nullptr, false);
324 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
325 test_url
= GetTestUrl("devtools", "navigation.html");
326 params
->SetString("url", test_url
.spec());
327 SendCommand("Page.navigate", params
.Pass(), true);
329 bool enough_results
= result_ids_
.size() >= 2u;
330 EXPECT_TRUE(enough_results
);
331 if (enough_results
) {
332 EXPECT_EQ(1, result_ids_
[0]); // Page.enable
333 EXPECT_EQ(2, result_ids_
[1]); // Page.navigate
336 enough_results
= notifications_
.size() >= 1u;
337 EXPECT_TRUE(enough_results
);
338 bool found_frame_notification
= false;
339 for (const std::string
& notification
: notifications_
) {
340 if (notification
== "Page.frameStartedLoading")
341 found_frame_notification
= true;
343 EXPECT_TRUE(found_frame_notification
);
346 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest
, CrossSiteNoDetach
) {
347 host_resolver()->AddRule("*", "127.0.0.1");
348 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
349 content::SetupCrossSiteRedirector(embedded_test_server());
351 GURL test_url1
= embedded_test_server()->GetURL(
352 "A.com", "/devtools/navigation.html");
353 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url1
, 1);
356 GURL test_url2
= embedded_test_server()->GetURL(
357 "B.com", "/devtools/navigation.html");
358 NavigateToURLBlockUntilNavigationsComplete(shell(), test_url2
, 1);
360 EXPECT_EQ(0u, notifications_
.size());
363 } // namespace content