1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/test/chromedriver/capabilities.h"
10 #include "base/callback.h"
11 #include "base/json/string_escape.h"
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_tokenizer.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/values.h"
20 #include "chrome/test/chromedriver/chrome/status.h"
21 #include "chrome/test/chromedriver/logging.h"
22 #include "net/base/net_util.h"
26 typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser;
30 const base::Value& option,
31 Capabilities* capabilities) {
32 if (!option.GetAsBoolean(to_set))
33 return Status(kUnknownError, "must be a boolean");
37 Status ParseString(std::string* to_set,
38 const base::Value& option,
39 Capabilities* capabilities) {
41 if (!option.GetAsString(&str))
42 return Status(kUnknownError, "must be a string");
44 return Status(kUnknownError, "cannot be empty");
49 Status ParseFilePath(base::FilePath* to_set,
50 const base::Value& option,
51 Capabilities* capabilities) {
52 base::FilePath::StringType str;
53 if (!option.GetAsString(&str))
54 return Status(kUnknownError, "must be a string");
55 *to_set = base::FilePath(str);
59 Status ParseDict(scoped_ptr<base::DictionaryValue>* to_set,
60 const base::Value& option,
61 Capabilities* capabilities) {
62 const base::DictionaryValue* dict = NULL;
63 if (!option.GetAsDictionary(&dict))
64 return Status(kUnknownError, "must be a dictionary");
65 to_set->reset(dict->DeepCopy());
69 Status IgnoreDeprecatedOption(
70 const char* option_name,
71 const base::Value& option,
72 Capabilities* capabilities) {
73 LOG(WARNING) << "Deprecated chrome option is ignored: " << option_name;
77 Status IgnoreCapability(const base::Value& option, Capabilities* capabilities) {
81 Status ParseLogPath(const base::Value& option, Capabilities* capabilities) {
82 if (!option.GetAsString(&capabilities->log_path))
83 return Status(kUnknownError, "must be a string");
87 Status ParseSwitches(const base::Value& option,
88 Capabilities* capabilities) {
89 const base::ListValue* switches_list = NULL;
90 if (!option.GetAsList(&switches_list))
91 return Status(kUnknownError, "must be a list");
92 for (size_t i = 0; i < switches_list->GetSize(); ++i) {
93 std::string arg_string;
94 if (!switches_list->GetString(i, &arg_string))
95 return Status(kUnknownError, "each argument must be a string");
96 capabilities->switches.SetUnparsedSwitch(arg_string);
101 Status ParseExtensions(const base::Value& option, Capabilities* capabilities) {
102 const base::ListValue* extensions = NULL;
103 if (!option.GetAsList(&extensions))
104 return Status(kUnknownError, "must be a list");
105 for (size_t i = 0; i < extensions->GetSize(); ++i) {
106 std::string extension;
107 if (!extensions->GetString(i, &extension)) {
108 return Status(kUnknownError,
109 "each extension must be a base64 encoded string");
111 capabilities->extensions.push_back(extension);
116 Status ParseProxy(const base::Value& option, Capabilities* capabilities) {
117 const base::DictionaryValue* proxy_dict;
118 if (!option.GetAsDictionary(&proxy_dict))
119 return Status(kUnknownError, "must be a dictionary");
120 std::string proxy_type;
121 if (!proxy_dict->GetString("proxyType", &proxy_type))
122 return Status(kUnknownError, "'proxyType' must be a string");
123 proxy_type = StringToLowerASCII(proxy_type);
124 if (proxy_type == "direct") {
125 capabilities->switches.SetSwitch("no-proxy-server");
126 } else if (proxy_type == "system") {
128 } else if (proxy_type == "pac") {
129 CommandLine::StringType proxy_pac_url;
130 if (!proxy_dict->GetString("proxyAutoconfigUrl", &proxy_pac_url))
131 return Status(kUnknownError, "'proxyAutoconfigUrl' must be a string");
132 capabilities->switches.SetSwitch("proxy-pac-url", proxy_pac_url);
133 } else if (proxy_type == "autodetect") {
134 capabilities->switches.SetSwitch("proxy-auto-detect");
135 } else if (proxy_type == "manual") {
136 const char* proxy_servers_options[][2] = {
137 {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
138 const base::Value* option_value = NULL;
139 std::string proxy_servers;
140 for (size_t i = 0; i < arraysize(proxy_servers_options); ++i) {
141 if (!proxy_dict->Get(proxy_servers_options[i][0], &option_value) ||
142 option_value->IsType(base::Value::TYPE_NULL)) {
146 if (!option_value->GetAsString(&value)) {
149 base::StringPrintf("'%s' must be a string",
150 proxy_servers_options[i][0]));
152 // Converts into Chrome proxy scheme.
153 // Example: "http=localhost:9000;ftp=localhost:8000".
154 if (!proxy_servers.empty())
155 proxy_servers += ";";
156 proxy_servers += base::StringPrintf(
157 "%s=%s", proxy_servers_options[i][1], value.c_str());
160 std::string proxy_bypass_list;
161 if (proxy_dict->Get("noProxy", &option_value) &&
162 !option_value->IsType(base::Value::TYPE_NULL)) {
163 if (!option_value->GetAsString(&proxy_bypass_list))
164 return Status(kUnknownError, "'noProxy' must be a string");
167 if (proxy_servers.empty() && proxy_bypass_list.empty()) {
168 return Status(kUnknownError,
169 "proxyType is 'manual' but no manual "
170 "proxy capabilities were found");
172 if (!proxy_servers.empty())
173 capabilities->switches.SetSwitch("proxy-server", proxy_servers);
174 if (!proxy_bypass_list.empty()) {
175 capabilities->switches.SetSwitch("proxy-bypass-list",
179 return Status(kUnknownError, "unrecognized proxy type:" + proxy_type);
184 Status ParseExcludeSwitches(const base::Value& option,
185 Capabilities* capabilities) {
186 const base::ListValue* switches = NULL;
187 if (!option.GetAsList(&switches))
188 return Status(kUnknownError, "must be a list");
189 for (size_t i = 0; i < switches->GetSize(); ++i) {
190 std::string switch_name;
191 if (!switches->GetString(i, &switch_name)) {
192 return Status(kUnknownError,
193 "each switch to be removed must be a string");
195 capabilities->exclude_switches.insert(switch_name);
200 Status ParseUseExistingBrowser(const base::Value& option,
201 Capabilities* capabilities) {
202 std::string server_addr;
203 if (!option.GetAsString(&server_addr))
204 return Status(kUnknownError, "must be 'host:port'");
206 std::vector<std::string> values;
207 base::SplitString(server_addr, ':', &values);
208 if (values.size() != 2)
209 return Status(kUnknownError, "must be 'host:port'");
212 base::StringToInt(values[1], &port);
214 return Status(kUnknownError, "port must be > 0");
216 capabilities->debugger_address = NetAddress(values[0], port);
220 Status ParseLoggingPrefs(const base::Value& option,
221 Capabilities* capabilities) {
222 const base::DictionaryValue* logging_prefs = NULL;
223 if (!option.GetAsDictionary(&logging_prefs))
224 return Status(kUnknownError, "must be a dictionary");
226 for (base::DictionaryValue::Iterator pref(*logging_prefs);
227 !pref.IsAtEnd(); pref.Advance()) {
228 std::string type = pref.key();
230 std::string level_name;
231 if (!pref.value().GetAsString(&level_name) ||
232 !WebDriverLog::NameToLevel(level_name, &level)) {
233 return Status(kUnknownError, "invalid log level for '" + type + "' log");
235 capabilities->logging_prefs.insert(std::make_pair(type, level));
240 Status ParseChromeOptions(
241 const base::Value& capability,
242 Capabilities* capabilities) {
243 const base::DictionaryValue* chrome_options = NULL;
244 if (!capability.GetAsDictionary(&chrome_options))
245 return Status(kUnknownError, "must be a dictionary");
247 bool is_android = chrome_options->HasKey("androidPackage");
248 bool is_existing = chrome_options->HasKey("debuggerAddress");
250 std::map<std::string, Parser> parser_map;
251 // Ignore 'args', 'binary' and 'extensions' capabilities by default, since the
252 // Java client always passes them.
253 parser_map["args"] = base::Bind(&IgnoreCapability);
254 parser_map["binary"] = base::Bind(&IgnoreCapability);
255 parser_map["extensions"] = base::Bind(&IgnoreCapability);
257 parser_map["androidActivity"] =
258 base::Bind(&ParseString, &capabilities->android_activity);
259 parser_map["androidDeviceSerial"] =
260 base::Bind(&ParseString, &capabilities->android_device_serial);
261 parser_map["androidPackage"] =
262 base::Bind(&ParseString, &capabilities->android_package);
263 parser_map["androidProcess"] =
264 base::Bind(&ParseString, &capabilities->android_process);
265 parser_map["args"] = base::Bind(&ParseSwitches);
266 } else if (is_existing) {
267 parser_map["debuggerAddress"] = base::Bind(&ParseUseExistingBrowser);
269 parser_map["args"] = base::Bind(&ParseSwitches);
270 parser_map["binary"] = base::Bind(&ParseFilePath, &capabilities->binary);
271 parser_map["detach"] = base::Bind(&ParseBoolean, &capabilities->detach);
272 parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches);
273 parser_map["extensions"] = base::Bind(&ParseExtensions);
274 parser_map["forceDevToolsScreenshot"] = base::Bind(
275 &ParseBoolean, &capabilities->force_devtools_screenshot);
276 parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync");
277 parser_map["localState"] =
278 base::Bind(&ParseDict, &capabilities->local_state);
279 parser_map["logPath"] = base::Bind(&ParseLogPath);
280 parser_map["minidumpPath"] =
281 base::Bind(&ParseString, &capabilities->minidump_path);
282 parser_map["prefs"] = base::Bind(&ParseDict, &capabilities->prefs);
285 for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd();
287 if (parser_map.find(it.key()) == parser_map.end()) {
288 return Status(kUnknownError,
289 "unrecognized chrome option: " + it.key());
291 Status status = parser_map[it.key()].Run(it.value(), capabilities);
292 if (status.IsError())
293 return Status(kUnknownError, "cannot parse " + it.key(), status);
300 Switches::Switches() {}
302 Switches::~Switches() {}
304 void Switches::SetSwitch(const std::string& name) {
305 SetSwitch(name, NativeString());
308 void Switches::SetSwitch(const std::string& name, const std::string& value) {
310 SetSwitch(name, UTF8ToUTF16(value));
312 switch_map_[name] = value;
316 void Switches::SetSwitch(const std::string& name, const string16& value) {
318 switch_map_[name] = value;
320 SetSwitch(name, UTF16ToUTF8(value));
324 void Switches::SetSwitch(const std::string& name, const base::FilePath& value) {
325 SetSwitch(name, value.value());
328 void Switches::SetFromSwitches(const Switches& switches) {
329 for (SwitchMap::const_iterator iter = switches.switch_map_.begin();
330 iter != switches.switch_map_.end();
332 switch_map_[iter->first] = iter->second;
336 void Switches::SetUnparsedSwitch(const std::string& unparsed_switch) {
338 size_t equals_index = unparsed_switch.find('=');
339 if (equals_index != std::string::npos)
340 value = unparsed_switch.substr(equals_index + 1);
343 size_t start_index = 0;
344 if (unparsed_switch.substr(0, 2) == "--")
346 name = unparsed_switch.substr(start_index, equals_index - start_index);
348 SetSwitch(name, value);
351 void Switches::RemoveSwitch(const std::string& name) {
352 switch_map_.erase(name);
355 bool Switches::HasSwitch(const std::string& name) const {
356 return switch_map_.count(name) > 0;
359 std::string Switches::GetSwitchValue(const std::string& name) const {
360 NativeString value = GetSwitchValueNative(name);
362 return UTF16ToUTF8(value);
368 Switches::NativeString Switches::GetSwitchValueNative(
369 const std::string& name) const {
370 SwitchMap::const_iterator iter = switch_map_.find(name);
371 if (iter == switch_map_.end())
372 return NativeString();
376 size_t Switches::GetSize() const {
377 return switch_map_.size();
380 void Switches::AppendToCommandLine(CommandLine* command) const {
381 for (SwitchMap::const_iterator iter = switch_map_.begin();
382 iter != switch_map_.end();
384 command->AppendSwitchNative(iter->first, iter->second);
388 std::string Switches::ToString() const {
390 SwitchMap::const_iterator iter = switch_map_.begin();
391 while (iter != switch_map_.end()) {
392 str += "--" + iter->first;
393 std::string value = GetSwitchValue(iter->first);
394 if (value.length()) {
395 if (value.find(' ') != std::string::npos)
396 value = base::GetDoubleQuotedJson(value);
400 if (iter == switch_map_.end())
407 Capabilities::Capabilities()
409 force_devtools_screenshot(false) {}
411 Capabilities::~Capabilities() {}
413 bool Capabilities::IsAndroid() const {
414 return !android_package.empty();
417 bool Capabilities::IsExistingBrowser() const {
418 return debugger_address.IsValid();
421 Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
422 std::map<std::string, Parser> parser_map;
423 parser_map["chromeOptions"] = base::Bind(&ParseChromeOptions);
424 parser_map["loggingPrefs"] = base::Bind(&ParseLoggingPrefs);
425 parser_map["proxy"] = base::Bind(&ParseProxy);
426 for (std::map<std::string, Parser>::iterator it = parser_map.begin();
427 it != parser_map.end(); ++it) {
428 const base::Value* capability = NULL;
429 if (desired_caps.Get(it->first, &capability)) {
430 Status status = it->second.Run(*capability, this);
431 if (status.IsError()) {
433 kUnknownError, "cannot parse capability: " + it->first, status);