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