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