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