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/capabilities.h" 6 7 #include <map> 8 9 #include "base/bind.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/mobile_device.h" 21 #include "chrome/test/chromedriver/chrome/status.h" 22 #include "chrome/test/chromedriver/logging.h" 23 #include "net/base/net_util.h" 24 25 namespace { 26 27 typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser; 28 29 Status ParseBoolean( 30 bool* to_set, 31 const base::Value& option, 32 Capabilities* capabilities) { 33 if (!option.GetAsBoolean(to_set)) 34 return Status(kUnknownError, "must be a boolean"); 35 return Status(kOk); 36 } 37 38 Status ParseString(std::string* to_set, 39 const base::Value& option, 40 Capabilities* capabilities) { 41 std::string str; 42 if (!option.GetAsString(&str)) 43 return Status(kUnknownError, "must be a string"); 44 if (str.empty()) 45 return Status(kUnknownError, "cannot be empty"); 46 *to_set = str; 47 return Status(kOk); 48 } 49 50 Status ParseInterval(int* to_set, 51 const base::Value& option, 52 Capabilities* capabilities) { 53 int parsed_int = 0; 54 if (!option.GetAsInteger(&parsed_int)) 55 return Status(kUnknownError, "must be an integer"); 56 if (parsed_int <= 0) 57 return Status(kUnknownError, "must be positive"); 58 *to_set = parsed_int; 59 return Status(kOk); 60 } 61 62 Status ParseFilePath(base::FilePath* to_set, 63 const base::Value& option, 64 Capabilities* capabilities) { 65 base::FilePath::StringType str; 66 if (!option.GetAsString(&str)) 67 return Status(kUnknownError, "must be a string"); 68 *to_set = base::FilePath(str); 69 return Status(kOk); 70 } 71 72 Status ParseDict(scoped_ptr<base::DictionaryValue>* to_set, 73 const base::Value& option, 74 Capabilities* capabilities) { 75 const base::DictionaryValue* dict = NULL; 76 if (!option.GetAsDictionary(&dict)) 77 return Status(kUnknownError, "must be a dictionary"); 78 to_set->reset(dict->DeepCopy()); 79 return Status(kOk); 80 } 81 82 Status IgnoreDeprecatedOption( 83 const char* option_name, 84 const base::Value& option, 85 Capabilities* capabilities) { 86 LOG(WARNING) << "Deprecated chrome option is ignored: " << option_name; 87 return Status(kOk); 88 } 89 90 Status IgnoreCapability(const base::Value& option, Capabilities* capabilities) { 91 return Status(kOk); 92 } 93 94 Status ParseLogPath(const base::Value& option, Capabilities* capabilities) { 95 if (!option.GetAsString(&capabilities->log_path)) 96 return Status(kUnknownError, "must be a string"); 97 return Status(kOk); 98 } 99 100 Status ParseDeviceName(std::string device_name, Capabilities* capabilities) { 101 scoped_ptr<MobileDevice> device; 102 Status status = FindMobileDevice(device_name, &device); 103 104 if (status.IsError()) { 105 return Status(kUnknownError, 106 "'" + device_name + "' must be a valid device", 107 status); 108 } 109 110 capabilities->device_metrics.reset(device->device_metrics.release()); 111 capabilities->switches.SetSwitch("user-agent", device->user_agent); 112 113 return Status(kOk); 114 } 115 116 Status ParseMobileEmulation(const base::Value& option, 117 Capabilities* capabilities) { 118 const base::DictionaryValue* mobile_emulation; 119 if (!option.GetAsDictionary(&mobile_emulation)) 120 return Status(kUnknownError, "'mobileEmulation' must be a dictionary"); 121 122 if (mobile_emulation->HasKey("deviceName")) { 123 // Cannot use any other options with deviceName. 124 if (mobile_emulation->size() > 1) 125 return Status(kUnknownError, "'deviceName' must be used alone"); 126 127 std::string device_name; 128 if (!mobile_emulation->GetString("deviceName", &device_name)) 129 return Status(kUnknownError, "'deviceName' must be a string"); 130 131 return ParseDeviceName(device_name, capabilities); 132 } 133 134 if (mobile_emulation->HasKey("deviceMetrics")) { 135 const base::DictionaryValue* metrics; 136 if (!mobile_emulation->GetDictionary("deviceMetrics", &metrics)) 137 return Status(kUnknownError, "'deviceMetrics' must be a dictionary"); 138 139 int width = 0; 140 int height = 0; 141 double device_scale_factor = 0; 142 if (!metrics->GetInteger("width", &width) || 143 !metrics->GetInteger("height", &height) || 144 !metrics->GetDouble("pixelRatio", &device_scale_factor)) 145 return Status(kUnknownError, "invalid 'deviceMetrics'"); 146 147 DeviceMetrics* device_metrics = 148 new DeviceMetrics(width, height, device_scale_factor); 149 capabilities->device_metrics = 150 scoped_ptr<DeviceMetrics>(device_metrics); 151 } 152 153 if (mobile_emulation->HasKey("userAgent")) { 154 std::string user_agent; 155 if (!mobile_emulation->GetString("userAgent", &user_agent)) 156 return Status(kUnknownError, "'userAgent' must be a string"); 157 158 capabilities->switches.SetSwitch("user-agent", user_agent); 159 } 160 161 return Status(kOk); 162 } 163 164 Status ParseSwitches(const base::Value& option, 165 Capabilities* capabilities) { 166 const base::ListValue* switches_list = NULL; 167 if (!option.GetAsList(&switches_list)) 168 return Status(kUnknownError, "must be a list"); 169 for (size_t i = 0; i < switches_list->GetSize(); ++i) { 170 std::string arg_string; 171 if (!switches_list->GetString(i, &arg_string)) 172 return Status(kUnknownError, "each argument must be a string"); 173 capabilities->switches.SetUnparsedSwitch(arg_string); 174 } 175 return Status(kOk); 176 } 177 178 Status ParseExtensions(const base::Value& option, Capabilities* capabilities) { 179 const base::ListValue* extensions = NULL; 180 if (!option.GetAsList(&extensions)) 181 return Status(kUnknownError, "must be a list"); 182 for (size_t i = 0; i < extensions->GetSize(); ++i) { 183 std::string extension; 184 if (!extensions->GetString(i, &extension)) { 185 return Status(kUnknownError, 186 "each extension must be a base64 encoded string"); 187 } 188 capabilities->extensions.push_back(extension); 189 } 190 return Status(kOk); 191 } 192 193 Status ParseProxy(const base::Value& option, Capabilities* capabilities) { 194 const base::DictionaryValue* proxy_dict; 195 if (!option.GetAsDictionary(&proxy_dict)) 196 return Status(kUnknownError, "must be a dictionary"); 197 std::string proxy_type; 198 if (!proxy_dict->GetString("proxyType", &proxy_type)) 199 return Status(kUnknownError, "'proxyType' must be a string"); 200 proxy_type = base::StringToLowerASCII(proxy_type); 201 if (proxy_type == "direct") { 202 capabilities->switches.SetSwitch("no-proxy-server"); 203 } else if (proxy_type == "system") { 204 // Chrome default. 205 } else if (proxy_type == "pac") { 206 CommandLine::StringType proxy_pac_url; 207 if (!proxy_dict->GetString("proxyAutoconfigUrl", &proxy_pac_url)) 208 return Status(kUnknownError, "'proxyAutoconfigUrl' must be a string"); 209 capabilities->switches.SetSwitch("proxy-pac-url", proxy_pac_url); 210 } else if (proxy_type == "autodetect") { 211 capabilities->switches.SetSwitch("proxy-auto-detect"); 212 } else if (proxy_type == "manual") { 213 const char* proxy_servers_options[][2] = { 214 {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}}; 215 const base::Value* option_value = NULL; 216 std::string proxy_servers; 217 for (size_t i = 0; i < arraysize(proxy_servers_options); ++i) { 218 if (!proxy_dict->Get(proxy_servers_options[i][0], &option_value) || 219 option_value->IsType(base::Value::TYPE_NULL)) { 220 continue; 221 } 222 std::string value; 223 if (!option_value->GetAsString(&value)) { 224 return Status( 225 kUnknownError, 226 base::StringPrintf("'%s' must be a string", 227 proxy_servers_options[i][0])); 228 } 229 // Converts into Chrome proxy scheme. 230 // Example: "http=localhost:9000;ftp=localhost:8000". 231 if (!proxy_servers.empty()) 232 proxy_servers += ";"; 233 proxy_servers += base::StringPrintf( 234 "%s=%s", proxy_servers_options[i][1], value.c_str()); 235 } 236 237 std::string proxy_bypass_list; 238 if (proxy_dict->Get("noProxy", &option_value) && 239 !option_value->IsType(base::Value::TYPE_NULL)) { 240 if (!option_value->GetAsString(&proxy_bypass_list)) 241 return Status(kUnknownError, "'noProxy' must be a string"); 242 } 243 244 if (proxy_servers.empty() && proxy_bypass_list.empty()) { 245 return Status(kUnknownError, 246 "proxyType is 'manual' but no manual " 247 "proxy capabilities were found"); 248 } 249 if (!proxy_servers.empty()) 250 capabilities->switches.SetSwitch("proxy-server", proxy_servers); 251 if (!proxy_bypass_list.empty()) { 252 capabilities->switches.SetSwitch("proxy-bypass-list", 253 proxy_bypass_list); 254 } 255 } else { 256 return Status(kUnknownError, "unrecognized proxy type:" + proxy_type); 257 } 258 return Status(kOk); 259 } 260 261 Status ParseExcludeSwitches(const base::Value& option, 262 Capabilities* capabilities) { 263 const base::ListValue* switches = NULL; 264 if (!option.GetAsList(&switches)) 265 return Status(kUnknownError, "must be a list"); 266 for (size_t i = 0; i < switches->GetSize(); ++i) { 267 std::string switch_name; 268 if (!switches->GetString(i, &switch_name)) { 269 return Status(kUnknownError, 270 "each switch to be removed must be a string"); 271 } 272 capabilities->exclude_switches.insert(switch_name); 273 } 274 return Status(kOk); 275 } 276 277 Status ParseUseRemoteBrowser(const base::Value& option, 278 Capabilities* capabilities) { 279 std::string server_addr; 280 if (!option.GetAsString(&server_addr)) 281 return Status(kUnknownError, "must be 'host:port'"); 282 283 std::vector<std::string> values; 284 base::SplitString(server_addr, ':', &values); 285 if (values.size() != 2) 286 return Status(kUnknownError, "must be 'host:port'"); 287 288 int port = 0; 289 base::StringToInt(values[1], &port); 290 if (port <= 0) 291 return Status(kUnknownError, "port must be > 0"); 292 293 capabilities->debugger_address = NetAddress(values[0], port); 294 return Status(kOk); 295 } 296 297 Status ParseLoggingPrefs(const base::Value& option, 298 Capabilities* capabilities) { 299 const base::DictionaryValue* logging_prefs = NULL; 300 if (!option.GetAsDictionary(&logging_prefs)) 301 return Status(kUnknownError, "must be a dictionary"); 302 303 for (base::DictionaryValue::Iterator pref(*logging_prefs); 304 !pref.IsAtEnd(); pref.Advance()) { 305 std::string type = pref.key(); 306 Log::Level level; 307 std::string level_name; 308 if (!pref.value().GetAsString(&level_name) || 309 !WebDriverLog::NameToLevel(level_name, &level)) { 310 return Status(kUnknownError, "invalid log level for '" + type + "' log"); 311 } 312 capabilities->logging_prefs.insert(std::make_pair(type, level)); 313 } 314 return Status(kOk); 315 } 316 317 Status ParseInspectorDomainStatus( 318 PerfLoggingPrefs::InspectorDomainStatus* to_set, 319 const base::Value& option, 320 Capabilities* capabilities) { 321 bool desired_value; 322 if (!option.GetAsBoolean(&desired_value)) 323 return Status(kUnknownError, "must be a boolean"); 324 if (desired_value) 325 *to_set = PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled; 326 else 327 *to_set = PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyDisabled; 328 return Status(kOk); 329 } 330 331 Status ParsePerfLoggingPrefs(const base::Value& option, 332 Capabilities* capabilities) { 333 const base::DictionaryValue* perf_logging_prefs = NULL; 334 if (!option.GetAsDictionary(&perf_logging_prefs)) 335 return Status(kUnknownError, "must be a dictionary"); 336 337 std::map<std::string, Parser> parser_map; 338 parser_map["bufferUsageReportingInterval"] = base::Bind(&ParseInterval, 339 &capabilities->perf_logging_prefs.buffer_usage_reporting_interval); 340 parser_map["enableNetwork"] = base::Bind( 341 &ParseInspectorDomainStatus, &capabilities->perf_logging_prefs.network); 342 parser_map["enablePage"] = base::Bind( 343 &ParseInspectorDomainStatus, &capabilities->perf_logging_prefs.page); 344 parser_map["enableTimeline"] = base::Bind( 345 &ParseInspectorDomainStatus, &capabilities->perf_logging_prefs.timeline); 346 parser_map["traceCategories"] = base::Bind( 347 &ParseString, &capabilities->perf_logging_prefs.trace_categories); 348 349 for (base::DictionaryValue::Iterator it(*perf_logging_prefs); !it.IsAtEnd(); 350 it.Advance()) { 351 if (parser_map.find(it.key()) == parser_map.end()) 352 return Status(kUnknownError, "unrecognized performance logging " 353 "option: " + it.key()); 354 Status status = parser_map[it.key()].Run(it.value(), capabilities); 355 if (status.IsError()) 356 return Status(kUnknownError, "cannot parse " + it.key(), status); 357 } 358 return Status(kOk); 359 } 360 361 Status ParseChromeOptions( 362 const base::Value& capability, 363 Capabilities* capabilities) { 364 const base::DictionaryValue* chrome_options = NULL; 365 if (!capability.GetAsDictionary(&chrome_options)) 366 return Status(kUnknownError, "must be a dictionary"); 367 368 bool is_android = chrome_options->HasKey("androidPackage"); 369 bool is_remote = chrome_options->HasKey("debuggerAddress"); 370 371 std::map<std::string, Parser> parser_map; 372 // Ignore 'args', 'binary' and 'extensions' capabilities by default, since the 373 // Java client always passes them. 374 parser_map["args"] = base::Bind(&IgnoreCapability); 375 parser_map["binary"] = base::Bind(&IgnoreCapability); 376 parser_map["extensions"] = base::Bind(&IgnoreCapability); 377 378 parser_map["perfLoggingPrefs"] = base::Bind(&ParsePerfLoggingPrefs); 379 380 if (is_android) { 381 parser_map["androidActivity"] = 382 base::Bind(&ParseString, &capabilities->android_activity); 383 parser_map["androidDeviceSerial"] = 384 base::Bind(&ParseString, &capabilities->android_device_serial); 385 parser_map["androidPackage"] = 386 base::Bind(&ParseString, &capabilities->android_package); 387 parser_map["androidProcess"] = 388 base::Bind(&ParseString, &capabilities->android_process); 389 parser_map["androidUseRunningApp"] = 390 base::Bind(&ParseBoolean, &capabilities->android_use_running_app); 391 parser_map["args"] = base::Bind(&ParseSwitches); 392 parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches); 393 parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync"); 394 } else if (is_remote) { 395 parser_map["debuggerAddress"] = base::Bind(&ParseUseRemoteBrowser); 396 } else { 397 parser_map["args"] = base::Bind(&ParseSwitches); 398 parser_map["binary"] = base::Bind(&ParseFilePath, &capabilities->binary); 399 parser_map["detach"] = base::Bind(&ParseBoolean, &capabilities->detach); 400 parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches); 401 parser_map["extensions"] = base::Bind(&ParseExtensions); 402 parser_map["forceDevToolsScreenshot"] = base::Bind( 403 &ParseBoolean, &capabilities->force_devtools_screenshot); 404 parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync"); 405 parser_map["localState"] = 406 base::Bind(&ParseDict, &capabilities->local_state); 407 parser_map["logPath"] = base::Bind(&ParseLogPath); 408 parser_map["minidumpPath"] = 409 base::Bind(&ParseString, &capabilities->minidump_path); 410 parser_map["mobileEmulation"] = base::Bind(&ParseMobileEmulation); 411 parser_map["prefs"] = base::Bind(&ParseDict, &capabilities->prefs); 412 } 413 414 for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd(); 415 it.Advance()) { 416 if (parser_map.find(it.key()) == parser_map.end()) { 417 return Status(kUnknownError, 418 "unrecognized chrome option: " + it.key()); 419 } 420 Status status = parser_map[it.key()].Run(it.value(), capabilities); 421 if (status.IsError()) 422 return Status(kUnknownError, "cannot parse " + it.key(), status); 423 } 424 return Status(kOk); 425 } 426 427 } // namespace 428 429 Switches::Switches() {} 430 431 Switches::~Switches() {} 432 433 void Switches::SetSwitch(const std::string& name) { 434 SetSwitch(name, NativeString()); 435 } 436 437 void Switches::SetSwitch(const std::string& name, const std::string& value) { 438 #if defined(OS_WIN) 439 SetSwitch(name, base::UTF8ToUTF16(value)); 440 #else 441 switch_map_[name] = value; 442 #endif 443 } 444 445 void Switches::SetSwitch(const std::string& name, const base::string16& value) { 446 #if defined(OS_WIN) 447 switch_map_[name] = value; 448 #else 449 SetSwitch(name, base::UTF16ToUTF8(value)); 450 #endif 451 } 452 453 void Switches::SetSwitch(const std::string& name, const base::FilePath& value) { 454 SetSwitch(name, value.value()); 455 } 456 457 void Switches::SetFromSwitches(const Switches& switches) { 458 for (SwitchMap::const_iterator iter = switches.switch_map_.begin(); 459 iter != switches.switch_map_.end(); 460 ++iter) { 461 switch_map_[iter->first] = iter->second; 462 } 463 } 464 465 void Switches::SetUnparsedSwitch(const std::string& unparsed_switch) { 466 std::string value; 467 size_t equals_index = unparsed_switch.find('='); 468 if (equals_index != std::string::npos) 469 value = unparsed_switch.substr(equals_index + 1); 470 471 std::string name; 472 size_t start_index = 0; 473 if (unparsed_switch.substr(0, 2) == "--") 474 start_index = 2; 475 name = unparsed_switch.substr(start_index, equals_index - start_index); 476 477 SetSwitch(name, value); 478 } 479 480 void Switches::RemoveSwitch(const std::string& name) { 481 switch_map_.erase(name); 482 } 483 484 bool Switches::HasSwitch(const std::string& name) const { 485 return switch_map_.count(name) > 0; 486 } 487 488 std::string Switches::GetSwitchValue(const std::string& name) const { 489 NativeString value = GetSwitchValueNative(name); 490 #if defined(OS_WIN) 491 return base::UTF16ToUTF8(value); 492 #else 493 return value; 494 #endif 495 } 496 497 Switches::NativeString Switches::GetSwitchValueNative( 498 const std::string& name) const { 499 SwitchMap::const_iterator iter = switch_map_.find(name); 500 if (iter == switch_map_.end()) 501 return NativeString(); 502 return iter->second; 503 } 504 505 size_t Switches::GetSize() const { 506 return switch_map_.size(); 507 } 508 509 void Switches::AppendToCommandLine(CommandLine* command) const { 510 for (SwitchMap::const_iterator iter = switch_map_.begin(); 511 iter != switch_map_.end(); 512 ++iter) { 513 command->AppendSwitchNative(iter->first, iter->second); 514 } 515 } 516 517 std::string Switches::ToString() const { 518 std::string str; 519 SwitchMap::const_iterator iter = switch_map_.begin(); 520 while (iter != switch_map_.end()) { 521 str += "--" + iter->first; 522 std::string value = GetSwitchValue(iter->first); 523 if (value.length()) { 524 if (value.find(' ') != std::string::npos) 525 value = base::GetQuotedJSONString(value); 526 str += "=" + value; 527 } 528 ++iter; 529 if (iter == switch_map_.end()) 530 break; 531 str += " "; 532 } 533 return str; 534 } 535 536 PerfLoggingPrefs::PerfLoggingPrefs() 537 : network(InspectorDomainStatus::kDefaultEnabled), 538 page(InspectorDomainStatus::kDefaultEnabled), 539 timeline(InspectorDomainStatus::kDefaultEnabled), 540 trace_categories(), 541 buffer_usage_reporting_interval(1000) {} 542 543 PerfLoggingPrefs::~PerfLoggingPrefs() {} 544 545 Capabilities::Capabilities() 546 : android_use_running_app(false), 547 detach(false), 548 force_devtools_screenshot(false) {} 549 550 Capabilities::~Capabilities() {} 551 552 bool Capabilities::IsAndroid() const { 553 return !android_package.empty(); 554 } 555 556 bool Capabilities::IsRemoteBrowser() const { 557 return debugger_address.IsValid(); 558 } 559 560 Status Capabilities::Parse(const base::DictionaryValue& desired_caps) { 561 std::map<std::string, Parser> parser_map; 562 parser_map["chromeOptions"] = base::Bind(&ParseChromeOptions); 563 parser_map["loggingPrefs"] = base::Bind(&ParseLoggingPrefs); 564 parser_map["proxy"] = base::Bind(&ParseProxy); 565 for (std::map<std::string, Parser>::iterator it = parser_map.begin(); 566 it != parser_map.end(); ++it) { 567 const base::Value* capability = NULL; 568 if (desired_caps.Get(it->first, &capability)) { 569 Status status = it->second.Run(*capability, this); 570 if (status.IsError()) { 571 return Status( 572 kUnknownError, "cannot parse capability: " + it->first, status); 573 } 574 } 575 } 576 // Perf log must be enabled if perf log prefs are specified; otherwise, error. 577 LoggingPrefs::const_iterator iter = logging_prefs.find( 578 WebDriverLog::kPerformanceType); 579 if (iter == logging_prefs.end() || iter->second == Log::kOff) { 580 const base::DictionaryValue* chrome_options = NULL; 581 if (desired_caps.GetDictionary("chromeOptions", &chrome_options) && 582 chrome_options->HasKey("perfLoggingPrefs")) { 583 return Status(kUnknownError, "perfLoggingPrefs specified, " 584 "but performance logging was not enabled"); 585 } 586 } 587 return Status(kOk); 588 } 589