browser_test_utils.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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/keycodes/keycode_converter.h"
35#include "ui/base/resource/resource_bundle.h"
36
37namespace content {
38namespace {
39
40class DOMOperationObserver : public NotificationObserver,
41                             public WebContentsObserver {
42 public:
43  explicit DOMOperationObserver(RenderViewHost* rvh)
44      : WebContentsObserver(WebContents::FromRenderViewHost(rvh)),
45        did_respond_(false) {
46    registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE,
47                   Source<RenderViewHost>(rvh));
48    message_loop_runner_ = new MessageLoopRunner;
49  }
50
51  virtual void Observe(int type,
52                       const NotificationSource& source,
53                       const NotificationDetails& details) OVERRIDE {
54    DCHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE);
55    Details<DomOperationNotificationDetails> dom_op_details(details);
56    response_ = dom_op_details->json;
57    did_respond_ = true;
58    message_loop_runner_->Quit();
59  }
60
61  // Overridden from WebContentsObserver:
62  virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
63    message_loop_runner_->Quit();
64  }
65
66  bool WaitAndGetResponse(std::string* response) WARN_UNUSED_RESULT {
67    message_loop_runner_->Run();
68    *response = response_;
69    return did_respond_;
70  }
71
72 private:
73  NotificationRegistrar registrar_;
74  std::string response_;
75  bool did_respond_;
76  scoped_refptr<MessageLoopRunner> message_loop_runner_;
77
78  DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver);
79};
80
81// Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
82bool ExecuteScriptHelper(RenderViewHost* render_view_host,
83                         const std::string& frame_xpath,
84                         const std::string& original_script,
85                         scoped_ptr<Value>* result) WARN_UNUSED_RESULT;
86
87// Executes the passed |original_script| in the frame pointed to by
88// |frame_xpath|.  If |result| is not NULL, stores the value that the evaluation
89// of the script in |result|.  Returns true on success.
90bool ExecuteScriptHelper(RenderViewHost* render_view_host,
91                         const std::string& frame_xpath,
92                         const std::string& original_script,
93                         scoped_ptr<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_view_host);
99  render_view_host->ExecuteJavascriptInWebFrame(UTF8ToUTF16(frame_xpath),
100                                                UTF8ToUTF16(script));
101  std::string json;
102  if (!dom_op_observer.WaitAndGetResponse(&json)) {
103    DLOG(ERROR) << "Cannot communicate with DOMOperationObserver.";
104    return false;
105  }
106
107  // Nothing more to do for callers that ignore the returned JS value.
108  if (!result)
109    return true;
110
111  base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
112  result->reset(reader.ReadToValue(json));
113  if (!result->get()) {
114    DLOG(ERROR) << reader.GetErrorMessage();
115    return false;
116  }
117
118  return true;
119}
120
121void BuildSimpleWebKeyEvent(WebKit::WebInputEvent::Type type,
122                            ui::KeyboardCode key_code,
123                            int native_key_code,
124                            int modifiers,
125                            NativeWebKeyboardEvent* event) {
126  event->nativeKeyCode = native_key_code;
127  event->windowsKeyCode = key_code;
128  event->setKeyIdentifierFromWindowsKeyCode();
129  event->type = type;
130  event->modifiers = modifiers;
131  event->isSystemKey = false;
132  event->timeStampSeconds = base::Time::Now().ToDoubleT();
133  event->skip_in_browser = true;
134
135  if (type == WebKit::WebInputEvent::Char ||
136      type == WebKit::WebInputEvent::RawKeyDown) {
137    event->text[0] = key_code;
138    event->unmodifiedText[0] = key_code;
139  }
140}
141
142void InjectRawKeyEvent(WebContents* web_contents,
143                       WebKit::WebInputEvent::Type type,
144                       ui::KeyboardCode key_code,
145                       int native_key_code,
146                       int modifiers) {
147  NativeWebKeyboardEvent event;
148  BuildSimpleWebKeyEvent(type, key_code, native_key_code, modifiers, &event);
149  web_contents->GetRenderViewHost()->ForwardKeyboardEvent(event);
150}
151
152void GetCookiesCallback(std::string* cookies_out,
153                        base::WaitableEvent* event,
154                        const std::string& cookies) {
155  *cookies_out = cookies;
156  event->Signal();
157}
158
159void GetCookiesOnIOThread(const GURL& url,
160                          net::URLRequestContextGetter* context_getter,
161                          base::WaitableEvent* event,
162                          std::string* cookies) {
163  net::CookieStore* cookie_store =
164      context_getter->GetURLRequestContext()->cookie_store();
165  cookie_store->GetCookiesWithOptionsAsync(
166      url, net::CookieOptions(),
167      base::Bind(&GetCookiesCallback, cookies, event));
168}
169
170void SetCookieCallback(bool* result,
171                       base::WaitableEvent* event,
172                       bool success) {
173  *result = success;
174  event->Signal();
175}
176
177void SetCookieOnIOThread(const GURL& url,
178                         const std::string& value,
179                         net::URLRequestContextGetter* context_getter,
180                         base::WaitableEvent* event,
181                         bool* result) {
182  net::CookieStore* cookie_store =
183      context_getter->GetURLRequestContext()->cookie_store();
184  cookie_store->SetCookieWithOptionsAsync(
185      url, value, net::CookieOptions(),
186      base::Bind(&SetCookieCallback, result, event));
187}
188
189}  // namespace
190
191
192GURL GetFileUrlWithQuery(const base::FilePath& path,
193                         const std::string& query_string) {
194  GURL url = net::FilePathToFileURL(path);
195  if (!query_string.empty()) {
196    GURL::Replacements replacements;
197    replacements.SetQueryStr(query_string);
198    return url.ReplaceComponents(replacements);
199  }
200  return url;
201}
202
203void WaitForLoadStop(WebContents* web_contents) {
204    WindowedNotificationObserver load_stop_observer(
205    NOTIFICATION_LOAD_STOP,
206    Source<NavigationController>(&web_contents->GetController()));
207  // In many cases, the load may have finished before we get here.  Only wait if
208  // the tab still has a pending navigation.
209  if (!web_contents->IsLoading())
210    return;
211  load_stop_observer.Wait();
212}
213
214void CrashTab(WebContents* web_contents) {
215  RenderProcessHost* rph = web_contents->GetRenderProcessHost();
216  WindowedNotificationObserver observer(
217      NOTIFICATION_RENDERER_PROCESS_CLOSED,
218      Source<RenderProcessHost>(rph));
219  base::KillProcess(rph->GetHandle(), 0, false);
220  observer.Wait();
221}
222
223void SimulateMouseClick(WebContents* web_contents,
224                        int modifiers,
225                        WebKit::WebMouseEvent::Button button) {
226  int x = web_contents->GetView()->GetContainerSize().width() / 2;
227  int y = web_contents->GetView()->GetContainerSize().height() / 2;
228  SimulateMouseClickAt(web_contents, modifiers, button, gfx::Point(x, y));
229}
230
231void SimulateMouseClickAt(WebContents* web_contents,
232                          int modifiers,
233                          WebKit::WebMouseEvent::Button button,
234                          const gfx::Point& point) {
235  WebKit::WebMouseEvent mouse_event;
236  mouse_event.type = WebKit::WebInputEvent::MouseDown;
237  mouse_event.button = button;
238  mouse_event.x = point.x();
239  mouse_event.y = point.y();
240  mouse_event.modifiers = modifiers;
241  // Mac needs globalX/globalY for events to plugins.
242  gfx::Rect offset;
243  web_contents->GetView()->GetContainerBounds(&offset);
244  mouse_event.globalX = point.x() + offset.x();
245  mouse_event.globalY = point.y() + offset.y();
246  mouse_event.clickCount = 1;
247  web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
248  mouse_event.type = WebKit::WebInputEvent::MouseUp;
249  web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
250}
251
252void SimulateMouseEvent(WebContents* web_contents,
253                        WebKit::WebInputEvent::Type type,
254                        const gfx::Point& point) {
255  WebKit::WebMouseEvent mouse_event;
256  mouse_event.type = type;
257  mouse_event.x = point.x();
258  mouse_event.y = point.y();
259  web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event);
260}
261
262void SimulateKeyPress(WebContents* web_contents,
263                      ui::KeyboardCode key_code,
264                      bool control,
265                      bool shift,
266                      bool alt,
267                      bool command) {
268  SimulateKeyPressWithCode(
269      web_contents, key_code, NULL, control, shift, alt, command);
270}
271
272void SimulateKeyPressWithCode(WebContents* web_contents,
273                              ui::KeyboardCode key_code,
274                              const char* code,
275                              bool control,
276                              bool shift,
277                              bool alt,
278                              bool command) {
279  ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance();
280  int native_key_code = key_converter->CodeToNativeKeycode(code);
281
282  int modifiers = 0;
283
284  // The order of these key down events shouldn't matter for our simulation.
285  // For our simulation we can use either the left keys or the right keys.
286  if (control) {
287    modifiers |= WebKit::WebInputEvent::ControlKey;
288    InjectRawKeyEvent(
289        web_contents,
290        WebKit::WebInputEvent::RawKeyDown,
291        ui::VKEY_CONTROL,
292        key_converter->CodeToNativeKeycode("ControlLeft"),
293        modifiers);
294  }
295
296  if (shift) {
297    modifiers |= WebKit::WebInputEvent::ShiftKey;
298    InjectRawKeyEvent(
299        web_contents,
300        WebKit::WebInputEvent::RawKeyDown,
301        ui::VKEY_SHIFT,
302        key_converter->CodeToNativeKeycode("ShiftLeft"),
303        modifiers);
304  }
305
306  if (alt) {
307    modifiers |= WebKit::WebInputEvent::AltKey;
308    InjectRawKeyEvent(
309        web_contents,
310        WebKit::WebInputEvent::RawKeyDown,
311        ui::VKEY_MENU,
312        key_converter->CodeToNativeKeycode("AltLeft"),
313        modifiers);
314  }
315
316  if (command) {
317    modifiers |= WebKit::WebInputEvent::MetaKey;
318    InjectRawKeyEvent(
319        web_contents,
320        WebKit::WebInputEvent::RawKeyDown,
321        ui::VKEY_COMMAND,
322        key_converter->CodeToNativeKeycode("OSLeft"),
323        modifiers);
324  }
325
326  InjectRawKeyEvent(
327      web_contents,
328      WebKit::WebInputEvent::RawKeyDown,
329      key_code,
330      native_key_code,
331      modifiers);
332
333  InjectRawKeyEvent(
334      web_contents,
335      WebKit::WebInputEvent::Char,
336      key_code,
337      native_key_code,
338      modifiers);
339
340  InjectRawKeyEvent(
341      web_contents,
342      WebKit::WebInputEvent::KeyUp,
343      key_code,
344      native_key_code,
345      modifiers);
346
347  // The order of these key releases shouldn't matter for our simulation.
348  if (control) {
349    modifiers &= ~WebKit::WebInputEvent::ControlKey;
350    InjectRawKeyEvent(
351        web_contents,
352        WebKit::WebInputEvent::KeyUp,
353        ui::VKEY_CONTROL,
354        key_converter->CodeToNativeKeycode("ControlLeft"),
355        modifiers);
356  }
357
358  if (shift) {
359    modifiers &= ~WebKit::WebInputEvent::ShiftKey;
360    InjectRawKeyEvent(
361        web_contents,
362        WebKit::WebInputEvent::KeyUp,
363        ui::VKEY_SHIFT,
364        key_converter->CodeToNativeKeycode("ShiftLeft"),
365        modifiers);
366  }
367
368  if (alt) {
369    modifiers &= ~WebKit::WebInputEvent::AltKey;
370    InjectRawKeyEvent(
371        web_contents,
372        WebKit::WebInputEvent::KeyUp,
373        ui::VKEY_MENU,
374        key_converter->CodeToNativeKeycode("AltLeft"),
375        modifiers);
376  }
377
378  if (command) {
379    modifiers &= ~WebKit::WebInputEvent::MetaKey;
380    InjectRawKeyEvent(
381        web_contents,
382        WebKit::WebInputEvent::KeyUp,
383        ui::VKEY_COMMAND,
384        key_converter->CodeToNativeKeycode("OSLeft"),
385        modifiers);
386  }
387
388  ASSERT_EQ(modifiers, 0);
389}
390
391namespace internal {
392
393ToRenderViewHost::ToRenderViewHost(WebContents* web_contents)
394    : render_view_host_(web_contents->GetRenderViewHost()) {
395}
396
397ToRenderViewHost::ToRenderViewHost(RenderViewHost* render_view_host)
398    : render_view_host_(render_view_host) {
399}
400
401}  // namespace internal
402
403bool ExecuteScriptInFrame(const internal::ToRenderViewHost& adapter,
404                          const std::string& frame_xpath,
405                          const std::string& original_script) {
406  std::string script =
407      original_script + ";window.domAutomationController.send(0);";
408  return ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script,
409                             NULL);
410}
411
412bool ExecuteScriptInFrameAndExtractInt(
413    const internal::ToRenderViewHost& adapter,
414    const std::string& frame_xpath,
415    const std::string& script,
416    int* result) {
417  DCHECK(result);
418  scoped_ptr<Value> value;
419  if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script,
420                           &value) || !value.get())
421    return false;
422
423  return value->GetAsInteger(result);
424}
425
426bool ExecuteScriptInFrameAndExtractBool(
427    const internal::ToRenderViewHost& adapter,
428    const std::string& frame_xpath,
429    const std::string& script,
430    bool* result) {
431  DCHECK(result);
432  scoped_ptr<Value> value;
433  if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script,
434                           &value) || !value.get())
435    return false;
436
437  return value->GetAsBoolean(result);
438}
439
440bool ExecuteScriptInFrameAndExtractString(
441    const internal::ToRenderViewHost& adapter,
442    const std::string& frame_xpath,
443    const std::string& script,
444    std::string* result) {
445  DCHECK(result);
446  scoped_ptr<Value> value;
447  if (!ExecuteScriptHelper(adapter.render_view_host(), frame_xpath, script,
448                           &value) || !value.get())
449    return false;
450
451  return value->GetAsString(result);
452}
453
454bool ExecuteScript(const internal::ToRenderViewHost& adapter,
455                   const std::string& script) {
456  return ExecuteScriptInFrame(adapter, std::string(), script);
457}
458
459bool ExecuteScriptAndExtractInt(const internal::ToRenderViewHost& adapter,
460                                const std::string& script, int* result) {
461  return ExecuteScriptInFrameAndExtractInt(adapter, std::string(), script,
462                                           result);
463}
464
465bool ExecuteScriptAndExtractBool(const internal::ToRenderViewHost& adapter,
466                                 const std::string& script, bool* result) {
467  return ExecuteScriptInFrameAndExtractBool(adapter, std::string(), script,
468                                            result);
469}
470
471bool ExecuteScriptAndExtractString(const internal::ToRenderViewHost& adapter,
472                                   const std::string& script,
473                                   std::string* result) {
474  return ExecuteScriptInFrameAndExtractString(adapter, std::string(), script,
475                                              result);
476}
477
478bool ExecuteWebUIResourceTest(
479    const internal::ToRenderViewHost& adapter,
480    const std::vector<int>& js_resource_ids) {
481  // Inject WebUI test runner script first prior to other scripts required to
482  // run the test as scripts may depend on it being declared.
483  std::vector<int> ids;
484  ids.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST);
485  ids.insert(ids.end(), js_resource_ids.begin(), js_resource_ids.end());
486
487  std::string script;
488  for (std::vector<int>::iterator iter = ids.begin();
489       iter != ids.end();
490       ++iter) {
491    ResourceBundle::GetSharedInstance().GetRawDataResource(*iter)
492        .AppendToString(&script);
493    script.append("\n");
494  }
495  if (!content::ExecuteScript(adapter, script))
496    return false;
497
498  content::DOMMessageQueue message_queue;
499  if (!content::ExecuteScript(adapter, "runTests()"))
500    return false;
501
502  std::string message;
503  do {
504    if (!message_queue.WaitForMessage(&message))
505      return false;
506  } while (message.compare("\"PENDING\"") == 0);
507
508  return message.compare("\"SUCCESS\"") == 0;
509}
510
511std::string GetCookies(BrowserContext* browser_context, const GURL& url) {
512  std::string cookies;
513  base::WaitableEvent event(true, false);
514  net::URLRequestContextGetter* context_getter =
515      browser_context->GetRequestContext();
516
517  BrowserThread::PostTask(
518      BrowserThread::IO, FROM_HERE,
519      base::Bind(&GetCookiesOnIOThread, url,
520                 make_scoped_refptr(context_getter), &event, &cookies));
521  event.Wait();
522  return cookies;
523}
524
525bool SetCookie(BrowserContext* browser_context,
526               const GURL& url,
527               const std::string& value) {
528  bool result = false;
529  base::WaitableEvent event(true, false);
530  net::URLRequestContextGetter* context_getter =
531      browser_context->GetRequestContext();
532
533  BrowserThread::PostTask(
534      BrowserThread::IO, FROM_HERE,
535      base::Bind(&SetCookieOnIOThread, url, value,
536                 make_scoped_refptr(context_getter), &event, &result));
537  event.Wait();
538  return result;
539}
540
541TitleWatcher::TitleWatcher(WebContents* web_contents,
542                           const string16& expected_title)
543    : WebContentsObserver(web_contents),
544      message_loop_runner_(new MessageLoopRunner) {
545  EXPECT_TRUE(web_contents != NULL);
546  expected_titles_.push_back(expected_title);
547}
548
549void TitleWatcher::AlsoWaitForTitle(const string16& expected_title) {
550  expected_titles_.push_back(expected_title);
551}
552
553TitleWatcher::~TitleWatcher() {
554}
555
556const string16& TitleWatcher::WaitAndGetTitle() {
557  message_loop_runner_->Run();
558  return observed_title_;
559}
560
561void TitleWatcher::DidStopLoading(RenderViewHost* render_view_host) {
562  // When navigating through the history, the restored NavigationEntry's title
563  // will be used. If the entry ends up having the same title after we return
564  // to it, as will usually be the case, then WebContentsObserver::TitleSet
565  // will then be suppressed, since the NavigationEntry's title hasn't changed.
566  TestTitle();
567}
568
569void TitleWatcher::TitleWasSet(NavigationEntry* entry, bool explicit_set) {
570  TestTitle();
571}
572
573void TitleWatcher::TestTitle() {
574  std::vector<string16>::const_iterator it =
575      std::find(expected_titles_.begin(),
576                expected_titles_.end(),
577                web_contents()->GetTitle());
578  if (it == expected_titles_.end())
579    return;
580
581  observed_title_ = *it;
582  message_loop_runner_->Quit();
583}
584
585DOMMessageQueue::DOMMessageQueue() : waiting_for_message_(false) {
586  registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE,
587                 NotificationService::AllSources());
588}
589
590DOMMessageQueue::~DOMMessageQueue() {}
591
592void DOMMessageQueue::Observe(int type,
593                              const NotificationSource& source,
594                              const NotificationDetails& details) {
595  Details<DomOperationNotificationDetails> dom_op_details(details);
596  Source<RenderViewHost> sender(source);
597  message_queue_.push(dom_op_details->json);
598  if (waiting_for_message_) {
599    waiting_for_message_ = false;
600    message_loop_runner_->Quit();
601  }
602}
603
604void DOMMessageQueue::ClearQueue() {
605  message_queue_ = std::queue<std::string>();
606}
607
608bool DOMMessageQueue::WaitForMessage(std::string* message) {
609  if (message_queue_.empty()) {
610    waiting_for_message_ = true;
611    // This will be quit when a new message comes in.
612    message_loop_runner_ = new MessageLoopRunner;
613    message_loop_runner_->Run();
614  }
615  // The queue should not be empty, unless we were quit because of a timeout.
616  if (message_queue_.empty())
617    return false;
618  if (message)
619    *message = message_queue_.front();
620  message_queue_.pop();
621  return true;
622}
623
624}  // namespace content
625