| 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 |