#include "chrome/test/chromedriver/server/http_handler.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notimplemented.h"
#include "base/strings/strcat.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/system/sys_info.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.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/command.h"
#include "chrome/test/chromedriver/commands.h"
#include "chrome/test/chromedriver/connection_session_map.h"
#include "chrome/test/chromedriver/constants/version.h"
#include "chrome/test/chromedriver/fedcm_commands.h"
#include "chrome/test/chromedriver/net/url_request_context_getter.h"
#include "chrome/test/chromedriver/server/http_server.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/webauthn_commands.h"
#include "chrome/test/chromedriver/window_commands.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/server/http_server_request_info.h"
#include "net/server/http_server_response_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/transitional_url_loader_factory_owner.h"
#include "url/url_util.h"
#if BUILDFLAG(IS_MAC)
#include "base/apple/scoped_nsautorelease_pool.h"
#endif
const char kCreateWebSocketPath[] =
"session/:sessionId/chromium/create_websocket";
const char kSendCommandFromWebSocket[] =
"session/:sessionId/chromium/send_command_from_websocket";
namespace {
const char kLocalStorage[] = "localStorage";
const char kSessionStorage[] = "sessionStorage";
const char kShutdownPath[] = "shutdown";
base::flat_set<std::string> kKnownBidiSessionCommands = {
"session.end",
"session.subscribe",
"session.unsubscribe",
"browsingContext.activate",
"browsingContext.captureScreenshot",
"browsingContext.close",
"browsingContext.create",
"browsingContext.getTree",
"browsingContext.handleUserPropmpt",
"browsingContext.navigate",
"browsingContext.print",
"browsingContext.reload",
"browsingContext.setViewport",
"network.addIntercept",
"network.continueRequest",
"network.continueResponse",
"network.continueWithAuth",
"network.failRequest",
"network.provideResponse",
"network.removeIntercept",
"script.addPreloadScript",
"script.disown",
"script.callFunction",
"script.evaluate",
"script.getRealms",
"script.removePreloadScript",
"input.performActions",
"input.releaseActions",
};
std::optional<base::Value> Clone(const std::optional<base::Value>& original) {
if (!original.has_value()) {
return std::nullopt;
}
return std::make_optional(original->Clone());
}
bool w3cMode(const std::string& session_id,
const SessionThreadMap& session_thread_map) {
if (session_id.length() > 0 && session_thread_map.count(session_id) > 0)
return session_thread_map.at(session_id)->w3cMode();
return kW3CDefault;
}
net::HttpServerResponseInfo CreateWebSocketRejectResponse(
net::HttpStatusCode code,
const std::string& msg) {
net::HttpServerResponseInfo response(code);
response.AddHeader("X-WebSocket-Reject-Reason", msg);
return response;
}
void AddBidiConnectionOnSessionThread(int connection_id,
SendTextFunc send_response,
CloseFunc close_connection) {
Session* session = GetThreadLocalSession();
if (session != nullptr) {
session->AddBidiConnection(connection_id, std::move(send_response),
std::move(close_connection));
}
}
void RemoveBidiConnectionOnSessionThread(int connection_id) {
Session* session = GetThreadLocalSession();
if (session != nullptr) {
session->RemoveBidiConnection(connection_id);
}
}
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;
}
}
class WrapperURLLoaderFactory : public network::mojom::URLLoaderFactory {
public:
explicit WrapperURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(std::move(url_loader_factory)),
network_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
WrapperURLLoaderFactory(const WrapperURLLoaderFactory&) = delete;
WrapperURLLoaderFactory& operator=(const WrapperURLLoaderFactory&) = delete;
void CreateLoaderAndStart(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
override {
if (network_task_runner_->RunsTasksInCurrentSequence()) {
url_loader_factory_->CreateLoaderAndStart(
std::move(loader), request_id, options, request, std::move(client),
traffic_annotation);
} else {
network_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WrapperURLLoaderFactory::CreateLoaderAndStart,
base::Unretained(this), std::move(loader), request_id,
options, request, std::move(client),
traffic_annotation));
}
}
void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory)
override {
NOTIMPLEMENTED();
}
private:
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
};
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() = default;
CommandMapping VendorPrefixedSessionCommandMapping(HttpMethod method,
std::string_view path_suffix,
const Command& command) {
return CommandMapping(
method,
base::StrCat({"session/:sessionId/", kChromeDriverCompanyPrefix, "/",
path_suffix}),
command);
}
HttpHandler::HttpHandler(const std::string& url_base)
: url_base_(url_base),
received_shutdown_(false),
command_map_(new CommandMap()) {
session_connection_map_.emplace("", std::vector<int>());
}
HttpHandler::HttpHandler(
const base::RepeatingClosure& quit_func,
const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
const scoped_refptr<base::SingleThreadTaskRunner> cmd_task_runner,
const std::string& url_base,
int adb_port)
: quit_func_(quit_func),
io_task_runner_(io_task_runner),
cmd_task_runner_(cmd_task_runner),
url_base_(url_base),
received_shutdown_(false) {
#if BUILDFLAG(IS_MAC)
base::apple::ScopedNSAutoreleasePool autorelease_pool;
#endif
context_getter_ = new URLRequestContextGetter(io_task_runner_);
socket_factory_ = CreateSyncWebSocketFactory(context_getter_.get());
adb_ = std::make_unique<AdbImpl>(io_task_runner_, adb_port);
device_manager_ = std::make_unique<DeviceManager>(adb_.get());
url_loader_factory_owner_ =
std::make_unique<network::TransitionalURLLoaderFactoryOwner>(
context_getter_.get());
wrapper_url_loader_factory_ = std::make_unique<WrapperURLLoaderFactory>(
url_loader_factory_owner_->GetURLLoaderFactory());
session_connection_map_.emplace("", std::vector<int>());
auto terminate_on_cmd = base::BindRepeating(&HttpHandler::OnSessionTerminated,
weak_ptr_factory_.GetWeakPtr());
Command init_session_cmd = WrapToCommand(
"InitSession",
base::BindRepeating(
&ExecuteInitSession,
InitSessionParams(wrapper_url_loader_factory_.get(), socket_factory_,
device_manager_.get(), cmd_task_runner,
terminate_on_cmd)));
Command create_and_init_session = base::BindRepeating(
&ExecuteCreateSession, &session_thread_map_, init_session_cmd);
CommandMapping commands[] = {
CommandMapping(kPost, internal::kNewSessionPathPattern,
WrapCreateNewSessionCommand(create_and_init_session)),
CommandMapping(kDelete, "session/:sessionId",
base::BindRepeating(
&ExecuteSessionCommand, &session_thread_map_, "Quit",
base::BindRepeating(&ExecuteQuit, false), true, true)),
CommandMapping(kGet, "status", base::BindRepeating(&ExecuteGetStatus)),
CommandMapping(kGet, "session/:sessionId/timeouts",
WrapToCommand("GetTimeouts",
base::BindRepeating(&ExecuteGetTimeouts))),
CommandMapping(kPost, "session/:sessionId/timeouts",
WrapToCommand("SetTimeouts",
base::BindRepeating(&ExecuteSetTimeouts))),
CommandMapping(
kPost, "session/:sessionId/url",
WrapToCommand("Navigate", base::BindRepeating(&ExecuteGet))),
CommandMapping(
kGet, "session/:sessionId/url",
WrapToCommand("GetUrl", base::BindRepeating(&ExecuteGetCurrentUrl))),
CommandMapping(
kPost, "session/:sessionId/back",
WrapToCommand("GoBack", base::BindRepeating(&ExecuteGoBack))),
CommandMapping(
kPost, "session/:sessionId/forward",
WrapToCommand("GoForward", base::BindRepeating(&ExecuteGoForward))),
CommandMapping(
kPost, "session/:sessionId/refresh",
WrapToCommand("Refresh", base::BindRepeating(&ExecuteRefresh))),
CommandMapping(
kGet, "session/:sessionId/title",
WrapToCommand("GetTitle", base::BindRepeating(&ExecuteGetTitle))),
CommandMapping(
kGet, "session/:sessionId/window",
WrapToCommand("GetWindow",
base::BindRepeating(&ExecuteGetCurrentWindowHandle))),
CommandMapping(
kDelete, "session/:sessionId/window",
WrapToCommand("CloseWindow", base::BindRepeating(&ExecuteClose))),
CommandMapping(
kPost, "session/:sessionId/window/new",
WrapToCommand("NewWindow", base::BindRepeating(&ExecuteNewWindow))),
CommandMapping(
kPost, "session/:sessionId/window",
WrapToCommand("SwitchToWindow",
base::BindRepeating(&ExecuteSwitchToWindow))),
CommandMapping(
kGet, "session/:sessionId/window/handles",
WrapToCommand("GetWindows",
base::BindRepeating(&ExecuteGetWindowHandles))),
CommandMapping(kPost, "session/:sessionId/frame",
WrapToCommand("SwitchToFrame",
base::BindRepeating(&ExecuteSwitchToFrame))),
CommandMapping(
kPost, "session/:sessionId/frame/parent",
WrapToCommand("SwitchToParentFrame",
base::BindRepeating(&ExecuteSwitchToParentFrame))),
CommandMapping(kGet, "session/:sessionId/window/rect",
WrapToCommand("GetWindowRect",
base::BindRepeating(&ExecuteGetWindowRect))),
CommandMapping(kPost, "session/:sessionId/window/rect",
WrapToCommand("SetWindowRect",
base::BindRepeating(&ExecuteSetWindowRect))),
CommandMapping(
kPost, "session/:sessionId/window/maximize",
WrapToCommand("MaximizeWindow",
base::BindRepeating(&ExecuteMaximizeWindow))),
CommandMapping(
kPost, "session/:sessionId/window/minimize",
WrapToCommand("MinimizeWindow",
base::BindRepeating(&ExecuteMinimizeWindow))),
CommandMapping(
kPost, "session/:sessionId/window/fullscreen",
WrapToCommand("FullscreenWindow",
base::BindRepeating(&ExecuteFullScreenWindow))),
CommandMapping(
kGet, "session/:sessionId/element/active",
WrapToCommand("GetActiveElement",
base::BindRepeating(&ExecuteGetActiveElement))),
CommandMapping(
kGet, "session/:sessionId/element/:id/shadow",
WrapToCommand("GetElementShadowRoot",
base::BindRepeating(&ExecuteGetElementShadowRoot))),
CommandMapping(
kPost, "session/:sessionId/shadow/:id/element",
WrapToCommand(
"FindChildElementFromShadowRoot",
base::BindRepeating(&ExecuteFindChildElementFromShadowRoot, 50))),
CommandMapping(
kPost, "session/:sessionId/shadow/:id/elements",
WrapToCommand("FindChildElementsFromShadowRoot",
base::BindRepeating(
&ExecuteFindChildElementsFromShadowRoot, 50))),
CommandMapping(
kPost, "session/:sessionId/element",
WrapToCommand("FindElement",
base::BindRepeating(&ExecuteFindElement, 50))),
CommandMapping(
kPost, "session/:sessionId/elements",
WrapToCommand("FindElements",
base::BindRepeating(&ExecuteFindElements, 50))),
CommandMapping(
kPost, "session/:sessionId/element/:id/element",
WrapToCommand("FindChildElement",
base::BindRepeating(&ExecuteFindChildElement, 50))),
CommandMapping(
kPost, "session/:sessionId/element/:id/elements",
WrapToCommand("FindChildElements",
base::BindRepeating(&ExecuteFindChildElements, 50))),
CommandMapping(
kGet, "session/:sessionId/element/:id/selected",
WrapToCommand("IsElementSelected",
base::BindRepeating(&ExecuteIsElementSelected))),
CommandMapping(
kGet, "session/:sessionId/element/:id/attribute/:name",
WrapToCommand("GetElementAttribute",
base::BindRepeating(&ExecuteGetElementAttribute))),
CommandMapping(
kGet, "session/:sessionId/element/:id/property/:name",
WrapToCommand("GetElementProperty",
base::BindRepeating(&ExecuteGetElementProperty))),
CommandMapping(kGet, "session/:sessionId/element/:id/css/:propertyName",
WrapToCommand("GetElementCSSProperty",
base::BindRepeating(
&ExecuteGetElementValueOfCSSProperty))),
CommandMapping(
kGet, "session/:sessionId/element/:id/text",
WrapToCommand("GetElementText",
base::BindRepeating(&ExecuteGetElementText))),
CommandMapping(
kGet, "session/:sessionId/element/:id/name",
WrapToCommand("GetElementTagName",
base::BindRepeating(&ExecuteGetElementTagName))),
CommandMapping(
kGet, "session/:sessionId/element/:id/rect",
WrapToCommand("GetElementRect",
base::BindRepeating(&ExecuteGetElementRect))),
CommandMapping(
kGet, "session/:sessionId/element/:id/enabled",
WrapToCommand("IsElementEnabled",
base::BindRepeating(&ExecuteIsElementEnabled))),
CommandMapping(
kGet, "session/:sessionId/element/:id/computedlabel",
WrapToCommand("GetComputedLabel",
base::BindRepeating(&ExecuteGetComputedLabel))),
CommandMapping(
kGet, "session/:sessionId/element/:id/computedrole",
WrapToCommand("GetComputedRole",
base::BindRepeating(&ExecuteGetComputedRole))),
CommandMapping(kPost, "session/:sessionId/element/:id/click",
WrapToCommand("ClickElement",
base::BindRepeating(&ExecuteClickElement))),
CommandMapping(kPost, "session/:sessionId/element/:id/clear",
WrapToCommand("ClearElement",
base::BindRepeating(&ExecuteClearElement))),
CommandMapping(
kPost, "session/:sessionId/element/:id/value",
WrapToCommand("TypeElement",
base::BindRepeating(&ExecuteSendKeysToElement))),
CommandMapping(kGet, "session/:sessionId/source",
WrapToCommand("GetSource",
base::BindRepeating(&ExecuteGetPageSource))),
CommandMapping(kPost, "session/:sessionId/execute/sync",
WrapToCommand("ExecuteScript",
base::BindRepeating(&ExecuteExecuteScript))),
CommandMapping(
kPost, "session/:sessionId/execute/async",
WrapToCommand("ExecuteAsyncScript",
base::BindRepeating(&ExecuteExecuteAsyncScript))),
CommandMapping(
kGet, "session/:sessionId/cookie",
WrapToCommand("GetCookies", base::BindRepeating(&ExecuteGetCookies))),
CommandMapping(
kGet, "session/:sessionId/cookie/:name",
WrapToCommand("GetNamedCookie",
base::BindRepeating(&ExecuteGetNamedCookie))),
CommandMapping(
kPost, "session/:sessionId/cookie",
WrapToCommand("AddCookie", base::BindRepeating(&ExecuteAddCookie))),
CommandMapping(kDelete, "session/:sessionId/cookie/:name",
WrapToCommand("DeleteCookie",
base::BindRepeating(&ExecuteDeleteCookie))),
CommandMapping(
kDelete, "session/:sessionId/cookie",
WrapToCommand("DeleteAllCookies",
base::BindRepeating(&ExecuteDeleteAllCookies))),
CommandMapping(
kPost, "session/:sessionId/actions",
WrapToCommand("PerformActions",
base::BindRepeating(&ExecutePerformActions))),
CommandMapping(
kDelete, "session/:sessionId/actions",
WrapToCommand("ReleaseActions",
base::BindRepeating(&ExecuteReleaseActions))),
CommandMapping(
kPost, "session/:sessionId/alert/dismiss",
WrapToCommand(
"DismissAlert",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteDismissAlert)))),
CommandMapping(
kPost, "session/:sessionId/alert/accept",
WrapToCommand(
"AcceptAlert",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteAcceptAlert)))),
CommandMapping(
kGet, "session/:sessionId/alert/text",
WrapToCommand(
"GetAlertMessage",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteGetAlertText)))),
CommandMapping(
kPost, "session/:sessionId/alert/text",
WrapToCommand(
"SetAlertPrompt",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteSetAlertText)))),
CommandMapping(
kGet, "session/:sessionId/screenshot",
WrapToCommand("Screenshot", base::BindRepeating(&ExecuteScreenshot))),
CommandMapping(
kGet, "session/:sessionId/element/:id/screenshot",
WrapToCommand("ElementScreenshot",
base::BindRepeating(&ExecuteElementScreenshot))),
CommandMapping(
kGet, "session/:sessionId/screenshot/full",
WrapToCommand("FullPageScreenshot",
base::BindRepeating(&ExecuteFullPageScreenshot))),
CommandMapping(
kPost, "session/:sessionId/print",
WrapToCommand("Print", base::BindRepeating(&ExecutePrint))),
CommandMapping(
kGet, "sessions",
base::BindRepeating(
&ExecuteGetSessions,
WrapToCommand("GetSessions", base::BindRepeating(
&ExecuteGetSessionCapabilities)),
&session_thread_map_)),
CommandMapping(
kGet, "session/:sessionId",
WrapToCommand("GetSessionCapabilities",
base::BindRepeating(&ExecuteGetSessionCapabilities),
false )),
CommandMapping(kPost, "session/:sessionId/timeouts/implicit_wait",
WrapToCommand("SetImplicitWait",
base::BindRepeating(&ExecuteImplicitlyWait),
false )),
CommandMapping(
kPost, "session/:sessionId/timeouts/async_script",
WrapToCommand("SetScriptTimeout",
base::BindRepeating(&ExecuteSetScriptTimeout),
false )),
CommandMapping(
kGet, "session/:sessionId/window_handle",
WrapToCommand("GetWindow",
base::BindRepeating(&ExecuteGetCurrentWindowHandle),
false )),
CommandMapping(
kGet, "session/:sessionId/window_handles",
WrapToCommand("GetWindows",
base::BindRepeating(&ExecuteGetWindowHandles),
false )),
CommandMapping(kPost, "session/:sessionId/execute",
WrapToCommand("ExecuteScript",
base::BindRepeating(&ExecuteExecuteScript),
false )),
CommandMapping(
kPost, "session/:sessionId/execute_async",
WrapToCommand("ExecuteAsyncScript",
base::BindRepeating(&ExecuteExecuteAsyncScript),
false )),
CommandMapping(kPost, "session/:sessionId/window/:windowHandle/size",
WrapToCommand("SetWindowSize",
base::BindRepeating(&ExecuteSetWindowSize),
false )),
CommandMapping(kGet, "session/:sessionId/window/:windowHandle/size",
WrapToCommand("GetWindowSize",
base::BindRepeating(&ExecuteGetWindowSize),
false )),
CommandMapping(
kPost, "session/:sessionId/window/:windowHandle/position",
WrapToCommand("SetWindowPosition",
base::BindRepeating(&ExecuteSetWindowPosition),
false )),
CommandMapping(
kGet, "session/:sessionId/window/:windowHandle/position",
WrapToCommand("GetWindowPosition",
base::BindRepeating(&ExecuteGetWindowPosition),
false )),
CommandMapping(kPost, "session/:sessionId/window/:windowHandle/maximize",
WrapToCommand("MaximizeWindow",
base::BindRepeating(&ExecuteMaximizeWindow),
false )),
CommandMapping(
kPost, "session/:sessionId/element/active",
WrapToCommand("GetActiveElement",
base::BindRepeating(&ExecuteGetActiveElement),
false )),
CommandMapping(kPost, "session/:sessionId/element/:id/submit",
WrapToCommand("SubmitElement",
base::BindRepeating(&ExecuteSubmitElement),
false )),
CommandMapping(
kPost, "session/:sessionId/keys",
WrapToCommand("Type",
base::BindRepeating(&ExecuteSendKeysToActiveElement),
false )),
CommandMapping(kGet, "session/:sessionId/element/:id/equals/:other",
WrapToCommand("IsElementEqual",
base::BindRepeating(&ExecuteElementEquals),
false )),
CommandMapping(
kGet, "session/:sessionId/element/:id/displayed",
WrapToCommand("IsElementDisplayed",
base::BindRepeating(&ExecuteIsElementDisplayed))),
CommandMapping(
kGet, "session/:sessionId/element/:id/location",
WrapToCommand("GetElementLocation",
base::BindRepeating(&ExecuteGetElementLocation),
false )),
CommandMapping(
kGet, "session/:sessionId/element/:id/location_in_view",
WrapToCommand("GetElementLocationInView",
base::BindRepeating(
&ExecuteGetElementLocationOnceScrolledIntoView),
false )),
CommandMapping(kGet, "session/:sessionId/element/:id/size",
WrapToCommand("GetElementSize",
base::BindRepeating(&ExecuteGetElementSize),
false )),
CommandMapping(
kGet, "session/:sessionId/alert_text",
WrapToCommand(
"GetAlertMessage",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteGetAlertText)),
false )),
CommandMapping(
kPost, "session/:sessionId/alert_text",
WrapToCommand(
"SetAlertPrompt",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteSetAlertText)),
false )),
CommandMapping(
kPost, "session/:sessionId/accept_alert",
WrapToCommand(
"AcceptAlert",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteAcceptAlert)),
false )),
CommandMapping(
kPost, "session/:sessionId/dismiss_alert",
WrapToCommand(
"DismissAlert",
base::BindRepeating(&ExecuteAlertCommand,
base::BindRepeating(&ExecuteDismissAlert)),
false )),
CommandMapping(
kPost, "session/:sessionId/moveto",
WrapToCommand("MouseMove", base::BindRepeating(&ExecuteMouseMoveTo),
false )),
CommandMapping(
kPost, "session/:sessionId/click",
WrapToCommand("Click", base::BindRepeating(&ExecuteMouseClick))),
CommandMapping(kPost, "session/:sessionId/buttondown",
WrapToCommand("MouseDown",
base::BindRepeating(&ExecuteMouseButtonDown),
false )),
CommandMapping(
kPost, "session/:sessionId/buttonup",
WrapToCommand("MouseUp", base::BindRepeating(&ExecuteMouseButtonUp),
false )),
CommandMapping(
kPost, "session/:sessionId/doubleclick",
WrapToCommand("DoubleClick",
base::BindRepeating(&ExecuteMouseDoubleClick),
false )),
CommandMapping(
kPost, "session/:sessionId/touch/click",
WrapToCommand("Tap", base::BindRepeating(&ExecuteTouchSingleTap),
false )),
CommandMapping(
kPost, "session/:sessionId/touch/down",
WrapToCommand("TouchDown", base::BindRepeating(&ExecuteTouchDown),
false )),
CommandMapping(
kPost, "session/:sessionId/touch/up",
WrapToCommand("TouchUp", base::BindRepeating(&ExecuteTouchUp),
false )),
CommandMapping(
kPost, "session/:sessionId/touch/move",
WrapToCommand("TouchMove", base::BindRepeating(&ExecuteTouchMove),
false )),
CommandMapping(
kPost, "session/:sessionId/touch/scroll",
WrapToCommand("TouchScroll", base::BindRepeating(&ExecuteTouchScroll),
false )),
CommandMapping(kPost, "session/:sessionId/touch/doubleclick",
WrapToCommand("TouchDoubleTap",
base::BindRepeating(&ExecuteTouchDoubleTap),
false )),
CommandMapping(kPost, "session/:sessionId/touch/longclick",
WrapToCommand("TouchLongPress",
base::BindRepeating(&ExecuteTouchLongPress),
false )),
CommandMapping(
kPost, "session/:sessionId/touch/flick",
WrapToCommand("TouchFlick", base::BindRepeating(&ExecuteFlick),
false )),
CommandMapping(kGet, "session/:sessionId/location",
WrapToCommand("GetGeolocation",
base::BindRepeating(&ExecuteGetLocation))),
CommandMapping(kPost, "session/:sessionId/location",
WrapToCommand("SetGeolocation",
base::BindRepeating(&ExecuteSetLocation))),
CommandMapping(kGet, "session/:sessionId/local_storage",
WrapToCommand("GetLocalStorageKeys",
base::BindRepeating(&ExecuteGetStorageKeys,
kLocalStorage),
false )),
CommandMapping(kPost, "session/:sessionId/local_storage",
WrapToCommand("SetLocalStorageKeys",
base::BindRepeating(&ExecuteSetStorageItem,
kLocalStorage),
false )),
CommandMapping(kDelete, "session/:sessionId/local_storage",
WrapToCommand("ClearLocalStorage",
base::BindRepeating(&ExecuteClearStorage,
kLocalStorage),
false )),
CommandMapping(kGet, "session/:sessionId/local_storage/key/:key",
WrapToCommand("GetLocalStorageItem",
base::BindRepeating(&ExecuteGetStorageItem,
kLocalStorage),
false )),
CommandMapping(
kDelete, "session/:sessionId/local_storage/key/:key",
WrapToCommand(
"RemoveLocalStorageItem",
base::BindRepeating(&ExecuteRemoveStorageItem, kLocalStorage),
false )),
CommandMapping(kGet, "session/:sessionId/local_storage/size",
WrapToCommand("GetLocalStorageSize",
base::BindRepeating(&ExecuteGetStorageSize,
kLocalStorage),
false )),
CommandMapping(kGet, "session/:sessionId/session_storage",
WrapToCommand("GetSessionStorageKeys",
base::BindRepeating(&ExecuteGetStorageKeys,
kSessionStorage),
false )),
CommandMapping(kPost, "session/:sessionId/session_storage",
WrapToCommand("SetSessionStorageItem",
base::BindRepeating(&ExecuteSetStorageItem,
kSessionStorage),
false )),
CommandMapping(kDelete, "session/:sessionId/session_storage",
WrapToCommand("ClearSessionStorage",
base::BindRepeating(&ExecuteClearStorage,
kSessionStorage),
false )),
CommandMapping(kGet, "session/:sessionId/session_storage/key/:key",
WrapToCommand("GetSessionStorageItem",
base::BindRepeating(&ExecuteGetStorageItem,
kSessionStorage),
false )),
CommandMapping(
kDelete, "session/:sessionId/session_storage/key/:key",
WrapToCommand(
"RemoveSessionStorageItem",
base::BindRepeating(&ExecuteRemoveStorageItem, kSessionStorage),
false )),
CommandMapping(kGet, "session/:sessionId/session_storage/size",
WrapToCommand("GetSessionStorageSize",
base::BindRepeating(&ExecuteGetStorageSize,
kSessionStorage),
false )),
CommandMapping(
kPost, "session/:sessionId/log",
WrapToCommand("GetLog", base::BindRepeating(&ExecuteGetLog))),
CommandMapping(
kGet, "session/:sessionId/log/types",
WrapToCommand("GetLogTypes",
base::BindRepeating(&ExecuteGetAvailableLogTypes),
false )),
CommandMapping(kGet, "session/:sessionId/application_cache/status",
base::BindRepeating(&ExecuteGetStatus)),
CommandMapping(
kPost, "session/:sessionId/reporting/generate_test_report",
WrapToCommand("GenerateTestReport",
base::BindRepeating(&ExecuteGenerateTestReport))),
CommandMapping(
kGet, "session/:sessionId/network_connection",
WrapToCommand("GetNetworkConnection",
base::BindRepeating(&ExecuteGetNetworkConnection))),
CommandMapping(
kPost, "session/:sessionId/network_connection",
WrapToCommand("SetNetworkConnection",
base::BindRepeating(&ExecuteSetNetworkConnection))),
CommandMapping(kPost, "session/:sessionId/webauthn/authenticator",
WrapToCommand("AddVirtualAuthenticator",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(
&ExecuteAddVirtualAuthenticator)))),
CommandMapping(
kDelete, "session/:sessionId/webauthn/authenticator/:authenticatorId",
WrapToCommand(
"RemoveVirtualAuthenticator",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteRemoveVirtualAuthenticator)))),
CommandMapping(
kPost,
"session/:sessionId/webauthn/authenticator/:authenticatorId/"
"credential",
WrapToCommand(
"AddCredential",
base::BindRepeating(&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteAddCredential)))),
CommandMapping(
kGet,
"session/:sessionId/webauthn/authenticator/:authenticatorId/"
"credentials",
WrapToCommand("GetCredentials",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteGetCredentials)))),
CommandMapping(
kDelete,
"session/:sessionId/webauthn/authenticator/:authenticatorId/"
"credentials/:credentialId",
WrapToCommand("RemoveCredential",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteRemoveCredential)))),
CommandMapping(
kDelete,
"session/:sessionId/webauthn/authenticator/:authenticatorId/"
"credentials",
WrapToCommand(
"RemoveAllCredentials",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteRemoveAllCredentials)))),
CommandMapping(
kPost,
"session/:sessionId/webauthn/authenticator/:authenticatorId/uv",
WrapToCommand("SetUserVerified",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteSetUserVerified)))),
CommandMapping(
kPost,
"session/:sessionId/webauthn/authenticator/:authenticatorId/"
"credentials/:credentialId/props",
WrapToCommand(
"SetCredentialProperties",
base::BindRepeating(
&ExecuteWebAuthnCommand,
base::BindRepeating(&ExecuteSetCredentialProperties)))),
CommandMapping(
kPost, "session/:sessionId/secure-payment-confirmation/set-mode",
WrapToCommand("SetSPCTransactionMode",
base::BindRepeating(&ExecuteSetSPCTransactionMode))),
CommandMapping(kPost, "session/:sessionId/fedcm/canceldialog",
WrapToCommand("CancelDialog",
base::BindRepeating(&ExecuteCancelDialog))),
CommandMapping(kPost, "session/:sessionId/fedcm/selectaccount",
WrapToCommand("SelectAccount",
base::BindRepeating(&ExecuteSelectAccount))),
CommandMapping(
kPost, "session/:sessionId/fedcm/clickdialogbutton",
WrapToCommand("ClickDialogButton",
base::BindRepeating(&ExecuteClickDialogButton))),
CommandMapping(kGet, "session/:sessionId/fedcm/accountlist",
WrapToCommand("GetAccounts",
base::BindRepeating(&ExecuteGetAccounts))),
CommandMapping(kGet, "session/:sessionId/fedcm/gettitle",
WrapToCommand("GetFedCmTitle",
base::BindRepeating(&ExecuteGetFedCmTitle))),
CommandMapping(kGet, "session/:sessionId/fedcm/getdialogtype",
WrapToCommand("GetDialogType",
base::BindRepeating(&ExecuteGetDialogType))),
CommandMapping(
kPost, "session/:sessionId/fedcm/setdelayenabled",
WrapToCommand("SetDelayEnabled",
base::BindRepeating(&ExecuteSetDelayEnabled))),
CommandMapping(kPost, "session/:sessionId/fedcm/resetcooldown",
WrapToCommand("ResetCooldown",
base::BindRepeating(&ExecuteResetCooldown))),
CommandMapping(
kDelete, "session/:sessionId/storage/run_bounce_tracking_mitigations",
WrapToCommand(
"RunBounceTrackingMitigations",
base::BindRepeating(&ExecuteRunBounceTrackingMitigations))),
CommandMapping(
kPost, "session/:sessionId/protected_audience/set_k_anonymity",
WrapToCommand(
"SetProtectedAudienceKAnonymity",
base::BindRepeating(&ExecuteSetProtectedAudienceKAnonymity))),
CommandMapping(
kPost, "session/:sessionId/custom-handlers/set-mode",
WrapToCommand("SetRPHRegistrationMode",
base::BindRepeating(&ExecuteSetRPHRegistrationMode))),
CommandMapping(
kPost, "session/:sessionId/sensor",
WrapToCommand("CreateVirtualSensor",
base::BindRepeating(&ExecuteCreateVirtualSensor))),
CommandMapping(
kPost, "session/:sessionId/sensor/:type",
WrapToCommand("UpdateVirtualSensor",
base::BindRepeating(&ExecuteUpdateVirtualSensor))),
CommandMapping(
kDelete, "session/:sessionId/sensor/:type",
WrapToCommand("RemoveVirtualSensor",
base::BindRepeating(&ExecuteRemoveVirtualSensor))),
CommandMapping(kGet, "session/:sessionId/sensor/:type",
WrapToCommand("GetVirtualSensorInformation",
base::BindRepeating(
&ExecuteGetVirtualSensorInformation))),
CommandMapping(kPost, "session/:sessionId/permissions",
WrapToCommand("SetPermission",
base::BindRepeating(&ExecuteSetPermission))),
CommandMapping(
kPost, "session/:sessionId/deviceposture",
WrapToCommand("SetDevicePosture",
base::BindRepeating(&ExecuteSetDevicePosture))),
CommandMapping(
kDelete, "session/:sessionId/deviceposture",
WrapToCommand("ClearDevicePosture",
base::BindRepeating(&ExecuteClearDevicePosture))),
CommandMapping(
kPost, "session/:sessionId/displayfeatures",
WrapToCommand("SetDisplayFeatures",
base::BindRepeating(&ExecuteSetDisplayFeatures))),
CommandMapping(
kDelete, "session/:sessionId/displayfeatures",
WrapToCommand("ClearDisplayFeatures",
base::BindRepeating(&ExecuteClearDisplayFeatures))),
CommandMapping(kPost, "session/:sessionId/pressuresource",
WrapToCommand("CreateVirtualPressureSource",
base::BindRepeating(
&ExecuteCreateVirtualPressureSource))),
CommandMapping(kPost, "session/:sessionId/pressuresource/:type",
WrapToCommand("UpdateVirtualPressureSource",
base::BindRepeating(
&ExecuteUpdateVirtualPressureSource))),
CommandMapping(kDelete, "session/:sessionId/pressuresource/:type",
WrapToCommand("RemoveVirtualPressureSource",
base::BindRepeating(
&ExecuteRemoveVirtualPressureSource))),
CommandMapping(
kPost, "session/:sessionId/se/log",
WrapToCommand("GetLog", base::BindRepeating(&ExecuteGetLog))),
CommandMapping(
kGet, "session/:sessionId/se/log/types",
WrapToCommand("GetLogTypes",
base::BindRepeating(&ExecuteGetAvailableLogTypes))),
CommandMapping(
kPost, "session/:sessionId/file",
WrapToCommand("UploadFile", base::BindRepeating(&ExecuteUploadFile))),
CommandMapping(kGet, "session/:sessionId/element/:id/value",
WrapToCommand("GetElementValue",
base::BindRepeating(&ExecuteGetElementValue),
false )),
CommandMapping(
kGet, kShutdownPath,
base::BindRepeating(
&ExecuteQuitAll,
WrapToCommand("QuitAll", base::BindRepeating(&ExecuteQuit, true)),
&session_thread_map_)),
CommandMapping(
kPost, kShutdownPath,
base::BindRepeating(
&ExecuteQuitAll,
WrapToCommand("QuitAll", base::BindRepeating(&ExecuteQuit, true)),
&session_thread_map_)),
CommandMapping(kPost, "session/:sessionId/time_zone",
WrapToCommand("SetTimeZone",
base::BindRepeating(&ExecuteSetTimeZone))),
CommandMapping(
kGet, "session/:sessionId/chromium/heap_snapshot",
WrapToCommand("HeapSnapshot",
base::BindRepeating(&ExecuteTakeHeapSnapshot))),
CommandMapping(
kGet, "session/:sessionId/chromium/network_conditions",
WrapToCommand("GetNetworkConditions",
base::BindRepeating(&ExecuteGetNetworkConditions))),
CommandMapping(
kPost, "session/:sessionId/chromium/network_conditions",
WrapToCommand("SetNetworkConditions",
base::BindRepeating(&ExecuteSetNetworkConditions))),
CommandMapping(
kDelete, "session/:sessionId/chromium/network_conditions",
WrapToCommand("DeleteNetworkConditions",
base::BindRepeating(&ExecuteDeleteNetworkConditions))),
CommandMapping(kPost, "session/:sessionId/chromium/send_command",
WrapToCommand("SendCommand",
base::BindRepeating(&ExecuteSendCommand))),
VendorPrefixedSessionCommandMapping(
kPost, "cdp/execute",
WrapToCommand("ExecuteCDP",
base::BindRepeating(&ExecuteSendCommandAndGetResult))),
CommandMapping(
kPost, "session/:sessionId/chromium/send_command_and_get_result",
WrapToCommand("SendCommandAndGetResult",
base::BindRepeating(&ExecuteSendCommandAndGetResult))),
VendorPrefixedSessionCommandMapping(
kPost, "page/freeze",
WrapToCommand("Freeze", base::BindRepeating(&ExecuteFreeze))),
VendorPrefixedSessionCommandMapping(
kPost, "page/resume",
WrapToCommand("Resume", base::BindRepeating(&ExecuteResume))),
VendorPrefixedSessionCommandMapping(
kPost, "cast/set_sink_to_use",
WrapToCommand("SetSinkToUse",
base::BindRepeating(&ExecuteSetSinkToUse))),
VendorPrefixedSessionCommandMapping(
kPost, "cast/start_desktop_mirroring",
WrapToCommand("StartDesktopMirroring",
base::BindRepeating(&ExecuteStartDesktopMirroring))),
VendorPrefixedSessionCommandMapping(
kPost, "cast/start_tab_mirroring",
WrapToCommand("StartTabMirroring",
base::BindRepeating(&ExecuteStartTabMirroring))),
VendorPrefixedSessionCommandMapping(
kPost, "cast/stop_casting",
WrapToCommand("StopCasting",
base::BindRepeating(&ExecuteStopCasting))),
VendorPrefixedSessionCommandMapping(
kGet, "cast/get_sinks",
WrapToCommand("GetSinks", base::BindRepeating(&ExecuteGetSinks))),
VendorPrefixedSessionCommandMapping(
kGet, "cast/get_issue_message",
WrapToCommand("GetIssueMessage",
base::BindRepeating(&ExecuteGetIssueMessage))),
CommandMapping(kGet, "session/:sessionId/alert",
WrapToCommand("IsAlertOpen",
base::BindRepeating(
&ExecuteAlertCommand,
base::BindRepeating(&ExecuteGetAlert)))),
CommandMapping(
kGet, "session/:sessionId/is_loading",
WrapToCommand("IsLoading", base::BindRepeating(&ExecuteIsLoading))),
CommandMapping(
kPost, kSendCommandFromWebSocket,
WrapToCommand("SendCommandFromWebSocket",
base::BindRepeating(&ExecuteSendCommandFromWebSocket))),
};
command_map_ =
std::make_unique<CommandMap>(std::begin(commands), std::end(commands));
static_bidi_command_map_.emplace(
"session.status", base::BindRepeating(&ExecuteBidiSessionStatus));
static_bidi_command_map_.emplace(
"session.new",
base::BindRepeating(&ExecuteBidiSessionNew, &session_thread_map_,
init_session_cmd));
session_bidi_command_map_.emplace(
"session.end",
base::BindRepeating(&ExecuteSessionCommand, &session_thread_map_, "Quit",
base::BindRepeating(&ExecuteBidiSessionEnd), true,
true));
forward_session_command_ = WrapToCommand(
"ForwardBidiCommand", base::BindRepeating(&ForwardBidiCommand));
}
HttpHandler::~HttpHandler() = default;
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;
}
base::WeakPtr<HttpHandler> HttpHandler::WeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
Command HttpHandler::WrapToCommand(const char* name,
const SessionCommand& session_command,
bool w3c_standard_command) {
return base::BindRepeating(&ExecuteSessionCommand, &session_thread_map_, name,
session_command, w3c_standard_command, false);
}
Command HttpHandler::WrapToCommand(const char* name,
const WindowCommand& window_command,
bool w3c_standard_command) {
return WrapToCommand(
name, base::BindRepeating(&ExecuteWindowCommand, window_command),
w3c_standard_command);
}
Command HttpHandler::WrapToCommand(const char* name,
const ElementCommand& element_command,
bool w3c_standard_command) {
return WrapToCommand(
name, base::BindRepeating(&ExecuteElementCommand, element_command),
w3c_standard_command);
}
void HttpHandler::HandleCommand(
const net::HttpServerRequestInfo& request,
const std::string& trimmed_path,
const HttpResponseSenderFunc& send_response_func) {
base::Value::Dict params;
std::string session_id;
CommandMap::const_iterator iter = command_map_->begin();
while (true) {
if (iter == command_map_->end()) {
if (w3cMode(session_id, session_thread_map_)) {
PrepareResponse(
trimmed_path, send_response_func,
Status(kUnknownCommand, "unknown command: " + trimmed_path),
nullptr, session_id, true);
} else {
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, ¶ms)) {
break;
}
++iter;
}
if (request.data.length()) {
std::optional<base::Value> parsed_body = base::JSONReader::Read(
request.data, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
base::Value::Dict* body_params =
parsed_body ? parsed_body->GetIfDict() : nullptr;
if (!body_params) {
if (w3cMode(session_id, session_thread_map_)) {
PrepareResponse(trimmed_path, send_response_func,
Status(kInvalidArgument, "missing command parameters"),
nullptr, session_id, true);
} else {
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.Merge(std::move(*body_params));
} else if (iter->method == kPost &&
w3cMode(session_id, session_thread_map_)) {
PrepareResponse(trimmed_path, send_response_func,
Status(kInvalidArgument, "missing command parameters"),
nullptr, session_id, true);
return;
}
iter->command.Run(params,
internal::IsNewSession(*iter)
? request.GetHeaderValue("host")
: session_id,
base::BindRepeating(&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: %s=%s,platform=%s %s %s",
base::ToLowerASCII(kChromeDriverProductShortName).c_str(),
kChromeDriverVersion, base::SysInfo::OperatingSystemName().c_str(),
base::SysInfo::OperatingSystemVersion().c_str(),
base::SysInfo::OperatingSystemArchitecture().c_str()));
base::Value::Dict error;
error.Set("message", full_status.message());
value = std::make_unique<base::Value>(std::move(error));
}
if (!value)
value = std::make_unique<base::Value>();
base::Value::Dict body_params;
body_params.Set("status", status.code());
body_params.Set("value", base::Value::FromUniquePtrValue(std::move(value)));
body_params.Set("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 = std::make_unique<net::HttpServerResponseInfo>(net::HTTP_OK);
break;
case kElementClickIntercepted:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kElementNotInteractable:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kInvalidArgument:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kInvalidCookieDomain:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kInvalidElementState:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kInvalidSelector:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kInvalidSessionId:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kJavaScriptError:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kMoveTargetOutOfBounds:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kNoSuchAlert:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kNoSuchCookie:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kNoSuchElement:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kNoSuchFrame:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kNoSuchWindow:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kScriptTimeout:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kSessionNotCreated:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kStaleElementReference:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kTimeout:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kUnableToSetCookie:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kUnexpectedAlertOpen:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kUnknownCommand:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kUnknownError:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kUnsupportedOperation:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kTargetDetached:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kElementNotVisible:
case kXPathLookupError:
case kNoSuchExecutionContext:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
break;
case kDisconnected:
case kTabCrashed:
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
case kNoSuchShadowRoot:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
case kDetachedShadowRoot:
response =
std::make_unique<net::HttpServerResponseInfo>(net::HTTP_NOT_FOUND);
break;
default:
DCHECK(false);
response = std::make_unique<net::HttpServerResponseInfo>(
net::HTTP_INTERNAL_SERVER_ERROR);
break;
}
if (!value)
value = std::make_unique<base::Value>();
base::Value::Dict body_params;
if (status.IsError()){
base::Value::Dict* inner_params = body_params.EnsureDict("value");
inner_params->Set("error", StatusCodeToString(status.code()));
inner_params->Set("message", status.message());
inner_params->Set("stacktrace", status.stack_trace());
if (status.code() == kUnexpectedAlertOpen) {
const std::string& message = status.message();
auto first = message.find("{");
auto last = message.find_last_of("}");
if (first == std::string::npos || last == std::string::npos) {
inner_params->SetByDottedPath("data.text", "");
} else {
std::string alert_text = message.substr(first, last - first);
auto colon = alert_text.find(":");
if (colon != std::string::npos && alert_text.size() > (colon + 2))
alert_text = alert_text.substr(colon + 2);
inner_params->SetByDottedPath("data.text", alert_text);
}
}
} else {
body_params.Set("value", base::Value::FromUniquePtrValue(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");
response->AddHeader("cache-control", "no-cache");
return response;
}
void HttpHandler::OnWebSocketRequest(HttpServerInterface* http_server,
int connection_id,
const net::HttpServerRequestInfo& info) {
std::string path = info.path;
std::vector<std::string> path_parts = base::SplitString(
path, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (path_parts.size() == 1 && path_parts[0] == "session") {
OnWebSocketUnboundConnectionRequest(http_server, connection_id, info);
return;
}
if (path_parts.size() == 2 && path_parts[0] == "session") {
std::string session_id = path_parts[1];
OnWebSocketAttachToSessionRequest(http_server, connection_id, session_id,
info);
return;
}
std::string err_msg = "bad request received path " + path;
VLOG(0) << "HttpHandler WebSocketRequest error " << err_msg;
SendWebSocketRejectResponse(
base::BindRepeating(&HttpServerInterface::SendResponse,
base::Unretained(http_server)),
connection_id, net::HTTP_BAD_REQUEST, err_msg);
}
void HttpHandler::CloseConnectionOnCommandThread(
HttpServerInterface* http_server,
int connection_id) {
auto close_connection_on_io_func = base::BindRepeating(
&HttpServerInterface::Close, base::Unretained(http_server));
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(close_connection_on_io_func, connection_id));
}
void HttpHandler::SendForwardedResponseOnCommandThread(
HttpServerInterface* http_server,
int connection_id,
std::string message) {
auto send_response_on_io_func = base::BindRepeating(
[](HttpServerInterface* http_server, int connection_id,
std::string data) {
http_server->SendOverWebSocket(connection_id, data);
},
base::Unretained(http_server));
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(send_response_on_io_func, connection_id,
std::move(message)));
}
void HttpHandler::OnWebSocketAttachToSessionRequest(
HttpServerInterface* http_server,
int connection_id,
const std::string& session_id,
const net::HttpServerRequestInfo& info) {
auto it = session_connection_map_.find(session_id);
if (it == session_connection_map_.end()) {
std::string err_msg = "bad request invalid session id " + session_id;
VLOG(0) << "HttpHandler WebSocketRequest error " << err_msg;
SendWebSocketRejectResponse(
base::BindRepeating(&HttpServerInterface::SendResponse,
base::Unretained(http_server)),
connection_id, net::HTTP_BAD_REQUEST, err_msg);
return;
}
session_connection_map_[session_id].push_back(connection_id);
connection_session_map_[connection_id] = session_id;
auto thread_it = session_thread_map_.find(session_id);
if (thread_it != session_thread_map_.end()) {
auto reply_on_command_thread = base::BindRepeating(
&HttpHandler::SendForwardedResponseOnCommandThread,
weak_ptr_factory_.GetWeakPtr(), http_server, connection_id);
auto close_on_command_thread = base::BindRepeating(
&HttpHandler::CloseConnectionOnCommandThread,
weak_ptr_factory_.GetWeakPtr(), http_server, connection_id);
thread_it->second->thread()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&AddBidiConnectionOnSessionThread, connection_id,
base::BindPostTask(
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(reply_on_command_thread)),
base::BindPostTask(
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(close_on_command_thread))));
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&HttpServerInterface::AcceptWebSocket,
base::Unretained(http_server), connection_id, info));
} else {
std::string err_msg = "session not found session_id=" + session_id;
VLOG(0) << "HttpHandler WebSocketRequest error " << err_msg;
SendWebSocketRejectResponse(
base::BindRepeating(&HttpServerInterface::SendResponse,
base::Unretained(http_server)),
connection_id, net::HTTP_BAD_REQUEST, err_msg);
}
}
void HttpHandler::OnWebSocketUnboundConnectionRequest(
HttpServerInterface* http_server,
int connection_id,
const net::HttpServerRequestInfo& info) {
auto it = connection_session_map_.find(connection_id);
if (it != connection_session_map_.end()) {
std::string err_msg =
"connection is already bound to session_id=" + it->second;
VLOG(0) << "HttpHandler WebSocketRequest error " << err_msg;
SendWebSocketRejectResponse(
base::BindRepeating(&HttpServerInterface::SendResponse,
base::Unretained(http_server)),
connection_id, net::HTTP_BAD_REQUEST, err_msg);
return;
}
session_connection_map_[""].push_back(connection_id);
connection_session_map_[connection_id] = "";
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&HttpServerInterface::AcceptWebSocket,
base::Unretained(http_server), connection_id, info));
}
void HttpHandler::SendResponseOverWebSocket(
HttpServerInterface* http_server,
int connection_id,
const std::optional<base::Value>& maybe_id,
const Status& status,
std::unique_ptr<base::Value> result,
const std::string& session_id,
bool w3c) {
base::Value::Dict response;
if (status.IsOk()) {
if (!result) {
return;
}
response.Set("type", "success");
if (maybe_id.has_value()) {
response.Set("id", maybe_id->Clone());
}
response.Set("result", std::move(*result));
} else {
response = internal::CreateBidiErrorResponse(status, Clone(maybe_id));
}
std::string message;
if (base::JSONWriter::Write(response, &message)) {
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&HttpServerInterface::SendOverWebSocket,
base::Unretained(http_server), connection_id, message));
} else {
LOG(WARNING) << "unable to serialize BiDi response";
}
}
Command HttpHandler::WrapCreateNewSessionCommand(Command command) {
using CommandCallbackWrapper = base::RepeatingCallback<void(
const CommandCallback&, const Status&, std::unique_ptr<base::Value>,
const std::string&, bool)>;
return base::BindRepeating(
[](Command create_and_init, CommandCallbackWrapper callback_to_prepend,
const base::Value::Dict& params, const std::string& session_id,
const CommandCallback& callback) {
create_and_init.Run(params, session_id,
base::BindRepeating(callback_to_prepend, callback));
},
command,
base::BindRepeating(&HttpHandler::OnNewSessionCreated,
weak_ptr_factory_.GetWeakPtr()));
}
void HttpHandler::OnNewSessionCreated(const CommandCallback& next_callback,
const Status& status,
std::unique_ptr<base::Value> result,
const std::string& session_id,
bool w3c) {
base::Value::Dict* dict = result ? result->GetIfDict() : nullptr;
if (status.IsOk() && dict &&
dict->FindByDottedPath("capabilities.webSocketUrl")) {
session_connection_map_.emplace(session_id, std::vector<int>{});
}
next_callback.Run(status, std::move(result), session_id, w3c);
}
void HttpHandler::OnSessionTerminated(std::string session_id) {
session_thread_map_.erase(session_id);
session_connection_map_.erase(session_id);
}
void HttpHandler::OnNewBidiSessionOnCmdThread(
HttpServerInterface* http_server,
int connection_id,
const std::optional<base::Value>& maybe_id,
const Status& status,
std::unique_ptr<base::Value> result,
const std::string& session_id,
bool w3c) {
std::vector<int>& unbound_connections = session_connection_map_[""];
auto conn_it = std::find(unbound_connections.begin(),
unbound_connections.end(), connection_id);
if (conn_it != unbound_connections.end()) {
unbound_connections.erase(conn_it);
}
session_connection_map_.emplace(session_id, std::vector<int>{connection_id});
connection_session_map_.insert_or_assign(connection_id, session_id);
auto reply_on_command_thread = base::BindRepeating(
&HttpHandler::SendForwardedResponseOnCommandThread,
weak_ptr_factory_.GetWeakPtr(), http_server, connection_id);
auto close_on_command_thread = base::BindRepeating(
&HttpHandler::CloseConnectionOnCommandThread,
weak_ptr_factory_.GetWeakPtr(), http_server, connection_id);
auto thread_it = session_thread_map_.find(session_id);
if (thread_it != session_thread_map_.end()) {
thread_it->second->thread()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&AddBidiConnectionOnSessionThread, connection_id,
base::BindPostTask(
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(reply_on_command_thread)),
base::BindPostTask(
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(close_on_command_thread))));
} else {
VLOG(0) << "session thread is not found";
}
SendResponseOverWebSocket(http_server, connection_id, Clone(maybe_id), status,
std::move(result), session_id, w3c);
}
void HttpHandler::OnWebSocketMessage(HttpServerInterface* http_server,
int connection_id,
const std::string& data) {
base::Value::Dict parsed;
Status status = internal::ParseBidiCommand(data, parsed);
auto it = connection_session_map_.find(connection_id);
base::Value* maybe_id_as_value = parsed.Find("id");
std::optional<base::Value> maybe_id =
maybe_id_as_value ? std::make_optional(maybe_id_as_value->Clone())
: std::nullopt;
if (it == connection_session_map_.end()) {
Status invalid_session_error{kInvalidSessionId, "session not found"};
SendResponseOverWebSocket(http_server, connection_id, std::move(maybe_id),
invalid_session_error, nullptr, "", true);
return;
}
std::string* method = parsed.FindString("method");
if (status.IsError()) {
SendResponseOverWebSocket(http_server, connection_id, std::move(maybe_id),
status, nullptr, it->second, true);
return;
}
std::string session_id = it->second;
auto cmd_it = static_bidi_command_map_.find(*method);
if (cmd_it != static_bidi_command_map_.end()) {
CommandCallback callback = base::BindRepeating(
&HttpHandler::SendResponseOverWebSocket, weak_ptr_factory_.GetWeakPtr(),
http_server, connection_id, Clone(maybe_id));
if (*method == "session.new") {
callback = base::BindRepeating(&HttpHandler::OnNewBidiSessionOnCmdThread,
weak_ptr_factory_.GetWeakPtr(),
base::Unretained(http_server),
connection_id, std::move(maybe_id));
}
cmd_it->second.Run(parsed, session_id, std::move(callback));
return;
}
if (session_id.empty()) {
if (kKnownBidiSessionCommands.contains(*method)) {
Status invalid_session_error{kInvalidSessionId, "session not found"};
SendResponseOverWebSocket(http_server, connection_id, std::move(maybe_id),
invalid_session_error, nullptr, "", true);
} else {
Status unknown_static_command = {kUnknownCommand, *method};
SendResponseOverWebSocket(http_server, connection_id, std::move(maybe_id),
unknown_static_command, nullptr, session_id,
true);
}
return;
}
cmd_it = session_bidi_command_map_.find(*method);
if (cmd_it != session_bidi_command_map_.end()) {
CommandCallback callback = base::BindRepeating(
&HttpHandler::SendResponseOverWebSocket, weak_ptr_factory_.GetWeakPtr(),
http_server, connection_id, std::move(maybe_id));
cmd_it->second.Run(parsed, session_id, std::move(callback));
return;
}
base::Value::Dict params;
params.Set("bidiCommand", std::move(parsed));
params.Set("connectionId", connection_id);
forward_session_command_.Run(
params, session_id,
base::BindRepeating(&HttpHandler::SendResponseOverWebSocket,
weak_ptr_factory_.GetWeakPtr(), http_server,
connection_id, std::move(maybe_id)));
}
void HttpHandler::OnWebSocketResponseOnCmdThread(
HttpServerInterface* http_server,
int connection_id,
const std::string& data) {
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&HttpServerInterface::SendOverWebSocket,
base::Unretained(http_server), connection_id, data));
}
void HttpHandler::OnWebSocketResponseOnSessionThread(
HttpServerInterface* http_server,
int connection_id,
const std::string& data) {
cmd_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&HttpHandler::OnWebSocketResponseOnCmdThread, WeakPtr(),
base::Unretained(http_server), connection_id, data));
}
void HttpHandler::OnClose(HttpServerInterface* http_server, int connection_id) {
auto it = connection_session_map_.find(connection_id);
if (it == connection_session_map_.end()) {
return;
}
std::string session_id = it->second;
auto ses_it = session_connection_map_.find(session_id);
if (ses_it == session_connection_map_.end()) {
VLOG(logging::LOGGING_WARNING)
<< "Session related entry is missing in session_connection_map_.";
return;
}
std::vector<int>& bucket = ses_it->second;
auto bucket_it = std::ranges::find(bucket, connection_id);
if (bucket_it == bucket.end()) {
return;
}
bucket.erase(bucket_it);
connection_session_map_.erase(it);
auto thread_it = session_thread_map_.find(session_id);
if (thread_it != session_thread_map_.end()) {
thread_it->second->thread()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&RemoveBidiConnectionOnSessionThread, connection_id));
}
}
void HttpHandler::SendWebSocketRejectResponse(
base::RepeatingCallback<void(int,
const net::HttpServerResponseInfo&,
const net::NetworkTrafficAnnotationTag&)>
send_http_response,
int connection_id,
net::HttpStatusCode code,
const std::string& msg) {
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(send_http_response), connection_id,
CreateWebSocketRejectResponse(code, msg),
TRAFFIC_ANNOTATION_FOR_TESTS));
}
const char internal::kNewSessionPathPattern[] = "session";
bool internal::MatchesCommand(const std::string& method,
const std::string& path,
const CommandMapping& command,
std::string* session_id,
base::Value::Dict* 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::Value::Dict 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<char16_t> output;
url::DecodeURLEscapeSequences(
path_parts[i], url::DecodeURLMode::kUTF8OrIsomorphic, &output);
std::string decoded = base::UTF16ToASCII(output.view());
base::ReplaceSubstringsAfterOffset(&decoded, 0 , "%%" , "%");
if (name == "sessionId")
*session_id = decoded;
else
params.Set(name, decoded);
} else if (command_path_parts[i] != path_parts[i]) {
return false;
}
}
out_params->Merge(std::move(params));
return true;
}
bool internal::IsNewSession(const CommandMapping& command) {
return command.method == kPost &&
command.path_pattern == internal::kNewSessionPathPattern;
}
Status internal::ParseBidiCommand(const std::string& data,
base::Value::Dict& parsed) {
Status status{kOk};
std::optional<base::Value> maybe_bidi_command =
base::JSONReader::Read(data, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
if (!maybe_bidi_command.has_value()) {
return Status{kInvalidArgument, "unable to parse BiDi command: " + data};
}
if (!maybe_bidi_command->is_dict()) {
return Status(kInvalidArgument,
"a JSON dictionary is expected as a BiDi command: " + data);
}
parsed = std::move(maybe_bidi_command->GetDict());
std::optional<double> maybe_id = parsed.FindDouble("id");
if (!maybe_id) {
return Status(kInvalidArgument,
"BiDi command has no 'id' of type js-uint: " + data);
}
std::string* maybe_method = parsed.FindString("method");
if (!maybe_method) {
return Status(kInvalidArgument,
"BiDi command has no 'method' of type string: " + data);
}
base::Value::Dict* maybe_params = parsed.FindDict("params");
if (!maybe_params) {
return Status(kInvalidArgument,
"BiDi command has no 'params' of type dictionary: " + data);
}
return status;
}
base::Value::Dict internal::CreateBidiErrorResponse(
Status status,
std::optional<base::Value> maybe_id) {
base::Value::Dict ret;
ret.Set("type", "error");
ret.Set("message", status.message());
ret.Set("error", StatusCodeToString(status.code()));
if (maybe_id.has_value()) {
ret.Set("id", std::move(*maybe_id));
}
return ret;
}