blob: d9a26fedc5129cf4d3608a5806591111482a3f6f [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/chromedriver/session_commands.h"
#include <list>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/logging.h" // For CHECK macros.
#include "base/memory/ref_counted.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/test/chromedriver/basic_types.h"
#include "chrome/test/chromedriver/capabilities.h"
#include "chrome/test/chromedriver/chrome/automation_extension.h"
#include "chrome/test/chromedriver/chrome/browser_info.h"
#include "chrome/test/chromedriver/chrome/chrome.h"
#include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
#include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
#include "chrome/test/chromedriver/chrome/device_manager.h"
#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
#include "chrome/test/chromedriver/chrome/geoposition.h"
#include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/web_view.h"
#include "chrome/test/chromedriver/chrome_launcher.h"
#include "chrome/test/chromedriver/command_listener.h"
#include "chrome/test/chromedriver/logging.h"
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/util.h"
#include "chrome/test/chromedriver/version.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
namespace {
const int kWifiMask = 0x2;
const int k4GMask = 0x8;
const int k3GMask = 0x10;
const int k2GMask = 0x20;
const int kAirplaneModeLatency = 0;
const int kAirplaneModeThroughput = 0;
const int kWifiLatency = 2;
const int kWifiThroughput = 30720 * 1024;
const int k4GLatency = 20;
const int k4GThroughput = 4096 * 1024;
const int k3GLatency = 100;
const int k3GThroughput = 750 * 1024;
const int k2GLatency = 300;
const int k2GThroughput = 250 * 1024;
Status EvaluateScriptAndIgnoreResult(Session* session, std::string expression) {
WebView* web_view = nullptr;
Status status = session->GetTargetWindow(&web_view);
if (status.IsError())
return status;
if (web_view->GetJavaScriptDialogManager()->IsDialogOpen())
return Status(kUnexpectedAlertOpen);
std::string frame_id = session->GetCurrentFrameId();
std::unique_ptr<base::Value> result;
return web_view->EvaluateScript(frame_id, expression, &result);
}
} // namespace
InitSessionParams::InitSessionParams(network::mojom::URLLoaderFactory* factory,
const SyncWebSocketFactory& socket_factory,
DeviceManager* device_manager)
: url_loader_factory(factory),
socket_factory(socket_factory),
device_manager(device_manager) {}
InitSessionParams::InitSessionParams(const InitSessionParams& other) = default;
InitSessionParams::~InitSessionParams() {}
// Look for W3C mode setting in InitSession command parameters.
bool GetW3CSetting(const base::DictionaryValue& params) {
bool w3c;
const base::ListValue* list;
const base::DictionaryValue* dict;
if (params.GetDictionary("capabilities.alwaysMatch", &dict)) {
if (dict->GetBoolean("goog:chromeOptions.w3c", &w3c) ||
dict->GetBoolean("chromeOptions.w3c", &w3c)) {
return w3c;
}
}
if (params.GetList("capabilities.firstMatch", &list) &&
list->GetDictionary(0, &dict)) {
if (dict->GetBoolean("goog:chromeOptions.w3c", &w3c) ||
dict->GetBoolean("chromeOptions.w3c", &w3c)) {
return w3c;
}
}
if (params.GetDictionary("desiredCapabilities", &dict)) {
if (dict->GetBoolean("goog:chromeOptions.w3c", &w3c) ||
dict->GetBoolean("chromeOptions.w3c", &w3c)) {
return w3c;
}
}
if (!params.HasKey("capabilities") && params.HasKey("desiredCapabilities")) {
return false;
}
return kW3CDefault;
}
namespace {
// Creates a JSON object (represented by base::DictionaryValue) that contains
// the capabilities, for returning to the client app as the result of New
// Session command.
std::unique_ptr<base::DictionaryValue> CreateCapabilities(
Session* session,
const Capabilities& capabilities,
const base::DictionaryValue& desired_caps) {
std::unique_ptr<base::DictionaryValue> caps(new base::DictionaryValue());
// Capabilities defined by W3C. Some of these capabilities have different
// names in legacy mode.
caps->SetString("browserName", "chrome");
caps->SetString(session->w3c_compliant ? "browserVersion" : "version",
session->chrome->GetBrowserInfo()->browser_version);
if (session->w3c_compliant)
caps->SetString(
"platformName",
base::ToLowerASCII(session->chrome->GetOperatingSystemName()));
else
caps->SetString("platform", session->chrome->GetOperatingSystemName());
caps->SetString("pageLoadStrategy", session->chrome->page_load_strategy());
caps->SetBoolean("acceptInsecureCerts", capabilities.accept_insecure_certs);
const base::Value* proxy = desired_caps.FindKey("proxy");
if (proxy == nullptr || proxy->is_none())
caps->SetKey("proxy", base::Value(base::Value::Type::DICTIONARY));
else
caps->SetKey("proxy", proxy->Clone());
// add setWindowRect based on whether we are desktop/android/remote
if (capabilities.IsAndroid() || capabilities.IsRemoteBrowser()) {
caps->SetBoolean("setWindowRect", false);
} else {
caps->SetBoolean("setWindowRect", true);
}
if (session->script_timeout == base::TimeDelta::Max())
caps->SetPath({"timeouts", "script"}, base::Value());
else
SetSafeInt(caps.get(), "timeouts.script",
session->script_timeout.InMilliseconds());
SetSafeInt(caps.get(), "timeouts.pageLoad",
session->page_load_timeout.InMilliseconds());
SetSafeInt(caps.get(), "timeouts.implicit",
session->implicit_wait.InMilliseconds());
caps->SetBoolean("strictFileInteractability",
session->strict_file_interactability);
caps->SetString(session->w3c_compliant ? "unhandledPromptBehavior"
: "unexpectedAlertBehaviour",
session->unhandled_prompt_behavior);
// Chrome-specific extensions.
caps->SetString("chrome.chromedriverVersion", kChromeDriverVersion);
caps->SetString(
"goog:chromeOptions.debuggerAddress",
session->chrome->GetBrowserInfo()->debugger_address.ToString());
ChromeDesktopImpl* desktop = NULL;
Status status = session->chrome->GetAsDesktop(&desktop);
if (status.IsOk()) {
caps->SetString("chrome.userDataDir",
desktop->command().GetSwitchValueNative("user-data-dir"));
caps->SetBoolean("networkConnectionEnabled",
desktop->IsNetworkConnectionEnabled());
}
// Legacy capabilities.
if (!session->w3c_compliant) {
caps->SetBoolean("javascriptEnabled", true);
caps->SetBoolean("takesScreenshot", true);
caps->SetBoolean("takesHeapSnapshot", true);
caps->SetBoolean("handlesAlerts", true);
caps->SetBoolean("databaseEnabled", false);
caps->SetBoolean("locationContextEnabled", true);
caps->SetBoolean("mobileEmulationEnabled",
session->chrome->IsMobileEmulationEnabled());
caps->SetBoolean("applicationCacheEnabled", false);
caps->SetBoolean("browserConnectionEnabled", false);
caps->SetBoolean("cssSelectorsEnabled", true);
caps->SetBoolean("webStorageEnabled", true);
caps->SetBoolean("rotatable", false);
caps->SetBoolean("acceptSslCerts", capabilities.accept_insecure_certs);
caps->SetBoolean("nativeEvents", true);
caps->SetBoolean("hasTouchScreen", session->chrome->HasTouchScreen());
}
return caps;
}
Status CheckSessionCreated(Session* session) {
WebView* web_view = NULL;
Status status = session->GetTargetWindow(&web_view);
if (status.IsError())
return Status(kSessionNotCreated, status);
status = web_view->ConnectIfNecessary();
if (status.IsError())
return Status(kSessionNotCreated, status);
base::ListValue args;
std::unique_ptr<base::Value> result(new base::Value(0));
status = web_view->CallFunction(session->GetCurrentFrameId(),
"function(s) { return 1; }", args, &result);
if (status.IsError())
return Status(kSessionNotCreated, status);
int response;
if (!result->GetAsInteger(&response) || response != 1) {
return Status(kSessionNotCreated,
"unexpected response from browser");
}
return Status(kOk);
}
Status InitSessionHelper(const InitSessionParams& bound_params,
Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
session->driver_log.reset(
new WebDriverLog(WebDriverLog::kDriverType, Log::kAll));
const base::DictionaryValue* desired_caps;
base::DictionaryValue merged_caps;
session->w3c_compliant = GetW3CSetting(params);
if (session->w3c_compliant) {
Status status = ProcessCapabilities(params, &merged_caps);
if (status.IsError())
return status;
desired_caps = &merged_caps;
} else if (!params.GetDictionary("desiredCapabilities", &desired_caps)) {
return Status(kSessionNotCreated,
"Missing or invalid capabilities");
}
Capabilities capabilities;
Status status = capabilities.Parse(*desired_caps, session->w3c_compliant);
if (status.IsError())
return status;
if (capabilities.unhandled_prompt_behavior.length() > 0) {
session->unhandled_prompt_behavior = capabilities.unhandled_prompt_behavior;
} else {
// W3C spec (https://www.w3.org/TR/webdriver/#dfn-handle-any-user-prompts)
// shows the default behavior to be dismiss and notify. For backward
// compatibility, in legacy mode default behavior is not handling prompt.
session->unhandled_prompt_behavior =
session->w3c_compliant ? kDismissAndNotify : kIgnore;
}
session->implicit_wait = capabilities.implicit_wait_timeout;
session->page_load_timeout = capabilities.page_load_timeout;
session->script_timeout = capabilities.script_timeout;
session->strict_file_interactability =
capabilities.strict_file_interactability;
Log::Level driver_level = Log::kWarning;
if (capabilities.logging_prefs.count(WebDriverLog::kDriverType))
driver_level = capabilities.logging_prefs[WebDriverLog::kDriverType];
session->driver_log->set_min_level(driver_level);
// Create Log's and DevToolsEventListener's for ones that are DevTools-based.
// Session will own the Log's, Chrome will own the listeners.
// Also create |CommandListener|s for the appropriate logs.
std::vector<std::unique_ptr<DevToolsEventListener>> devtools_event_listeners;
std::vector<std::unique_ptr<CommandListener>> command_listeners;
status = CreateLogs(capabilities,
session,
&session->devtools_logs,
&devtools_event_listeners,
&command_listeners);
if (status.IsError())
return status;
// |session| will own the |CommandListener|s.
session->command_listeners.swap(command_listeners);
status =
LaunchChrome(bound_params.url_loader_factory, bound_params.socket_factory,
bound_params.device_manager, capabilities,
std::move(devtools_event_listeners), &session->chrome,
session->w3c_compliant);
if (status.IsError())
return status;
if (capabilities.accept_insecure_certs) {
status = session->chrome->SetAcceptInsecureCerts();
if (status.IsError())
return status;
}
status = session->chrome->GetWebViewIdForFirstTab(&session->window,
session->w3c_compliant);
if (status.IsError())
return status;
session->detach = capabilities.detach;
session->capabilities =
CreateCapabilities(session, capabilities, *desired_caps);
std::string download_directory;
if (capabilities.prefs &&
capabilities.prefs->GetString("download.default_directory",
&download_directory))
session->headless_download_directory =
std::make_unique<std::string>(download_directory);
else
session->headless_download_directory = std::make_unique<std::string>(".");
WebView* first_view;
session->chrome->GetWebViewById(session->window, &first_view);
status = first_view->OverrideDownloadDirectoryIfNeeded(
*session->headless_download_directory);
if (status.IsError())
return status;
if (session->w3c_compliant) {
std::unique_ptr<base::DictionaryValue> capabilities =
std::unique_ptr<base::DictionaryValue>(
session->capabilities->DeepCopy());
base::DictionaryValue body;
body.SetDictionary("capabilities", std::move(capabilities));
body.SetString("sessionId", session->id);
value->reset(body.DeepCopy());
} else {
value->reset(session->capabilities->DeepCopy());
}
return CheckSessionCreated(session);
}
} // namespace
bool MergeCapabilities(const base::DictionaryValue* always_match,
const base::DictionaryValue* first_match,
base::DictionaryValue* merged) {
CHECK(always_match);
CHECK(first_match);
CHECK(merged);
merged->Clear();
for (base::DictionaryValue::Iterator it(*first_match); !it.IsAtEnd();
it.Advance()) {
if (always_match->HasKey(it.key())) {
// firstMatch cannot have the same |keys| as alwaysMatch.
return false;
}
}
// merge the capabilities together since guarenteed no key collisions
merged->MergeDictionary(always_match);
merged->MergeDictionary(first_match);
return true;
}
// Implementation of "matching capabilities", as defined in W3C spec at
// https://www.w3.org/TR/webdriver/#dfn-matching-capabilities.
// It checks some requested capabilities and make sure they are supported.
// Currently, we only check "browserName" and "platformName", but more can be
// added as necessary.
bool MatchCapabilities(const base::DictionaryValue* capabilities) {
const base::Value* name;
if (capabilities->Get("browserName", &name) && !name->is_none()) {
if (!(name->is_string() && name->GetString() == "chrome"))
return false;
}
const base::Value* platform_name_value;
if (capabilities->Get("platformName", &platform_name_value) &&
!platform_name_value->is_none()) {
if (platform_name_value->is_string()) {
std::string requested_platform_name = platform_name_value->GetString();
std::string actual_platform_name =
base::ToLowerASCII(base::SysInfo::OperatingSystemName());
bool is_android =
capabilities->HasKey("goog:chromeOptions.androidPackage");
bool is_remote =
capabilities->HasKey("goog:chromeOptions.debuggerAddress");
if (requested_platform_name == "any" || is_remote) {
// "any" can be used as a wild card for platformName
// and if |is_remote| there is no easy way to know
// target platform, so will skip check
} else if (is_android && requested_platform_name != "android") {
return false;
} else if (requested_platform_name == "mac" ||
requested_platform_name == "windows" ||
requested_platform_name == "linux") {
if (!base::StartsWith(actual_platform_name, requested_platform_name,
base::CompareCase::SENSITIVE))
return false;
} else if (requested_platform_name != actual_platform_name) {
return false;
}
} else {
return false;
}
}
return true;
}
// Implementation of "process capabilities", as defined in W3C spec at
// https://www.w3.org/TR/webdriver/#processing-capabilities. Step numbers in
// the comments correspond to the step numbers in the spec.
Status ProcessCapabilities(const base::DictionaryValue& params,
base::DictionaryValue* result_capabilities) {
// 1. Get the property "capabilities" from parameters.
const base::DictionaryValue* capabilities_request;
if (!params.GetDictionary("capabilities", &capabilities_request))
return Status(kInvalidArgument, "'capabilities' must be a JSON object");
// 2. Get the property "alwaysMatch" from capabilities request.
const base::DictionaryValue empty_object;
const base::DictionaryValue* required_capabilities;
const base::Value* required_capabilities_value =
capabilities_request->FindKey("alwaysMatch");
if (required_capabilities_value == nullptr) {
required_capabilities = &empty_object;
} else if (required_capabilities_value->GetAsDictionary(
&required_capabilities)) {
Capabilities cap;
Status status = cap.Parse(*required_capabilities);
if (status.IsError())
return status;
} else {
return Status(kInvalidArgument, "'alwaysMatch' must be a JSON object");
}
// 3. Get the property "firstMatch" from capabilities request.
base::ListValue default_list;
const base::ListValue* all_first_match_capabilities;
const base::Value* all_first_match_capabilities_value =
capabilities_request->FindKey("firstMatch");
if (all_first_match_capabilities_value == nullptr) {
default_list.Append(std::make_unique<base::DictionaryValue>());
all_first_match_capabilities = &default_list;
} else if (all_first_match_capabilities_value->GetAsList(
&all_first_match_capabilities)) {
if (all_first_match_capabilities->GetSize() < 1)
return Status(kInvalidArgument,
"'firstMatch' must contain at least one entry");
} else {
return Status(kInvalidArgument, "'firstMatch' must be a JSON list");
}
// 4. Let validated first match capabilities be an empty JSON List.
std::vector<const base::DictionaryValue*> validated_first_match_capabilities;
// 5. Validate all first match capabilities.
for (size_t i = 0; i < all_first_match_capabilities->GetSize(); ++i) {
const base::DictionaryValue* first_match;
if (!all_first_match_capabilities->GetDictionary(i, &first_match)) {
return Status(kInvalidArgument,
base::StringPrintf(
"entry %zu of 'firstMatch' must be a JSON object", i));
}
Capabilities cap;
Status status = cap.Parse(*first_match);
if (status.IsError())
return Status(
kInvalidArgument,
base::StringPrintf("entry %zu of 'firstMatch' is invalid", i),
status);
validated_first_match_capabilities.push_back(first_match);
}
// 6. Let merged capabilities be an empty List.
std::vector<base::DictionaryValue> merged_capabilities;
// 7. Merge capabilities.
for (size_t i = 0; i < validated_first_match_capabilities.size(); ++i) {
const base::DictionaryValue* first_match_capabilities =
validated_first_match_capabilities[i];
base::DictionaryValue merged;
if (!MergeCapabilities(required_capabilities, first_match_capabilities,
&merged)) {
return Status(
kInvalidArgument,
base::StringPrintf(
"unable to merge 'alwaysMatch' with entry %zu of 'firstMatch'",
i));
}
merged_capabilities.emplace_back();
merged_capabilities.back().Swap(&merged);
}
// 8. Match capabilities.
for (auto& capabilities : merged_capabilities) {
if (MatchCapabilities(&capabilities)) {
capabilities.Swap(result_capabilities);
return Status(kOk);
}
}
// 9. The spec says "return success with data null", but then the caller is
// instructed to return error when the data is null. Since we don't have a
// convenient way to return data null, we will take a shortcut and return an
// error directly.
return Status(kSessionNotCreated, "No matching capabilities found");
}
Status ExecuteInitSession(const InitSessionParams& bound_params,
Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
Status status = InitSessionHelper(bound_params, session, params, value);
if (status.IsError()) {
session->quit = true;
if (session->chrome != NULL)
session->chrome->Quit();
}
return status;
}
Status ExecuteQuit(bool allow_detach,
Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
session->quit = true;
if (allow_detach && session->detach)
return Status(kOk);
else
return session->chrome->Quit();
}
Status ExecuteGetSessionCapabilities(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
value->reset(session->capabilities->DeepCopy());
return Status(kOk);
}
Status ExecuteGetCurrentWindowHandle(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebView* web_view = NULL;
Status status = session->GetTargetWindow(&web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
value->reset(new base::Value(WebViewIdToWindowHandle(web_view->GetId())));
return Status(kOk);
}
Status ExecuteLaunchApp(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string id;
if (!params.GetString("id", &id))
return Status(kInvalidArgument, "'id' must be a string");
ChromeDesktopImpl* desktop = NULL;
Status status = session->chrome->GetAsDesktop(&desktop);
if (status.IsError())
return status;
AutomationExtension* extension = NULL;
status = desktop->GetAutomationExtension(&extension, session->w3c_compliant);
if (status.IsError())
return status;
return extension->LaunchApp(id);
}
Status ExecuteClose(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::list<std::string> web_view_ids;
Status status = session->chrome->GetWebViewIds(&web_view_ids,
session->w3c_compliant);
if (status.IsError())
return status;
bool is_last_web_view = web_view_ids.size() == 1u;
web_view_ids.clear();
WebView* web_view = NULL;
status = session->GetTargetWindow(&web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
status = web_view->HandleReceivedEvents();
if (status.IsError())
return status;
JavaScriptDialogManager* dialog_manager =
web_view->GetJavaScriptDialogManager();
if (dialog_manager->IsDialogOpen()) {
std::string alert_text;
status = dialog_manager->GetDialogMessage(&alert_text);
if (status.IsError())
return status;
// Close the dialog depending on the unexpectedalert behaviour set by user
// before returning an error, so that subsequent commands do not fail.
const std::string& prompt_behavior = session->unhandled_prompt_behavior;
if (prompt_behavior == kAccept || prompt_behavior == kAcceptAndNotify)
status = dialog_manager->HandleDialog(true, session->prompt_text.get());
else if (prompt_behavior == kDismiss ||
prompt_behavior == kDismissAndNotify)
status = dialog_manager->HandleDialog(false, session->prompt_text.get());
if (status.IsError())
return status;
// For backward compatibility, in legacy mode we always notify.
if (!session->w3c_compliant || prompt_behavior == kAcceptAndNotify ||
prompt_behavior == kDismissAndNotify || prompt_behavior == kIgnore)
return Status(kUnexpectedAlertOpen, "{Alert text : " + alert_text + "}");
}
status = session->chrome->CloseWebView(web_view->GetId());
if (status.IsError())
return status;
status = ExecuteGetWindowHandles(session, base::DictionaryValue(), value);
if ((status.code() == kChromeNotReachable && is_last_web_view) ||
(status.IsOk() && (*value)->GetList().empty())) {
// If the only open window was closed, close is the same as calling "quit".
session->quit = true;
return session->chrome->Quit();
}
return status;
}
Status ExecuteGetWindowHandles(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::list<std::string> web_view_ids;
Status status = session->chrome->GetWebViewIds(&web_view_ids,
session->w3c_compliant);
if (status.IsError())
return status;
std::unique_ptr<base::ListValue> window_ids(new base::ListValue());
for (std::list<std::string>::const_iterator it = web_view_ids.begin();
it != web_view_ids.end(); ++it) {
window_ids->AppendString(WebViewIdToWindowHandle(*it));
}
*value = std::move(window_ids);
return Status(kOk);
}
Status ExecuteSwitchToWindow(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string name;
if (session->w3c_compliant) {
if (!params.GetString("handle", &name))
return Status(kInvalidArgument, "'handle' must be a string");
} else {
if (!params.GetString("name", &name))
return Status(kInvalidArgument, "'name' must be a string");
}
std::list<std::string> web_view_ids;
Status status = session->chrome->GetWebViewIds(&web_view_ids,
session->w3c_compliant);
if (status.IsError())
return status;
std::string web_view_id;
bool found = false;
if (WindowHandleToWebViewId(name, &web_view_id)) {
// Check if any web_view matches |web_view_id|.
for (std::list<std::string>::const_iterator it = web_view_ids.begin();
it != web_view_ids.end(); ++it) {
if (*it == web_view_id) {
found = true;
break;
}
}
} else {
// Check if any of the tab window names match |name|.
const char* kGetWindowNameScript = "function() { return window.name; }";
base::ListValue args;
for (std::list<std::string>::const_iterator it = web_view_ids.begin();
it != web_view_ids.end(); ++it) {
std::unique_ptr<base::Value> result;
WebView* web_view;
status = session->chrome->GetWebViewById(*it, &web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
status = web_view->CallFunction(
std::string(), kGetWindowNameScript, args, &result);
if (status.IsError())
return status;
std::string window_name;
if (!result->GetAsString(&window_name))
return Status(kUnknownError, "failed to get window name");
if (window_name == name) {
web_view_id = *it;
found = true;
break;
}
}
}
if (!found)
return Status(kNoSuchWindow);
if (session->overridden_geoposition) {
WebView* web_view;
Status status = session->chrome->GetWebViewById(web_view_id, &web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
status = web_view->OverrideGeolocation(*session->overridden_geoposition);
if (status.IsError())
return status;
}
if (session->overridden_network_conditions) {
WebView* web_view;
Status status = session->chrome->GetWebViewById(web_view_id, &web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
status = web_view->OverrideNetworkConditions(
*session->overridden_network_conditions);
if (status.IsError())
return status;
}
if (session->headless_download_directory) {
WebView* web_view;
Status status = session->chrome->GetWebViewById(web_view_id, &web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
status = web_view->OverrideDownloadDirectoryIfNeeded(
*session->headless_download_directory);
if (status.IsError())
return status;
}
status = session->chrome->ActivateWebView(web_view_id);
if (status.IsError())
return status;
session->window = web_view_id;
session->SwitchToTopFrame();
session->mouse_position = WebPoint(0, 0);
return Status(kOk);
}
// Handles legacy format SetTimeout command.
// TODO(johnchen@chromium.org): Remove when we stop supporting legacy protocol.
Status ExecuteSetTimeoutLegacy(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
double ms_double;
if (!params.GetDouble("ms", &ms_double))
return Status(kInvalidArgument, "'ms' must be a double");
std::string type;
if (!params.GetString("type", &type))
return Status(kInvalidArgument, "'type' must be a string");
base::TimeDelta timeout =
base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double));
if (type == "implicit") {
session->implicit_wait = timeout;
} else if (type == "script") {
session->script_timeout = timeout;
} else if (type == "page load") {
session->page_load_timeout =
((timeout < base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
: timeout);
} else {
return Status(kInvalidArgument, "unknown type of timeout:" + type);
}
return Status(kOk);
}
Status ExecuteSetTimeoutsW3C(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
for (const auto& setting : params.DictItems()) {
int64_t timeout_ms_int64 = -1;
base::TimeDelta timeout;
const std::string& type = setting.first;
if (setting.second.is_none()) {
if (type == "script")
timeout = base::TimeDelta::Max();
else
return Status(kInvalidArgument, "timeout can not be null");
} else {
if (!GetOptionalSafeInt(&params, setting.first, &timeout_ms_int64)
|| timeout_ms_int64 < 0)
return Status(kInvalidArgument,
"value must be a non-negative integer");
else
timeout = base::TimeDelta::FromMilliseconds(timeout_ms_int64);
}
if (type == "script") {
session->script_timeout = timeout;
} else if (type == "pageLoad") {
session->page_load_timeout = timeout;
} else if (type == "implicit") {
session->implicit_wait = timeout;
}
}
return Status(kOk);
}
Status ExecuteSetTimeouts(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
// TODO(johnchen@chromium.org): Remove legacy version support when we stop
// supporting non-W3C protocol. At that time, we can delete the legacy
// function and merge the W3C function into this function.
if (params.HasKey("ms")) {
return ExecuteSetTimeoutLegacy(session, params, value);
} else {
return ExecuteSetTimeoutsW3C(session, params, value);
}
}
Status ExecuteGetTimeouts(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::DictionaryValue timeouts;
if (session->script_timeout == base::TimeDelta::Max())
timeouts.SetKey("script", base::Value());
else
SetSafeInt(&timeouts, "script", session->script_timeout.InMilliseconds());
SetSafeInt(&timeouts, "pageLoad",
session->page_load_timeout.InMilliseconds());
SetSafeInt(&timeouts, "implicit", session->implicit_wait.InMilliseconds());
value->reset(timeouts.DeepCopy());
return Status(kOk);
}
Status ExecuteSetScriptTimeout(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
double ms;
if (!params.GetDouble("ms", &ms) || ms < 0)
return Status(kInvalidArgument, "'ms' must be a non-negative number");
session->script_timeout =
base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
return Status(kOk);
}
Status ExecuteImplicitlyWait(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
double ms;
if (!params.GetDouble("ms", &ms) || ms < 0)
return Status(kInvalidArgument, "'ms' must be a non-negative number");
session->implicit_wait =
base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
return Status(kOk);
}
Status ExecuteIsLoading(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebView* web_view = NULL;
Status status = session->GetTargetWindow(&web_view);
if (status.IsError())
return status;
status = web_view->ConnectIfNecessary();
if (status.IsError())
return status;
bool is_pending;
status = web_view->IsPendingNavigation(
session->GetCurrentFrameId(), nullptr, &is_pending);
if (status.IsError())
return status;
value->reset(new base::Value(is_pending));
return Status(kOk);
}
Status ExecuteGetLocation(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
if (!session->overridden_geoposition) {
return Status(kUnknownError,
"Location must be set before it can be retrieved");
}
base::DictionaryValue location;
location.SetDouble("latitude", session->overridden_geoposition->latitude);
location.SetDouble("longitude", session->overridden_geoposition->longitude);
location.SetDouble("accuracy", session->overridden_geoposition->accuracy);
// Set a dummy altitude to make WebDriver clients happy.
// https://code.google.com/p/chromedriver/issues/detail?id=281
location.SetDouble("altitude", 0);
value->reset(location.DeepCopy());
return Status(kOk);
}
Status ExecuteGetNetworkConnection(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
ChromeDesktopImpl* desktop = nullptr;
Status status = session->chrome->GetAsDesktop(&desktop);
if (status.IsError())
return status;
if (!desktop->IsNetworkConnectionEnabled())
return Status(kUnknownError, "network connection must be enabled");
int connection_type = 0;
connection_type = desktop->GetNetworkConnection();
value->reset(new base::Value(connection_type));
return Status(kOk);
}
Status ExecuteGetNetworkConditions(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
if (!session->overridden_network_conditions) {
return Status(kUnknownError,
"network conditions must be set before it can be retrieved");
}
base::DictionaryValue conditions;
conditions.SetBoolean("offline",
session->overridden_network_conditions->offline);
conditions.SetInteger("latency",
session->overridden_network_conditions->latency);
conditions.SetInteger(
"download_throughput",
session->overridden_network_conditions->download_throughput);
conditions.SetInteger(
"upload_throughput",
session->overridden_network_conditions->upload_throughput);
value->reset(conditions.DeepCopy());
return Status(kOk);
}
Status ExecuteSetNetworkConnection(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
ChromeDesktopImpl* desktop = nullptr;
Status status = session->chrome->GetAsDesktop(&desktop);
if (status.IsError())
return status;
if (!desktop->IsNetworkConnectionEnabled())
return Status(kUnknownError, "network connection must be enabled");
int connection_type;
if (!params.GetInteger("parameters.type", &connection_type))
return Status(kInvalidArgument, "invalid connection_type");
desktop->SetNetworkConnection(connection_type);
std::unique_ptr<NetworkConditions> network_conditions(
new NetworkConditions());
if (connection_type & kWifiMask) {
network_conditions->latency = kWifiLatency;
network_conditions->upload_throughput = kWifiThroughput;
network_conditions->download_throughput = kWifiThroughput;
network_conditions->offline = false;
} else if (connection_type & k4GMask) {
network_conditions->latency = k4GLatency;
network_conditions->upload_throughput = k4GThroughput;
network_conditions->download_throughput = k4GThroughput;
network_conditions->offline = false;
} else if (connection_type & k3GMask) {
network_conditions->latency = k3GLatency;
network_conditions->upload_throughput = k3GThroughput;
network_conditions->download_throughput = k3GThroughput;
network_conditions->offline = false;
} else if (connection_type & k2GMask) {
network_conditions->latency = k2GLatency;
network_conditions->upload_throughput = k2GThroughput;
network_conditions->download_throughput = k2GThroughput;
network_conditions->offline = false;
} else {
network_conditions->latency = kAirplaneModeLatency;
network_conditions->upload_throughput = kAirplaneModeThroughput;
network_conditions->download_throughput = kAirplaneModeThroughput;
network_conditions->offline = true;
}
session->overridden_network_conditions.reset(
network_conditions.release());
// Applies overridden network connection to all WebViews of the session
// to ensure that network emulation is applied on a per-session basis
// rather than the just to the current WebView.
std::list<std::string> web_view_ids;
status = session->chrome->GetWebViewIds(&web_view_ids,
session->w3c_compliant);
if (status.IsError())
return status;
for (std::string web_view_id : web_view_ids) {
WebView* web_view;
status = session->chrome->GetWebViewById(web_view_id, &web_view);
if (status.IsError())
return status;
web_view->OverrideNetworkConditions(
*session->overridden_network_conditions);
}
value->reset(new base::Value(connection_type));
return Status(kOk);
}
Status ExecuteGetWindowPosition(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
int x, y;
Status status = session->chrome->GetWindowPosition(session->window, &x, &y);
if (status.IsError())
return status;
base::DictionaryValue position;
position.SetInteger("x", x);
position.SetInteger("y", y);
value->reset(position.DeepCopy());
return Status(kOk);
}
Status ExecuteSetWindowPosition(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
double x = 0;
double y = 0;
if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y))
return Status(kInvalidArgument, "missing or invalid 'x' or 'y'");
return session->chrome->SetWindowPosition(session->window,
static_cast<int>(x),
static_cast<int>(y));
}
Status ExecuteGetWindowSize(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
int width, height;
Status status =
session->chrome->GetWindowSize(session->window, &width, &height);
if (status.IsError())
return status;
base::DictionaryValue size;
size.SetInteger("width", width);
size.SetInteger("height", height);
value->reset(size.DeepCopy());
return Status(kOk);
}
Status ExecuteSetWindowSize(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
double width = 0;
double height = 0;
if (!params.GetDouble("width", &width) ||
!params.GetDouble("height", &height))
return Status(kInvalidArgument, "missing or invalid 'width' or 'height'");
return session->chrome->SetWindowSize(session->window,
static_cast<int>(width),
static_cast<int>(height));
}
Status ExecuteGetAvailableLogTypes(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::unique_ptr<base::ListValue> types(new base::ListValue());
std::vector<WebDriverLog*> logs = session->GetAllLogs();
for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
log != logs.end();
++log) {
types->AppendString((*log)->type());
}
*value = std::move(types);
return Status(kOk);
}
Status ExecuteGetLog(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string log_type;
if (!params.GetString("type", &log_type)) {
return Status(kInvalidArgument, "missing or invalid 'type'");
}
// Evaluate a JavaScript in the renderer process for the current tab, to flush
// out any pending logging-related events.
Status status = EvaluateScriptAndIgnoreResult(session, "1");
if (status.IsError()) {
// Sometimes a WebDriver client fetches logs to diagnose an error that has
// occurred. It's possible that in the case of an error, the renderer is no
// be longer available, but we should return the logs anyway. So log (but
// don't fail on) any error that we get while evaluating the script.
LOG(WARNING) << "Unable to evaluate script: " << status.message();
}
std::vector<WebDriverLog*> logs = session->GetAllLogs();
for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
log != logs.end();
++log) {
if (log_type == (*log)->type()) {
*value = (*log)->GetAndClearEntries();
return Status(kOk);
}
}
return Status(kInvalidArgument, "log type '" + log_type + "' not found");
}
Status ExecuteUploadFile(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string base64_zip_data;
if (!params.GetString("file", &base64_zip_data))
return Status(kInvalidArgument, "missing or invalid 'file'");
std::string zip_data;
if (!Base64Decode(base64_zip_data, &zip_data))
return Status(kUnknownError, "unable to decode 'file'");
if (!session->temp_dir.IsValid()) {
if (!session->temp_dir.CreateUniqueTempDir())
return Status(kUnknownError, "unable to create temp dir");
}
base::FilePath upload_dir;
if (!base::CreateTemporaryDirInDir(session->temp_dir.GetPath(),
FILE_PATH_LITERAL("upload"),
&upload_dir)) {
return Status(kUnknownError, "unable to create temp dir");
}
std::string error_msg;
base::FilePath upload;
Status status = UnzipSoleFile(upload_dir, zip_data, &upload);
if (status.IsError())
return Status(kUnknownError, "unable to unzip 'file'", status);
value->reset(new base::Value(upload.value()));
return Status(kOk);
}
Status ExecuteUnimplementedCommand(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
return Status(kUnknownCommand);
}
Status ExecuteGenerateTestReport(Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebView* web_view = nullptr;
Status status = session->GetTargetWindow(&web_view);
if (status.IsError())
return status;
std::string message, group;
if (!params.GetString("message", &message))
return Status(kInvalidArgument, "missing parameter 'message'");
if (!params.GetString("group", &group))
group = "default";
base::DictionaryValue body;
body.SetString("message", message);
body.SetString("group", group);
web_view->SendCommandAndGetResult("Page.generateTestReport", body, value);
return Status(kOk);
}