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/kill.h" 11 #include "base/rand_util.h" 12 #include "base/strings/string_number_conversions.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/synchronization/waitable_event.h" 15 #include "base/test/test_timeouts.h" 16 #include "base/values.h" 17 #include "content/public/browser/browser_context.h" 18 #include "content/public/browser/dom_operation_notification_details.h" 19 #include "content/public/browser/notification_service.h" 20 #include "content/public/browser/notification_types.h" 21 #include "content/public/browser/render_process_host.h" 22 #include "content/public/browser/render_view_host.h" 23 #include "content/public/browser/web_contents.h" 24 #include "content/public/browser/web_contents_observer.h" 25 #include "content/public/browser/web_contents_view.h" 26 #include "content/public/test/test_utils.h" 27 #include "grit/webui_resources.h" 28 #include "net/base/net_util.h" 29 #include "net/cookies/cookie_store.h" 30 #include "net/test/python_utils.h" 31 #include "net/url_request/url_request_context.h" 32 #include "net/url_request/url_request_context_getter.h" 33 #include "testing/gtest/include/gtest/gtest.h" 34 #include "ui/base/resource/resource_bundle.h" 35 #include "ui/events/keycodes/dom4/keycode_converter.h" 36 37 namespace content { 38 namespace { 39 40 class DOMOperationObserver : public NotificationObserver, 41 public WebContentsObserver { 42 public: 43 explicit DOMOperationObserver(RenderViewHost* rvh) 44 : WebContentsObserver(WebContents::FromRenderViewHost(rvh)), 45 did_respond_(false) { 46 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, 47 Source<RenderViewHost>(rvh)); 48 message_loop_runner_ = new MessageLoopRunner; 49 } 50 51 virtual void Observe(int type, 52 const NotificationSource& source, 53 const NotificationDetails& details) OVERRIDE { 54 DCHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE); 55 Details<DomOperationNotificationDetails> dom_op_details(details); 56 response_ = dom_op_details->json; 57 did_respond_ = true; 58 message_loop_runner_->Quit(); 59 } 60 61 // Overridden from WebContentsObserver: 62 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE { 63 message_loop_runner_->Quit(); 64 } 65 66 bool WaitAndGetResponse(std::string* response) WARN_UNUSED_RESULT { 67 message_loop_runner_->Run(); 68 *response = response_; 69 return did_respond_; 70 } 71 72 private: 73 NotificationRegistrar registrar_; 74 std::string response_; 75 bool did_respond_; 76 scoped_refptr<MessageLoopRunner> message_loop_runner_; 77 78 DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver); 79 }; 80 81 // Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute. 82 bool ExecuteScriptHelper(RenderViewHost* render_view_host, 83 const std::string& frame_xpath, 84 const std::string& original_script, 85 scoped_ptr<Value>* result) WARN_UNUSED_RESULT; 86 87 // Executes the passed |original_script| in the frame pointed to by 88 // |frame_xpath|. If |result| is not NULL, stores the value that the evaluation 89 // of the script in |result|. Returns true on success. 90 bool ExecuteScriptHelper(RenderViewHost* render_view_host, 91 const std::string& frame_xpath, 92 const std::string& original_script, 93 scoped_ptr<Value>* result) { 94 // TODO(jcampan): we should make the domAutomationController not require an 95 // automation id. 96 std::string script = 97 "window.domAutomationController.setAutomationId(0);" + original_script; 98 DOMOperationObserver dom_op_observer(render_view_host); 99 render_view_host->ExecuteJavascriptInWebFrame(UTF8ToUTF16(frame_xpath), 100 UTF8ToUTF16(script)); 101 std::string json; 102 if (!dom_op_observer.WaitAndGetResponse(&json)) { 103 DLOG(ERROR) << "Cannot communicate with DOMOperationObserver."; 104 return false; 105 } 106 107 // Nothing more to do for callers that ignore the returned JS value. 108 if (!result) 109 return true; 110 111 base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS); 112 result->reset(reader.ReadToValue(json)); 113 if (!result->get()) { 114 DLOG(ERROR) << reader.GetErrorMessage(); 115 return false; 116 } 117 118 return true; 119 } 120 121 void BuildSimpleWebKeyEvent(blink::WebInputEvent::Type type, 122 ui::KeyboardCode key_code, 123 int native_key_code, 124 int modifiers, 125 NativeWebKeyboardEvent* event) { 126 event->nativeKeyCode = native_key_code; 127 event->windowsKeyCode = key_code; 128 event->setKeyIdentifierFromWindowsKeyCode(); 129 event->type = type; 130 event->modifiers = modifiers; 131 event->isSystemKey = false; 132 event->timeStampSeconds = base::Time::Now().ToDoubleT(); 133 event->skip_in_browser = true; 134 135 if (type == blink::WebInputEvent::Char || 136 type == blink::WebInputEvent::RawKeyDown) { 137 event->text[0] = key_code; 138 event->unmodifiedText[0] = key_code; 139 } 140 } 141 142 void InjectRawKeyEvent(WebContents* web_contents, 143 blink::WebInputEvent::Type type, 144 ui::KeyboardCode key_code, 145 int native_key_code, 146 int modifiers) { 147 NativeWebKeyboardEvent event; 148 BuildSimpleWebKeyEvent(type, key_code, native_key_code, modifiers, &event); 149 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event); 150 } 151 152 void GetCookiesCallback(std::string* cookies_out, 153 base::WaitableEvent* event, 154 const std::string& cookies) { 155 *cookies_out = cookies; 156 event->Signal(); 157 } 158 159 void GetCookiesOnIOThread(const GURL& url, 160 net::URLRequestContextGetter* context_getter, 161 base::WaitableEvent* event, 162 std::string* cookies) { 163 net::CookieStore* cookie_store = 164 context_getter->GetURLRequestContext()->cookie_store(); 165 cookie_store->GetCookiesWithOptionsAsync( 166 url, net::CookieOptions(), 167 base::Bind(&GetCookiesCallback, cookies, event)); 168 } 169 170 void SetCookieCallback(bool* result, 171 base::WaitableEvent* event, 172 bool success) { 173 *result = success; 174 event->Signal(); 175 } 176 177 void SetCookieOnIOThread(const GURL& url, 178 const std::string& value, 179 net::URLRequestContextGetter* context_getter, 180 base::WaitableEvent* event, 181 bool* result) { 182 net::CookieStore* cookie_store = 183 context_getter->GetURLRequestContext()->cookie_store(); 184 cookie_store->SetCookieWithOptionsAsync( 185 url, value, net::CookieOptions(), 186 base::Bind(&SetCookieCallback, result, event)); 187 } 188 189 } // namespace 190 191 192 GURL GetFileUrlWithQuery(const base::FilePath& path, 193 const std::string& query_string) { 194 GURL url = net::FilePathToFileURL(path); 195 if (!query_string.empty()) { 196 GURL::Replacements replacements; 197 replacements.SetQueryStr(query_string); 198 return url.ReplaceComponents(replacements); 199 } 200 return url; 201 } 202 203 void WaitForLoadStop(WebContents* web_contents) { 204 WindowedNotificationObserver load_stop_observer( 205 NOTIFICATION_LOAD_STOP, 206 Source<NavigationController>(&web_contents->GetController())); 207 // In many cases, the load may have finished before we get here. Only wait if 208 // the tab still has a pending navigation. 209 if (!web_contents->IsLoading()) 210 return; 211 load_stop_observer.Wait(); 212 } 213 214 void CrashTab(WebContents* web_contents) { 215 RenderProcessHost* rph = web_contents->GetRenderProcessHost(); 216 WindowedNotificationObserver observer( 217 NOTIFICATION_RENDERER_PROCESS_CLOSED, 218 Source<RenderProcessHost>(rph)); 219 base::KillProcess(rph->GetHandle(), 0, false); 220 observer.Wait(); 221 } 222 223 void SimulateMouseClick(WebContents* web_contents, 224 int modifiers, 225 blink::WebMouseEvent::Button button) { 226 int x = web_contents->GetView()->GetContainerSize().width() / 2; 227 int y = web_contents->GetView()->GetContainerSize().height() / 2; 228 SimulateMouseClickAt(web_contents, modifiers, button, gfx::Point(x, y)); 229 } 230 231 void SimulateMouseClickAt(WebContents* web_contents, 232 int modifiers, 233 blink::WebMouseEvent::Button button, 234 const gfx::Point& point) { 235 blink::WebMouseEvent mouse_event; 236 mouse_event.type = blink::WebInputEvent::MouseDown; 237 mouse_event.button = button; 238 mouse_event.x = point.x(); 239 mouse_event.y = point.y(); 240 mouse_event.modifiers = modifiers; 241 // Mac needs globalX/globalY for events to plugins. 242 gfx::Rect offset; 243 web_contents->GetView()->GetContainerBounds(&offset); 244 mouse_event.globalX = point.x() + offset.x(); 245 mouse_event.globalY = point.y() + offset.y(); 246 mouse_event.clickCount = 1; 247 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 248 mouse_event.type = blink::WebInputEvent::MouseUp; 249 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 250 } 251 252 void SimulateMouseEvent(WebContents* web_contents, 253 blink::WebInputEvent::Type type, 254 const gfx::Point& point) { 255 blink::WebMouseEvent mouse_event; 256 mouse_event.type = type; 257 mouse_event.x = point.x(); 258 mouse_event.y = point.y(); 259 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 260 } 261 262 void SimulateKeyPress(WebContents* web_contents, 263 ui::KeyboardCode key_code, 264 bool control, 265 bool shift, 266 bool alt, 267 bool command) { 268 SimulateKeyPressWithCode( 269 web_contents, key_code, NULL, control, shift, alt, command); 270 } 271 272 void SimulateKeyPressWithCode(WebContents* web_contents, 273 ui::KeyboardCode key_code, 274 const char* code, 275 bool control, 276 bool shift, 277 bool alt, 278 bool command) { 279 ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance(); 280 int native_key_code = key_converter->CodeToNativeKeycode(code); 281 282 int modifiers = 0; 283 284 // The order of these key down events shouldn't matter for our simulation. 285 // For our simulation we can use either the left keys or the right keys. 286 if (control) { 287 modifiers |= blink::WebInputEvent::ControlKey; 288 InjectRawKeyEvent( 289 web_contents, 290 blink::WebInputEvent::RawKeyDown, 291 ui::VKEY_CONTROL, 292 key_converter->CodeToNativeKeycode("ControlLeft"), 293 modifiers); 294 } 295 296 if (shift) { 297 modifiers |= blink::WebInputEvent::ShiftKey; 298 InjectRawKeyEvent( 299 web_contents, 300 blink::WebInputEvent::RawKeyDown, 301 ui::VKEY_SHIFT, 302 key_converter->CodeToNativeKeycode("ShiftLeft"), 303 modifiers); 304 } 305 306 if (alt) { 307 modifiers |= blink::WebInputEvent::AltKey; 308 InjectRawKeyEvent( 309 web_contents, 310 blink::WebInputEvent::RawKeyDown, 311 ui::VKEY_MENU, 312 key_converter->CodeToNativeKeycode("AltLeft"), 313 modifiers); 314 } 315 316 if (command) { 317 modifiers |= blink::WebInputEvent::MetaKey; 318 InjectRawKeyEvent( 319 web_contents, 320 blink::WebInputEvent::RawKeyDown, 321 ui::VKEY_COMMAND, 322 key_converter->CodeToNativeKeycode("OSLeft"), 323 modifiers); 324 } 325 326 InjectRawKeyEvent( 327 web_contents, 328 blink::WebInputEvent::RawKeyDown, 329 key_code, 330 native_key_code, 331 modifiers); 332 333 InjectRawKeyEvent( 334 web_contents, 335 blink::WebInputEvent::Char, 336 key_code, 337 native_key_code, 338 modifiers); 339 340 InjectRawKeyEvent( 341 web_contents, 342 blink::WebInputEvent::KeyUp, 343 key_code, 344 native_key_code, 345 modifiers); 346 347 // The order of these key releases shouldn't matter for our simulation. 348 if (control) { 349 modifiers &= ~blink::WebInputEvent::ControlKey; 350 InjectRawKeyEvent( 351 web_contents, 352 blink::WebInputEvent::KeyUp, 353 ui::VKEY_CONTROL, 354 key_converter->CodeToNativeKeycode("ControlLeft"), 355 modifiers); 356 } 357 358 if (shift) { 359 modifiers &= ~blink::WebInputEvent::ShiftKey; 360 InjectRawKeyEvent( 361 web_contents, 362 blink::WebInputEvent::KeyUp, 363 ui::VKEY_SHIFT, 364 key_converter->CodeToNativeKeycode("ShiftLeft"), 365 modifiers); 366 } 367 368 if (alt) { 369 modifiers &= ~blink::WebInputEvent::AltKey; 370 InjectRawKeyEvent( 371 web_contents, 372 blink::WebInputEvent::KeyUp, 373 ui::VKEY_MENU, 374 key_converter->CodeToNativeKeycode("AltLeft"), 375 modifiers); 376 } 377 378 if (command) { 379 modifiers &= ~blink::WebInputEvent::MetaKey; 380 InjectRawKeyEvent( 381 web_contents, 382 blink::WebInputEvent::KeyUp, 383 ui::VKEY_COMMAND, 384 key_converter->CodeToNativeKeycode("OSLeft"), 385 modifiers); 386 } 387 388 ASSERT_EQ(modifiers, 0); 389 } 390 391 namespace internal { 392 393 ToRenderViewHost::ToRenderViewHost(WebContents* web_contents) 394 : render_view_host_(web_contents->GetRenderViewHost()) { 395 } 396 397 ToRenderViewHost::ToRenderViewHost(RenderViewHost* render_view_host) 398 : render_view_host_(render_view_host) { 399 } 400 401 } // namespace internal 402 403 bool ExecuteScriptInFrame(const internal::ToRenderViewHost& adapter, 404 const std::string& frame_xpath, 405 const std::string& original_script) { 406 std::string script = 407 original_script + ";window.domAutomationController.send(0);"; 408 return ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 409 NULL); 410 } 411 412 bool ExecuteScriptInFrameAndExtractInt( 413 const internal::ToRenderViewHost& adapter, 414 const std::string& frame_xpath, 415 const std::string& script, 416 int* result) { 417 DCHECK(result); 418 scoped_ptr<Value> value; 419 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 420 &value) || !value.get()) 421 return false; 422 423 return value->GetAsInteger(result); 424 } 425 426 bool ExecuteScriptInFrameAndExtractBool( 427 const internal::ToRenderViewHost& adapter, 428 const std::string& frame_xpath, 429 const std::string& script, 430 bool* result) { 431 DCHECK(result); 432 scoped_ptr<Value> value; 433 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 434 &value) || !value.get()) 435 return false; 436 437 return value->GetAsBoolean(result); 438 } 439 440 bool ExecuteScriptInFrameAndExtractString( 441 const internal::ToRenderViewHost& adapter, 442 const std::string& frame_xpath, 443 const std::string& script, 444 std::string* result) { 445 DCHECK(result); 446 scoped_ptr<Value> value; 447 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 448 &value) || !value.get()) 449 return false; 450 451 return value->GetAsString(result); 452 } 453 454 bool ExecuteScript(const internal::ToRenderViewHost& adapter, 455 const std::string& script) { 456 return ExecuteScriptInFrame(adapter, std::string(), script); 457 } 458 459 bool ExecuteScriptAndExtractInt(const internal::ToRenderViewHost& adapter, 460 const std::string& script, int* result) { 461 return ExecuteScriptInFrameAndExtractInt(adapter, std::string(), script, 462 result); 463 } 464 465 bool ExecuteScriptAndExtractBool(const internal::ToRenderViewHost& adapter, 466 const std::string& script, bool* result) { 467 return ExecuteScriptInFrameAndExtractBool(adapter, std::string(), script, 468 result); 469 } 470 471 bool ExecuteScriptAndExtractString(const internal::ToRenderViewHost& adapter, 472 const std::string& script, 473 std::string* result) { 474 return ExecuteScriptInFrameAndExtractString(adapter, std::string(), script, 475 result); 476 } 477 478 bool ExecuteWebUIResourceTest( 479 const internal::ToRenderViewHost& adapter, 480 const std::vector<int>& js_resource_ids) { 481 // Inject WebUI test runner script first prior to other scripts required to 482 // run the test as scripts may depend on it being declared. 483 std::vector<int> ids; 484 ids.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST); 485 ids.insert(ids.end(), js_resource_ids.begin(), js_resource_ids.end()); 486 487 std::string script; 488 for (std::vector<int>::iterator iter = ids.begin(); 489 iter != ids.end(); 490 ++iter) { 491 ResourceBundle::GetSharedInstance().GetRawDataResource(*iter) 492 .AppendToString(&script); 493 script.append("\n"); 494 } 495 if (!content::ExecuteScript(adapter, script)) 496 return false; 497 498 content::DOMMessageQueue message_queue; 499 if (!content::ExecuteScript(adapter, "runTests()")) 500 return false; 501 502 std::string message; 503 do { 504 if (!message_queue.WaitForMessage(&message)) 505 return false; 506 } while (message.compare("\"PENDING\"") == 0); 507 508 return message.compare("\"SUCCESS\"") == 0; 509 } 510 511 std::string GetCookies(BrowserContext* browser_context, const GURL& url) { 512 std::string cookies; 513 base::WaitableEvent event(true, false); 514 net::URLRequestContextGetter* context_getter = 515 browser_context->GetRequestContext(); 516 517 BrowserThread::PostTask( 518 BrowserThread::IO, FROM_HERE, 519 base::Bind(&GetCookiesOnIOThread, url, 520 make_scoped_refptr(context_getter), &event, &cookies)); 521 event.Wait(); 522 return cookies; 523 } 524 525 bool SetCookie(BrowserContext* browser_context, 526 const GURL& url, 527 const std::string& value) { 528 bool result = false; 529 base::WaitableEvent event(true, false); 530 net::URLRequestContextGetter* context_getter = 531 browser_context->GetRequestContext(); 532 533 BrowserThread::PostTask( 534 BrowserThread::IO, FROM_HERE, 535 base::Bind(&SetCookieOnIOThread, url, value, 536 make_scoped_refptr(context_getter), &event, &result)); 537 event.Wait(); 538 return result; 539 } 540 541 TitleWatcher::TitleWatcher(WebContents* web_contents, 542 const base::string16& expected_title) 543 : WebContentsObserver(web_contents), 544 message_loop_runner_(new MessageLoopRunner) { 545 EXPECT_TRUE(web_contents != NULL); 546 expected_titles_.push_back(expected_title); 547 } 548 549 void TitleWatcher::AlsoWaitForTitle(const base::string16& expected_title) { 550 expected_titles_.push_back(expected_title); 551 } 552 553 TitleWatcher::~TitleWatcher() { 554 } 555 556 const base::string16& TitleWatcher::WaitAndGetTitle() { 557 message_loop_runner_->Run(); 558 return observed_title_; 559 } 560 561 void TitleWatcher::DidStopLoading(RenderViewHost* render_view_host) { 562 // When navigating through the history, the restored NavigationEntry's title 563 // will be used. If the entry ends up having the same title after we return 564 // to it, as will usually be the case, then WebContentsObserver::TitleSet 565 // will then be suppressed, since the NavigationEntry's title hasn't changed. 566 TestTitle(); 567 } 568 569 void TitleWatcher::TitleWasSet(NavigationEntry* entry, bool explicit_set) { 570 TestTitle(); 571 } 572 573 void TitleWatcher::TestTitle() { 574 std::vector<base::string16>::const_iterator it = 575 std::find(expected_titles_.begin(), 576 expected_titles_.end(), 577 web_contents()->GetTitle()); 578 if (it == expected_titles_.end()) 579 return; 580 581 observed_title_ = *it; 582 message_loop_runner_->Quit(); 583 } 584 585 WebContentsDestroyedWatcher::WebContentsDestroyedWatcher( 586 WebContents* web_contents) 587 : WebContentsObserver(web_contents), 588 message_loop_runner_(new MessageLoopRunner) { 589 EXPECT_TRUE(web_contents != NULL); 590 } 591 592 WebContentsDestroyedWatcher::~WebContentsDestroyedWatcher() { 593 } 594 595 void WebContentsDestroyedWatcher::Wait() { 596 message_loop_runner_->Run(); 597 } 598 599 void WebContentsDestroyedWatcher::WebContentsDestroyed( 600 WebContents* web_contents) { 601 message_loop_runner_->Quit(); 602 } 603 604 DOMMessageQueue::DOMMessageQueue() : waiting_for_message_(false) { 605 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, 606 NotificationService::AllSources()); 607 } 608 609 DOMMessageQueue::~DOMMessageQueue() {} 610 611 void DOMMessageQueue::Observe(int type, 612 const NotificationSource& source, 613 const NotificationDetails& details) { 614 Details<DomOperationNotificationDetails> dom_op_details(details); 615 Source<RenderViewHost> sender(source); 616 message_queue_.push(dom_op_details->json); 617 if (waiting_for_message_) { 618 waiting_for_message_ = false; 619 message_loop_runner_->Quit(); 620 } 621 } 622 623 void DOMMessageQueue::ClearQueue() { 624 message_queue_ = std::queue<std::string>(); 625 } 626 627 bool DOMMessageQueue::WaitForMessage(std::string* message) { 628 if (message_queue_.empty()) { 629 waiting_for_message_ = true; 630 // This will be quit when a new message comes in. 631 message_loop_runner_ = new MessageLoopRunner; 632 message_loop_runner_->Run(); 633 } 634 // The queue should not be empty, unless we were quit because of a timeout. 635 if (message_queue_.empty()) 636 return false; 637 if (message) 638 *message = message_queue_.front(); 639 message_queue_.pop(); 640 return true; 641 } 642 643 } // namespace content 644