OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "content/public/test/browser_test_utils.h" | |
6 | |
7 #include "base/command_line.h" | |
8 #include "base/json/json_reader.h" | |
9 #include "base/path_service.h" | |
10 #include "base/process_util.h" | |
11 #include "base/rand_util.h" | |
12 #include "base/string_number_conversions.h" | |
13 #include "base/test/test_timeouts.h" | |
14 #include "base/utf_string_conversions.h" | |
15 #include "net/base/net_util.h" | |
16 #include "content/public/browser/dom_operation_notification_details.h" | |
17 #include "content/public/browser/notification_types.h" | |
18 #include "content/public/browser/render_process_host.h" | |
19 #include "content/public/browser/render_view_host.h" | |
20 #include "content/public/browser/web_contents.h" | |
21 #include "content/public/browser/web_contents_observer.h" | |
22 #include "content/public/browser/web_contents_view.h" | |
23 #include "content/public/test/test_utils.h" | |
24 #include "net/test/python_utils.h" | |
25 #include "testing/gtest/include/gtest/gtest.h" | |
26 | |
27 static const int kDefaultWsPort = 8880; | |
28 | |
29 namespace content { | |
30 | |
31 namespace { | |
32 | |
33 class DOMOperationObserver : public NotificationObserver, | |
34 public WebContentsObserver { | |
35 public: | |
36 explicit DOMOperationObserver(RenderViewHost* render_view_host) | |
37 : WebContentsObserver(WebContents::FromRenderViewHost(render_view_host)), | |
38 did_respond_(false) { | |
39 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, | |
40 Source<RenderViewHost>(render_view_host)); | |
41 message_loop_runner_ = new MessageLoopRunner; | |
42 } | |
43 | |
44 virtual void Observe(int type, | |
45 const NotificationSource& source, | |
46 const NotificationDetails& details) OVERRIDE { | |
47 DCHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE); | |
48 Details<DomOperationNotificationDetails> dom_op_details(details); | |
49 response_ = dom_op_details->json; | |
50 did_respond_ = true; | |
51 message_loop_runner_->Quit(); | |
52 } | |
53 | |
54 // Overridden from WebContentsObserver: | |
55 virtual void RenderViewGone(base::TerminationStatus status) OVERRIDE { | |
56 message_loop_runner_->Quit(); | |
57 } | |
58 | |
59 bool WaitAndGetResponse(std::string* response) WARN_UNUSED_RESULT { | |
60 message_loop_runner_->Run(); | |
61 *response = response_; | |
62 return did_respond_; | |
63 } | |
64 | |
65 private: | |
66 NotificationRegistrar registrar_; | |
67 std::string response_; | |
68 bool did_respond_; | |
69 scoped_refptr<MessageLoopRunner> message_loop_runner_; | |
70 | |
71 DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver); | |
72 }; | |
73 | |
74 // Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute. | |
75 bool ExecuteJavaScriptHelper(RenderViewHost* render_view_host, | |
76 const std::wstring& frame_xpath, | |
77 const std::wstring& original_script, | |
78 scoped_ptr<Value>* result) WARN_UNUSED_RESULT; | |
79 | |
80 // Executes the passed |original_script| in the frame pointed to by | |
81 // |frame_xpath|. If |result| is not NULL, stores the value that the evaluation | |
82 // of the script in |result|. Returns true on success. | |
83 bool ExecuteJavaScriptHelper(RenderViewHost* render_view_host, | |
84 const std::wstring& frame_xpath, | |
85 const std::wstring& original_script, | |
86 scoped_ptr<Value>* result) { | |
87 // TODO(jcampan): we should make the domAutomationController not require an | |
88 // automation id. | |
89 std::wstring script = L"window.domAutomationController.setAutomationId(0);" + | |
90 original_script; | |
91 DOMOperationObserver dom_op_observer(render_view_host); | |
92 render_view_host->ExecuteJavascriptInWebFrame(WideToUTF16Hack(frame_xpath), | |
93 WideToUTF16Hack(script)); | |
94 std::string json; | |
95 if (!dom_op_observer.WaitAndGetResponse(&json)) { | |
96 DLOG(ERROR) << "Cannot communicate with DOMOperationObserver."; | |
97 return false; | |
98 } | |
99 | |
100 // Nothing more to do for callers that ignore the returned JS value. | |
101 if (!result) | |
102 return true; | |
103 | |
104 base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS); | |
105 result->reset(reader.ReadToValue(json)); | |
106 if (!result->get()) { | |
107 DLOG(ERROR) << reader.GetErrorMessage(); | |
108 return false; | |
109 } | |
110 | |
111 return true; | |
112 } | |
113 | |
114 void BuildSimpleWebKeyEvent(WebKit::WebInputEvent::Type type, | |
115 ui::KeyboardCode key, | |
116 bool control, | |
117 bool shift, | |
118 bool alt, | |
119 bool command, | |
120 NativeWebKeyboardEvent* event) { | |
121 event->nativeKeyCode = 0; | |
122 event->windowsKeyCode = key; | |
123 event->setKeyIdentifierFromWindowsKeyCode(); | |
124 event->type = type; | |
125 event->modifiers = 0; | |
126 event->isSystemKey = false; | |
127 event->timeStampSeconds = base::Time::Now().ToDoubleT(); | |
128 event->skip_in_browser = true; | |
129 | |
130 if (type == WebKit::WebInputEvent::Char || | |
131 type == WebKit::WebInputEvent::RawKeyDown) { | |
132 event->text[0] = key; | |
133 event->unmodifiedText[0] = key; | |
134 } | |
135 | |
136 if (control) | |
137 event->modifiers |= WebKit::WebInputEvent::ControlKey; | |
138 | |
139 if (shift) | |
140 event->modifiers |= WebKit::WebInputEvent::ShiftKey; | |
141 | |
142 if (alt) | |
143 event->modifiers |= WebKit::WebInputEvent::AltKey; | |
144 | |
145 if (command) | |
146 event->modifiers |= WebKit::WebInputEvent::MetaKey; | |
147 } | |
148 | |
149 } // namespace | |
150 | |
151 | |
152 GURL GetFileUrlWithQuery(const FilePath& path, | |
153 const std::string& query_string) { | |
154 GURL url = net::FilePathToFileURL(path); | |
155 if (!query_string.empty()) { | |
156 GURL::Replacements replacements; | |
157 replacements.SetQueryStr(query_string); | |
158 return url.ReplaceComponents(replacements); | |
159 } | |
160 return url; | |
161 } | |
162 | |
163 void WaitForLoadStop(WebContents* web_contents) { | |
164 WindowedNotificationObserver load_stop_observer( | |
165 NOTIFICATION_LOAD_STOP, | |
166 Source<NavigationController>(&web_contents->GetController())); | |
167 // In many cases, the load may have finished before we get here. Only wait if | |
168 // the tab still has a pending navigation. | |
169 if (!web_contents->IsLoading()) | |
170 return; | |
171 load_stop_observer.Wait(); | |
172 } | |
173 | |
174 void CrashTab(WebContents* web_contents) { | |
175 RenderProcessHost* rph = web_contents->GetRenderProcessHost(); | |
176 WindowedNotificationObserver observer( | |
177 NOTIFICATION_RENDERER_PROCESS_CLOSED, | |
178 Source<RenderProcessHost>(rph)); | |
179 base::KillProcess(rph->GetHandle(), 0, false); | |
180 observer.Wait(); | |
181 } | |
182 | |
183 void SimulateMouseClick(WebContents* web_contents) { | |
184 int x = web_contents->GetView()->GetContainerSize().width() / 2; | |
185 int y = web_contents->GetView()->GetContainerSize().height() / 2; | |
186 WebKit::WebMouseEvent mouse_event; | |
187 mouse_event.type = WebKit::WebInputEvent::MouseDown; | |
188 mouse_event.button = WebKit::WebMouseEvent::ButtonLeft; | |
189 mouse_event.x = x; | |
190 mouse_event.y = y; | |
191 // Mac needs globalX/globalY for events to plugins. | |
192 gfx::Rect offset; | |
193 web_contents->GetView()->GetContainerBounds(&offset); | |
194 mouse_event.globalX = x + offset.x(); | |
195 mouse_event.globalY = y + offset.y(); | |
196 mouse_event.clickCount = 1; | |
197 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); | |
198 mouse_event.type = WebKit::WebInputEvent::MouseUp; | |
199 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); | |
200 } | |
201 | |
202 void SimulateMouseEvent(WebContents* web_contents, | |
203 WebKit::WebInputEvent::Type type, | |
204 const gfx::Point& point) { | |
205 WebKit::WebMouseEvent mouse_event; | |
206 mouse_event.type = type; | |
207 mouse_event.x = point.x(); | |
208 mouse_event.y = point.y(); | |
209 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); | |
210 } | |
211 | |
212 void SimulateKeyPress(WebContents* web_contents, | |
213 ui::KeyboardCode key, | |
214 bool control, | |
215 bool shift, | |
216 bool alt, | |
217 bool command) { | |
218 NativeWebKeyboardEvent event_down; | |
219 BuildSimpleWebKeyEvent( | |
220 WebKit::WebInputEvent::RawKeyDown, key, control, shift, alt, command, | |
221 &event_down); | |
222 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event_down); | |
223 | |
224 NativeWebKeyboardEvent char_event; | |
225 BuildSimpleWebKeyEvent( | |
226 WebKit::WebInputEvent::Char, key, control, shift, alt, command, | |
227 &char_event); | |
228 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(char_event); | |
229 | |
230 NativeWebKeyboardEvent event_up; | |
231 BuildSimpleWebKeyEvent( | |
232 WebKit::WebInputEvent::KeyUp, key, control, shift, alt, command, | |
233 &event_up); | |
234 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event_up); | |
235 } | |
236 | |
237 bool ExecuteJavaScript(RenderViewHost* render_view_host, | |
238 const std::wstring& frame_xpath, | |
239 const std::wstring& original_script) { | |
240 std::wstring script = | |
241 original_script + L";window.domAutomationController.send(0);"; | |
242 return ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, NULL); | |
243 } | |
244 | |
245 bool ExecuteJavaScriptAndExtractInt(RenderViewHost* render_view_host, | |
246 const std::wstring& frame_xpath, | |
247 const std::wstring& script, | |
248 int* result) { | |
249 DCHECK(result); | |
250 scoped_ptr<Value> value; | |
251 if (!ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, &value) || | |
252 !value.get()) | |
253 return false; | |
254 | |
255 return value->GetAsInteger(result); | |
256 } | |
257 | |
258 bool ExecuteJavaScriptAndExtractBool(RenderViewHost* render_view_host, | |
259 const std::wstring& frame_xpath, | |
260 const std::wstring& script, | |
261 bool* result) { | |
262 DCHECK(result); | |
263 scoped_ptr<Value> value; | |
264 if (!ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, &value) || | |
265 !value.get()) | |
266 return false; | |
267 | |
268 return value->GetAsBoolean(result); | |
269 } | |
270 | |
271 bool ExecuteJavaScriptAndExtractString(RenderViewHost* render_view_host, | |
272 const std::wstring& frame_xpath, | |
273 const std::wstring& script, | |
274 std::string* result) { | |
275 DCHECK(result); | |
276 scoped_ptr<Value> value; | |
277 if (!ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, &value) || | |
278 !value.get()) | |
279 return false; | |
280 | |
281 return value->GetAsString(result); | |
282 } | |
283 | |
284 TitleWatcher::TitleWatcher(WebContents* web_contents, | |
285 const string16& expected_title) | |
286 : web_contents_(web_contents), | |
287 expected_title_observed_(false), | |
288 quit_loop_on_observation_(false) { | |
289 EXPECT_TRUE(web_contents != NULL); | |
290 expected_titles_.push_back(expected_title); | |
291 notification_registrar_.Add(this, | |
292 NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, | |
293 Source<WebContents>(web_contents)); | |
294 | |
295 // When navigating through the history, the restored NavigationEntry's title | |
296 // will be used. If the entry ends up having the same title after we return | |
297 // to it, as will usually be the case, the | |
298 // NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED will then be suppressed, since the | |
299 // NavigationEntry's title hasn't changed. | |
300 notification_registrar_.Add( | |
301 this, | |
302 NOTIFICATION_LOAD_STOP, | |
303 Source<NavigationController>(&web_contents->GetController())); | |
304 } | |
305 | |
306 void TitleWatcher::AlsoWaitForTitle(const string16& expected_title) { | |
307 expected_titles_.push_back(expected_title); | |
308 } | |
309 | |
310 TitleWatcher::~TitleWatcher() { | |
311 } | |
312 | |
313 const string16& TitleWatcher::WaitAndGetTitle() { | |
314 if (expected_title_observed_) | |
315 return observed_title_; | |
316 quit_loop_on_observation_ = true; | |
317 message_loop_runner_ = new MessageLoopRunner; | |
318 message_loop_runner_->Run(); | |
319 return observed_title_; | |
320 } | |
321 | |
322 void TitleWatcher::Observe(int type, | |
323 const NotificationSource& source, | |
324 const NotificationDetails& details) { | |
325 if (type == NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED) { | |
326 WebContents* source_contents = Source<WebContents>(source).ptr(); | |
327 ASSERT_EQ(web_contents_, source_contents); | |
328 } else if (type == NOTIFICATION_LOAD_STOP) { | |
329 NavigationController* controller = | |
330 Source<NavigationController>(source).ptr(); | |
331 ASSERT_EQ(&web_contents_->GetController(), controller); | |
332 } else { | |
333 FAIL() << "Unexpected notification received."; | |
334 } | |
335 | |
336 std::vector<string16>::const_iterator it = | |
337 std::find(expected_titles_.begin(), | |
338 expected_titles_.end(), | |
339 web_contents_->GetTitle()); | |
340 if (it == expected_titles_.end()) | |
341 return; | |
342 observed_title_ = *it; | |
343 expected_title_observed_ = true; | |
344 if (quit_loop_on_observation_) { | |
345 // Only call Quit once, on first Observe: | |
346 quit_loop_on_observation_ = false; | |
347 message_loop_runner_->Quit(); | |
348 } | |
349 } | |
350 | |
351 TestWebSocketServer::TestWebSocketServer() | |
352 : started_(false), | |
353 port_(kDefaultWsPort), | |
354 secure_(false) { | |
355 #if defined(OS_POSIX) | |
356 process_group_id_ = base::kNullProcessHandle; | |
357 #endif | |
358 } | |
359 | |
360 int TestWebSocketServer::UseRandomPort() { | |
361 port_ = base::RandInt(1024, 65535); | |
362 return port_; | |
363 } | |
364 | |
365 void TestWebSocketServer::UseTLS() { | |
366 secure_ = true; | |
367 } | |
368 | |
369 bool TestWebSocketServer::Start(const FilePath& root_directory) { | |
370 if (started_) | |
371 return true; | |
372 // Append CommandLine arguments after the server script, switches won't work. | |
373 scoped_ptr<CommandLine> cmd_line(CreateWebSocketServerCommandLine()); | |
374 cmd_line->AppendArg("--server=start"); | |
375 cmd_line->AppendArg("--chromium"); | |
376 cmd_line->AppendArg("--register_cygwin"); | |
377 cmd_line->AppendArgNative(FILE_PATH_LITERAL("--root=") + | |
378 root_directory.value()); | |
379 cmd_line->AppendArg("--port=" + base::IntToString(port_)); | |
380 if (secure_) | |
381 cmd_line->AppendArg("--tls"); | |
382 if (!temp_dir_.CreateUniqueTempDir()) { | |
383 LOG(ERROR) << "Unable to create a temporary directory."; | |
384 return false; | |
385 } | |
386 cmd_line->AppendArgNative(FILE_PATH_LITERAL("--output-dir=") + | |
387 temp_dir_.path().value()); | |
388 websocket_pid_file_ = temp_dir_.path().AppendASCII("websocket.pid"); | |
389 cmd_line->AppendArgNative(FILE_PATH_LITERAL("--pidfile=") + | |
390 websocket_pid_file_.value()); | |
391 SetPythonPath(); | |
392 | |
393 base::LaunchOptions options; | |
394 base::ProcessHandle process_handle; | |
395 | |
396 #if defined(OS_POSIX) | |
397 options.new_process_group = true; | |
398 #elif defined(OS_WIN) | |
399 job_handle_.Set(CreateJobObject(NULL, NULL)); | |
400 if (!job_handle_.IsValid()) { | |
401 LOG(ERROR) << "Could not create JobObject."; | |
402 return false; | |
403 } | |
404 | |
405 if (!base::SetJobObjectAsKillOnJobClose(job_handle_.Get())) { | |
406 LOG(ERROR) << "Could not SetInformationJobObject."; | |
407 return false; | |
408 } | |
409 | |
410 options.inherit_handles = true; | |
411 options.job_handle = job_handle_.Get(); | |
412 #endif | |
413 | |
414 // Launch a new WebSocket server process. | |
415 if (!base::LaunchProcess(*cmd_line.get(), options, &process_handle)) { | |
416 LOG(ERROR) << "Unable to launch websocket server:\n" | |
417 << cmd_line.get()->GetCommandLineString(); | |
418 return false; | |
419 } | |
420 #if defined(OS_POSIX) | |
421 process_group_id_ = process_handle; | |
422 #endif | |
423 int exit_code; | |
424 bool wait_success = base::WaitForExitCodeWithTimeout( | |
425 process_handle, | |
426 &exit_code, | |
427 TestTimeouts::action_max_timeout()); | |
428 base::CloseProcessHandle(process_handle); | |
429 | |
430 if (!wait_success || exit_code != 0) { | |
431 LOG(ERROR) << "Failed to run new-run-webkit-websocketserver: " | |
432 << "wait_success = " << wait_success << ", " | |
433 << "exit_code = " << exit_code << ", " | |
434 << "command_line = " << cmd_line.get()->GetCommandLineString(); | |
435 return false; | |
436 } | |
437 | |
438 started_ = true; | |
439 return true; | |
440 } | |
441 | |
442 CommandLine* TestWebSocketServer::CreatePythonCommandLine() { | |
443 // Note: Python's first argument must be the script; do not append CommandLine | |
444 // switches, as they would precede the script path and break this CommandLine. | |
445 FilePath path; | |
446 CHECK(GetPythonRunTime(&path)); | |
447 return new CommandLine(path); | |
448 } | |
449 | |
450 void TestWebSocketServer::SetPythonPath() { | |
451 FilePath scripts_path; | |
452 PathService::Get(base::DIR_SOURCE_ROOT, &scripts_path); | |
453 | |
454 scripts_path = scripts_path | |
455 .Append(FILE_PATH_LITERAL("third_party")) | |
456 .Append(FILE_PATH_LITERAL("WebKit")) | |
457 .Append(FILE_PATH_LITERAL("Tools")) | |
458 .Append(FILE_PATH_LITERAL("Scripts")); | |
459 AppendToPythonPath(scripts_path); | |
460 } | |
461 | |
462 CommandLine* TestWebSocketServer::CreateWebSocketServerCommandLine() { | |
463 FilePath src_path; | |
464 // Get to 'src' dir. | |
465 PathService::Get(base::DIR_SOURCE_ROOT, &src_path); | |
466 | |
467 FilePath script_path(src_path); | |
468 script_path = script_path.AppendASCII("third_party"); | |
469 script_path = script_path.AppendASCII("WebKit"); | |
470 script_path = script_path.AppendASCII("Tools"); | |
471 script_path = script_path.AppendASCII("Scripts"); | |
472 script_path = script_path.AppendASCII("new-run-webkit-websocketserver"); | |
473 | |
474 CommandLine* cmd_line = CreatePythonCommandLine(); | |
475 cmd_line->AppendArgPath(script_path); | |
476 return cmd_line; | |
477 } | |
478 | |
479 TestWebSocketServer::~TestWebSocketServer() { | |
480 if (!started_) | |
481 return; | |
482 // Append CommandLine arguments after the server script, switches won't work. | |
483 scoped_ptr<CommandLine> cmd_line(CreateWebSocketServerCommandLine()); | |
484 cmd_line->AppendArg("--server=stop"); | |
485 cmd_line->AppendArg("--chromium"); | |
486 cmd_line->AppendArgNative(FILE_PATH_LITERAL("--pidfile=") + | |
487 websocket_pid_file_.value()); | |
488 base::LaunchOptions options; | |
489 options.wait = true; | |
490 base::LaunchProcess(*cmd_line.get(), options, NULL); | |
491 | |
492 #if defined(OS_POSIX) | |
493 // Just to make sure that the server process terminates certainly. | |
494 if (process_group_id_ != base::kNullProcessHandle) | |
495 base::KillProcessGroup(process_group_id_); | |
496 #endif | |
497 } | |
498 | |
499 } // namespace content | |
OLD | NEW |