blob: 8f166cc4f6fdc957bee571d50844757c540f835a [file] [log] [blame]
// Copyright (c) 2013 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 "chrome/test/chromedriver/server/http_handler.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h" // For CHECK macros.
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/test/chromedriver/alert_commands.h"
#include "chrome/test/chromedriver/chrome/adb_impl.h"
#include "chrome/test/chromedriver/chrome/device_manager.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/net/port_server.h"
#include "chrome/test/chromedriver/net/url_request_context_getter.h"
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/session_thread_map.h"
#include "chrome/test/chromedriver/util.h"
#include "chrome/test/chromedriver/version.h"
#include "net/server/http_server_request_info.h"
#include "net/server/http_server_response_info.h"
#include "url/url_util.h"
#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#endif
namespace {
const char kLocalStorage[] = "localStorage";
const char kSessionStorage[] = "sessionStorage";
const char kShutdownPath[] = "shutdown";
} // namespace
CommandMapping::CommandMapping(HttpMethod method,
const std::string& path_pattern,
const Command& command)
: method(method), path_pattern(path_pattern), command(command) {}
CommandMapping::CommandMapping(const CommandMapping& other) = default;
CommandMapping::~CommandMapping() {}
HttpHandler::HttpHandler(const std::string& url_base)
: url_base_(url_base),
received_shutdown_(false),
command_map_(new CommandMap()),
weak_ptr_factory_(this) {}
HttpHandler::HttpHandler(
const base::Closure& quit_func,
const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
const std::string& url_base,
int adb_port,
std::unique_ptr<PortServer> port_server)
: quit_func_(quit_func),
url_base_(url_base),
received_shutdown_(false),
weak_ptr_factory_(this) {
#if defined(OS_MACOSX)
base::mac::ScopedNSAutoreleasePool autorelease_pool;
#endif
context_getter_ = new URLRequestContextGetter(io_task_runner);
socket_factory_ = CreateSyncWebSocketFactory(context_getter_.get());
adb_.reset(new AdbImpl(io_task_runner, adb_port));
device_manager_.reset(new DeviceManager(adb_.get()));
port_server_ = std::move(port_server);
port_manager_.reset(new PortManager(12000, 13000));
CommandMapping commands[] = {
CommandMapping(
kPost, internal::kNewSessionPathPattern,
base::Bind(
&ExecuteCreateSession, &session_thread_map_,
WrapToCommand(
"InitSession",
base::Bind(&ExecuteInitSession,
InitSessionParams(context_getter_, socket_factory_,
device_manager_.get(),
port_server_.get(),
port_manager_.get()))))),
CommandMapping(
kGet, "sessions",
base::Bind(&ExecuteGetSessions,
WrapToCommand("GetSessions",
base::Bind(&ExecuteGetSessionCapabilities)),
&session_thread_map_)),
CommandMapping(kGet, "session/:sessionId",
WrapToCommand("GetSessionCapabilities",
base::Bind(&ExecuteGetSessionCapabilities))),
CommandMapping(kDelete, "session/:sessionId",
base::Bind(&ExecuteSessionCommand, &session_thread_map_,
"Quit", base::Bind(&ExecuteQuit, false), true)),
CommandMapping(kGet, "session/:sessionId/window",
WrapToCommand("GetWindow",
base::Bind(&ExecuteGetCurrentWindowHandle))),
CommandMapping(kGet, "session/:sessionId/window_handle",
WrapToCommand("GetWindow",
base::Bind(&ExecuteGetCurrentWindowHandle))),
CommandMapping(
kGet, "session/:sessionId/window/handles",
WrapToCommand("GetWindows", base::Bind(&ExecuteGetWindowHandles))),
CommandMapping(
kGet, "session/:sessionId/window_handles",
WrapToCommand("GetWindows", base::Bind(&ExecuteGetWindowHandles))),
CommandMapping(kPost, "session/:sessionId/url",
WrapToCommand("Navigate", base::Bind(&ExecuteGet))),
CommandMapping(kPost, "session/:sessionId/chromium/launch_app",
WrapToCommand("LaunchApp", base::Bind(&ExecuteLaunchApp))),
CommandMapping(kGet, "session/:sessionId/alert",
WrapToCommand("IsAlertOpen",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteGetAlert)))),
CommandMapping(
kPost, "session/:sessionId/alert/dismiss",
WrapToCommand("DismissAlert",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteDismissAlert)))),
CommandMapping(
kPost, "session/:sessionId/dismiss_alert",
WrapToCommand("DismissAlert",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteDismissAlert)))),
CommandMapping(
kPost, "session/:sessionId/alert/accept",
WrapToCommand("AcceptAlert",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteAcceptAlert)))),
CommandMapping(
kPost, "session/:sessionId/accept_alert",
WrapToCommand("AcceptAlert",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteAcceptAlert)))),
CommandMapping(
kGet, "session/:sessionId/alert/text",
WrapToCommand("GetAlertMessage",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteGetAlertText)))),
CommandMapping(
kGet, "session/:sessionId/alert_text",
WrapToCommand("GetAlertMessage",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteGetAlertText)))),
CommandMapping(
kPost, "session/:sessionId/alert/text",
WrapToCommand("SetAlertPrompt",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteSetAlertText)))),
CommandMapping(
kPost, "session/:sessionId/alert_text",
WrapToCommand("SetAlertPrompt",
base::Bind(&ExecuteAlertCommand,
base::Bind(&ExecuteSetAlertText)))),
CommandMapping(kPost, "session/:sessionId/forward",
WrapToCommand("GoForward", base::Bind(&ExecuteGoForward))),
CommandMapping(kPost, "session/:sessionId/back",
WrapToCommand("GoBack", base::Bind(&ExecuteGoBack))),
CommandMapping(kPost, "session/:sessionId/refresh",
WrapToCommand("Refresh", base::Bind(&ExecuteRefresh))),
// still support old endpoints for executing scripts
CommandMapping(
kPost, "session/:sessionId/execute",
WrapToCommand("ExecuteScript", base::Bind(&ExecuteExecuteScript))),
CommandMapping(kPost, "session/:sessionId/execute_async",
WrapToCommand("ExecuteAsyncScript",
base::Bind(&ExecuteExecuteAsyncScript))),
CommandMapping(
kPost, "session/:sessionId/execute/sync",
WrapToCommand("ExecuteScript", base::Bind(&ExecuteExecuteScript))),
CommandMapping(kPost, "session/:sessionId/execute/async",
WrapToCommand("ExecuteAsyncScript",
base::Bind(&ExecuteExecuteAsyncScript))),
CommandMapping(
kGet, "session/:sessionId/url",
WrapToCommand("GetUrl", base::Bind(&ExecuteGetCurrentUrl))),
CommandMapping(kGet, "session/:sessionId/title",
WrapToCommand("GetTitle", base::Bind(&ExecuteGetTitle))),
CommandMapping(
kGet, "session/:sessionId/source",
WrapToCommand("GetSource", base::Bind(&ExecuteGetPageSource))),
CommandMapping(
kGet, "session/:sessionId/screenshot",
WrapToCommand("Screenshot", base::Bind(&ExecuteScreenshot))),
CommandMapping(
kGet, "session/:sessionId/chromium/heap_snapshot",
WrapToCommand("HeapSnapshot", base::Bind(&ExecuteTakeHeapSnapshot))),
CommandMapping(
kPost, "session/:sessionId/visible",
WrapToCommand("Visible", base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kGet, "session/:sessionId/visible",
WrapToCommand("Visible", base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kPost, "session/:sessionId/element",
WrapToCommand("FindElement", base::Bind(&ExecuteFindElement, 50))),
CommandMapping(
kPost, "session/:sessionId/elements",
WrapToCommand("FindElements", base::Bind(&ExecuteFindElements, 50))),
CommandMapping(kGet, "session/:sessionId/element/active",
WrapToCommand("GetActiveElement",
base::Bind(&ExecuteGetActiveElement))),
CommandMapping(kPost, "session/:sessionId/element/active",
WrapToCommand("GetActiveElement",
base::Bind(&ExecuteGetActiveElement))),
CommandMapping(kPost, "session/:sessionId/element/:id/element",
WrapToCommand("FindChildElement",
base::Bind(&ExecuteFindChildElement, 50))),
CommandMapping(kPost, "session/:sessionId/element/:id/elements",
WrapToCommand("FindChildElements",
base::Bind(&ExecuteFindChildElements, 50))),
CommandMapping(
kPost, "session/:sessionId/element/:id/click",
WrapToCommand("ClickElement", base::Bind(&ExecuteClickElement))),
CommandMapping(
kPost, "session/:sessionId/element/:id/clear",
WrapToCommand("ClearElement", base::Bind(&ExecuteClearElement))),
CommandMapping(
kPost, "session/:sessionId/element/:id/submit",
WrapToCommand("SubmitElement", base::Bind(&ExecuteSubmitElement))),
CommandMapping(
kGet, "session/:sessionId/element/:id/text",
WrapToCommand("GetElementText", base::Bind(&ExecuteGetElementText))),
CommandMapping(
kPost, "session/:sessionId/element/:id/value",
WrapToCommand("TypeElement", base::Bind(&ExecuteSendKeysToElement))),
CommandMapping(
kPost, "session/:sessionId/file",
WrapToCommand("UploadFile", base::Bind(&ExecuteUploadFile))),
CommandMapping(kGet, "session/:sessionId/element/:id/value",
WrapToCommand("GetElementValue",
base::Bind(&ExecuteGetElementValue))),
CommandMapping(kGet, "session/:sessionId/element/:id/name",
WrapToCommand("GetElementTagName",
base::Bind(&ExecuteGetElementTagName))),
CommandMapping(kGet, "session/:sessionId/element/:id/selected",
WrapToCommand("IsElementSelected",
base::Bind(&ExecuteIsElementSelected))),
CommandMapping(kGet, "session/:sessionId/element/:id/enabled",
WrapToCommand("IsElementEnabled",
base::Bind(&ExecuteIsElementEnabled))),
CommandMapping(kGet, "session/:sessionId/element/:id/displayed",
WrapToCommand("IsElementDisplayed",
base::Bind(&ExecuteIsElementDisplayed))),
CommandMapping(
kPost, "session/:sessionId/element/:id/hover",
WrapToCommand("HoverElement", base::Bind(&ExecuteHoverOverElement))),
CommandMapping(kGet, "session/:sessionId/element/:id/location",
WrapToCommand("GetElementLocation",
base::Bind(&ExecuteGetElementLocation))),
CommandMapping(
kGet, "session/:sessionId/element/:id/location_in_view",
WrapToCommand(
"GetElementLocationInView",
base::Bind(&ExecuteGetElementLocationOnceScrolledIntoView))),
CommandMapping(
kGet, "session/:sessionId/element/:id/size",
WrapToCommand("GetElementSize", base::Bind(&ExecuteGetElementSize))),
CommandMapping(kGet, "session/:sessionId/element/:id/attribute/:name",
WrapToCommand("GetElementAttribute",
base::Bind(&ExecuteGetElementAttribute))),
CommandMapping(
kGet, "session/:sessionId/element/:id/equals/:other",
WrapToCommand("IsElementEqual", base::Bind(&ExecuteElementEquals))),
CommandMapping(
kGet, "session/:sessionId/cookie",
WrapToCommand("GetCookies", base::Bind(&ExecuteGetCookies))),
CommandMapping(
kGet, "session/:sessionId/cookie/:name",
WrapToCommand("GetNamedCookie", base::Bind(&ExecuteGetNamedCookie))),
CommandMapping(kPost, "session/:sessionId/cookie",
WrapToCommand("AddCookie", base::Bind(&ExecuteAddCookie))),
CommandMapping(kDelete, "session/:sessionId/cookie",
WrapToCommand("DeleteAllCookies",
base::Bind(&ExecuteDeleteAllCookies))),
CommandMapping(
kDelete, "session/:sessionId/cookie/:name",
WrapToCommand("DeleteCookie", base::Bind(&ExecuteDeleteCookie))),
CommandMapping(
kPost, "session/:sessionId/frame",
WrapToCommand("SwitchToFrame", base::Bind(&ExecuteSwitchToFrame))),
CommandMapping(kPost, "session/:sessionId/frame/parent",
WrapToCommand("SwitchToParentFrame",
base::Bind(&ExecuteSwitchToParentFrame))),
CommandMapping(
kPost, "session/:sessionId/window",
WrapToCommand("SwitchToWindow", base::Bind(&ExecuteSwitchToWindow))),
CommandMapping(
kGet, "session/:sessionId/window/rect",
WrapToCommand("GetWindowRect", base::Bind(&ExecuteGetWindowRect))),
CommandMapping(
kGet, "session/:sessionId/window/:windowHandle/size",
WrapToCommand("GetWindowSize", base::Bind(&ExecuteGetWindowSize))),
CommandMapping(kGet, "session/:sessionId/window/:windowHandle/position",
WrapToCommand("GetWindowPosition",
base::Bind(&ExecuteGetWindowPosition))),
CommandMapping(
kPost, "session/:sessionId/window/:windowHandle/size",
WrapToCommand("SetWindowSize", base::Bind(&ExecuteSetWindowSize))),
CommandMapping(kPost, "session/:sessionId/window/:windowHandle/position",
WrapToCommand("SetWindowPosition",
base::Bind(&ExecuteSetWindowPosition))),
CommandMapping(
kPost, "session/:sessionId/window/:windowHandle/maximize",
WrapToCommand("MaximizeWindow", base::Bind(&ExecuteMaximizeWindow))),
CommandMapping(kPost, "session/:sessionId/window/fullscreen",
WrapToCommand("FullscreenWindow",
base::Bind(&ExecuteFullScreenWindow))),
CommandMapping(kDelete, "session/:sessionId/window",
WrapToCommand("CloseWindow", base::Bind(&ExecuteClose))),
CommandMapping(
kPost, "session/:sessionId/element/:id/drag",
WrapToCommand("Drag", base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kGet, "session/:sessionId/element/:id/css/:propertyName",
WrapToCommand("GetElementCSSProperty",
base::Bind(&ExecuteGetElementValueOfCSSProperty))),
CommandMapping(
kPost, "session/:sessionId/timeouts/implicit_wait",
WrapToCommand("SetImplicitWait", base::Bind(&ExecuteImplicitlyWait))),
CommandMapping(kPost, "session/:sessionId/timeouts/async_script",
WrapToCommand("SetScriptTimeout",
base::Bind(&ExecuteSetScriptTimeout))),
CommandMapping(
kPost, "session/:sessionId/timeouts",
WrapToCommand("SetTimeout", base::Bind(&ExecuteSetTimeout))),
CommandMapping(
kGet, "session/:sessionId/timeouts",
WrapToCommand("GetTimeouts", base::Bind(&ExecuteGetTimeouts))),
CommandMapping(kPost, "session/:sessionId/execute_sql",
WrapToCommand("ExecuteSql",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kGet, "session/:sessionId/location",
WrapToCommand("GetGeolocation", base::Bind(&ExecuteGetLocation))),
CommandMapping(
kPost, "session/:sessionId/location",
WrapToCommand("SetGeolocation", base::Bind(&ExecuteSetLocation))),
CommandMapping(kPost, "session/:sessionId/network_connection",
WrapToCommand("SetNetworkConnection",
base::Bind(&ExecuteSetNetworkConnection))),
CommandMapping(kGet, "session/:sessionId/network_connection",
WrapToCommand("GetNetworkConnection",
base::Bind(&ExecuteGetNetworkConnection))),
CommandMapping(kGet, "session/:sessionId/chromium/network_conditions",
WrapToCommand("GetNetworkConditions",
base::Bind(&ExecuteGetNetworkConditions))),
CommandMapping(kPost, "session/:sessionId/chromium/network_conditions",
WrapToCommand("SetNetworkConditions",
base::Bind(&ExecuteSetNetworkConditions))),
CommandMapping(
kDelete, "session/:sessionId/chromium/network_conditions",
WrapToCommand("DeleteNetworkConditions",
base::Bind(&ExecuteDeleteNetworkConditions))),
CommandMapping(kGet, "session/:sessionId/application_cache/status",
base::Bind(&ExecuteGetStatus)),
CommandMapping(kGet, "session/:sessionId/browser_connection",
WrapToCommand("GetBrowserConnection",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(kPost, "session/:sessionId/browser_connection",
WrapToCommand("SetBrowserConnection",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kGet, "session/:sessionId/local_storage/key/:key",
WrapToCommand("GetLocalStorageItem",
base::Bind(&ExecuteGetStorageItem, kLocalStorage))),
CommandMapping(
kDelete, "session/:sessionId/local_storage/key/:key",
WrapToCommand("RemoveLocalStorageItem",
base::Bind(&ExecuteRemoveStorageItem, kLocalStorage))),
CommandMapping(
kGet, "session/:sessionId/local_storage",
WrapToCommand("GetLocalStorageKeys",
base::Bind(&ExecuteGetStorageKeys, kLocalStorage))),
CommandMapping(
kPost, "session/:sessionId/local_storage",
WrapToCommand("SetLocalStorageKeys",
base::Bind(&ExecuteSetStorageItem, kLocalStorage))),
CommandMapping(
kDelete, "session/:sessionId/local_storage",
WrapToCommand("ClearLocalStorage",
base::Bind(&ExecuteClearStorage, kLocalStorage))),
CommandMapping(
kGet, "session/:sessionId/local_storage/size",
WrapToCommand("GetLocalStorageSize",
base::Bind(&ExecuteGetStorageSize, kLocalStorage))),
CommandMapping(
kGet, "session/:sessionId/session_storage/key/:key",
WrapToCommand("GetSessionStorageItem",
base::Bind(&ExecuteGetStorageItem, kSessionStorage))),
CommandMapping(kDelete, "session/:sessionId/session_storage/key/:key",
WrapToCommand("RemoveSessionStorageItem",
base::Bind(&ExecuteRemoveStorageItem,
kSessionStorage))),
CommandMapping(
kGet, "session/:sessionId/session_storage",
WrapToCommand("GetSessionStorageKeys",
base::Bind(&ExecuteGetStorageKeys, kSessionStorage))),
CommandMapping(
kPost, "session/:sessionId/session_storage",
WrapToCommand("SetSessionStorageItem",
base::Bind(&ExecuteSetStorageItem, kSessionStorage))),
CommandMapping(
kDelete, "session/:sessionId/session_storage",
WrapToCommand("ClearSessionStorage",
base::Bind(&ExecuteClearStorage, kSessionStorage))),
CommandMapping(
kGet, "session/:sessionId/session_storage/size",
WrapToCommand("GetSessionStorageSize",
base::Bind(&ExecuteGetStorageSize, kSessionStorage))),
CommandMapping(kGet, "session/:sessionId/orientation",
WrapToCommand("GetScreenOrientation",
base::Bind(&ExecuteGetScreenOrientation))),
CommandMapping(kPost, "session/:sessionId/orientation",
WrapToCommand("SetScreenOrientation",
base::Bind(&ExecuteSetScreenOrientation))),
CommandMapping(
kDelete, "session/:sessionId/orientation",
WrapToCommand("DeleteScreenOrientation",
base::Bind(&ExecuteDeleteScreenOrientation))),
CommandMapping(kPost, "session/:sessionId/click",
WrapToCommand("Click", base::Bind(&ExecuteMouseClick))),
CommandMapping(
kPost, "session/:sessionId/doubleclick",
WrapToCommand("DoubleClick", base::Bind(&ExecuteMouseDoubleClick))),
CommandMapping(
kPost, "session/:sessionId/buttondown",
WrapToCommand("MouseDown", base::Bind(&ExecuteMouseButtonDown))),
CommandMapping(
kPost, "session/:sessionId/buttonup",
WrapToCommand("MouseUp", base::Bind(&ExecuteMouseButtonUp))),
CommandMapping(
kPost, "session/:sessionId/moveto",
WrapToCommand("MouseMove", base::Bind(&ExecuteMouseMoveTo))),
CommandMapping(
kPost, "session/:sessionId/keys",
WrapToCommand("Type", base::Bind(&ExecuteSendKeysToActiveElement))),
CommandMapping(kGet, "session/:sessionId/ime/available_engines",
WrapToCommand("GetAvailableEngines",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(kGet, "session/:sessionId/ime/active_engine",
WrapToCommand("GetActiveEngine",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kGet, "session/:sessionId/ime/activated",
WrapToCommand("Activated", base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(kPost, "session/:sessionId/ime/deactivate",
WrapToCommand("Deactivate",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kPost, "session/:sessionId/ime/activate",
WrapToCommand("Activate", base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(kPost, "session/:sessionId/touch/click",
WrapToCommand("Tap", base::Bind(&ExecuteTouchSingleTap))),
CommandMapping(kPost, "session/:sessionId/touch/down",
WrapToCommand("TouchDown", base::Bind(&ExecuteTouchDown))),
CommandMapping(kPost, "session/:sessionId/touch/up",
WrapToCommand("TouchUp", base::Bind(&ExecuteTouchUp))),
CommandMapping(kPost, "session/:sessionId/touch/move",
WrapToCommand("TouchMove", base::Bind(&ExecuteTouchMove))),
CommandMapping(
kPost, "session/:sessionId/touch/scroll",
WrapToCommand("TouchScroll", base::Bind(&ExecuteTouchScroll))),
CommandMapping(
kPost, "session/:sessionId/touch/doubleclick",
WrapToCommand("TouchDoubleTap", base::Bind(&ExecuteTouchDoubleTap))),
CommandMapping(
kPost, "session/:sessionId/touch/longclick",
WrapToCommand("TouchLongPress", base::Bind(&ExecuteTouchLongPress))),
CommandMapping(kPost, "session/:sessionId/touch/flick",
WrapToCommand("TouchFlick", base::Bind(&ExecuteFlick))),
CommandMapping(kPost, "session/:sessionId/log",
WrapToCommand("GetLog", base::Bind(&ExecuteGetLog))),
CommandMapping(kGet, "session/:sessionId/log/types",
WrapToCommand("GetLogTypes",
base::Bind(&ExecuteGetAvailableLogTypes))),
CommandMapping(kPost, "session/:sessionId/actions",
WrapToCommand("PerformActions",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(kDelete, "session/:sessionId/actions",
WrapToCommand("DeleteActions",
base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(
kPost, "Logs",
WrapToCommand("Logs", base::Bind(&ExecuteUnimplementedCommand))),
CommandMapping(kGet, "status", base::Bind(&ExecuteGetStatus)),
// Custom Chrome commands:
// Allow quit all to be called with GET or POST.
CommandMapping(
kGet, kShutdownPath,
base::Bind(&ExecuteQuitAll,
WrapToCommand("QuitAll", base::Bind(&ExecuteQuit, true)),
&session_thread_map_)),
CommandMapping(
kPost, kShutdownPath,
base::Bind(&ExecuteQuitAll,
WrapToCommand("QuitAll", base::Bind(&ExecuteQuit, true)),
&session_thread_map_)),
CommandMapping(kGet, "session/:sessionId/is_loading",
WrapToCommand("IsLoading", base::Bind(&ExecuteIsLoading))),
CommandMapping(kGet, "session/:sessionId/autoreport",
WrapToCommand("IsAutoReporting",
base::Bind(&ExecuteIsAutoReporting))),
CommandMapping(kPost, "session/:sessionId/autoreport",
WrapToCommand("SetAutoReporting",
base::Bind(&ExecuteSetAutoReporting))),
CommandMapping(
kPost, "session/:sessionId/touch/pinch",
WrapToCommand("TouchPinch", base::Bind(&ExecuteTouchPinch))),
CommandMapping(
kPost, "session/:sessionId/chromium/send_command",
WrapToCommand("SendCommand", base::Bind(&ExecuteSendCommand))),
CommandMapping(
kPost, "session/:sessionId/chromium/send_command_and_get_result",
WrapToCommand("SendCommandAndGetResult",
base::Bind(&ExecuteSendCommandAndGetResult))),
};
command_map_.reset(
new CommandMap(commands, commands + arraysize(commands)));
}
HttpHandler::~HttpHandler() {}
void HttpHandler::Handle(const net::HttpServerRequestInfo& request,
const HttpResponseSenderFunc& send_response_func) {
CHECK(thread_checker_.CalledOnValidThread());
if (received_shutdown_)
return;
std::string path = request.path;
if (!base::StartsWith(path, url_base_, base::CompareCase::SENSITIVE)) {
std::unique_ptr<net::HttpServerResponseInfo> response(
new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
response->SetBody("unhandled request", "text/plain");
send_response_func.Run(std::move(response));
return;
}
path.erase(0, url_base_.length());
HandleCommand(request, path, send_response_func);
if (path == kShutdownPath)
received_shutdown_ = true;
}
Command HttpHandler::WrapToCommand(
const char* name,
const SessionCommand& session_command) {
return base::Bind(&ExecuteSessionCommand,
&session_thread_map_,
name,
session_command,
false);
}
Command HttpHandler::WrapToCommand(
const char* name,
const WindowCommand& window_command) {
return WrapToCommand(name, base::Bind(&ExecuteWindowCommand, window_command));
}
Command HttpHandler::WrapToCommand(
const char* name,
const ElementCommand& element_command) {
return WrapToCommand(name,
base::Bind(&ExecuteElementCommand, element_command));
}
void HttpHandler::HandleCommand(
const net::HttpServerRequestInfo& request,
const std::string& trimmed_path,
const HttpResponseSenderFunc& send_response_func) {
base::DictionaryValue params;
std::string session_id;
CommandMap::const_iterator iter = command_map_->begin();
while (true) {
if (iter == command_map_->end()) {
std::unique_ptr<net::HttpServerResponseInfo> response(
new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
response->SetBody("unknown command: " + trimmed_path, "text/plain");
send_response_func.Run(std::move(response));
return;
}
if (internal::MatchesCommand(
request.method, trimmed_path, *iter, &session_id, &params)) {
break;
}
++iter;
}
if (request.data.length()) {
base::DictionaryValue* body_params;
std::unique_ptr<base::Value> parsed_body =
base::JSONReader::Read(request.data);
if (!parsed_body || !parsed_body->GetAsDictionary(&body_params)) {
std::unique_ptr<net::HttpServerResponseInfo> response(
new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
response->SetBody("missing command parameters", "text/plain");
send_response_func.Run(std::move(response));
return;
}
params.MergeDictionary(body_params);
}
iter->command.Run(params,
session_id,
base::Bind(&HttpHandler::PrepareResponse,
weak_ptr_factory_.GetWeakPtr(),
trimmed_path,
send_response_func));
}
void HttpHandler::PrepareResponse(
const std::string& trimmed_path,
const HttpResponseSenderFunc& send_response_func,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id,
bool w3c_compliant) {
CHECK(thread_checker_.CalledOnValidThread());
std::unique_ptr<net::HttpServerResponseInfo> response;
if (w3c_compliant)
response = PrepareStandardResponse(
trimmed_path, status, std::move(value), session_id);
else
response = PrepareLegacyResponse(trimmed_path,
status,
std::move(value),
session_id);
send_response_func.Run(std::move(response));
if (trimmed_path == kShutdownPath)
quit_func_.Run();
}
std::unique_ptr<net::HttpServerResponseInfo> HttpHandler::PrepareLegacyResponse(
const std::string& trimmed_path,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id) {
if (status.code() == kUnknownCommand) {
std::unique_ptr<net::HttpServerResponseInfo> response(
new net::HttpServerResponseInfo(net::HTTP_NOT_IMPLEMENTED));
response->SetBody("unimplemented command: " + trimmed_path, "text/plain");
return response;
}
if (status.IsError()) {
Status full_status(status);
full_status.AddDetails(base::StringPrintf(
"Driver info: chromedriver=%s,platform=%s %s %s",
kChromeDriverVersion,
base::SysInfo::OperatingSystemName().c_str(),
base::SysInfo::OperatingSystemVersion().c_str(),
base::SysInfo::OperatingSystemArchitecture().c_str()));
std::unique_ptr<base::DictionaryValue> error(new base::DictionaryValue());
error->SetString("message", full_status.message());
value = std::move(error);
}
if (!value)
value = base::MakeUnique<base::Value>();
base::DictionaryValue body_params;
body_params.SetInteger("status", status.code());
body_params.Set("value", std::move(value));
body_params.SetString("sessionId", session_id);
std::string body;
base::JSONWriter::WriteWithOptions(
body_params, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
&body);
std::unique_ptr<net::HttpServerResponseInfo> response(
new net::HttpServerResponseInfo(net::HTTP_OK));
response->SetBody(body, "application/json; charset=utf-8");
return response;
}
std::unique_ptr<net::HttpServerResponseInfo>
HttpHandler::PrepareStandardResponse(
const std::string& trimmed_path,
const Status& status,
std::unique_ptr<base::Value> value,
const std::string& session_id) {
std::unique_ptr<net::HttpServerResponseInfo> response;
switch (status.code()) {
case kOk:
response.reset(new net::HttpServerResponseInfo(net::HTTP_OK));
break;
// error codes
case kElementNotInteractable:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kInvalidArgument:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kInvalidCookieDomain:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kInvalidElementState:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kInvalidSelector:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kJavaScriptError:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kNoSuchCookie:
response.reset(new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
break;
case kNoSuchElement:
response.reset(new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
break;
case kNoSuchFrame:
response.reset(new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
break;
case kNoSuchWindow:
response.reset(new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
break;
case kScriptTimeout:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_REQUEST_TIMEOUT));
break;
case kSessionNotCreatedException:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_INTERNAL_SERVER_ERROR));
break;
case kStaleElementReference:
response.reset(new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
break;
case kTimeout:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_REQUEST_TIMEOUT));
break;
case kUnableToSetCookie:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_INTERNAL_SERVER_ERROR));
break;
case kUnexpectedAlertOpen:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_INTERNAL_SERVER_ERROR));
break;
case kUnknownCommand:
response.reset(new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
break;
case kUnknownError:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_INTERNAL_SERVER_ERROR));
break;
case kUnsupportedOperation:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_INTERNAL_SERVER_ERROR));
break;
// TODO(kereliuk): evaluate the usage of these as they relate to the spec
case kElementNotVisible:
case kXPathLookupError:
case kNoAlertOpen:
case kNoSuchExecutionContext:
response.reset(new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
break;
case kNoSuchSession:
case kChromeNotReachable:
case kDisconnected:
case kForbidden:
case kTabCrashed:
response.reset(
new net::HttpServerResponseInfo(net::HTTP_INTERNAL_SERVER_ERROR));
break;
}
if (!value)
value = base::MakeUnique<base::Value>();
base::DictionaryValue body_params;
if (status.IsError()){
// Separates status default message from additional details.
std::vector<std::string> status_details = base::SplitString(
status.message(), ":\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
std::string message;
for (size_t i=1; i<status_details.size();++i)
message += status_details[i];
std::unique_ptr<base::DictionaryValue> inner_params(
new base::DictionaryValue());
inner_params->SetString("error", status_details[0]);
inner_params->SetString("message", message);
inner_params->SetString("stacktrace", status.stack_trace());
body_params.SetDictionary("value", std::move(inner_params));
} else {
body_params.Set("value", std::move(value));
}
std::string body;
base::JSONWriter::WriteWithOptions(
body_params, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
&body);
response->SetBody(body, "application/json; charset=utf-8");
return response;
}
namespace internal {
const char kNewSessionPathPattern[] = "session";
bool MatchesMethod(HttpMethod command_method, const std::string& method) {
std::string lower_method = base::ToLowerASCII(method);
switch (command_method) {
case kGet:
return lower_method == "get";
case kPost:
return lower_method == "post" || lower_method == "put";
case kDelete:
return lower_method == "delete";
}
return false;
}
bool MatchesCommand(const std::string& method,
const std::string& path,
const CommandMapping& command,
std::string* session_id,
base::DictionaryValue* out_params) {
if (!MatchesMethod(command.method, method))
return false;
std::vector<std::string> path_parts = base::SplitString(
path, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::vector<std::string> command_path_parts = base::SplitString(
command.path_pattern, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (path_parts.size() != command_path_parts.size())
return false;
base::DictionaryValue params;
for (size_t i = 0; i < path_parts.size(); ++i) {
CHECK(command_path_parts[i].length());
if (command_path_parts[i][0] == ':') {
std::string name = command_path_parts[i];
name.erase(0, 1);
CHECK(name.length());
url::RawCanonOutputT<base::char16> output;
url::DecodeURLEscapeSequences(
path_parts[i].data(), path_parts[i].length(), &output);
std::string decoded = base::UTF16ToASCII(
base::string16(output.data(), output.length()));
// Due to crbug.com/533361, the url decoding libraries decodes all of the
// % escape sequences except for %%. We need to handle this case manually.
// So, replacing all the instances of "%%" with "%".
base::ReplaceSubstringsAfterOffset(&decoded, 0 , "%%" , "%");
if (name == "sessionId")
*session_id = decoded;
else
params.SetString(name, decoded);
} else if (command_path_parts[i] != path_parts[i]) {
return false;
}
}
out_params->MergeDictionary(&params);
return true;
}
} // namespace internal