browser_test_utils.cc revision 3551c9c881056c480085172ff9840cab31610854
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 36static const int kDefaultWsPort = 8880; 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<RenderViewHost>(rvh)); 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(RenderViewHost* render_view_host, 84 const std::string& frame_xpath, 85 const std::string& original_script, 86 scoped_ptr<Value>* result) WARN_UNUSED_RESULT; 87 88// Executes the passed |original_script| in the frame pointed to by 89// |frame_xpath|. If |result| is not NULL, stores the value that the evaluation 90// of the script in |result|. Returns true on success. 91bool ExecuteScriptHelper(RenderViewHost* render_view_host, 92 const std::string& frame_xpath, 93 const std::string& original_script, 94 scoped_ptr<Value>* result) { 95 // TODO(jcampan): we should make the domAutomationController not require an 96 // automation id. 97 std::string script = 98 "window.domAutomationController.setAutomationId(0);" + original_script; 99 DOMOperationObserver dom_op_observer(render_view_host); 100 render_view_host->ExecuteJavascriptInWebFrame(UTF8ToUTF16(frame_xpath), 101 UTF8ToUTF16(script)); 102 std::string json; 103 if (!dom_op_observer.WaitAndGetResponse(&json)) { 104 DLOG(ERROR) << "Cannot communicate with DOMOperationObserver."; 105 return false; 106 } 107 108 // Nothing more to do for callers that ignore the returned JS value. 109 if (!result) 110 return true; 111 112 base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS); 113 result->reset(reader.ReadToValue(json)); 114 if (!result->get()) { 115 DLOG(ERROR) << reader.GetErrorMessage(); 116 return false; 117 } 118 119 return true; 120} 121 122void BuildSimpleWebKeyEvent(WebKit::WebInputEvent::Type type, 123 ui::KeyboardCode key, 124 bool control, 125 bool shift, 126 bool alt, 127 bool command, 128 NativeWebKeyboardEvent* event) { 129 event->nativeKeyCode = 0; 130 event->windowsKeyCode = key; 131 event->setKeyIdentifierFromWindowsKeyCode(); 132 event->type = type; 133 event->modifiers = 0; 134 event->isSystemKey = false; 135 event->timeStampSeconds = base::Time::Now().ToDoubleT(); 136 event->skip_in_browser = true; 137 138 if (type == WebKit::WebInputEvent::Char || 139 type == WebKit::WebInputEvent::RawKeyDown) { 140 event->text[0] = key; 141 event->unmodifiedText[0] = key; 142 } 143 144 if (control) 145 event->modifiers |= WebKit::WebInputEvent::ControlKey; 146 147 if (shift) 148 event->modifiers |= WebKit::WebInputEvent::ShiftKey; 149 150 if (alt) 151 event->modifiers |= WebKit::WebInputEvent::AltKey; 152 153 if (command) 154 event->modifiers |= WebKit::WebInputEvent::MetaKey; 155} 156 157void GetCookiesCallback(std::string* cookies_out, 158 base::WaitableEvent* event, 159 const std::string& cookies) { 160 *cookies_out = cookies; 161 event->Signal(); 162} 163 164void GetCookiesOnIOThread(const GURL& url, 165 net::URLRequestContextGetter* context_getter, 166 base::WaitableEvent* event, 167 std::string* cookies) { 168 net::CookieStore* cookie_store = 169 context_getter->GetURLRequestContext()->cookie_store(); 170 cookie_store->GetCookiesWithOptionsAsync( 171 url, net::CookieOptions(), 172 base::Bind(&GetCookiesCallback, cookies, event)); 173} 174 175void SetCookieCallback(bool* result, 176 base::WaitableEvent* event, 177 bool success) { 178 *result = success; 179 event->Signal(); 180} 181 182void SetCookieOnIOThread(const GURL& url, 183 const std::string& value, 184 net::URLRequestContextGetter* context_getter, 185 base::WaitableEvent* event, 186 bool* result) { 187 net::CookieStore* cookie_store = 188 context_getter->GetURLRequestContext()->cookie_store(); 189 cookie_store->SetCookieWithOptionsAsync( 190 url, value, net::CookieOptions(), 191 base::Bind(&SetCookieCallback, result, event)); 192} 193 194} // namespace 195 196 197GURL GetFileUrlWithQuery(const base::FilePath& path, 198 const std::string& query_string) { 199 GURL url = net::FilePathToFileURL(path); 200 if (!query_string.empty()) { 201 GURL::Replacements replacements; 202 replacements.SetQueryStr(query_string); 203 return url.ReplaceComponents(replacements); 204 } 205 return url; 206} 207 208void WaitForLoadStop(WebContents* web_contents) { 209 WindowedNotificationObserver load_stop_observer( 210 NOTIFICATION_LOAD_STOP, 211 Source<NavigationController>(&web_contents->GetController())); 212 // In many cases, the load may have finished before we get here. Only wait if 213 // the tab still has a pending navigation. 214 if (!web_contents->IsLoading()) 215 return; 216 load_stop_observer.Wait(); 217} 218 219void CrashTab(WebContents* web_contents) { 220 RenderProcessHost* rph = web_contents->GetRenderProcessHost(); 221 WindowedNotificationObserver observer( 222 NOTIFICATION_RENDERER_PROCESS_CLOSED, 223 Source<RenderProcessHost>(rph)); 224 base::KillProcess(rph->GetHandle(), 0, false); 225 observer.Wait(); 226} 227 228void SimulateMouseClick(WebContents* web_contents, 229 int modifiers, 230 WebKit::WebMouseEvent::Button button) { 231 int x = web_contents->GetView()->GetContainerSize().width() / 2; 232 int y = web_contents->GetView()->GetContainerSize().height() / 2; 233 SimulateMouseClickAt(web_contents, modifiers, button, gfx::Point(x, y)); 234} 235 236void SimulateMouseClickAt(WebContents* web_contents, 237 int modifiers, 238 WebKit::WebMouseEvent::Button button, 239 const gfx::Point& point) { 240 WebKit::WebMouseEvent mouse_event; 241 mouse_event.type = WebKit::WebInputEvent::MouseDown; 242 mouse_event.button = button; 243 mouse_event.x = point.x(); 244 mouse_event.y = point.y(); 245 mouse_event.modifiers = modifiers; 246 // Mac needs globalX/globalY for events to plugins. 247 gfx::Rect offset; 248 web_contents->GetView()->GetContainerBounds(&offset); 249 mouse_event.globalX = point.x() + offset.x(); 250 mouse_event.globalY = point.y() + offset.y(); 251 mouse_event.clickCount = 1; 252 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 253 mouse_event.type = WebKit::WebInputEvent::MouseUp; 254 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 255} 256 257void SimulateMouseEvent(WebContents* web_contents, 258 WebKit::WebInputEvent::Type type, 259 const gfx::Point& point) { 260 WebKit::WebMouseEvent mouse_event; 261 mouse_event.type = type; 262 mouse_event.x = point.x(); 263 mouse_event.y = point.y(); 264 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 265} 266 267void SimulateKeyPress(WebContents* web_contents, 268 ui::KeyboardCode key, 269 bool control, 270 bool shift, 271 bool alt, 272 bool command) { 273 NativeWebKeyboardEvent event_down; 274 BuildSimpleWebKeyEvent( 275 WebKit::WebInputEvent::RawKeyDown, key, control, shift, alt, command, 276 &event_down); 277 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event_down); 278 279 NativeWebKeyboardEvent char_event; 280 BuildSimpleWebKeyEvent( 281 WebKit::WebInputEvent::Char, key, control, shift, alt, command, 282 &char_event); 283 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(char_event); 284 285 NativeWebKeyboardEvent event_up; 286 BuildSimpleWebKeyEvent( 287 WebKit::WebInputEvent::KeyUp, key, control, shift, alt, command, 288 &event_up); 289 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event_up); 290} 291 292namespace internal { 293 294ToRenderViewHost::ToRenderViewHost(WebContents* web_contents) 295 : render_view_host_(web_contents->GetRenderViewHost()) { 296} 297 298ToRenderViewHost::ToRenderViewHost(RenderViewHost* render_view_host) 299 : render_view_host_(render_view_host) { 300} 301 302} // namespace internal 303 304bool ExecuteScriptInFrame(const internal::ToRenderViewHost& adapter, 305 const std::string& frame_xpath, 306 const std::string& original_script) { 307 std::string script = 308 original_script + ";window.domAutomationController.send(0);"; 309 return ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 310 NULL); 311} 312 313bool ExecuteScriptInFrameAndExtractInt( 314 const internal::ToRenderViewHost& adapter, 315 const std::string& frame_xpath, 316 const std::string& script, 317 int* result) { 318 DCHECK(result); 319 scoped_ptr<Value> value; 320 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 321 &value) || !value.get()) 322 return false; 323 324 return value->GetAsInteger(result); 325} 326 327bool ExecuteScriptInFrameAndExtractBool( 328 const internal::ToRenderViewHost& adapter, 329 const std::string& frame_xpath, 330 const std::string& script, 331 bool* result) { 332 DCHECK(result); 333 scoped_ptr<Value> value; 334 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 335 &value) || !value.get()) 336 return false; 337 338 return value->GetAsBoolean(result); 339} 340 341bool ExecuteScriptInFrameAndExtractString( 342 const internal::ToRenderViewHost& adapter, 343 const std::string& frame_xpath, 344 const std::string& script, 345 std::string* result) { 346 DCHECK(result); 347 scoped_ptr<Value> value; 348 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 349 &value) || !value.get()) 350 return false; 351 352 return value->GetAsString(result); 353} 354 355bool ExecuteScript(const internal::ToRenderViewHost& adapter, 356 const std::string& script) { 357 return ExecuteScriptInFrame(adapter, std::string(), script); 358} 359 360bool ExecuteScriptAndExtractInt(const internal::ToRenderViewHost& adapter, 361 const std::string& script, int* result) { 362 return ExecuteScriptInFrameAndExtractInt(adapter, std::string(), script, 363 result); 364} 365 366bool ExecuteScriptAndExtractBool(const internal::ToRenderViewHost& adapter, 367 const std::string& script, bool* result) { 368 return ExecuteScriptInFrameAndExtractBool(adapter, std::string(), script, 369 result); 370} 371 372bool ExecuteScriptAndExtractString(const internal::ToRenderViewHost& adapter, 373 const std::string& script, 374 std::string* result) { 375 return ExecuteScriptInFrameAndExtractString(adapter, std::string(), script, 376 result); 377} 378 379bool ExecuteWebUIResourceTest( 380 const internal::ToRenderViewHost& adapter, 381 const std::vector<int>& js_resource_ids) { 382 // Inject WebUI test runner script first prior to other scripts required to 383 // run the test as scripts may depend on it being declared. 384 std::vector<int> ids; 385 ids.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST); 386 ids.insert(ids.end(), js_resource_ids.begin(), js_resource_ids.end()); 387 388 std::string script; 389 for (std::vector<int>::iterator iter = ids.begin(); 390 iter != ids.end(); 391 ++iter) { 392 ResourceBundle::GetSharedInstance().GetRawDataResource(*iter) 393 .AppendToString(&script); 394 script.append("\n"); 395 } 396 if (!content::ExecuteScript(adapter, script)) 397 return false; 398 399 content::DOMMessageQueue message_queue; 400 if (!content::ExecuteScript(adapter, "runTests()")) 401 return false; 402 403 std::string message; 404 do { 405 if (!message_queue.WaitForMessage(&message)) 406 return false; 407 } while (message.compare("\"PENDING\"") == 0); 408 409 return message.compare("\"SUCCESS\"") == 0; 410} 411 412std::string GetCookies(BrowserContext* browser_context, const GURL& url) { 413 std::string cookies; 414 base::WaitableEvent event(true, false); 415 net::URLRequestContextGetter* context_getter = 416 browser_context->GetRequestContext(); 417 418 BrowserThread::PostTask( 419 BrowserThread::IO, FROM_HERE, 420 base::Bind(&GetCookiesOnIOThread, url, 421 make_scoped_refptr(context_getter), &event, &cookies)); 422 event.Wait(); 423 return cookies; 424} 425 426bool SetCookie(BrowserContext* browser_context, 427 const GURL& url, 428 const std::string& value) { 429 bool result = false; 430 base::WaitableEvent event(true, false); 431 net::URLRequestContextGetter* context_getter = 432 browser_context->GetRequestContext(); 433 434 BrowserThread::PostTask( 435 BrowserThread::IO, FROM_HERE, 436 base::Bind(&SetCookieOnIOThread, url, value, 437 make_scoped_refptr(context_getter), &event, &result)); 438 event.Wait(); 439 return result; 440} 441 442TitleWatcher::TitleWatcher(WebContents* web_contents, 443 const string16& expected_title) 444 : web_contents_(web_contents), 445 expected_title_observed_(false), 446 quit_loop_on_observation_(false) { 447 EXPECT_TRUE(web_contents != NULL); 448 expected_titles_.push_back(expected_title); 449 notification_registrar_.Add(this, 450 NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, 451 Source<WebContents>(web_contents)); 452 453 // When navigating through the history, the restored NavigationEntry's title 454 // will be used. If the entry ends up having the same title after we return 455 // to it, as will usually be the case, the 456 // NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED will then be suppressed, since the 457 // NavigationEntry's title hasn't changed. 458 notification_registrar_.Add( 459 this, 460 NOTIFICATION_LOAD_STOP, 461 Source<NavigationController>(&web_contents->GetController())); 462} 463 464void TitleWatcher::AlsoWaitForTitle(const string16& expected_title) { 465 expected_titles_.push_back(expected_title); 466} 467 468TitleWatcher::~TitleWatcher() { 469} 470 471const string16& TitleWatcher::WaitAndGetTitle() { 472 if (expected_title_observed_) 473 return observed_title_; 474 quit_loop_on_observation_ = true; 475 message_loop_runner_ = new MessageLoopRunner; 476 message_loop_runner_->Run(); 477 return observed_title_; 478} 479 480void TitleWatcher::Observe(int type, 481 const NotificationSource& source, 482 const NotificationDetails& details) { 483 if (type == NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED) { 484 WebContents* source_contents = Source<WebContents>(source).ptr(); 485 ASSERT_EQ(web_contents_, source_contents); 486 } else if (type == NOTIFICATION_LOAD_STOP) { 487 NavigationController* controller = 488 Source<NavigationController>(source).ptr(); 489 ASSERT_EQ(&web_contents_->GetController(), controller); 490 } else { 491 FAIL() << "Unexpected notification received."; 492 } 493 494 std::vector<string16>::const_iterator it = 495 std::find(expected_titles_.begin(), 496 expected_titles_.end(), 497 web_contents_->GetTitle()); 498 if (it == expected_titles_.end()) 499 return; 500 observed_title_ = *it; 501 expected_title_observed_ = true; 502 if (quit_loop_on_observation_) { 503 // Only call Quit once, on first Observe: 504 quit_loop_on_observation_ = false; 505 message_loop_runner_->Quit(); 506 } 507} 508 509DOMMessageQueue::DOMMessageQueue() : waiting_for_message_(false) { 510 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, 511 NotificationService::AllSources()); 512} 513 514DOMMessageQueue::~DOMMessageQueue() {} 515 516void DOMMessageQueue::Observe(int type, 517 const NotificationSource& source, 518 const NotificationDetails& details) { 519 Details<DomOperationNotificationDetails> dom_op_details(details); 520 Source<RenderViewHost> sender(source); 521 message_queue_.push(dom_op_details->json); 522 if (waiting_for_message_) { 523 waiting_for_message_ = false; 524 message_loop_runner_->Quit(); 525 } 526} 527 528void DOMMessageQueue::ClearQueue() { 529 message_queue_ = std::queue<std::string>(); 530} 531 532bool DOMMessageQueue::WaitForMessage(std::string* message) { 533 if (message_queue_.empty()) { 534 waiting_for_message_ = true; 535 // This will be quit when a new message comes in. 536 message_loop_runner_ = new MessageLoopRunner; 537 message_loop_runner_->Run(); 538 } 539 // The queue should not be empty, unless we were quit because of a timeout. 540 if (message_queue_.empty()) 541 return false; 542 if (message) 543 *message = message_queue_.front(); 544 message_queue_.pop(); 545 return true; 546} 547 548} // namespace content 549