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/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