browser_test_utils.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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_frame_host.h" 22#include "content/public/browser/render_process_host.h" 23#include "content/public/browser/render_view_host.h" 24#include "content/public/browser/web_contents.h" 25#include "content/public/browser/web_contents_observer.h" 26#include "content/public/browser/web_contents_view.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->GetView()->GetContainerSize().width() / 2; 225 int y = web_contents->GetView()->GetContainerSize().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; 241 web_contents->GetView()->GetContainerBounds(&offset); 242 mouse_event.globalX = point.x() + offset.x(); 243 mouse_event.globalY = point.y() + offset.y(); 244 mouse_event.clickCount = 1; 245 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 246 mouse_event.type = blink::WebInputEvent::MouseUp; 247 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 248} 249 250void SimulateMouseEvent(WebContents* web_contents, 251 blink::WebInputEvent::Type type, 252 const gfx::Point& point) { 253 blink::WebMouseEvent mouse_event; 254 mouse_event.type = type; 255 mouse_event.x = point.x(); 256 mouse_event.y = point.y(); 257 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 258} 259 260void SimulateKeyPress(WebContents* web_contents, 261 ui::KeyboardCode key_code, 262 bool control, 263 bool shift, 264 bool alt, 265 bool command) { 266 SimulateKeyPressWithCode( 267 web_contents, key_code, NULL, control, shift, alt, command); 268} 269 270void SimulateKeyPressWithCode(WebContents* web_contents, 271 ui::KeyboardCode key_code, 272 const char* code, 273 bool control, 274 bool shift, 275 bool alt, 276 bool command) { 277 ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance(); 278 int native_key_code = key_converter->CodeToNativeKeycode(code); 279 280 int modifiers = 0; 281 282 // The order of these key down events shouldn't matter for our simulation. 283 // For our simulation we can use either the left keys or the right keys. 284 if (control) { 285 modifiers |= blink::WebInputEvent::ControlKey; 286 InjectRawKeyEvent( 287 web_contents, 288 blink::WebInputEvent::RawKeyDown, 289 ui::VKEY_CONTROL, 290 key_converter->CodeToNativeKeycode("ControlLeft"), 291 modifiers); 292 } 293 294 if (shift) { 295 modifiers |= blink::WebInputEvent::ShiftKey; 296 InjectRawKeyEvent( 297 web_contents, 298 blink::WebInputEvent::RawKeyDown, 299 ui::VKEY_SHIFT, 300 key_converter->CodeToNativeKeycode("ShiftLeft"), 301 modifiers); 302 } 303 304 if (alt) { 305 modifiers |= blink::WebInputEvent::AltKey; 306 InjectRawKeyEvent( 307 web_contents, 308 blink::WebInputEvent::RawKeyDown, 309 ui::VKEY_MENU, 310 key_converter->CodeToNativeKeycode("AltLeft"), 311 modifiers); 312 } 313 314 if (command) { 315 modifiers |= blink::WebInputEvent::MetaKey; 316 InjectRawKeyEvent( 317 web_contents, 318 blink::WebInputEvent::RawKeyDown, 319 ui::VKEY_COMMAND, 320 key_converter->CodeToNativeKeycode("OSLeft"), 321 modifiers); 322 } 323 324 InjectRawKeyEvent( 325 web_contents, 326 blink::WebInputEvent::RawKeyDown, 327 key_code, 328 native_key_code, 329 modifiers); 330 331 InjectRawKeyEvent( 332 web_contents, 333 blink::WebInputEvent::Char, 334 key_code, 335 native_key_code, 336 modifiers); 337 338 InjectRawKeyEvent( 339 web_contents, 340 blink::WebInputEvent::KeyUp, 341 key_code, 342 native_key_code, 343 modifiers); 344 345 // The order of these key releases shouldn't matter for our simulation. 346 if (control) { 347 modifiers &= ~blink::WebInputEvent::ControlKey; 348 InjectRawKeyEvent( 349 web_contents, 350 blink::WebInputEvent::KeyUp, 351 ui::VKEY_CONTROL, 352 key_converter->CodeToNativeKeycode("ControlLeft"), 353 modifiers); 354 } 355 356 if (shift) { 357 modifiers &= ~blink::WebInputEvent::ShiftKey; 358 InjectRawKeyEvent( 359 web_contents, 360 blink::WebInputEvent::KeyUp, 361 ui::VKEY_SHIFT, 362 key_converter->CodeToNativeKeycode("ShiftLeft"), 363 modifiers); 364 } 365 366 if (alt) { 367 modifiers &= ~blink::WebInputEvent::AltKey; 368 InjectRawKeyEvent( 369 web_contents, 370 blink::WebInputEvent::KeyUp, 371 ui::VKEY_MENU, 372 key_converter->CodeToNativeKeycode("AltLeft"), 373 modifiers); 374 } 375 376 if (command) { 377 modifiers &= ~blink::WebInputEvent::MetaKey; 378 InjectRawKeyEvent( 379 web_contents, 380 blink::WebInputEvent::KeyUp, 381 ui::VKEY_COMMAND, 382 key_converter->CodeToNativeKeycode("OSLeft"), 383 modifiers); 384 } 385 386 ASSERT_EQ(modifiers, 0); 387} 388 389namespace internal { 390 391ToRenderFrameHost::ToRenderFrameHost(WebContents* web_contents) 392 : render_frame_host_(web_contents->GetMainFrame()) { 393} 394 395ToRenderFrameHost::ToRenderFrameHost(RenderViewHost* render_view_host) 396 : render_frame_host_(render_view_host->GetMainFrame()) { 397} 398 399ToRenderFrameHost::ToRenderFrameHost(RenderFrameHost* render_frame_host) 400 : render_frame_host_(render_frame_host) { 401} 402 403} // namespace internal 404 405bool ExecuteScript(const internal::ToRenderFrameHost& adapter, 406 const std::string& script) { 407 std::string new_script = 408 script + ";window.domAutomationController.send(0);"; 409 return ExecuteScriptHelper(adapter.render_frame_host(), new_script, NULL); 410} 411 412bool ExecuteScriptAndExtractInt(const internal::ToRenderFrameHost& adapter, 413 const std::string& script, int* result) { 414 DCHECK(result); 415 scoped_ptr<base::Value> value; 416 if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) || 417 !value.get()) { 418 return false; 419 } 420 421 return value->GetAsInteger(result); 422} 423 424bool ExecuteScriptAndExtractBool(const internal::ToRenderFrameHost& adapter, 425 const std::string& script, bool* result) { 426 DCHECK(result); 427 scoped_ptr<base::Value> value; 428 if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) || 429 !value.get()) { 430 return false; 431 } 432 433 return value->GetAsBoolean(result); 434} 435 436bool ExecuteScriptAndExtractString(const internal::ToRenderFrameHost& adapter, 437 const std::string& script, 438 std::string* result) { 439 DCHECK(result); 440 scoped_ptr<base::Value> value; 441 if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) || 442 !value.get()) { 443 return false; 444 } 445 446 return value->GetAsString(result); 447} 448 449namespace { 450void AddToSetIfFrameMatchesPredicate( 451 std::set<RenderFrameHost*>* frame_set, 452 const base::Callback<bool(RenderFrameHost*)>& predicate, 453 RenderFrameHost* host) { 454 if (predicate.Run(host)) 455 frame_set->insert(host); 456} 457} 458 459RenderFrameHost* FrameMatchingPredicate( 460 WebContents* web_contents, 461 const base::Callback<bool(RenderFrameHost*)>& predicate) { 462 std::set<RenderFrameHost*> frame_set; 463 web_contents->ForEachFrame( 464 base::Bind(&AddToSetIfFrameMatchesPredicate, &frame_set, predicate)); 465 DCHECK_EQ(1U, frame_set.size()); 466 return *frame_set.begin(); 467} 468 469bool FrameMatchesName(const std::string& name, RenderFrameHost* frame) { 470 return frame->GetFrameName() == name; 471} 472 473bool FrameIsChildOfMainFrame(RenderFrameHost* frame) { 474 return frame->GetParent() && !frame->GetParent()->GetParent(); 475} 476 477bool FrameHasSourceUrl(const GURL& url, RenderFrameHost* frame) { 478 return frame->GetLastCommittedURL() == url; 479} 480 481bool ExecuteWebUIResourceTest(WebContents* web_contents, 482 const std::vector<int>& js_resource_ids) { 483 // Inject WebUI test runner script first prior to other scripts required to 484 // run the test as scripts may depend on it being declared. 485 std::vector<int> ids; 486 ids.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST); 487 ids.insert(ids.end(), js_resource_ids.begin(), js_resource_ids.end()); 488 489 std::string script; 490 for (std::vector<int>::iterator iter = ids.begin(); 491 iter != ids.end(); 492 ++iter) { 493 ResourceBundle::GetSharedInstance().GetRawDataResource(*iter) 494 .AppendToString(&script); 495 script.append("\n"); 496 } 497 if (!ExecuteScript(web_contents, script)) 498 return false; 499 500 DOMMessageQueue message_queue; 501 if (!ExecuteScript(web_contents, "runTests()")) 502 return false; 503 504 std::string message; 505 do { 506 if (!message_queue.WaitForMessage(&message)) 507 return false; 508 } while (message.compare("\"PENDING\"") == 0); 509 510 return message.compare("\"SUCCESS\"") == 0; 511} 512 513std::string GetCookies(BrowserContext* browser_context, const GURL& url) { 514 std::string cookies; 515 base::WaitableEvent event(true, false); 516 net::URLRequestContextGetter* context_getter = 517 browser_context->GetRequestContext(); 518 519 BrowserThread::PostTask( 520 BrowserThread::IO, FROM_HERE, 521 base::Bind(&GetCookiesOnIOThread, url, 522 make_scoped_refptr(context_getter), &event, &cookies)); 523 event.Wait(); 524 return cookies; 525} 526 527bool SetCookie(BrowserContext* browser_context, 528 const GURL& url, 529 const std::string& value) { 530 bool result = false; 531 base::WaitableEvent event(true, false); 532 net::URLRequestContextGetter* context_getter = 533 browser_context->GetRequestContext(); 534 535 BrowserThread::PostTask( 536 BrowserThread::IO, FROM_HERE, 537 base::Bind(&SetCookieOnIOThread, url, value, 538 make_scoped_refptr(context_getter), &event, &result)); 539 event.Wait(); 540 return result; 541} 542 543TitleWatcher::TitleWatcher(WebContents* web_contents, 544 const base::string16& expected_title) 545 : WebContentsObserver(web_contents), 546 message_loop_runner_(new MessageLoopRunner) { 547 EXPECT_TRUE(web_contents != NULL); 548 expected_titles_.push_back(expected_title); 549} 550 551void TitleWatcher::AlsoWaitForTitle(const base::string16& expected_title) { 552 expected_titles_.push_back(expected_title); 553} 554 555TitleWatcher::~TitleWatcher() { 556} 557 558const base::string16& TitleWatcher::WaitAndGetTitle() { 559 TestTitle(); 560 message_loop_runner_->Run(); 561 return observed_title_; 562} 563 564void TitleWatcher::DidStopLoading(RenderViewHost* render_view_host) { 565 // When navigating through the history, the restored NavigationEntry's title 566 // will be used. If the entry ends up having the same title after we return 567 // to it, as will usually be the case, then WebContentsObserver::TitleSet 568 // will then be suppressed, since the NavigationEntry's title hasn't changed. 569 TestTitle(); 570} 571 572void TitleWatcher::TitleWasSet(NavigationEntry* entry, bool explicit_set) { 573 TestTitle(); 574} 575 576void TitleWatcher::TestTitle() { 577 std::vector<base::string16>::const_iterator it = 578 std::find(expected_titles_.begin(), 579 expected_titles_.end(), 580 web_contents()->GetTitle()); 581 if (it == expected_titles_.end()) 582 return; 583 584 observed_title_ = *it; 585 message_loop_runner_->Quit(); 586} 587 588WebContentsDestroyedWatcher::WebContentsDestroyedWatcher( 589 WebContents* web_contents) 590 : WebContentsObserver(web_contents), 591 message_loop_runner_(new MessageLoopRunner) { 592 EXPECT_TRUE(web_contents != NULL); 593} 594 595WebContentsDestroyedWatcher::~WebContentsDestroyedWatcher() { 596} 597 598void WebContentsDestroyedWatcher::Wait() { 599 message_loop_runner_->Run(); 600} 601 602void WebContentsDestroyedWatcher::WebContentsDestroyed( 603 WebContents* web_contents) { 604 message_loop_runner_->Quit(); 605} 606 607RenderProcessHostWatcher::RenderProcessHostWatcher( 608 RenderProcessHost* render_process_host, WatchType type) 609 : render_process_host_(render_process_host), 610 type_(type), 611 message_loop_runner_(new MessageLoopRunner) { 612 render_process_host_->AddObserver(this); 613} 614 615RenderProcessHostWatcher::RenderProcessHostWatcher( 616 WebContents* web_contents, WatchType type) 617 : render_process_host_(web_contents->GetRenderProcessHost()), 618 type_(type), 619 message_loop_runner_(new MessageLoopRunner) { 620 render_process_host_->AddObserver(this); 621} 622 623RenderProcessHostWatcher::~RenderProcessHostWatcher() { 624 if (render_process_host_) 625 render_process_host_->RemoveObserver(this); 626} 627 628void RenderProcessHostWatcher::Wait() { 629 message_loop_runner_->Run(); 630} 631 632void RenderProcessHostWatcher::RenderProcessExited( 633 RenderProcessHost* host, 634 base::ProcessHandle handle, 635 base::TerminationStatus status, 636 int exit_code) { 637 if (type_ == WATCH_FOR_PROCESS_EXIT) 638 message_loop_runner_->Quit(); 639} 640 641void RenderProcessHostWatcher::RenderProcessHostDestroyed( 642 RenderProcessHost* host) { 643 render_process_host_ = NULL; 644 if (type_ == WATCH_FOR_HOST_DESTRUCTION) 645 message_loop_runner_->Quit(); 646} 647 648DOMMessageQueue::DOMMessageQueue() { 649 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, 650 NotificationService::AllSources()); 651} 652 653DOMMessageQueue::~DOMMessageQueue() {} 654 655void DOMMessageQueue::Observe(int type, 656 const NotificationSource& source, 657 const NotificationDetails& details) { 658 Details<DomOperationNotificationDetails> dom_op_details(details); 659 message_queue_.push(dom_op_details->json); 660 if (message_loop_runner_) 661 message_loop_runner_->Quit(); 662} 663 664void DOMMessageQueue::ClearQueue() { 665 message_queue_ = std::queue<std::string>(); 666} 667 668bool DOMMessageQueue::WaitForMessage(std::string* message) { 669 DCHECK(message); 670 if (message_queue_.empty()) { 671 // This will be quit when a new message comes in. 672 message_loop_runner_ = new MessageLoopRunner; 673 message_loop_runner_->Run(); 674 } 675 // The queue should not be empty, unless we were quit because of a timeout. 676 if (message_queue_.empty()) 677 return false; 678 *message = message_queue_.front(); 679 message_queue_.pop(); 680 return true; 681} 682 683} // namespace content 684