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