1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/test/chromedriver/server/http_handler.h"
7 #include "base/json/json_reader.h"
8 #include "base/json/json_writer.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/string_util.h"
11 #include "base/strings/string_split.h"
12 #include "base/values.h"
13 #include "chrome/test/chromedriver/chrome/status.h"
14 #include "chrome/test/chromedriver/command_executor.h"
15 #include "chrome/test/chromedriver/command_names.h"
16 #include "chrome/test/chromedriver/server/http_response.h"
17 #include "chrome/test/chromedriver/util.h"
21 const char kShutdownPath
[] = "shutdown";
25 HttpRequest::HttpRequest(HttpMethod method
,
26 const std::string
& path
,
27 const std::string
& body
)
28 : method(method
), path(path
), body(body
) {}
30 HttpRequest::~HttpRequest() {}
32 CommandMapping::CommandMapping(HttpMethod method
,
33 const std::string
& path_pattern
,
34 const std::string
& name
)
35 : method(method
), path_pattern(path_pattern
), name(name
) {}
37 CommandMapping::~CommandMapping() {}
40 scoped_ptr
<HttpHandler::CommandMap
> HttpHandler::CreateCommandMap() {
41 CommandMapping commands
[] = {
42 CommandMapping(kPost
, "session", CommandNames::kNewSession
),
43 CommandMapping(kGet
, "session/:sessionId",
44 CommandNames::kGetSessionCapabilities
),
45 CommandMapping(kDelete
, "session/:sessionId", CommandNames::kQuit
),
46 CommandMapping(kGet
, "session/:sessionId/window_handle",
47 CommandNames::kGetCurrentWindowHandle
),
48 CommandMapping(kGet
, "session/:sessionId/window_handles",
49 CommandNames::kGetWindowHandles
),
50 CommandMapping(kPost
, "session/:sessionId/url", CommandNames::kGet
),
51 CommandMapping(kGet
, "session/:sessionId/alert", CommandNames::kGetAlert
),
52 CommandMapping(kPost
, "session/:sessionId/dismiss_alert",
53 CommandNames::kDismissAlert
),
54 CommandMapping(kPost
, "session/:sessionId/accept_alert",
55 CommandNames::kAcceptAlert
),
56 CommandMapping(kGet
, "session/:sessionId/alert_text",
57 CommandNames::kGetAlertText
),
58 CommandMapping(kPost
, "session/:sessionId/alert_text",
59 CommandNames::kSetAlertValue
),
60 CommandMapping(kPost
, "session/:sessionId/forward",
61 CommandNames::kGoForward
),
62 CommandMapping(kPost
, "session/:sessionId/back", CommandNames::kGoBack
),
63 CommandMapping(kPost
, "session/:sessionId/refresh",
64 CommandNames::kRefresh
),
65 CommandMapping(kPost
, "session/:sessionId/execute",
66 CommandNames::kExecuteScript
),
67 CommandMapping(kPost
, "session/:sessionId/execute_async",
68 CommandNames::kExecuteAsyncScript
),
69 CommandMapping(kGet
, "session/:sessionId/url",
70 CommandNames::kGetCurrentUrl
),
71 CommandMapping(kGet
, "session/:sessionId/title", CommandNames::kGetTitle
),
72 CommandMapping(kGet
, "session/:sessionId/source",
73 CommandNames::kGetPageSource
),
74 CommandMapping(kGet
, "session/:sessionId/screenshot",
75 CommandNames::kScreenshot
),
76 CommandMapping(kPost
, "session/:sessionId/visible",
77 CommandNames::kSetBrowserVisible
),
78 CommandMapping(kGet
, "session/:sessionId/visible",
79 CommandNames::kIsBrowserVisible
),
80 CommandMapping(kPost
, "session/:sessionId/element",
81 CommandNames::kFindElement
),
82 CommandMapping(kPost
, "session/:sessionId/elements",
83 CommandNames::kFindElements
),
84 CommandMapping(kPost
, "session/:sessionId/element/active",
85 CommandNames::kGetActiveElement
),
86 CommandMapping(kPost
, "session/:sessionId/element/:id/element",
87 CommandNames::kFindChildElement
),
88 CommandMapping(kPost
, "session/:sessionId/element/:id/elements",
89 CommandNames::kFindChildElements
),
90 CommandMapping(kPost
, "session/:sessionId/element/:id/click",
91 CommandNames::kClickElement
),
92 CommandMapping(kPost
, "session/:sessionId/element/:id/clear",
93 CommandNames::kClearElement
),
94 CommandMapping(kPost
, "session/:sessionId/element/:id/submit",
95 CommandNames::kSubmitElement
),
96 CommandMapping(kGet
, "session/:sessionId/element/:id/text",
97 CommandNames::kGetElementText
),
98 CommandMapping(kPost
, "session/:sessionId/element/:id/value",
99 CommandNames::kSendKeysToElement
),
100 CommandMapping(kPost
, "session/:sessionId/file",
101 CommandNames::kUploadFile
),
102 CommandMapping(kGet
, "session/:sessionId/element/:id/value",
103 CommandNames::kGetElementValue
),
104 CommandMapping(kGet
, "session/:sessionId/element/:id/name",
105 CommandNames::kGetElementTagName
),
106 CommandMapping(kGet
, "session/:sessionId/element/:id/selected",
107 CommandNames::kIsElementSelected
),
108 CommandMapping(kGet
, "session/:sessionId/element/:id/enabled",
109 CommandNames::kIsElementEnabled
),
110 CommandMapping(kGet
, "session/:sessionId/element/:id/displayed",
111 CommandNames::kIsElementDisplayed
),
112 CommandMapping(kPost
, "session/:sessionId/element/:id/hover",
113 CommandNames::kHoverOverElement
),
114 CommandMapping(kGet
, "session/:sessionId/element/:id/location",
115 CommandNames::kGetElementLocation
),
116 CommandMapping(kGet
, "session/:sessionId/element/:id/location_in_view",
117 CommandNames::kGetElementLocationOnceScrolledIntoView
),
118 CommandMapping(kGet
, "session/:sessionId/element/:id/size",
119 CommandNames::kGetElementSize
),
120 CommandMapping(kGet
, "session/:sessionId/element/:id/attribute/:name",
121 CommandNames::kGetElementAttribute
),
122 CommandMapping(kGet
, "session/:sessionId/element/:id/equals/:other",
123 CommandNames::kElementEquals
),
124 CommandMapping(kGet
, "session/:sessionId/cookie",
125 CommandNames::kGetCookies
),
126 CommandMapping(kPost
, "session/:sessionId/cookie",
127 CommandNames::kAddCookie
),
128 CommandMapping(kDelete
, "session/:sessionId/cookie",
129 CommandNames::kDeleteAllCookies
),
130 CommandMapping(kDelete
, "session/:sessionId/cookie/:name",
131 CommandNames::kDeleteCookie
),
132 CommandMapping(kPost
, "session/:sessionId/frame",
133 CommandNames::kSwitchToFrame
),
134 CommandMapping(kPost
, "session/:sessionId/window",
135 CommandNames::kSwitchToWindow
),
136 CommandMapping(kGet
, "session/:sessionId/window/:windowHandle/size",
137 CommandNames::kGetWindowSize
),
138 CommandMapping(kGet
, "session/:sessionId/window/:windowHandle/position",
139 CommandNames::kGetWindowPosition
),
140 CommandMapping(kPost
, "session/:sessionId/window/:windowHandle/size",
141 CommandNames::kSetWindowSize
),
142 CommandMapping(kPost
, "session/:sessionId/window/:windowHandle/position",
143 CommandNames::kSetWindowPosition
),
144 CommandMapping(kPost
, "session/:sessionId/window/:windowHandle/maximize",
145 CommandNames::kMaximizeWindow
),
146 CommandMapping(kDelete
, "session/:sessionId/window",
147 CommandNames::kClose
),
148 CommandMapping(kPost
, "session/:sessionId/element/:id/drag",
149 CommandNames::kDragElement
),
150 CommandMapping(kGet
, "session/:sessionId/element/:id/css/:propertyName",
151 CommandNames::kGetElementValueOfCssProperty
),
152 CommandMapping(kPost
, "session/:sessionId/timeouts/implicit_wait",
153 CommandNames::kImplicitlyWait
),
154 CommandMapping(kPost
, "session/:sessionId/timeouts/async_script",
155 CommandNames::kSetScriptTimeout
),
156 CommandMapping(kPost
, "session/:sessionId/timeouts",
157 CommandNames::kSetTimeout
),
158 CommandMapping(kPost
, "session/:sessionId/execute_sql",
159 CommandNames::kExecuteSQL
),
160 CommandMapping(kGet
, "session/:sessionId/location",
161 CommandNames::kGetLocation
),
162 CommandMapping(kPost
, "session/:sessionId/location",
163 CommandNames::kSetLocation
),
164 CommandMapping(kGet
, "session/:sessionId/application_cache/status",
165 CommandNames::kGetStatus
),
166 CommandMapping(kGet
, "session/:sessionId/browser_connection",
167 CommandNames::kIsBrowserOnline
),
168 CommandMapping(kPost
, "session/:sessionId/browser_connection",
169 CommandNames::kSetBrowserOnline
),
170 CommandMapping(kGet
, "session/:sessionId/local_storage/key/:key",
171 CommandNames::kGetLocalStorageItem
),
172 CommandMapping(kDelete
, "session/:sessionId/local_storage/key/:key",
173 CommandNames::kRemoveLocalStorageItem
),
174 CommandMapping(kGet
, "session/:sessionId/local_storage",
175 CommandNames::kGetLocalStorageKeys
),
176 CommandMapping(kPost
, "session/:sessionId/local_storage",
177 CommandNames::kSetLocalStorageItem
),
178 CommandMapping(kDelete
, "session/:sessionId/local_storage",
179 CommandNames::kClearLocalStorage
),
180 CommandMapping(kGet
, "session/:sessionId/local_storage/size",
181 CommandNames::kGetLocalStorageSize
),
182 CommandMapping(kGet
, "session/:sessionId/session_storage/key/:key",
183 CommandNames::kGetSessionStorageItem
),
184 CommandMapping(kDelete
, "session/:sessionId/session_storage/key/:key",
185 CommandNames::kRemoveSessionStorageItem
),
186 CommandMapping(kGet
, "session/:sessionId/session_storage",
187 CommandNames::kGetSessionStorageKey
),
188 CommandMapping(kPost
, "session/:sessionId/session_storage",
189 CommandNames::kSetSessionStorageItem
),
190 CommandMapping(kDelete
, "session/:sessionId/session_storage",
191 CommandNames::kClearSessionStorage
),
192 CommandMapping(kGet
, "session/:sessionId/session_storage/size",
193 CommandNames::kGetSessionStorageSize
),
194 CommandMapping(kGet
, "session/:sessionId/orientation",
195 CommandNames::kGetScreenOrientation
),
196 CommandMapping(kPost
, "session/:sessionId/orientation",
197 CommandNames::kSetScreenOrientation
),
198 CommandMapping(kPost
, "session/:sessionId/click",
199 CommandNames::kMouseClick
),
200 CommandMapping(kPost
, "session/:sessionId/doubleclick",
201 CommandNames::kMouseDoubleClick
),
202 CommandMapping(kPost
, "session/:sessionId/buttondown",
203 CommandNames::kMouseButtonDown
),
204 CommandMapping(kPost
, "session/:sessionId/buttonup",
205 CommandNames::kMouseButtonUp
),
206 CommandMapping(kPost
, "session/:sessionId/moveto",
207 CommandNames::kMouseMoveTo
),
208 CommandMapping(kPost
, "session/:sessionId/keys",
209 CommandNames::kSendKeysToActiveElement
),
210 CommandMapping(kGet
, "session/:sessionId/ime/available_engines",
211 CommandNames::kImeGetAvailableEngines
),
212 CommandMapping(kGet
, "session/:sessionId/ime/active_engine",
213 CommandNames::kImeGetActiveEngine
),
214 CommandMapping(kGet
, "session/:sessionId/ime/activated",
215 CommandNames::kImeIsActivated
),
216 CommandMapping(kPost
, "session/:sessionId/ime/deactivate",
217 CommandNames::kImeDeactivate
),
218 CommandMapping(kPost
, "session/:sessionId/ime/activate",
219 CommandNames::kImeActivateEngine
),
220 CommandMapping(kPost
, "session/:sessionId/touch/click",
221 CommandNames::kTouchSingleTap
),
222 CommandMapping(kPost
, "session/:sessionId/touch/down",
223 CommandNames::kTouchDown
),
224 CommandMapping(kPost
, "session/:sessionId/touch/up",
225 CommandNames::kTouchUp
),
226 CommandMapping(kPost
, "session/:sessionId/touch/move",
227 CommandNames::kTouchMove
),
228 CommandMapping(kPost
, "session/:sessionId/touch/scroll",
229 CommandNames::kTouchScroll
),
230 CommandMapping(kPost
, "session/:sessionId/touch/doubleclick",
231 CommandNames::kTouchDoubleTap
),
232 CommandMapping(kPost
, "session/:sessionId/touch/longclick",
233 CommandNames::kTouchLongPress
),
234 CommandMapping(kPost
, "session/:sessionId/touch/flick",
235 CommandNames::kTouchFlick
),
236 CommandMapping(kPost
, "session/:sessionId/log", CommandNames::kGetLog
),
237 CommandMapping(kGet
, "session/:sessionId/log/types",
238 CommandNames::kGetAvailableLogTypes
),
239 CommandMapping(kPost
, "logs", CommandNames::kGetSessionLogs
),
240 CommandMapping(kGet
, "status", CommandNames::kStatus
),
242 // Custom Chrome commands:
243 // Allow quit all to be called with GET or POST.
244 CommandMapping(kGet
, kShutdownPath
, CommandNames::kQuitAll
),
245 CommandMapping(kPost
, kShutdownPath
, CommandNames::kQuitAll
),
247 return scoped_ptr
<CommandMap
>(
248 new CommandMap(commands
, commands
+ arraysize(commands
)));
251 HttpHandler::HttpHandler(scoped_ptr
<CommandExecutor
> executor
,
252 scoped_ptr
<CommandMap
> command_map
,
253 const std::string
& url_base
)
254 : executor_(executor
.Pass()),
255 command_map_(command_map
.Pass()),
256 url_base_(url_base
) {
260 HttpHandler::~HttpHandler() {}
262 void HttpHandler::Handle(const HttpRequest
& request
,
263 HttpResponse
* response
) {
264 std::string path
= request
.path
;
265 if (!StartsWithASCII(path
, url_base_
, true)) {
266 *response
= HttpResponse(HttpResponse::kBadRequest
);
267 response
->set_body("unhandled request");
271 path
.erase(0, url_base_
.length());
273 if (!HandleWebDriverCommand(request
, path
, response
)) {
274 *response
= HttpResponse(HttpResponse::kNotFound
);
275 response
->set_body("unknown command: " + path
);
280 bool HttpHandler::ShouldShutdown(const HttpRequest
& request
) {
281 return request
.path
== url_base_
+ kShutdownPath
;
284 bool HttpHandler::HandleWebDriverCommand(
285 const HttpRequest
& request
,
286 const std::string
& trimmed_path
,
287 HttpResponse
* response
) {
288 base::DictionaryValue params
;
289 std::string session_id
;
290 CommandMap::const_iterator iter
= command_map_
->begin();
292 if (iter
== command_map_
->end()) {
295 if (internal::MatchesCommand(
296 request
.method
, trimmed_path
, *iter
, &session_id
, ¶ms
)) {
302 if (request
.method
== kPost
&& request
.body
.length()) {
303 base::DictionaryValue
* body_params
;
304 scoped_ptr
<base::Value
> parsed_body(base::JSONReader::Read(request
.body
));
305 if (!parsed_body
|| !parsed_body
->GetAsDictionary(&body_params
)) {
306 *response
= HttpResponse(HttpResponse::kBadRequest
);
307 response
->set_body("missing command parameters");
310 params
.MergeDictionary(body_params
);
313 StatusCode status
= kOk
;
314 scoped_ptr
<base::Value
> value
;
315 std::string out_session_id
;
316 executor_
->ExecuteCommand(
317 iter
->name
, params
, session_id
, &status
, &value
, &out_session_id
);
319 if (status
== kUnknownCommand
) {
320 *response
= HttpResponse(HttpResponse::kNotImplemented
);
321 response
->set_body("unimplemented command: " + iter
->name
);
325 if (iter
->name
== CommandNames::kNewSession
&& status
== kOk
) {
326 // Creating a session involves a HTTP request to /session, which is
327 // supposed to redirect to /session/:sessionId, which returns the
329 *response
= HttpResponse(HttpResponse::kSeeOther
);
330 response
->AddHeader("Location", url_base_
+ "session/" + out_session_id
);
334 base::DictionaryValue body_params
;
335 body_params
.SetInteger("status", status
);
336 body_params
.Set("value", value
.release());
337 body_params
.SetString("sessionId", out_session_id
);
339 base::JSONWriter::WriteWithOptions(
340 &body_params
, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION
,
342 *response
= HttpResponse(HttpResponse::kOk
);
343 response
->SetMimeType("application/json; charset=utf-8");
344 response
->set_body(body
);
350 bool MatchesCommand(HttpMethod method
,
351 const std::string
& path
,
352 const CommandMapping
& command
,
353 std::string
* session_id
,
354 base::DictionaryValue
* out_params
) {
355 if (method
!= command
.method
)
358 std::vector
<std::string
> path_parts
;
359 base::SplitString(path
, '/', &path_parts
);
360 std::vector
<std::string
> command_path_parts
;
361 base::SplitString(command
.path_pattern
, '/', &command_path_parts
);
362 if (path_parts
.size() != command_path_parts
.size())
365 base::DictionaryValue params
;
366 for (size_t i
= 0; i
< path_parts
.size(); ++i
) {
367 CHECK(command_path_parts
[i
].length());
368 if (command_path_parts
[i
][0] == ':') {
369 std::string name
= command_path_parts
[i
];
371 CHECK(name
.length());
372 if (name
== "sessionId")
373 *session_id
= path_parts
[i
];
375 params
.SetString(name
, path_parts
[i
]);
376 } else if (command_path_parts
[i
] != path_parts
[i
]) {
380 out_params
->MergeDictionary(¶ms
);