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
["androidUseRunningApp"] =
266 base::Bind(&ParseBoolean
, &capabilities
->android_use_running_app
);
267 parser_map
["args"] = base::Bind(&ParseSwitches
);
268 parser_map
["loadAsync"] = base::Bind(&IgnoreDeprecatedOption
, "loadAsync");
269 } else if (is_existing
) {
270 parser_map
["debuggerAddress"] = base::Bind(&ParseUseExistingBrowser
);
272 parser_map
["args"] = base::Bind(&ParseSwitches
);
273 parser_map
["binary"] = base::Bind(&ParseFilePath
, &capabilities
->binary
);
274 parser_map
["detach"] = base::Bind(&ParseBoolean
, &capabilities
->detach
);
275 parser_map
["excludeSwitches"] = base::Bind(&ParseExcludeSwitches
);
276 parser_map
["extensions"] = base::Bind(&ParseExtensions
);
277 parser_map
["forceDevToolsScreenshot"] = base::Bind(
278 &ParseBoolean
, &capabilities
->force_devtools_screenshot
);
279 parser_map
["loadAsync"] = base::Bind(&IgnoreDeprecatedOption
, "loadAsync");
280 parser_map
["localState"] =
281 base::Bind(&ParseDict
, &capabilities
->local_state
);
282 parser_map
["logPath"] = base::Bind(&ParseLogPath
);
283 parser_map
["minidumpPath"] =
284 base::Bind(&ParseString
, &capabilities
->minidump_path
);
285 parser_map
["prefs"] = base::Bind(&ParseDict
, &capabilities
->prefs
);
288 for (base::DictionaryValue::Iterator
it(*chrome_options
); !it
.IsAtEnd();
290 if (parser_map
.find(it
.key()) == parser_map
.end()) {
291 return Status(kUnknownError
,
292 "unrecognized chrome option: " + it
.key());
294 Status status
= parser_map
[it
.key()].Run(it
.value(), capabilities
);
295 if (status
.IsError())
296 return Status(kUnknownError
, "cannot parse " + it
.key(), status
);
303 Switches::Switches() {}
305 Switches::~Switches() {}
307 void Switches::SetSwitch(const std::string
& name
) {
308 SetSwitch(name
, NativeString());
311 void Switches::SetSwitch(const std::string
& name
, const std::string
& value
) {
313 SetSwitch(name
, base::UTF8ToUTF16(value
));
315 switch_map_
[name
] = value
;
319 void Switches::SetSwitch(const std::string
& name
, const base::string16
& value
) {
321 switch_map_
[name
] = value
;
323 SetSwitch(name
, base::UTF16ToUTF8(value
));
327 void Switches::SetSwitch(const std::string
& name
, const base::FilePath
& value
) {
328 SetSwitch(name
, value
.value());
331 void Switches::SetFromSwitches(const Switches
& switches
) {
332 for (SwitchMap::const_iterator iter
= switches
.switch_map_
.begin();
333 iter
!= switches
.switch_map_
.end();
335 switch_map_
[iter
->first
] = iter
->second
;
339 void Switches::SetUnparsedSwitch(const std::string
& unparsed_switch
) {
341 size_t equals_index
= unparsed_switch
.find('=');
342 if (equals_index
!= std::string::npos
)
343 value
= unparsed_switch
.substr(equals_index
+ 1);
346 size_t start_index
= 0;
347 if (unparsed_switch
.substr(0, 2) == "--")
349 name
= unparsed_switch
.substr(start_index
, equals_index
- start_index
);
351 SetSwitch(name
, value
);
354 void Switches::RemoveSwitch(const std::string
& name
) {
355 switch_map_
.erase(name
);
358 bool Switches::HasSwitch(const std::string
& name
) const {
359 return switch_map_
.count(name
) > 0;
362 std::string
Switches::GetSwitchValue(const std::string
& name
) const {
363 NativeString value
= GetSwitchValueNative(name
);
365 return base::UTF16ToUTF8(value
);
371 Switches::NativeString
Switches::GetSwitchValueNative(
372 const std::string
& name
) const {
373 SwitchMap::const_iterator iter
= switch_map_
.find(name
);
374 if (iter
== switch_map_
.end())
375 return NativeString();
379 size_t Switches::GetSize() const {
380 return switch_map_
.size();
383 void Switches::AppendToCommandLine(CommandLine
* command
) const {
384 for (SwitchMap::const_iterator iter
= switch_map_
.begin();
385 iter
!= switch_map_
.end();
387 command
->AppendSwitchNative(iter
->first
, iter
->second
);
391 std::string
Switches::ToString() const {
393 SwitchMap::const_iterator iter
= switch_map_
.begin();
394 while (iter
!= switch_map_
.end()) {
395 str
+= "--" + iter
->first
;
396 std::string value
= GetSwitchValue(iter
->first
);
397 if (value
.length()) {
398 if (value
.find(' ') != std::string::npos
)
399 value
= base::GetQuotedJSONString(value
);
403 if (iter
== switch_map_
.end())
410 Capabilities::Capabilities()
411 : android_use_running_app(false),
413 force_devtools_screenshot(false) {}
415 Capabilities::~Capabilities() {}
417 bool Capabilities::IsAndroid() const {
418 return !android_package
.empty();
421 bool Capabilities::IsExistingBrowser() const {
422 return debugger_address
.IsValid();
425 Status
Capabilities::Parse(const base::DictionaryValue
& desired_caps
) {
426 std::map
<std::string
, Parser
> parser_map
;
427 parser_map
["chromeOptions"] = base::Bind(&ParseChromeOptions
);
428 parser_map
["loggingPrefs"] = base::Bind(&ParseLoggingPrefs
);
429 parser_map
["proxy"] = base::Bind(&ParseProxy
);
430 for (std::map
<std::string
, Parser
>::iterator it
= parser_map
.begin();
431 it
!= parser_map
.end(); ++it
) {
432 const base::Value
* capability
= NULL
;
433 if (desired_caps
.Get(it
->first
, &capability
)) {
434 Status status
= it
->second
.Run(*capability
, this);
435 if (status
.IsError()) {
437 kUnknownError
, "cannot parse capability: " + it
->first
, status
);