Home | History | Annotate | Download | only in chromedriver
      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 
     40 std::string WebViewIdToWindowHandle(const std::string& web_view_id) {
     41   return kWindowHandlePrefix + web_view_id;
     42 }
     43 
     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 
     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 
     67 InitSessionParams::~InitSessionParams() {}
     68 
     69 namespace {
     70 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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