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