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.
5 #include "content/public/test/browser_test_utils.h"
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 #include "ui/events/keycodes/dom4/keycode_converter.h"
40 class DOMOperationObserver
: public NotificationObserver
,
41 public WebContentsObserver
{
43 explicit DOMOperationObserver(RenderViewHost
* rvh
)
44 : WebContentsObserver(WebContents::FromRenderViewHost(rvh
)),
46 registrar_
.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE
,
47 Source
<RenderViewHost
>(rvh
));
48 message_loop_runner_
= new MessageLoopRunner
;
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
;
58 message_loop_runner_
->Quit();
61 // Overridden from WebContentsObserver:
62 virtual void RenderProcessGone(base::TerminationStatus status
) OVERRIDE
{
63 message_loop_runner_
->Quit();
66 bool WaitAndGetResponse(std::string
* response
) WARN_UNUSED_RESULT
{
67 message_loop_runner_
->Run();
68 *response
= response_
;
73 NotificationRegistrar registrar_
;
74 std::string response_
;
76 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
78 DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver
);
81 // Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
82 bool ExecuteScriptHelper(RenderViewHost
* render_view_host
,
83 const std::string
& frame_xpath
,
84 const std::string
& original_script
,
85 scoped_ptr
<base::Value
>* result
) WARN_UNUSED_RESULT
;
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.
90 bool ExecuteScriptHelper(RenderViewHost
* render_view_host
,
91 const std::string
& frame_xpath
,
92 const std::string
& original_script
,
93 scoped_ptr
<base::Value
>* result
) {
94 // TODO(jcampan): we should make the domAutomationController not require an
97 "window.domAutomationController.setAutomationId(0);" + original_script
;
98 DOMOperationObserver
dom_op_observer(render_view_host
);
99 render_view_host
->ExecuteJavascriptInWebFrame(base::UTF8ToUTF16(frame_xpath
),
100 base::UTF8ToUTF16(script
));
102 if (!dom_op_observer
.WaitAndGetResponse(&json
)) {
103 DLOG(ERROR
) << "Cannot communicate with DOMOperationObserver.";
107 // Nothing more to do for callers that ignore the returned JS value.
111 base::JSONReader
reader(base::JSON_ALLOW_TRAILING_COMMAS
);
112 result
->reset(reader
.ReadToValue(json
));
113 if (!result
->get()) {
114 DLOG(ERROR
) << reader
.GetErrorMessage();
121 void BuildSimpleWebKeyEvent(blink::WebInputEvent::Type type
,
122 ui::KeyboardCode key_code
,
125 NativeWebKeyboardEvent
* event
) {
126 event
->nativeKeyCode
= native_key_code
;
127 event
->windowsKeyCode
= key_code
;
128 event
->setKeyIdentifierFromWindowsKeyCode();
130 event
->modifiers
= modifiers
;
131 event
->isSystemKey
= false;
132 event
->timeStampSeconds
= base::Time::Now().ToDoubleT();
133 event
->skip_in_browser
= true;
135 if (type
== blink::WebInputEvent::Char
||
136 type
== blink::WebInputEvent::RawKeyDown
) {
137 event
->text
[0] = key_code
;
138 event
->unmodifiedText
[0] = key_code
;
142 void InjectRawKeyEvent(WebContents
* web_contents
,
143 blink::WebInputEvent::Type type
,
144 ui::KeyboardCode key_code
,
147 NativeWebKeyboardEvent event
;
148 BuildSimpleWebKeyEvent(type
, key_code
, native_key_code
, modifiers
, &event
);
149 web_contents
->GetRenderViewHost()->ForwardKeyboardEvent(event
);
152 void GetCookiesCallback(std::string
* cookies_out
,
153 base::WaitableEvent
* event
,
154 const std::string
& cookies
) {
155 *cookies_out
= cookies
;
159 void 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
));
170 void SetCookieCallback(bool* result
,
171 base::WaitableEvent
* event
,
177 void SetCookieOnIOThread(const GURL
& url
,
178 const std::string
& value
,
179 net::URLRequestContextGetter
* context_getter
,
180 base::WaitableEvent
* event
,
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
));
192 GURL
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
);
203 void 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())
211 load_stop_observer
.Wait();
214 void CrashTab(WebContents
* web_contents
) {
215 RenderProcessHost
* rph
= web_contents
->GetRenderProcessHost();
216 RenderProcessHostWatcher
watcher(
217 rph
, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT
);
218 base::KillProcess(rph
->GetHandle(), 0, false);
222 void SimulateMouseClick(WebContents
* web_contents
,
224 blink::WebMouseEvent::Button button
) {
225 int x
= web_contents
->GetView()->GetContainerSize().width() / 2;
226 int y
= web_contents
->GetView()->GetContainerSize().height() / 2;
227 SimulateMouseClickAt(web_contents
, modifiers
, button
, gfx::Point(x
, y
));
230 void SimulateMouseClickAt(WebContents
* web_contents
,
232 blink::WebMouseEvent::Button button
,
233 const gfx::Point
& point
) {
234 blink::WebMouseEvent mouse_event
;
235 mouse_event
.type
= blink::WebInputEvent::MouseDown
;
236 mouse_event
.button
= button
;
237 mouse_event
.x
= point
.x();
238 mouse_event
.y
= point
.y();
239 mouse_event
.modifiers
= modifiers
;
240 // Mac needs globalX/globalY for events to plugins.
242 web_contents
->GetView()->GetContainerBounds(&offset
);
243 mouse_event
.globalX
= point
.x() + offset
.x();
244 mouse_event
.globalY
= point
.y() + offset
.y();
245 mouse_event
.clickCount
= 1;
246 web_contents
->GetRenderViewHost()->ForwardMouseEvent(mouse_event
);
247 mouse_event
.type
= blink::WebInputEvent::MouseUp
;
248 web_contents
->GetRenderViewHost()->ForwardMouseEvent(mouse_event
);
251 void SimulateMouseEvent(WebContents
* web_contents
,
252 blink::WebInputEvent::Type type
,
253 const gfx::Point
& point
) {
254 blink::WebMouseEvent mouse_event
;
255 mouse_event
.type
= type
;
256 mouse_event
.x
= point
.x();
257 mouse_event
.y
= point
.y();
258 web_contents
->GetRenderViewHost()->ForwardMouseEvent(mouse_event
);
261 void SimulateKeyPress(WebContents
* web_contents
,
262 ui::KeyboardCode key_code
,
267 SimulateKeyPressWithCode(
268 web_contents
, key_code
, NULL
, control
, shift
, alt
, command
);
271 void SimulateKeyPressWithCode(WebContents
* web_contents
,
272 ui::KeyboardCode key_code
,
278 ui::KeycodeConverter
* key_converter
= ui::KeycodeConverter::GetInstance();
279 int native_key_code
= key_converter
->CodeToNativeKeycode(code
);
283 // The order of these key down events shouldn't matter for our simulation.
284 // For our simulation we can use either the left keys or the right keys.
286 modifiers
|= blink::WebInputEvent::ControlKey
;
289 blink::WebInputEvent::RawKeyDown
,
291 key_converter
->CodeToNativeKeycode("ControlLeft"),
296 modifiers
|= blink::WebInputEvent::ShiftKey
;
299 blink::WebInputEvent::RawKeyDown
,
301 key_converter
->CodeToNativeKeycode("ShiftLeft"),
306 modifiers
|= blink::WebInputEvent::AltKey
;
309 blink::WebInputEvent::RawKeyDown
,
311 key_converter
->CodeToNativeKeycode("AltLeft"),
316 modifiers
|= blink::WebInputEvent::MetaKey
;
319 blink::WebInputEvent::RawKeyDown
,
321 key_converter
->CodeToNativeKeycode("OSLeft"),
327 blink::WebInputEvent::RawKeyDown
,
334 blink::WebInputEvent::Char
,
341 blink::WebInputEvent::KeyUp
,
346 // The order of these key releases shouldn't matter for our simulation.
348 modifiers
&= ~blink::WebInputEvent::ControlKey
;
351 blink::WebInputEvent::KeyUp
,
353 key_converter
->CodeToNativeKeycode("ControlLeft"),
358 modifiers
&= ~blink::WebInputEvent::ShiftKey
;
361 blink::WebInputEvent::KeyUp
,
363 key_converter
->CodeToNativeKeycode("ShiftLeft"),
368 modifiers
&= ~blink::WebInputEvent::AltKey
;
371 blink::WebInputEvent::KeyUp
,
373 key_converter
->CodeToNativeKeycode("AltLeft"),
378 modifiers
&= ~blink::WebInputEvent::MetaKey
;
381 blink::WebInputEvent::KeyUp
,
383 key_converter
->CodeToNativeKeycode("OSLeft"),
387 ASSERT_EQ(modifiers
, 0);
392 ToRenderViewHost::ToRenderViewHost(WebContents
* web_contents
)
393 : render_view_host_(web_contents
->GetRenderViewHost()) {
396 ToRenderViewHost::ToRenderViewHost(RenderViewHost
* render_view_host
)
397 : render_view_host_(render_view_host
) {
400 } // namespace internal
402 bool ExecuteScriptInFrame(const internal::ToRenderViewHost
& adapter
,
403 const std::string
& frame_xpath
,
404 const std::string
& original_script
) {
406 original_script
+ ";window.domAutomationController.send(0);";
407 return ExecuteScriptHelper(adapter
.render_view_host(), frame_xpath
, script
,
411 bool ExecuteScriptInFrameAndExtractInt(
412 const internal::ToRenderViewHost
& adapter
,
413 const std::string
& frame_xpath
,
414 const std::string
& script
,
417 scoped_ptr
<base::Value
> value
;
418 if (!ExecuteScriptHelper(adapter
.render_view_host(), frame_xpath
, script
,
419 &value
) || !value
.get())
422 return value
->GetAsInteger(result
);
425 bool ExecuteScriptInFrameAndExtractBool(
426 const internal::ToRenderViewHost
& adapter
,
427 const std::string
& frame_xpath
,
428 const std::string
& script
,
431 scoped_ptr
<base::Value
> value
;
432 if (!ExecuteScriptHelper(adapter
.render_view_host(), frame_xpath
, script
,
433 &value
) || !value
.get())
436 return value
->GetAsBoolean(result
);
439 bool ExecuteScriptInFrameAndExtractString(
440 const internal::ToRenderViewHost
& adapter
,
441 const std::string
& frame_xpath
,
442 const std::string
& script
,
443 std::string
* result
) {
445 scoped_ptr
<base::Value
> value
;
446 if (!ExecuteScriptHelper(adapter
.render_view_host(), frame_xpath
, script
,
447 &value
) || !value
.get())
450 return value
->GetAsString(result
);
453 bool ExecuteScript(const internal::ToRenderViewHost
& adapter
,
454 const std::string
& script
) {
455 return ExecuteScriptInFrame(adapter
, std::string(), script
);
458 bool ExecuteScriptAndExtractInt(const internal::ToRenderViewHost
& adapter
,
459 const std::string
& script
, int* result
) {
460 return ExecuteScriptInFrameAndExtractInt(adapter
, std::string(), script
,
464 bool ExecuteScriptAndExtractBool(const internal::ToRenderViewHost
& adapter
,
465 const std::string
& script
, bool* result
) {
466 return ExecuteScriptInFrameAndExtractBool(adapter
, std::string(), script
,
470 bool ExecuteScriptAndExtractString(const internal::ToRenderViewHost
& adapter
,
471 const std::string
& script
,
472 std::string
* result
) {
473 return ExecuteScriptInFrameAndExtractString(adapter
, std::string(), script
,
477 bool ExecuteWebUIResourceTest(
478 const internal::ToRenderViewHost
& adapter
,
479 const std::vector
<int>& js_resource_ids
) {
480 // Inject WebUI test runner script first prior to other scripts required to
481 // run the test as scripts may depend on it being declared.
482 std::vector
<int> ids
;
483 ids
.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST
);
484 ids
.insert(ids
.end(), js_resource_ids
.begin(), js_resource_ids
.end());
487 for (std::vector
<int>::iterator iter
= ids
.begin();
490 ResourceBundle::GetSharedInstance().GetRawDataResource(*iter
)
491 .AppendToString(&script
);
494 if (!content::ExecuteScript(adapter
, script
))
497 content::DOMMessageQueue message_queue
;
498 if (!content::ExecuteScript(adapter
, "runTests()"))
503 if (!message_queue
.WaitForMessage(&message
))
505 } while (message
.compare("\"PENDING\"") == 0);
507 return message
.compare("\"SUCCESS\"") == 0;
510 std::string
GetCookies(BrowserContext
* browser_context
, const GURL
& url
) {
512 base::WaitableEvent
event(true, false);
513 net::URLRequestContextGetter
* context_getter
=
514 browser_context
->GetRequestContext();
516 BrowserThread::PostTask(
517 BrowserThread::IO
, FROM_HERE
,
518 base::Bind(&GetCookiesOnIOThread
, url
,
519 make_scoped_refptr(context_getter
), &event
, &cookies
));
524 bool SetCookie(BrowserContext
* browser_context
,
526 const std::string
& value
) {
528 base::WaitableEvent
event(true, false);
529 net::URLRequestContextGetter
* context_getter
=
530 browser_context
->GetRequestContext();
532 BrowserThread::PostTask(
533 BrowserThread::IO
, FROM_HERE
,
534 base::Bind(&SetCookieOnIOThread
, url
, value
,
535 make_scoped_refptr(context_getter
), &event
, &result
));
540 TitleWatcher::TitleWatcher(WebContents
* web_contents
,
541 const base::string16
& expected_title
)
542 : WebContentsObserver(web_contents
),
543 message_loop_runner_(new MessageLoopRunner
) {
544 EXPECT_TRUE(web_contents
!= NULL
);
545 expected_titles_
.push_back(expected_title
);
548 void TitleWatcher::AlsoWaitForTitle(const base::string16
& expected_title
) {
549 expected_titles_
.push_back(expected_title
);
552 TitleWatcher::~TitleWatcher() {
555 const base::string16
& TitleWatcher::WaitAndGetTitle() {
556 message_loop_runner_
->Run();
557 return observed_title_
;
560 void TitleWatcher::DidStopLoading(RenderViewHost
* render_view_host
) {
561 // When navigating through the history, the restored NavigationEntry's title
562 // will be used. If the entry ends up having the same title after we return
563 // to it, as will usually be the case, then WebContentsObserver::TitleSet
564 // will then be suppressed, since the NavigationEntry's title hasn't changed.
568 void TitleWatcher::TitleWasSet(NavigationEntry
* entry
, bool explicit_set
) {
572 void TitleWatcher::TestTitle() {
573 std::vector
<base::string16
>::const_iterator it
=
574 std::find(expected_titles_
.begin(),
575 expected_titles_
.end(),
576 web_contents()->GetTitle());
577 if (it
== expected_titles_
.end())
580 observed_title_
= *it
;
581 message_loop_runner_
->Quit();
584 WebContentsDestroyedWatcher::WebContentsDestroyedWatcher(
585 WebContents
* web_contents
)
586 : WebContentsObserver(web_contents
),
587 message_loop_runner_(new MessageLoopRunner
) {
588 EXPECT_TRUE(web_contents
!= NULL
);
591 WebContentsDestroyedWatcher::~WebContentsDestroyedWatcher() {
594 void WebContentsDestroyedWatcher::Wait() {
595 message_loop_runner_
->Run();
598 void WebContentsDestroyedWatcher::WebContentsDestroyed(
599 WebContents
* web_contents
) {
600 message_loop_runner_
->Quit();
603 RenderProcessHostWatcher::RenderProcessHostWatcher(
604 RenderProcessHost
* render_process_host
, WatchType type
)
605 : render_process_host_(render_process_host
),
607 message_loop_runner_(new MessageLoopRunner
) {
608 render_process_host_
->AddObserver(this);
611 RenderProcessHostWatcher::RenderProcessHostWatcher(
612 WebContents
* web_contents
, WatchType type
)
613 : render_process_host_(web_contents
->GetRenderProcessHost()),
615 message_loop_runner_(new MessageLoopRunner
) {
616 render_process_host_
->AddObserver(this);
619 RenderProcessHostWatcher::~RenderProcessHostWatcher() {
620 if (render_process_host_
)
621 render_process_host_
->RemoveObserver(this);
624 void RenderProcessHostWatcher::Wait() {
625 message_loop_runner_
->Run();
628 void RenderProcessHostWatcher::RenderProcessExited(
629 RenderProcessHost
* host
,
630 base::ProcessHandle handle
,
631 base::TerminationStatus status
,
633 if (type_
== WATCH_FOR_PROCESS_EXIT
)
634 message_loop_runner_
->Quit();
637 void RenderProcessHostWatcher::RenderProcessHostDestroyed(
638 RenderProcessHost
* host
) {
639 render_process_host_
= NULL
;
640 if (type_
== WATCH_FOR_HOST_DESTRUCTION
)
641 message_loop_runner_
->Quit();
644 DOMMessageQueue::DOMMessageQueue() : waiting_for_message_(false) {
645 registrar_
.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE
,
646 NotificationService::AllSources());
649 DOMMessageQueue::~DOMMessageQueue() {}
651 void DOMMessageQueue::Observe(int type
,
652 const NotificationSource
& source
,
653 const NotificationDetails
& details
) {
654 Details
<DomOperationNotificationDetails
> dom_op_details(details
);
655 Source
<RenderViewHost
> sender(source
);
656 message_queue_
.push(dom_op_details
->json
);
657 if (waiting_for_message_
) {
658 waiting_for_message_
= false;
659 message_loop_runner_
->Quit();
663 void DOMMessageQueue::ClearQueue() {
664 message_queue_
= std::queue
<std::string
>();
667 bool DOMMessageQueue::WaitForMessage(std::string
* message
) {
668 if (message_queue_
.empty()) {
669 waiting_for_message_
= true;
670 // This will be quit when a new message comes in.
671 message_loop_runner_
= new MessageLoopRunner
;
672 message_loop_runner_
->Run();
674 // The queue should not be empty, unless we were quit because of a timeout.
675 if (message_queue_
.empty())
678 *message
= message_queue_
.front();
679 message_queue_
.pop();
683 } // namespace content