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.
4
5 #include "chrome/test/chromedriver/session_commands.h"
6
7 #include <list>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/file_util.h"
12 #include "base/logging.h" // For CHECK macros.
13 #include "base/memory/ref_counted.h"
14 #include "base/message_loop/message_loop_proxy.h"
15 #include "base/synchronization/lock.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "base/values.h"
18 #include "chrome/test/chromedriver/basic_types.h"
19 #include "chrome/test/chromedriver/capabilities.h"
20 #include "chrome/test/chromedriver/chrome/automation_extension.h"
21 #include "chrome/test/chromedriver/chrome/chrome.h"
22 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
23 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
24 #include "chrome/test/chromedriver/chrome/device_manager.h"
25 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
26 #include "chrome/test/chromedriver/chrome/geoposition.h"
27 #include "chrome/test/chromedriver/chrome/status.h"
28 #include "chrome/test/chromedriver/chrome/web_view.h"
29 #include "chrome/test/chromedriver/chrome_launcher.h"
30 #include "chrome/test/chromedriver/logging.h"
31 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
32 #include "chrome/test/chromedriver/session.h"
33 #include "chrome/test/chromedriver/util.h"
34 #include "chrome/test/chromedriver/version.h"
35
36 namespace {
37
38 const char kWindowHandlePrefix[] = "CDwindow-";
39
WebViewIdToWindowHandle(const std::string & web_view_id)40 std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
41 return kWindowHandlePrefix + web_view_id;
42 }
43
WindowHandleToWebViewId(const std::string & window_handle,std::string * web_view_id)44 bool WindowHandleToWebViewId(const std::string& window_handle,
45 std::string* web_view_id) {
46 if (window_handle.find(kWindowHandlePrefix) != 0u)
47 return false;
48 *web_view_id = window_handle.substr(
49 std::string(kWindowHandlePrefix).length());
50 return true;
51 }
52
53 } // namespace
54
InitSessionParams(scoped_refptr<URLRequestContextGetter> context_getter,const SyncWebSocketFactory & socket_factory,DeviceManager * device_manager,PortServer * port_server,PortManager * port_manager)55 InitSessionParams::InitSessionParams(
56 scoped_refptr<URLRequestContextGetter> context_getter,
57 const SyncWebSocketFactory& socket_factory,
58 DeviceManager* device_manager,
59 PortServer* port_server,
60 PortManager* port_manager)
61 : context_getter(context_getter),
62 socket_factory(socket_factory),
63 device_manager(device_manager),
64 port_server(port_server),
65 port_manager(port_manager) {}
66
~InitSessionParams()67 InitSessionParams::~InitSessionParams() {}
68
69 namespace {
70
CreateCapabilities(Chrome * chrome)71 scoped_ptr<base::DictionaryValue> CreateCapabilities(Chrome* chrome) {
72 scoped_ptr<base::DictionaryValue> caps(new base::DictionaryValue());
73 caps->SetString("browserName", "chrome");
74 caps->SetString("version", chrome->GetVersion());
75 caps->SetString("chrome.chromedriverVersion", kChromeDriverVersion);
76 caps->SetString("platform", chrome->GetOperatingSystemName());
77 caps->SetBoolean("javascriptEnabled", true);
78 caps->SetBoolean("takesScreenshot", true);
79 caps->SetBoolean("takesHeapSnapshot", true);
80 caps->SetBoolean("handlesAlerts", true);
81 caps->SetBoolean("databaseEnabled", false);
82 caps->SetBoolean("locationContextEnabled", true);
83 caps->SetBoolean("applicationCacheEnabled", false);
84 caps->SetBoolean("browserConnectionEnabled", false);
85 caps->SetBoolean("cssSelectorsEnabled", true);
86 caps->SetBoolean("webStorageEnabled", true);
87 caps->SetBoolean("rotatable", false);
88 caps->SetBoolean("acceptSslCerts", true);
89 caps->SetBoolean("nativeEvents", true);
90 scoped_ptr<base::DictionaryValue> chrome_caps(new base::DictionaryValue());
91 if (chrome->GetAsDesktop()) {
92 chrome_caps->SetString(
93 "userDataDir",
94 chrome->GetAsDesktop()->command().GetSwitchValueNative(
95 "user-data-dir"));
96 }
97 caps->Set("chrome", chrome_caps.release());
98 return caps.Pass();
99 }
100
101
InitSessionHelper(const InitSessionParams & bound_params,Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)102 Status InitSessionHelper(
103 const InitSessionParams& bound_params,
104 Session* session,
105 const base::DictionaryValue& params,
106 scoped_ptr<base::Value>* value) {
107 session->driver_log.reset(
108 new WebDriverLog(WebDriverLog::kDriverType, Log::kAll));
109 const base::DictionaryValue* desired_caps;
110 if (!params.GetDictionary("desiredCapabilities", &desired_caps))
111 return Status(kUnknownError, "cannot find dict 'desiredCapabilities'");
112
113 Capabilities capabilities;
114 Status status = capabilities.Parse(*desired_caps);
115 if (status.IsError())
116 return status;
117
118 Log::Level driver_level = Log::kWarning;
119 if (capabilities.logging_prefs.count(WebDriverLog::kDriverType))
120 driver_level = capabilities.logging_prefs[WebDriverLog::kDriverType];
121 session->driver_log->set_min_level(driver_level);
122
123 // Create Log's and DevToolsEventListener's for ones that are DevTools-based.
124 // Session will own the Log's, Chrome will own the listeners.
125 ScopedVector<DevToolsEventListener> devtools_event_listeners;
126 status = CreateLogs(capabilities,
127 &session->devtools_logs,
128 &devtools_event_listeners);
129 if (status.IsError())
130 return status;
131
132 status = LaunchChrome(bound_params.context_getter.get(),
133 bound_params.socket_factory,
134 bound_params.device_manager,
135 bound_params.port_server,
136 bound_params.port_manager,
137 capabilities,
138 devtools_event_listeners,
139 &session->chrome);
140 if (status.IsError())
141 return status;
142
143 std::list<std::string> web_view_ids;
144 status = session->chrome->GetWebViewIds(&web_view_ids);
145 if (status.IsError() || web_view_ids.empty()) {
146 return status.IsError() ? status :
147 Status(kUnknownError, "unable to discover open window in chrome");
148 }
149
150 session->window = web_view_ids.front();
151 session->detach = capabilities.detach;
152 session->force_devtools_screenshot = capabilities.force_devtools_screenshot;
153 session->capabilities = CreateCapabilities(session->chrome.get());
154 value->reset(session->capabilities->DeepCopy());
155 return Status(kOk);
156 }
157
158 } // namespace
159
ExecuteInitSession(const InitSessionParams & bound_params,Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)160 Status ExecuteInitSession(
161 const InitSessionParams& bound_params,
162 Session* session,
163 const base::DictionaryValue& params,
164 scoped_ptr<base::Value>* value) {
165 Status status = InitSessionHelper(bound_params, session, params, value);
166 if (status.IsError())
167 session->quit = true;
168 return status;
169 }
170
ExecuteQuit(bool allow_detach,Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)171 Status ExecuteQuit(
172 bool allow_detach,
173 Session* session,
174 const base::DictionaryValue& params,
175 scoped_ptr<base::Value>* value) {
176 session->quit = true;
177 if (allow_detach && session->detach)
178 return Status(kOk);
179 else
180 return session->chrome->Quit();
181 }
182
ExecuteGetSessionCapabilities(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)183 Status ExecuteGetSessionCapabilities(
184 Session* session,
185 const base::DictionaryValue& params,
186 scoped_ptr<base::Value>* value) {
187 value->reset(session->capabilities->DeepCopy());
188 return Status(kOk);
189 }
190
ExecuteGetCurrentWindowHandle(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)191 Status ExecuteGetCurrentWindowHandle(
192 Session* session,
193 const base::DictionaryValue& params,
194 scoped_ptr<base::Value>* value) {
195 WebView* web_view = NULL;
196 Status status = session->GetTargetWindow(&web_view);
197 if (status.IsError())
198 return status;
199
200 value->reset(new StringValue(WebViewIdToWindowHandle(web_view->GetId())));
201 return Status(kOk);
202 }
203
ExecuteClose(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)204 Status ExecuteClose(
205 Session* session,
206 const base::DictionaryValue& params,
207 scoped_ptr<base::Value>* value) {
208 std::list<std::string> web_view_ids;
209 Status status = session->chrome->GetWebViewIds(&web_view_ids);
210 if (status.IsError())
211 return status;
212 bool is_last_web_view = web_view_ids.size() == 1u;
213 web_view_ids.clear();
214
215 WebView* web_view = NULL;
216 status = session->GetTargetWindow(&web_view);
217 if (status.IsError())
218 return status;
219
220 status = session->chrome->CloseWebView(web_view->GetId());
221 if (status.IsError())
222 return status;
223
224 status = session->chrome->GetWebViewIds(&web_view_ids);
225 if ((status.code() == kChromeNotReachable && is_last_web_view) ||
226 (status.IsOk() && web_view_ids.empty())) {
227 // If no window is open, close is the equivalent of calling "quit".
228 session->quit = true;
229 return session->chrome->Quit();
230 }
231
232 return status;
233 }
234
ExecuteGetWindowHandles(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)235 Status ExecuteGetWindowHandles(
236 Session* session,
237 const base::DictionaryValue& params,
238 scoped_ptr<base::Value>* value) {
239 std::list<std::string> web_view_ids;
240 Status status = session->chrome->GetWebViewIds(&web_view_ids);
241 if (status.IsError())
242 return status;
243 scoped_ptr<base::ListValue> window_ids(new base::ListValue());
244 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
245 it != web_view_ids.end(); ++it) {
246 window_ids->AppendString(WebViewIdToWindowHandle(*it));
247 }
248 value->reset(window_ids.release());
249 return Status(kOk);
250 }
251
ExecuteSwitchToWindow(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)252 Status ExecuteSwitchToWindow(
253 Session* session,
254 const base::DictionaryValue& params,
255 scoped_ptr<base::Value>* value) {
256 std::string name;
257 if (!params.GetString("name", &name) || name.empty())
258 return Status(kUnknownError, "'name' must be a nonempty string");
259
260 std::list<std::string> web_view_ids;
261 Status status = session->chrome->GetWebViewIds(&web_view_ids);
262 if (status.IsError())
263 return status;
264
265 std::string web_view_id;
266 bool found = false;
267 if (WindowHandleToWebViewId(name, &web_view_id)) {
268 // Check if any web_view matches |web_view_id|.
269 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
270 it != web_view_ids.end(); ++it) {
271 if (*it == web_view_id) {
272 found = true;
273 break;
274 }
275 }
276 } else {
277 // Check if any of the tab window names match |name|.
278 const char* kGetWindowNameScript = "function() { return window.name; }";
279 base::ListValue args;
280 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
281 it != web_view_ids.end(); ++it) {
282 scoped_ptr<base::Value> result;
283 WebView* web_view;
284 status = session->chrome->GetWebViewById(*it, &web_view);
285 if (status.IsError())
286 return status;
287 status = web_view->ConnectIfNecessary();
288 if (status.IsError())
289 return status;
290 status = web_view->CallFunction(
291 std::string(), kGetWindowNameScript, args, &result);
292 if (status.IsError())
293 return status;
294 std::string window_name;
295 if (!result->GetAsString(&window_name))
296 return Status(kUnknownError, "failed to get window name");
297 if (window_name == name) {
298 web_view_id = *it;
299 found = true;
300 break;
301 }
302 }
303 }
304
305 if (!found)
306 return Status(kNoSuchWindow);
307
308 if (session->overridden_geoposition) {
309 WebView* web_view;
310 status = session->chrome->GetWebViewById(web_view_id, &web_view);
311 if (status.IsError())
312 return status;
313 status = web_view->ConnectIfNecessary();
314 if (status.IsError())
315 return status;
316 status = web_view->OverrideGeolocation(*session->overridden_geoposition);
317 if (status.IsError())
318 return status;
319 }
320
321 session->window = web_view_id;
322 session->SwitchToTopFrame();
323 session->mouse_position = WebPoint(0, 0);
324 return Status(kOk);
325 }
326
ExecuteSetTimeout(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)327 Status ExecuteSetTimeout(
328 Session* session,
329 const base::DictionaryValue& params,
330 scoped_ptr<base::Value>* value) {
331 double ms_double;
332 if (!params.GetDouble("ms", &ms_double))
333 return Status(kUnknownError, "'ms' must be a double");
334 std::string type;
335 if (!params.GetString("type", &type))
336 return Status(kUnknownError, "'type' must be a string");
337
338 base::TimeDelta timeout =
339 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double));
340 // TODO(frankf): implicit and script timeout should be cleared
341 // if negative timeout is specified.
342 if (type == "implicit") {
343 session->implicit_wait = timeout;
344 } else if (type == "script") {
345 session->script_timeout = timeout;
346 } else if (type == "page load") {
347 session->page_load_timeout =
348 ((timeout < base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
349 : timeout);
350 } else {
351 return Status(kUnknownError, "unknown type of timeout:" + type);
352 }
353 return Status(kOk);
354 }
355
ExecuteSetScriptTimeout(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)356 Status ExecuteSetScriptTimeout(
357 Session* session,
358 const base::DictionaryValue& params,
359 scoped_ptr<base::Value>* value) {
360 double ms;
361 if (!params.GetDouble("ms", &ms) || ms < 0)
362 return Status(kUnknownError, "'ms' must be a non-negative number");
363 session->script_timeout =
364 base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
365 return Status(kOk);
366 }
367
ExecuteImplicitlyWait(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)368 Status ExecuteImplicitlyWait(
369 Session* session,
370 const base::DictionaryValue& params,
371 scoped_ptr<base::Value>* value) {
372 double ms;
373 if (!params.GetDouble("ms", &ms) || ms < 0)
374 return Status(kUnknownError, "'ms' must be a non-negative number");
375 session->implicit_wait =
376 base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
377 return Status(kOk);
378 }
379
ExecuteIsLoading(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)380 Status ExecuteIsLoading(
381 Session* session,
382 const base::DictionaryValue& params,
383 scoped_ptr<base::Value>* value) {
384 WebView* web_view = NULL;
385 Status status = session->GetTargetWindow(&web_view);
386 if (status.IsError())
387 return status;
388
389 status = web_view->ConnectIfNecessary();
390 if (status.IsError())
391 return status;
392
393 bool is_pending;
394 status = web_view->IsPendingNavigation(
395 session->GetCurrentFrameId(), &is_pending);
396 if (status.IsError())
397 return status;
398 value->reset(new base::FundamentalValue(is_pending));
399 return Status(kOk);
400 }
401
ExecuteGetLocation(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)402 Status ExecuteGetLocation(
403 Session* session,
404 const base::DictionaryValue& params,
405 scoped_ptr<base::Value>* value) {
406 if (!session->overridden_geoposition) {
407 return Status(kUnknownError,
408 "Location must be set before it can be retrieved");
409 }
410 base::DictionaryValue location;
411 location.SetDouble("latitude", session->overridden_geoposition->latitude);
412 location.SetDouble("longitude", session->overridden_geoposition->longitude);
413 location.SetDouble("accuracy", session->overridden_geoposition->accuracy);
414 // Set a dummy altitude to make WebDriver clients happy.
415 // https://code.google.com/p/chromedriver/issues/detail?id=281
416 location.SetDouble("altitude", 0);
417 value->reset(location.DeepCopy());
418 return Status(kOk);
419 }
420
ExecuteGetWindowPosition(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)421 Status ExecuteGetWindowPosition(
422 Session* session,
423 const base::DictionaryValue& params,
424 scoped_ptr<base::Value>* value) {
425 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
426 if (!desktop) {
427 return Status(
428 kUnknownError,
429 "command only supported for desktop Chrome without debuggerAddress");
430 }
431
432 AutomationExtension* extension = NULL;
433 Status status = desktop->GetAutomationExtension(&extension);
434 if (status.IsError())
435 return status;
436
437 int x, y;
438 status = extension->GetWindowPosition(&x, &y);
439 if (status.IsError())
440 return status;
441
442 base::DictionaryValue position;
443 position.SetInteger("x", x);
444 position.SetInteger("y", y);
445 value->reset(position.DeepCopy());
446 return Status(kOk);
447 }
448
ExecuteSetWindowPosition(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)449 Status ExecuteSetWindowPosition(
450 Session* session,
451 const base::DictionaryValue& params,
452 scoped_ptr<base::Value>* value) {
453 double x, y;
454 if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y))
455 return Status(kUnknownError, "missing or invalid 'x' or 'y'");
456
457 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
458 if (!desktop) {
459 return Status(
460 kUnknownError,
461 "command only supported for desktop Chrome without debuggerAddress");
462 }
463
464 AutomationExtension* extension = NULL;
465 Status status = desktop->GetAutomationExtension(&extension);
466 if (status.IsError())
467 return status;
468
469 return extension->SetWindowPosition(static_cast<int>(x), static_cast<int>(y));
470 }
471
ExecuteGetWindowSize(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)472 Status ExecuteGetWindowSize(
473 Session* session,
474 const base::DictionaryValue& params,
475 scoped_ptr<base::Value>* value) {
476 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
477 if (!desktop) {
478 return Status(
479 kUnknownError,
480 "command only supported for desktop Chrome without debuggerAddress");
481 }
482
483 AutomationExtension* extension = NULL;
484 Status status = desktop->GetAutomationExtension(&extension);
485 if (status.IsError())
486 return status;
487
488 int width, height;
489 status = extension->GetWindowSize(&width, &height);
490 if (status.IsError())
491 return status;
492
493 base::DictionaryValue size;
494 size.SetInteger("width", width);
495 size.SetInteger("height", height);
496 value->reset(size.DeepCopy());
497 return Status(kOk);
498 }
499
ExecuteSetWindowSize(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)500 Status ExecuteSetWindowSize(
501 Session* session,
502 const base::DictionaryValue& params,
503 scoped_ptr<base::Value>* value) {
504 double width, height;
505 if (!params.GetDouble("width", &width) ||
506 !params.GetDouble("height", &height))
507 return Status(kUnknownError, "missing or invalid 'width' or 'height'");
508
509 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
510 if (!desktop) {
511 return Status(
512 kUnknownError,
513 "command only supported for desktop Chrome without debuggerAddress");
514 }
515
516 AutomationExtension* extension = NULL;
517 Status status = desktop->GetAutomationExtension(&extension);
518 if (status.IsError())
519 return status;
520
521 return extension->SetWindowSize(
522 static_cast<int>(width), static_cast<int>(height));
523 }
524
ExecuteMaximizeWindow(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)525 Status ExecuteMaximizeWindow(
526 Session* session,
527 const base::DictionaryValue& params,
528 scoped_ptr<base::Value>* value) {
529 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
530 if (!desktop) {
531 return Status(
532 kUnknownError,
533 "command only supported for desktop Chrome without debuggerAddress");
534 }
535
536 AutomationExtension* extension = NULL;
537 Status status = desktop->GetAutomationExtension(&extension);
538 if (status.IsError())
539 return status;
540
541 return extension->MaximizeWindow();
542 }
543
ExecuteGetAvailableLogTypes(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)544 Status ExecuteGetAvailableLogTypes(
545 Session* session,
546 const base::DictionaryValue& params,
547 scoped_ptr<base::Value>* value) {
548 scoped_ptr<base::ListValue> types(new base::ListValue());
549 std::vector<WebDriverLog*> logs = session->GetAllLogs();
550 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
551 log != logs.end();
552 ++log) {
553 types->AppendString((*log)->type());
554 }
555 *value = types.Pass();
556 return Status(kOk);
557 }
558
ExecuteGetLog(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)559 Status ExecuteGetLog(
560 Session* session,
561 const base::DictionaryValue& params,
562 scoped_ptr<base::Value>* value) {
563 std::string log_type;
564 if (!params.GetString("type", &log_type)) {
565 return Status(kUnknownError, "missing or invalid 'type'");
566 }
567 std::vector<WebDriverLog*> logs = session->GetAllLogs();
568 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
569 log != logs.end();
570 ++log) {
571 if (log_type == (*log)->type()) {
572 *value = (*log)->GetAndClearEntries();
573 return Status(kOk);
574 }
575 }
576 return Status(kUnknownError, "log type '" + log_type + "' not found");
577 }
578
ExecuteUploadFile(Session * session,const base::DictionaryValue & params,scoped_ptr<base::Value> * value)579 Status ExecuteUploadFile(
580 Session* session,
581 const base::DictionaryValue& params,
582 scoped_ptr<base::Value>* value) {
583 std::string base64_zip_data;
584 if (!params.GetString("file", &base64_zip_data))
585 return Status(kUnknownError, "missing or invalid 'file'");
586 std::string zip_data;
587 if (!Base64Decode(base64_zip_data, &zip_data))
588 return Status(kUnknownError, "unable to decode 'file'");
589
590 if (!session->temp_dir.IsValid()) {
591 if (!session->temp_dir.CreateUniqueTempDir())
592 return Status(kUnknownError, "unable to create temp dir");
593 }
594 base::FilePath upload_dir;
595 if (!base::CreateTemporaryDirInDir(session->temp_dir.path(),
596 FILE_PATH_LITERAL("upload"),
597 &upload_dir)) {
598 return Status(kUnknownError, "unable to create temp dir");
599 }
600 std::string error_msg;
601 base::FilePath upload;
602 Status status = UnzipSoleFile(upload_dir, zip_data, &upload);
603 if (status.IsError())
604 return Status(kUnknownError, "unable to unzip 'file'", status);
605
606 value->reset(new base::StringValue(upload.value()));
607 return Status(kOk);
608 }
609