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 "net/base/net_util.h" 28 #include "net/cookies/cookie_store.h" 29 #include "net/test/python_utils.h" 30 #include "net/url_request/url_request_context.h" 31 #include "net/url_request/url_request_context_getter.h" 32 #include "testing/gtest/include/gtest/gtest.h" 33 34 static const int kDefaultWsPort = 8880; 35 36 namespace content { 37 namespace { 38 39 class DOMOperationObserver : public NotificationObserver, 40 public WebContentsObserver { 41 public: 42 explicit DOMOperationObserver(RenderViewHost* rvh) 43 : WebContentsObserver(WebContents::FromRenderViewHost(rvh)), 44 did_respond_(false) { 45 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, 46 Source<RenderViewHost>(rvh)); 47 message_loop_runner_ = new MessageLoopRunner; 48 } 49 50 virtual void Observe(int type, 51 const NotificationSource& source, 52 const NotificationDetails& details) OVERRIDE { 53 DCHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE); 54 Details<DomOperationNotificationDetails> dom_op_details(details); 55 response_ = dom_op_details->json; 56 did_respond_ = true; 57 message_loop_runner_->Quit(); 58 } 59 60 // Overridden from WebContentsObserver: 61 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE { 62 message_loop_runner_->Quit(); 63 } 64 65 bool WaitAndGetResponse(std::string* response) WARN_UNUSED_RESULT { 66 message_loop_runner_->Run(); 67 *response = response_; 68 return did_respond_; 69 } 70 71 private: 72 NotificationRegistrar registrar_; 73 std::string response_; 74 bool did_respond_; 75 scoped_refptr<MessageLoopRunner> message_loop_runner_; 76 77 DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver); 78 }; 79 80 // Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute. 81 bool ExecuteScriptHelper(RenderViewHost* render_view_host, 82 const std::string& frame_xpath, 83 const std::string& original_script, 84 scoped_ptr<Value>* result) WARN_UNUSED_RESULT; 85 86 // Executes the passed |original_script| in the frame pointed to by 87 // |frame_xpath|. If |result| is not NULL, stores the value that the evaluation 88 // of the script in |result|. Returns true on success. 89 bool ExecuteScriptHelper(RenderViewHost* render_view_host, 90 const std::string& frame_xpath, 91 const std::string& original_script, 92 scoped_ptr<Value>* result) { 93 // TODO(jcampan): we should make the domAutomationController not require an 94 // automation id. 95 std::string script = 96 "window.domAutomationController.setAutomationId(0);" + original_script; 97 DOMOperationObserver dom_op_observer(render_view_host); 98 render_view_host->ExecuteJavascriptInWebFrame(UTF8ToUTF16(frame_xpath), 99 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 120 void BuildSimpleWebKeyEvent(WebKit::WebInputEvent::Type type, 121 ui::KeyboardCode key, 122 bool control, 123 bool shift, 124 bool alt, 125 bool command, 126 NativeWebKeyboardEvent* event) { 127 event->nativeKeyCode = 0; 128 event->windowsKeyCode = key; 129 event->setKeyIdentifierFromWindowsKeyCode(); 130 event->type = type; 131 event->modifiers = 0; 132 event->isSystemKey = false; 133 event->timeStampSeconds = base::Time::Now().ToDoubleT(); 134 event->skip_in_browser = true; 135 136 if (type == WebKit::WebInputEvent::Char || 137 type == WebKit::WebInputEvent::RawKeyDown) { 138 event->text[0] = key; 139 event->unmodifiedText[0] = key; 140 } 141 142 if (control) 143 event->modifiers |= WebKit::WebInputEvent::ControlKey; 144 145 if (shift) 146 event->modifiers |= WebKit::WebInputEvent::ShiftKey; 147 148 if (alt) 149 event->modifiers |= WebKit::WebInputEvent::AltKey; 150 151 if (command) 152 event->modifiers |= WebKit::WebInputEvent::MetaKey; 153 } 154 155 void GetCookiesCallback(std::string* cookies_out, 156 base::WaitableEvent* event, 157 const std::string& cookies) { 158 *cookies_out = cookies; 159 event->Signal(); 160 } 161 162 void GetCookiesOnIOThread(const GURL& url, 163 net::URLRequestContextGetter* context_getter, 164 base::WaitableEvent* event, 165 std::string* cookies) { 166 net::CookieStore* cookie_store = 167 context_getter->GetURLRequestContext()->cookie_store(); 168 cookie_store->GetCookiesWithOptionsAsync( 169 url, net::CookieOptions(), 170 base::Bind(&GetCookiesCallback, cookies, event)); 171 } 172 173 void SetCookieCallback(bool* result, 174 base::WaitableEvent* event, 175 bool success) { 176 *result = success; 177 event->Signal(); 178 } 179 180 void SetCookieOnIOThread(const GURL& url, 181 const std::string& value, 182 net::URLRequestContextGetter* context_getter, 183 base::WaitableEvent* event, 184 bool* result) { 185 net::CookieStore* cookie_store = 186 context_getter->GetURLRequestContext()->cookie_store(); 187 cookie_store->SetCookieWithOptionsAsync( 188 url, value, net::CookieOptions(), 189 base::Bind(&SetCookieCallback, result, event)); 190 } 191 192 } // namespace 193 194 195 GURL GetFileUrlWithQuery(const base::FilePath& path, 196 const std::string& query_string) { 197 GURL url = net::FilePathToFileURL(path); 198 if (!query_string.empty()) { 199 GURL::Replacements replacements; 200 replacements.SetQueryStr(query_string); 201 return url.ReplaceComponents(replacements); 202 } 203 return url; 204 } 205 206 void WaitForLoadStop(WebContents* web_contents) { 207 WindowedNotificationObserver load_stop_observer( 208 NOTIFICATION_LOAD_STOP, 209 Source<NavigationController>(&web_contents->GetController())); 210 // In many cases, the load may have finished before we get here. Only wait if 211 // the tab still has a pending navigation. 212 if (!web_contents->IsLoading()) 213 return; 214 load_stop_observer.Wait(); 215 } 216 217 void CrashTab(WebContents* web_contents) { 218 RenderProcessHost* rph = web_contents->GetRenderProcessHost(); 219 WindowedNotificationObserver observer( 220 NOTIFICATION_RENDERER_PROCESS_CLOSED, 221 Source<RenderProcessHost>(rph)); 222 base::KillProcess(rph->GetHandle(), 0, false); 223 observer.Wait(); 224 } 225 226 void SimulateMouseClick(WebContents* web_contents, 227 int modifiers, 228 WebKit::WebMouseEvent::Button button) { 229 int x = web_contents->GetView()->GetContainerSize().width() / 2; 230 int y = web_contents->GetView()->GetContainerSize().height() / 2; 231 SimulateMouseClickAt(web_contents, modifiers, button, gfx::Point(x, y)); 232 } 233 234 void SimulateMouseClickAt(WebContents* web_contents, 235 int modifiers, 236 WebKit::WebMouseEvent::Button button, 237 const gfx::Point& point) { 238 WebKit::WebMouseEvent mouse_event; 239 mouse_event.type = WebKit::WebInputEvent::MouseDown; 240 mouse_event.button = button; 241 mouse_event.x = point.x(); 242 mouse_event.y = point.y(); 243 mouse_event.modifiers = modifiers; 244 // Mac needs globalX/globalY for events to plugins. 245 gfx::Rect offset; 246 web_contents->GetView()->GetContainerBounds(&offset); 247 mouse_event.globalX = point.x() + offset.x(); 248 mouse_event.globalY = point.y() + offset.y(); 249 mouse_event.clickCount = 1; 250 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 251 mouse_event.type = WebKit::WebInputEvent::MouseUp; 252 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 253 } 254 255 void SimulateMouseEvent(WebContents* web_contents, 256 WebKit::WebInputEvent::Type type, 257 const gfx::Point& point) { 258 WebKit::WebMouseEvent mouse_event; 259 mouse_event.type = type; 260 mouse_event.x = point.x(); 261 mouse_event.y = point.y(); 262 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); 263 } 264 265 void SimulateKeyPress(WebContents* web_contents, 266 ui::KeyboardCode key, 267 bool control, 268 bool shift, 269 bool alt, 270 bool command) { 271 NativeWebKeyboardEvent event_down; 272 BuildSimpleWebKeyEvent( 273 WebKit::WebInputEvent::RawKeyDown, key, control, shift, alt, command, 274 &event_down); 275 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event_down); 276 277 NativeWebKeyboardEvent char_event; 278 BuildSimpleWebKeyEvent( 279 WebKit::WebInputEvent::Char, key, control, shift, alt, command, 280 &char_event); 281 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(char_event); 282 283 NativeWebKeyboardEvent event_up; 284 BuildSimpleWebKeyEvent( 285 WebKit::WebInputEvent::KeyUp, key, control, shift, alt, command, 286 &event_up); 287 web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event_up); 288 } 289 290 namespace internal { 291 292 ToRenderViewHost::ToRenderViewHost(WebContents* web_contents) 293 : render_view_host_(web_contents->GetRenderViewHost()) { 294 } 295 296 ToRenderViewHost::ToRenderViewHost(RenderViewHost* render_view_host) 297 : render_view_host_(render_view_host) { 298 } 299 300 } // namespace internal 301 302 bool ExecuteScriptInFrame(const internal::ToRenderViewHost& adapter, 303 const std::string& frame_xpath, 304 const std::string& original_script) { 305 std::string script = 306 original_script + ";window.domAutomationController.send(0);"; 307 return ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 308 NULL); 309 } 310 311 bool ExecuteScriptInFrameAndExtractInt( 312 const internal::ToRenderViewHost& adapter, 313 const std::string& frame_xpath, 314 const std::string& script, 315 int* result) { 316 DCHECK(result); 317 scoped_ptr<Value> value; 318 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 319 &value) || !value.get()) 320 return false; 321 322 return value->GetAsInteger(result); 323 } 324 325 bool ExecuteScriptInFrameAndExtractBool( 326 const internal::ToRenderViewHost& adapter, 327 const std::string& frame_xpath, 328 const std::string& script, 329 bool* result) { 330 DCHECK(result); 331 scoped_ptr<Value> value; 332 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 333 &value) || !value.get()) 334 return false; 335 336 return value->GetAsBoolean(result); 337 } 338 339 bool ExecuteScriptInFrameAndExtractString( 340 const internal::ToRenderViewHost& adapter, 341 const std::string& frame_xpath, 342 const std::string& script, 343 std::string* result) { 344 DCHECK(result); 345 scoped_ptr<Value> value; 346 if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script, 347 &value) || !value.get()) 348 return false; 349 350 return value->GetAsString(result); 351 } 352 353 bool ExecuteScript(const internal::ToRenderViewHost& adapter, 354 const std::string& script) { 355 return ExecuteScriptInFrame(adapter, std::string(), script); 356 } 357 358 bool ExecuteScriptAndExtractInt(const internal::ToRenderViewHost& adapter, 359 const std::string& script, int* result) { 360 return ExecuteScriptInFrameAndExtractInt(adapter, std::string(), script, 361 result); 362 } 363 364 bool ExecuteScriptAndExtractBool(const internal::ToRenderViewHost& adapter, 365 const std::string& script, bool* result) { 366 return ExecuteScriptInFrameAndExtractBool(adapter, std::string(), script, 367 result); 368 } 369 370 bool ExecuteScriptAndExtractString(const internal::ToRenderViewHost& adapter, 371 const std::string& script, 372 std::string* result) { 373 return ExecuteScriptInFrameAndExtractString(adapter, std::string(), script, 374 result); 375 } 376 377 std::string GetCookies(BrowserContext* browser_context, const GURL& url) { 378 std::string cookies; 379 base::WaitableEvent event(true, false); 380 net::URLRequestContextGetter* context_getter = 381 browser_context->GetRequestContext(); 382 383 BrowserThread::PostTask( 384 BrowserThread::IO, FROM_HERE, 385 base::Bind(&GetCookiesOnIOThread, url, 386 make_scoped_refptr(context_getter), &event, &cookies)); 387 event.Wait(); 388 return cookies; 389 } 390 391 bool SetCookie(BrowserContext* browser_context, 392 const GURL& url, 393 const std::string& value) { 394 bool result = false; 395 base::WaitableEvent event(true, false); 396 net::URLRequestContextGetter* context_getter = 397 browser_context->GetRequestContext(); 398 399 BrowserThread::PostTask( 400 BrowserThread::IO, FROM_HERE, 401 base::Bind(&SetCookieOnIOThread, url, value, 402 make_scoped_refptr(context_getter), &event, &result)); 403 event.Wait(); 404 return result; 405 } 406 407 TitleWatcher::TitleWatcher(WebContents* web_contents, 408 const string16& expected_title) 409 : web_contents_(web_contents), 410 expected_title_observed_(false), 411 quit_loop_on_observation_(false) { 412 EXPECT_TRUE(web_contents != NULL); 413 expected_titles_.push_back(expected_title); 414 notification_registrar_.Add(this, 415 NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, 416 Source<WebContents>(web_contents)); 417 418 // When navigating through the history, the restored NavigationEntry's title 419 // will be used. If the entry ends up having the same title after we return 420 // to it, as will usually be the case, the 421 // NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED will then be suppressed, since the 422 // NavigationEntry's title hasn't changed. 423 notification_registrar_.Add( 424 this, 425 NOTIFICATION_LOAD_STOP, 426 Source<NavigationController>(&web_contents->GetController())); 427 } 428 429 void TitleWatcher::AlsoWaitForTitle(const string16& expected_title) { 430 expected_titles_.push_back(expected_title); 431 } 432 433 TitleWatcher::~TitleWatcher() { 434 } 435 436 const string16& TitleWatcher::WaitAndGetTitle() { 437 if (expected_title_observed_) 438 return observed_title_; 439 quit_loop_on_observation_ = true; 440 message_loop_runner_ = new MessageLoopRunner; 441 message_loop_runner_->Run(); 442 return observed_title_; 443 } 444 445 void TitleWatcher::Observe(int type, 446 const NotificationSource& source, 447 const NotificationDetails& details) { 448 if (type == NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED) { 449 WebContents* source_contents = Source<WebContents>(source).ptr(); 450 ASSERT_EQ(web_contents_, source_contents); 451 } else if (type == NOTIFICATION_LOAD_STOP) { 452 NavigationController* controller = 453 Source<NavigationController>(source).ptr(); 454 ASSERT_EQ(&web_contents_->GetController(), controller); 455 } else { 456 FAIL() << "Unexpected notification received."; 457 } 458 459 std::vector<string16>::const_iterator it = 460 std::find(expected_titles_.begin(), 461 expected_titles_.end(), 462 web_contents_->GetTitle()); 463 if (it == expected_titles_.end()) 464 return; 465 observed_title_ = *it; 466 expected_title_observed_ = true; 467 if (quit_loop_on_observation_) { 468 // Only call Quit once, on first Observe: 469 quit_loop_on_observation_ = false; 470 message_loop_runner_->Quit(); 471 } 472 } 473 474 DOMMessageQueue::DOMMessageQueue() : waiting_for_message_(false) { 475 registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE, 476 NotificationService::AllSources()); 477 } 478 479 DOMMessageQueue::~DOMMessageQueue() {} 480 481 void DOMMessageQueue::Observe(int type, 482 const NotificationSource& source, 483 const NotificationDetails& details) { 484 Details<DomOperationNotificationDetails> dom_op_details(details); 485 Source<RenderViewHost> sender(source); 486 message_queue_.push(dom_op_details->json); 487 if (waiting_for_message_) { 488 waiting_for_message_ = false; 489 message_loop_runner_->Quit(); 490 } 491 } 492 493 void DOMMessageQueue::ClearQueue() { 494 message_queue_ = std::queue<std::string>(); 495 } 496 497 bool DOMMessageQueue::WaitForMessage(std::string* message) { 498 if (message_queue_.empty()) { 499 waiting_for_message_ = true; 500 // This will be quit when a new message comes in. 501 message_loop_runner_ = new MessageLoopRunner; 502 message_loop_runner_->Run(); 503 } 504 // The queue should not be empty, unless we were quit because of a timeout. 505 if (message_queue_.empty()) 506 return false; 507 if (message) 508 *message = message_queue_.front(); 509 message_queue_.pop(); 510 return true; 511 } 512 513 } // namespace content 514