blob: c016ffa434a5857444aba50be20a444f30679def [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/public/test/browser_test_utils.h"
#include <stddef.h>
#include <tuple>
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/process/kill.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_timeouts.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/accessibility/accessibility_mode_helper.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/histogram_fetcher.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/base/filename_util.h"
#include "net/cookies/cookie_store.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/python_utils.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/compositor/test/draw_waiter_for_test.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/latency_info.h"
#include "ui/resources/grit/webui_resources.h"
#if defined(USE_AURA)
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "ui/aura/test/window_event_dispatcher_test_api.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#endif // USE_AURA
namespace content {
namespace {
class DOMOperationObserver : public NotificationObserver,
public WebContentsObserver {
public:
explicit DOMOperationObserver(RenderFrameHost* rfh)
: WebContentsObserver(WebContents::FromRenderFrameHost(rfh)),
did_respond_(false) {
registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE,
Source<WebContents>(web_contents()));
message_loop_runner_ = new MessageLoopRunner;
}
void Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) override {
DCHECK(type == NOTIFICATION_DOM_OPERATION_RESPONSE);
Details<std::string> dom_op_result(details);
if (!did_respond_) {
response_ = *dom_op_result.ptr();
did_respond_ = true;
message_loop_runner_->Quit();
}
}
// Overridden from WebContentsObserver:
void RenderProcessGone(base::TerminationStatus status) override {
message_loop_runner_->Quit();
}
bool WaitAndGetResponse(std::string* response) WARN_UNUSED_RESULT {
message_loop_runner_->Run();
*response = response_;
return did_respond_;
}
private:
NotificationRegistrar registrar_;
std::string response_;
bool did_respond_;
scoped_refptr<MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver);
};
class InterstitialObserver : public content::WebContentsObserver {
public:
InterstitialObserver(content::WebContents* web_contents,
const base::Closure& attach_callback,
const base::Closure& detach_callback)
: WebContentsObserver(web_contents),
attach_callback_(attach_callback),
detach_callback_(detach_callback) {
}
~InterstitialObserver() override {}
// WebContentsObserver methods:
void DidAttachInterstitialPage() override { attach_callback_.Run(); }
void DidDetachInterstitialPage() override { detach_callback_.Run(); }
private:
base::Closure attach_callback_;
base::Closure detach_callback_;
DISALLOW_COPY_AND_ASSIGN(InterstitialObserver);
};
// Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
bool ExecuteScriptHelper(RenderFrameHost* render_frame_host,
const std::string& original_script,
std::unique_ptr<base::Value>* result)
WARN_UNUSED_RESULT;
// Executes the passed |original_script| in the frame specified by
// |render_frame_host|. If |result| is not NULL, stores the value that the
// evaluation of the script in |result|. Returns true on success.
bool ExecuteScriptHelper(RenderFrameHost* render_frame_host,
const std::string& original_script,
std::unique_ptr<base::Value>* result) {
// TODO(jcampan): we should make the domAutomationController not require an
// automation id.
std::string script =
"window.domAutomationController.setAutomationId(0);" + original_script;
DOMOperationObserver dom_op_observer(render_frame_host);
render_frame_host->ExecuteJavaScriptWithUserGestureForTests(
base::UTF8ToUTF16(script));
std::string json;
if (!dom_op_observer.WaitAndGetResponse(&json)) {
DLOG(ERROR) << "Cannot communicate with DOMOperationObserver.";
return false;
}
// Nothing more to do for callers that ignore the returned JS value.
if (!result)
return true;
base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
*result = reader.ReadToValue(json);
if (!*result) {
DLOG(ERROR) << reader.GetErrorMessage();
return false;
}
return true;
}
// Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
bool ExecuteScriptInIsolatedWorldHelper(RenderFrameHost* render_frame_host,
const int world_id,
const std::string& original_script,
std::unique_ptr<base::Value>* result)
WARN_UNUSED_RESULT;
bool ExecuteScriptInIsolatedWorldHelper(RenderFrameHost* render_frame_host,
const int world_id,
const std::string& original_script,
std::unique_ptr<base::Value>* result) {
std::string script =
"window.domAutomationController.setAutomationId(0);" + original_script;
DOMOperationObserver dom_op_observer(render_frame_host);
render_frame_host->ExecuteJavaScriptInIsolatedWorld(
base::UTF8ToUTF16(script),
content::RenderFrameHost::JavaScriptResultCallback(), world_id);
std::string json;
if (!dom_op_observer.WaitAndGetResponse(&json)) {
DLOG(ERROR) << "Cannot communicate with DOMOperationObserver.";
return false;
}
// Nothing more to do for callers that ignore the returned JS value.
if (!result)
return true;
base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
*result = reader.ReadToValue(json);
if (!*result) {
DLOG(ERROR) << reader.GetErrorMessage();
return false;
}
return true;
}
void BuildSimpleWebKeyEvent(blink::WebInputEvent::Type type,
ui::DomKey key,
ui::DomCode code,
ui::KeyboardCode key_code,
int modifiers,
NativeWebKeyboardEvent* event) {
event->domKey = key;
event->domCode = static_cast<int>(code);
event->nativeKeyCode = ui::KeycodeConverter::DomCodeToNativeKeycode(code);
event->windowsKeyCode = key_code;
event->setKeyIdentifierFromWindowsKeyCode();
event->type = type;
event->modifiers = modifiers;
event->isSystemKey = false;
event->timeStampSeconds =
(base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
event->skip_in_browser = true;
if (type == blink::WebInputEvent::Char ||
type == blink::WebInputEvent::RawKeyDown) {
event->text[0] = key_code;
event->unmodifiedText[0] = key_code;
}
}
void InjectRawKeyEvent(WebContents* web_contents,
blink::WebInputEvent::Type type,
ui::DomKey key,
ui::DomCode code,
ui::KeyboardCode key_code,
int modifiers) {
NativeWebKeyboardEvent event;
BuildSimpleWebKeyEvent(type, key, code, key_code, modifiers, &event);
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents);
RenderWidgetHostImpl* main_frame_rwh =
web_contents_impl->GetMainFrame()->GetRenderWidgetHost();
web_contents_impl->GetFocusedRenderWidgetHost(main_frame_rwh)
->ForwardKeyboardEvent(event);
}
void GetCookiesCallback(std::string* cookies_out,
base::WaitableEvent* event,
const std::string& cookies) {
*cookies_out = cookies;
event->Signal();
}
void GetCookiesOnIOThread(const GURL& url,
net::URLRequestContextGetter* context_getter,
base::WaitableEvent* event,
std::string* cookies) {
net::CookieStore* cookie_store =
context_getter->GetURLRequestContext()->cookie_store();
cookie_store->GetCookiesWithOptionsAsync(
url, net::CookieOptions(),
base::Bind(&GetCookiesCallback, cookies, event));
}
void SetCookieCallback(bool* result,
base::WaitableEvent* event,
bool success) {
*result = success;
event->Signal();
}
void SetCookieOnIOThread(const GURL& url,
const std::string& value,
net::URLRequestContextGetter* context_getter,
base::WaitableEvent* event,
bool* result) {
net::CookieStore* cookie_store =
context_getter->GetURLRequestContext()->cookie_store();
cookie_store->SetCookieWithOptionsAsync(
url, value, net::CookieOptions(),
base::Bind(&SetCookieCallback, result, event));
}
std::unique_ptr<net::test_server::HttpResponse>
CrossSiteRedirectResponseHandler(const GURL& server_base_url,
const net::test_server::HttpRequest& request) {
net::HttpStatusCode http_status_code;
// Inspect the prefix and extract the remainder of the url into |params|.
size_t length_of_chosen_prefix;
std::string prefix_302("/cross-site/");
std::string prefix_307("/cross-site-307/");
if (base::StartsWith(request.relative_url, prefix_302,
base::CompareCase::SENSITIVE)) {
http_status_code = net::HTTP_MOVED_PERMANENTLY;
length_of_chosen_prefix = prefix_302.length();
} else if (base::StartsWith(request.relative_url, prefix_307,
base::CompareCase::SENSITIVE)) {
http_status_code = net::HTTP_TEMPORARY_REDIRECT;
length_of_chosen_prefix = prefix_307.length();
} else {
// Unrecognized prefix - let somebody else handle this request.
return std::unique_ptr<net::test_server::HttpResponse>();
}
std::string params = request.relative_url.substr(length_of_chosen_prefix);
// A hostname to redirect to must be included in the URL, therefore at least
// one '/' character is expected.
size_t slash = params.find('/');
if (slash == std::string::npos)
return std::unique_ptr<net::test_server::HttpResponse>();
// Replace the host of the URL with the one passed in the URL.
GURL::Replacements replace_host;
replace_host.SetHostStr(base::StringPiece(params).substr(0, slash));
GURL redirect_server = server_base_url.ReplaceComponents(replace_host);
// Append the real part of the path to the new URL.
std::string path = params.substr(slash + 1);
GURL redirect_target(redirect_server.Resolve(path));
DCHECK(redirect_target.is_valid());
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(http_status_code);
http_response->AddCustomHeader("Location", redirect_target.spec());
return std::move(http_response);
}
} // namespace
bool NavigateIframeToURL(WebContents* web_contents,
std::string iframe_id,
const GURL& url) {
std::string script = base::StringPrintf(
"setTimeout(\""
"var iframes = document.getElementById('%s');iframes.src='%s';"
"\",0)",
iframe_id.c_str(), url.spec().c_str());
TestNavigationObserver load_observer(web_contents);
bool result = ExecuteScript(web_contents, script);
load_observer.Wait();
return result;
}
GURL GetFileUrlWithQuery(const base::FilePath& path,
const std::string& query_string) {
GURL url = net::FilePathToFileURL(path);
if (!query_string.empty()) {
GURL::Replacements replacements;
replacements.SetQueryStr(query_string);
return url.ReplaceComponents(replacements);
}
return url;
}
void WaitForLoadStopWithoutSuccessCheck(WebContents* web_contents) {
// In many cases, the load may have finished before we get here. Only wait if
// the tab still has a pending navigation.
if (web_contents->IsLoading()) {
WindowedNotificationObserver load_stop_observer(
NOTIFICATION_LOAD_STOP,
Source<NavigationController>(&web_contents->GetController()));
load_stop_observer.Wait();
}
}
bool WaitForLoadStop(WebContents* web_contents) {
WaitForLoadStopWithoutSuccessCheck(web_contents);
return IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL);
}
bool IsLastCommittedEntryOfPageType(WebContents* web_contents,
content::PageType page_type) {
NavigationEntry* last_entry =
web_contents->GetController().GetLastCommittedEntry();
if (!last_entry)
return false;
return last_entry->GetPageType() == page_type;
}
void CrashTab(WebContents* web_contents) {
RenderProcessHost* rph = web_contents->GetRenderProcessHost();
RenderProcessHostWatcher watcher(
rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
rph->Shutdown(0, false);
watcher.Wait();
}
#if defined(USE_AURA)
bool IsResizeComplete(aura::test::WindowEventDispatcherTestApi* dispatcher_test,
RenderWidgetHostImpl* widget_host) {
return !dispatcher_test->HoldingPointerMoves() &&
!widget_host->resize_ack_pending_for_testing();
}
void WaitForResizeComplete(WebContents* web_contents) {
aura::Window* content = web_contents->GetContentNativeView();
if (!content)
return;
aura::WindowTreeHost* window_host = content->GetHost();
aura::WindowEventDispatcher* dispatcher = window_host->dispatcher();
aura::test::WindowEventDispatcherTestApi dispatcher_test(dispatcher);
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
if (!IsResizeComplete(&dispatcher_test, widget_host)) {
WindowedNotificationObserver resize_observer(
NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
base::Bind(IsResizeComplete, &dispatcher_test, widget_host));
resize_observer.Wait();
}
}
#elif defined(OS_ANDROID)
bool IsResizeComplete(RenderWidgetHostImpl* widget_host) {
return !widget_host->resize_ack_pending_for_testing();
}
void WaitForResizeComplete(WebContents* web_contents) {
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
if (!IsResizeComplete(widget_host)) {
WindowedNotificationObserver resize_observer(
NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
base::Bind(IsResizeComplete, widget_host));
resize_observer.Wait();
}
}
#endif
void SimulateMouseClick(WebContents* web_contents,
int modifiers,
blink::WebMouseEvent::Button button) {
int x = web_contents->GetContainerBounds().width() / 2;
int y = web_contents->GetContainerBounds().height() / 2;
SimulateMouseClickAt(web_contents, modifiers, button, gfx::Point(x, y));
}
void SimulateMouseClickAt(WebContents* web_contents,
int modifiers,
blink::WebMouseEvent::Button button,
const gfx::Point& point) {
blink::WebMouseEvent mouse_event;
mouse_event.type = blink::WebInputEvent::MouseDown;
mouse_event.button = button;
mouse_event.x = point.x();
mouse_event.y = point.y();
mouse_event.modifiers = modifiers;
// Mac needs globalX/globalY for events to plugins.
gfx::Rect offset = web_contents->GetContainerBounds();
mouse_event.globalX = point.x() + offset.x();
mouse_event.globalY = point.y() + offset.y();
mouse_event.clickCount = 1;
web_contents->GetRenderViewHost()->GetWidget()->ForwardMouseEvent(
mouse_event);
mouse_event.type = blink::WebInputEvent::MouseUp;
web_contents->GetRenderViewHost()->GetWidget()->ForwardMouseEvent(
mouse_event);
}
void SimulateMouseEvent(WebContents* web_contents,
blink::WebInputEvent::Type type,
const gfx::Point& point) {
blink::WebMouseEvent mouse_event;
mouse_event.type = type;
mouse_event.x = point.x();
mouse_event.y = point.y();
web_contents->GetRenderViewHost()->GetWidget()->ForwardMouseEvent(
mouse_event);
}
void SimulateMouseWheelEvent(WebContents* web_contents,
const gfx::Point& point,
const gfx::Vector2d& delta) {
blink::WebMouseWheelEvent wheel_event;
wheel_event.type = blink::WebInputEvent::MouseWheel;
wheel_event.x = point.x();
wheel_event.y = point.y();
wheel_event.deltaX = delta.x();
wheel_event.deltaY = delta.y();
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
widget_host->ForwardWheelEvent(wheel_event);
}
void SimulateGestureScrollSequence(WebContents* web_contents,
const gfx::Point& point,
const gfx::Vector2dF& delta) {
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
blink::WebGestureEvent scroll_begin;
scroll_begin.type = blink::WebGestureEvent::GestureScrollBegin;
scroll_begin.sourceDevice = blink::WebGestureDeviceTouchpad;
scroll_begin.x = point.x();
scroll_begin.y = point.y();
widget_host->ForwardGestureEvent(scroll_begin);
blink::WebGestureEvent scroll_update;
scroll_update.type = blink::WebGestureEvent::GestureScrollUpdate;
scroll_update.sourceDevice = blink::WebGestureDeviceTouchpad;
scroll_update.x = point.x();
scroll_update.y = point.y();
scroll_update.data.scrollUpdate.deltaX = delta.x();
scroll_update.data.scrollUpdate.deltaY = delta.y();
scroll_update.data.scrollUpdate.velocityX = 0;
scroll_update.data.scrollUpdate.velocityY = 0;
widget_host->ForwardGestureEvent(scroll_update);
blink::WebGestureEvent scroll_end;
scroll_end.type = blink::WebGestureEvent::GestureScrollEnd;
scroll_end.sourceDevice = blink::WebGestureDeviceTouchpad;
scroll_end.x = point.x() + delta.x();
scroll_end.y = point.y() + delta.y();
widget_host->ForwardGestureEvent(scroll_end);
}
void SimulateGestureFlingSequence(WebContents* web_contents,
const gfx::Point& point,
const gfx::Vector2dF& velocity) {
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
blink::WebGestureEvent scroll_begin;
scroll_begin.type = blink::WebGestureEvent::GestureScrollBegin;
scroll_begin.sourceDevice = blink::WebGestureDeviceTouchpad;
scroll_begin.x = point.x();
scroll_begin.y = point.y();
widget_host->ForwardGestureEvent(scroll_begin);
blink::WebGestureEvent scroll_end;
scroll_end.type = blink::WebGestureEvent::GestureScrollEnd;
scroll_end.sourceDevice = blink::WebGestureDeviceTouchpad;
scroll_end.x = point.x();
scroll_end.y = point.y();
widget_host->ForwardGestureEvent(scroll_end);
blink::WebGestureEvent fling_start;
fling_start.type = blink::WebGestureEvent::GestureFlingStart;
fling_start.sourceDevice = blink::WebGestureDeviceTouchpad;
fling_start.x = point.x();
fling_start.y = point.y();
fling_start.data.flingStart.targetViewport = false;
fling_start.data.flingStart.velocityX = velocity.x();
fling_start.data.flingStart.velocityY = velocity.y();
widget_host->ForwardGestureEvent(fling_start);
}
void SimulateTapAt(WebContents* web_contents, const gfx::Point& point) {
blink::WebGestureEvent tap;
tap.type = blink::WebGestureEvent::GestureTap;
tap.sourceDevice = blink::WebGestureDeviceTouchpad;
tap.x = point.x();
tap.y = point.y();
tap.modifiers = blink::WebInputEvent::ControlKey;
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
widget_host->ForwardGestureEvent(tap);
}
void SimulateTapWithModifiersAt(WebContents* web_contents,
unsigned modifiers,
const gfx::Point& point) {
blink::WebGestureEvent tap;
tap.type = blink::WebGestureEvent::GestureTap;
tap.sourceDevice = blink::WebGestureDeviceTouchpad;
tap.x = point.x();
tap.y = point.y();
tap.modifiers = modifiers;
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
widget_host->ForwardGestureEvent(tap);
}
#if defined(USE_AURA)
void SimulateTouchPressAt(WebContents* web_contents, const gfx::Point& point) {
ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, point, 0, base::TimeDelta());
static_cast<RenderWidgetHostViewAura*>(
web_contents->GetRenderWidgetHostView())
->OnTouchEvent(&touch);
}
#endif
void SimulateKeyPress(WebContents* web_contents,
ui::DomKey key,
ui::DomCode code,
ui::KeyboardCode key_code,
bool control,
bool shift,
bool alt,
bool command) {
int modifiers = 0;
// The order of these key down events shouldn't matter for our simulation.
// For our simulation we can use either the left keys or the right keys.
if (control) {
modifiers |= blink::WebInputEvent::ControlKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::RawKeyDown,
ui::DomKey::CONTROL, ui::DomCode::CONTROL_LEFT,
ui::VKEY_CONTROL, modifiers);
}
if (shift) {
modifiers |= blink::WebInputEvent::ShiftKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::RawKeyDown,
ui::DomKey::SHIFT, ui::DomCode::SHIFT_LEFT,
ui::VKEY_SHIFT, modifiers);
}
if (alt) {
modifiers |= blink::WebInputEvent::AltKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::RawKeyDown,
ui::DomKey::ALT, ui::DomCode::ALT_LEFT, ui::VKEY_MENU,
modifiers);
}
if (command) {
modifiers |= blink::WebInputEvent::MetaKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::RawKeyDown,
ui::DomKey::META, ui::DomCode::META_LEFT,
ui::VKEY_COMMAND, modifiers);
}
InjectRawKeyEvent(web_contents, blink::WebInputEvent::RawKeyDown, key, code,
key_code, modifiers);
InjectRawKeyEvent(web_contents, blink::WebInputEvent::Char, key, code,
key_code, modifiers);
InjectRawKeyEvent(web_contents, blink::WebInputEvent::KeyUp, key, code,
key_code, modifiers);
// The order of these key releases shouldn't matter for our simulation.
if (control) {
modifiers &= ~blink::WebInputEvent::ControlKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::KeyUp,
ui::DomKey::CONTROL, ui::DomCode::CONTROL_LEFT,
ui::VKEY_CONTROL, modifiers);
}
if (shift) {
modifiers &= ~blink::WebInputEvent::ShiftKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::KeyUp,
ui::DomKey::SHIFT, ui::DomCode::SHIFT_LEFT,
ui::VKEY_SHIFT, modifiers);
}
if (alt) {
modifiers &= ~blink::WebInputEvent::AltKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::KeyUp,
ui::DomKey::ALT, ui::DomCode::ALT_LEFT, ui::VKEY_MENU,
modifiers);
}
if (command) {
modifiers &= ~blink::WebInputEvent::MetaKey;
InjectRawKeyEvent(web_contents, blink::WebInputEvent::KeyUp,
ui::DomKey::META, ui::DomCode::META_LEFT,
ui::VKEY_COMMAND, modifiers);
}
ASSERT_EQ(modifiers, 0);
}
ToRenderFrameHost::ToRenderFrameHost(WebContents* web_contents)
: render_frame_host_(web_contents->GetMainFrame()) {
}
ToRenderFrameHost::ToRenderFrameHost(RenderViewHost* render_view_host)
: render_frame_host_(render_view_host->GetMainFrame()) {
}
ToRenderFrameHost::ToRenderFrameHost(RenderFrameHost* render_frame_host)
: render_frame_host_(render_frame_host) {
}
bool ExecuteScript(const ToRenderFrameHost& adapter,
const std::string& script) {
std::string new_script =
script + ";window.domAutomationController.send(0);";
return ExecuteScriptHelper(adapter.render_frame_host(), new_script, NULL);
}
bool ExecuteScriptAndExtractDouble(const ToRenderFrameHost& adapter,
const std::string& script, double* result) {
DCHECK(result);
std::unique_ptr<base::Value> value;
if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
!value.get()) {
return false;
}
return value->GetAsDouble(result);
}
bool ExecuteScriptAndExtractInt(const ToRenderFrameHost& adapter,
const std::string& script, int* result) {
DCHECK(result);
std::unique_ptr<base::Value> value;
if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
!value.get()) {
return false;
}
return value->GetAsInteger(result);
}
bool ExecuteScriptAndExtractBool(const ToRenderFrameHost& adapter,
const std::string& script, bool* result) {
DCHECK(result);
std::unique_ptr<base::Value> value;
if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
!value.get()) {
return false;
}
return value->GetAsBoolean(result);
}
bool ExecuteScriptInIsolatedWorldAndExtractBool(
const ToRenderFrameHost& adapter,
const int world_id,
const std::string& script,
bool* result) {
DCHECK(result);
std::unique_ptr<base::Value> value;
if (!ExecuteScriptInIsolatedWorldHelper(adapter.render_frame_host(), world_id,
script, &value) ||
!value.get()) {
return false;
}
return value->GetAsBoolean(result);
}
bool ExecuteScriptAndExtractString(const ToRenderFrameHost& adapter,
const std::string& script,
std::string* result) {
DCHECK(result);
std::unique_ptr<base::Value> value;
if (!ExecuteScriptHelper(adapter.render_frame_host(), script, &value) ||
!value.get()) {
return false;
}
return value->GetAsString(result);
}
namespace {
void AddToSetIfFrameMatchesPredicate(
std::set<RenderFrameHost*>* frame_set,
const base::Callback<bool(RenderFrameHost*)>& predicate,
RenderFrameHost* host) {
if (predicate.Run(host))
frame_set->insert(host);
}
}
RenderFrameHost* FrameMatchingPredicate(
WebContents* web_contents,
const base::Callback<bool(RenderFrameHost*)>& predicate) {
std::set<RenderFrameHost*> frame_set;
web_contents->ForEachFrame(
base::Bind(&AddToSetIfFrameMatchesPredicate, &frame_set, predicate));
DCHECK_EQ(1U, frame_set.size());
return *frame_set.begin();
}
bool FrameMatchesName(const std::string& name, RenderFrameHost* frame) {
return frame->GetFrameName() == name;
}
bool FrameIsChildOfMainFrame(RenderFrameHost* frame) {
return frame->GetParent() && !frame->GetParent()->GetParent();
}
bool FrameHasSourceUrl(const GURL& url, RenderFrameHost* frame) {
return frame->GetLastCommittedURL() == url;
}
RenderFrameHost* ChildFrameAt(RenderFrameHost* frame, size_t index) {
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(frame);
if (index >= rfh->frame_tree_node()->child_count())
return nullptr;
return rfh->frame_tree_node()->child_at(index)->current_frame_host();
}
bool ExecuteWebUIResourceTest(WebContents* web_contents,
const std::vector<int>& js_resource_ids) {
// Inject WebUI test runner script first prior to other scripts required to
// run the test as scripts may depend on it being declared.
std::vector<int> ids;
ids.push_back(IDR_WEBUI_JS_WEBUI_RESOURCE_TEST);
ids.insert(ids.end(), js_resource_ids.begin(), js_resource_ids.end());
std::string script;
for (std::vector<int>::iterator iter = ids.begin();
iter != ids.end();
++iter) {
scoped_refptr<base::RefCountedMemory> resource =
ResourceBundle::GetSharedInstance().LoadDataResourceBytes(*iter);
script.append(resource->front_as<char>(), resource->size());
script.append("\n");
}
if (!ExecuteScript(web_contents, script))
return false;
DOMMessageQueue message_queue;
if (!ExecuteScript(web_contents, "runTests()"))
return false;
std::string message;
do {
if (!message_queue.WaitForMessage(&message))
return false;
} while (message.compare("\"PENDING\"") == 0);
return message.compare("\"SUCCESS\"") == 0;
}
std::string GetCookies(BrowserContext* browser_context, const GURL& url) {
std::string cookies;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
net::URLRequestContextGetter* context_getter =
BrowserContext::GetDefaultStoragePartition(browser_context)->
GetURLRequestContext();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&GetCookiesOnIOThread, url, base::RetainedRef(context_getter),
&event, &cookies));
event.Wait();
return cookies;
}
bool SetCookie(BrowserContext* browser_context,
const GURL& url,
const std::string& value) {
bool result = false;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
net::URLRequestContextGetter* context_getter =
BrowserContext::GetDefaultStoragePartition(browser_context)->
GetURLRequestContext();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&SetCookieOnIOThread, url, value,
base::RetainedRef(context_getter), &event, &result));
event.Wait();
return result;
}
void FetchHistogramsFromChildProcesses() {
scoped_refptr<content::MessageLoopRunner> runner = new MessageLoopRunner;
FetchHistogramsAsynchronously(
base::MessageLoop::current(),
runner->QuitClosure(),
// If this call times out, it means that a child process is not
// responding, which is something we should not ignore. The timeout is
// set to be longer than the normal browser test timeout so that it will
// be prempted by the normal timeout.
TestTimeouts::action_max_timeout());
runner->Run();
}
void SetupCrossSiteRedirector(net::EmbeddedTestServer* embedded_test_server) {
embedded_test_server->RegisterRequestHandler(
base::Bind(&CrossSiteRedirectResponseHandler,
embedded_test_server->base_url()));
}
void WaitForInterstitialAttach(content::WebContents* web_contents) {
if (web_contents->ShowingInterstitialPage())
return;
scoped_refptr<content::MessageLoopRunner> loop_runner(
new content::MessageLoopRunner);
InterstitialObserver observer(web_contents,
loop_runner->QuitClosure(),
base::Closure());
loop_runner->Run();
}
void WaitForInterstitialDetach(content::WebContents* web_contents) {
RunTaskAndWaitForInterstitialDetach(web_contents, base::Closure());
}
void RunTaskAndWaitForInterstitialDetach(content::WebContents* web_contents,
const base::Closure& task) {
if (!web_contents || !web_contents->ShowingInterstitialPage())
return;
scoped_refptr<content::MessageLoopRunner> loop_runner(
new content::MessageLoopRunner);
InterstitialObserver observer(web_contents,
base::Closure(),
loop_runner->QuitClosure());
if (!task.is_null())
task.Run();
// At this point, web_contents may have been deleted.
loop_runner->Run();
}
bool WaitForRenderFrameReady(RenderFrameHost* rfh) {
if (!rfh)
return false;
std::string result;
EXPECT_TRUE(
content::ExecuteScriptAndExtractString(
rfh,
"(function() {"
" var done = false;"
" function checkState() {"
" if (!done && document.readyState == 'complete') {"
" done = true;"
" window.domAutomationController.send('pageLoadComplete');"
" }"
" }"
" checkState();"
" document.addEventListener('readystatechange', checkState);"
"})();",
&result));
return result == "pageLoadComplete";
}
void EnableAccessibilityForWebContents(WebContents* web_contents) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents);
web_contents_impl->SetAccessibilityMode(AccessibilityModeComplete);
}
void WaitForAccessibilityFocusChange() {
scoped_refptr<content::MessageLoopRunner> loop_runner(
new content::MessageLoopRunner);
BrowserAccessibilityManager::SetFocusChangeCallbackForTesting(
loop_runner->QuitClosure());
loop_runner->Run();
}
ui::AXNodeData GetFocusedAccessibilityNodeInfo(WebContents* web_contents) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents);
BrowserAccessibilityManager* manager =
web_contents_impl->GetRootBrowserAccessibilityManager();
if (!manager)
return ui::AXNodeData();
BrowserAccessibility* focused_node = manager->GetFocus();
return focused_node->GetData();
}
TitleWatcher::TitleWatcher(WebContents* web_contents,
const base::string16& expected_title)
: WebContentsObserver(web_contents),
message_loop_runner_(new MessageLoopRunner) {
EXPECT_TRUE(web_contents != NULL);
expected_titles_.push_back(expected_title);
}
void TitleWatcher::AlsoWaitForTitle(const base::string16& expected_title) {
expected_titles_.push_back(expected_title);
}
TitleWatcher::~TitleWatcher() {
}
const base::string16& TitleWatcher::WaitAndGetTitle() {
TestTitle();
message_loop_runner_->Run();
return observed_title_;
}
void TitleWatcher::DidStopLoading() {
// When navigating through the history, the restored NavigationEntry's title
// will be used. If the entry ends up having the same title after we return
// to it, as will usually be the case, then WebContentsObserver::TitleSet
// will then be suppressed, since the NavigationEntry's title hasn't changed.
TestTitle();
}
void TitleWatcher::TitleWasSet(NavigationEntry* entry, bool explicit_set) {
TestTitle();
}
void TitleWatcher::TestTitle() {
std::vector<base::string16>::const_iterator it =
std::find(expected_titles_.begin(),
expected_titles_.end(),
web_contents()->GetTitle());
if (it == expected_titles_.end())
return;
observed_title_ = *it;
message_loop_runner_->Quit();
}
RenderProcessHostWatcher::RenderProcessHostWatcher(
RenderProcessHost* render_process_host, WatchType type)
: render_process_host_(render_process_host),
type_(type),
did_exit_normally_(true),
message_loop_runner_(new MessageLoopRunner) {
render_process_host_->AddObserver(this);
}
RenderProcessHostWatcher::RenderProcessHostWatcher(
WebContents* web_contents, WatchType type)
: render_process_host_(web_contents->GetRenderProcessHost()),
type_(type),
did_exit_normally_(true),
message_loop_runner_(new MessageLoopRunner) {
render_process_host_->AddObserver(this);
}
RenderProcessHostWatcher::~RenderProcessHostWatcher() {
if (render_process_host_)
render_process_host_->RemoveObserver(this);
}
void RenderProcessHostWatcher::Wait() {
message_loop_runner_->Run();
}
void RenderProcessHostWatcher::RenderProcessExited(
RenderProcessHost* host,
base::TerminationStatus status,
int exit_code) {
did_exit_normally_ = status == base::TERMINATION_STATUS_NORMAL_TERMINATION;
if (type_ == WATCH_FOR_PROCESS_EXIT)
message_loop_runner_->Quit();
}
void RenderProcessHostWatcher::RenderProcessHostDestroyed(
RenderProcessHost* host) {
render_process_host_ = NULL;
if (type_ == WATCH_FOR_HOST_DESTRUCTION)
message_loop_runner_->Quit();
}
DOMMessageQueue::DOMMessageQueue() {
registrar_.Add(this, NOTIFICATION_DOM_OPERATION_RESPONSE,
NotificationService::AllSources());
}
DOMMessageQueue::~DOMMessageQueue() {}
void DOMMessageQueue::Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) {
Details<std::string> dom_op_result(details);
message_queue_.push(*dom_op_result.ptr());
if (message_loop_runner_.get())
message_loop_runner_->Quit();
}
void DOMMessageQueue::ClearQueue() {
message_queue_ = std::queue<std::string>();
}
bool DOMMessageQueue::WaitForMessage(std::string* message) {
DCHECK(message);
if (message_queue_.empty()) {
// This will be quit when a new message comes in.
message_loop_runner_ = new MessageLoopRunner;
message_loop_runner_->Run();
}
// The queue should not be empty, unless we were quit because of a timeout.
if (message_queue_.empty())
return false;
*message = message_queue_.front();
message_queue_.pop();
return true;
}
class WebContentsAddedObserver::RenderViewCreatedObserver
: public WebContentsObserver {
public:
explicit RenderViewCreatedObserver(WebContents* web_contents)
: WebContentsObserver(web_contents),
render_view_created_called_(false),
main_frame_created_called_(false) {}
// WebContentsObserver:
void RenderViewCreated(RenderViewHost* rvh) override {
render_view_created_called_ = true;
}
void RenderFrameCreated(RenderFrameHost* rfh) override {
if (rfh == web_contents()->GetMainFrame())
main_frame_created_called_ = true;
}
bool render_view_created_called_;
bool main_frame_created_called_;
};
WebContentsAddedObserver::WebContentsAddedObserver()
: web_contents_created_callback_(
base::Bind(&WebContentsAddedObserver::WebContentsCreated,
base::Unretained(this))),
web_contents_(NULL) {
WebContentsImpl::FriendZone::AddCreatedCallbackForTesting(
web_contents_created_callback_);
}
WebContentsAddedObserver::~WebContentsAddedObserver() {
WebContentsImpl::FriendZone::RemoveCreatedCallbackForTesting(
web_contents_created_callback_);
}
void WebContentsAddedObserver::WebContentsCreated(WebContents* web_contents) {
DCHECK(!web_contents_);
web_contents_ = web_contents;
child_observer_.reset(new RenderViewCreatedObserver(web_contents));
if (runner_.get())
runner_->QuitClosure().Run();
}
WebContents* WebContentsAddedObserver::GetWebContents() {
if (web_contents_)
return web_contents_;
runner_ = new MessageLoopRunner();
runner_->Run();
return web_contents_;
}
bool WebContentsAddedObserver::RenderViewCreatedCalled() {
if (child_observer_) {
return child_observer_->render_view_created_called_ &&
child_observer_->main_frame_created_called_;
}
return false;
}
bool RequestFrame(WebContents* web_contents) {
DCHECK(web_contents);
return RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget())
->ScheduleComposite();
}
FrameWatcher::FrameWatcher() : MessageFilter(), frames_to_wait_(0) {}
FrameWatcher::~FrameWatcher() {
}
void FrameWatcher::ReceivedFrameSwap(cc::CompositorFrameMetadata metadata) {
--frames_to_wait_;
last_metadata_ = metadata;
if (frames_to_wait_ == 0)
quit_.Run();
}
bool FrameWatcher::OnMessageReceived(const IPC::Message& message) {
if (message.type() == ViewHostMsg_SwapCompositorFrame::ID) {
ViewHostMsg_SwapCompositorFrame::Param param;
if (!ViewHostMsg_SwapCompositorFrame::Read(&message, &param))
return false;
std::unique_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame);
std::get<1>(param).AssignTo(frame.get());
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&FrameWatcher::ReceivedFrameSwap, this, frame->metadata));
}
return false;
}
void FrameWatcher::AttachTo(WebContents* web_contents) {
DCHECK(web_contents);
RenderWidgetHostImpl* widget_host = RenderWidgetHostImpl::From(
web_contents->GetRenderViewHost()->GetWidget());
widget_host->GetProcess()->GetChannel()->AddFilter(this);
}
void FrameWatcher::WaitFrames(int frames_to_wait) {
if (frames_to_wait <= 0)
return;
base::RunLoop run_loop;
base::AutoReset<base::Closure> reset_quit(&quit_, run_loop.QuitClosure());
base::AutoReset<int> reset_frames_to_wait(&frames_to_wait_, frames_to_wait);
run_loop.Run();
}
const cc::CompositorFrameMetadata& FrameWatcher::LastMetadata() {
return last_metadata_;
}
MainThreadFrameObserver::MainThreadFrameObserver(
RenderWidgetHost* render_widget_host)
: render_widget_host_(render_widget_host),
routing_id_(render_widget_host_->GetProcess()->GetNextRoutingID()) {
// TODO(lfg): We should look into adding a way to observe RenderWidgetHost
// messages similarly to what WebContentsObserver can do with RFH and RVW.
render_widget_host_->GetProcess()->AddRoute(routing_id_, this);
}
MainThreadFrameObserver::~MainThreadFrameObserver() {
render_widget_host_->GetProcess()->RemoveRoute(routing_id_);
}
void MainThreadFrameObserver::Wait() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
render_widget_host_->Send(new ViewMsg_WaitForNextFrameForTests(
render_widget_host_->GetRoutingID(), routing_id_));
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
run_loop_.reset(nullptr);
}
void MainThreadFrameObserver::Quit() {
if (run_loop_)
run_loop_->Quit();
}
bool MainThreadFrameObserver::OnMessageReceived(const IPC::Message& msg) {
if (msg.type() == ViewHostMsg_WaitForNextFrameForTests_ACK::ID &&
msg.routing_id() == routing_id_) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&MainThreadFrameObserver::Quit, base::Unretained(this)));
}
return true;
}
InputMsgWatcher::InputMsgWatcher(RenderWidgetHost* render_widget_host,
blink::WebInputEvent::Type type)
: BrowserMessageFilter(InputMsgStart),
wait_for_type_(type),
ack_result_(INPUT_EVENT_ACK_STATE_UNKNOWN) {
render_widget_host->GetProcess()->AddFilter(this);
}
InputMsgWatcher::~InputMsgWatcher() {}
void InputMsgWatcher::ReceivedAck(blink::WebInputEvent::Type ack_type,
uint32_t ack_state) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (wait_for_type_ == ack_type) {
ack_result_ = ack_state;
if (!quit_.is_null())
quit_.Run();
}
}
bool InputMsgWatcher::OnMessageReceived(const IPC::Message& message) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (message.type() == InputHostMsg_HandleInputEvent_ACK::ID) {
InputHostMsg_HandleInputEvent_ACK::Param params;
InputHostMsg_HandleInputEvent_ACK::Read(&message, &params);
blink::WebInputEvent::Type ack_type = std::get<0>(params).type;
InputEventAckState ack_state = std::get<0>(params).state;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&InputMsgWatcher::ReceivedAck, this, ack_type, ack_state));
}
return false;
}
uint32_t InputMsgWatcher::WaitForAck() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (ack_result_ != INPUT_EVENT_ACK_STATE_UNKNOWN)
return ack_result_;
base::RunLoop run_loop;
base::AutoReset<base::Closure> reset_quit(&quit_, run_loop.QuitClosure());
run_loop.Run();
return ack_result_;
}
} // namespace content