proxy_config_service_impl.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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/browser/chromeos/proxy_config_service_impl.h" 6 7#include <ostream> 8 9#include "base/logging.h" 10#include "base/string_util.h" 11#include "base/task.h" 12#include "chrome/browser/browser_thread.h" 13#include "chrome/browser/chromeos/cros/cros_library.h" 14#include "chrome/common/json_value_serializer.h" 15 16namespace chromeos { 17 18namespace { 19 20const char* SourceToString(ProxyConfigServiceImpl::ProxyConfig::Source source) { 21 switch (source) { 22 case ProxyConfigServiceImpl::ProxyConfig::SOURCE_NONE: 23 return "SOURCE_NONE"; 24 case ProxyConfigServiceImpl::ProxyConfig::SOURCE_POLICY: 25 return "SOURCE_POLICY"; 26 case ProxyConfigServiceImpl::ProxyConfig::SOURCE_OWNER: 27 return "SOURCE_OWNER"; 28 } 29 NOTREACHED() << "Unrecognized source type"; 30 return ""; 31} 32 33std::ostream& operator<<(std::ostream& out, 34 const ProxyConfigServiceImpl::ProxyConfig::ManualProxy& proxy) { 35 out << " " << SourceToString(proxy.source) << "\n" 36 << " server: " << (proxy.server.is_valid() ? proxy.server.ToURI() : "") 37 << "\n"; 38 return out; 39} 40 41std::ostream& operator<<(std::ostream& out, 42 const ProxyConfigServiceImpl::ProxyConfig& config) { 43 switch (config.mode) { 44 case ProxyConfigServiceImpl::ProxyConfig::MODE_DIRECT: 45 out << "Direct connection:\n " 46 << SourceToString(config.automatic_proxy.source) << "\n"; 47 break; 48 case ProxyConfigServiceImpl::ProxyConfig::MODE_AUTO_DETECT: 49 out << "Auto detection:\n " 50 << SourceToString(config.automatic_proxy.source) << "\n"; 51 break; 52 case ProxyConfigServiceImpl::ProxyConfig::MODE_PAC_SCRIPT: 53 out << "Custom PAC script:\n " 54 << SourceToString(config.automatic_proxy.source) 55 << "\n PAC: " << config.automatic_proxy.pac_url << "\n"; 56 break; 57 case ProxyConfigServiceImpl::ProxyConfig::MODE_SINGLE_PROXY: 58 out << "Single proxy:\n" << config.single_proxy; 59 break; 60 case ProxyConfigServiceImpl::ProxyConfig::MODE_PROXY_PER_SCHEME: 61 out << "HTTP proxy: " << config.http_proxy; 62 out << "HTTPS proxy: " << config.https_proxy; 63 out << "FTP proxy: " << config.ftp_proxy; 64 out << "SOCKS proxy: " << config.socks_proxy; 65 break; 66 default: 67 NOTREACHED() << "Unrecognized proxy config mode"; 68 break; 69 } 70 if (config.mode == ProxyConfigServiceImpl::ProxyConfig::MODE_SINGLE_PROXY || 71 config.mode == 72 ProxyConfigServiceImpl::ProxyConfig::MODE_PROXY_PER_SCHEME) { 73 out << "Bypass list: "; 74 if (config.bypass_rules.rules().empty()) { 75 out << "[None]"; 76 } else { 77 const net::ProxyBypassRules& bypass_rules = config.bypass_rules; 78 net::ProxyBypassRules::RuleList::const_iterator it; 79 for (it = bypass_rules.rules().begin(); 80 it != bypass_rules.rules().end(); ++it) { 81 out << "\n " << (*it)->ToString(); 82 } 83 } 84 } 85 return out; 86} 87 88std::string ProxyConfigToString( 89 const ProxyConfigServiceImpl::ProxyConfig& proxy_config) { 90 std::ostringstream stream; 91 stream << proxy_config; 92 return stream.str(); 93} 94 95// Name of signed setting persisted on device, writeable only by owner. 96const char* kSettingProxyEverywhere = "cros.proxy.everywhere"; 97 98// Names used for dictionary values to serialize chromeos::ProxyConfig. 99const char* kMode = "mode"; 100const char* kSource = "src"; 101const char* kAutomaticProxy = "auto"; 102const char* kSingleProxy = "single"; 103const char* kHttpProxy = "http"; 104const char* kHttpsProxy = "https"; 105const char* kFtpProxy = "ftp"; 106const char* kSocksProxy = "socks"; 107const char* kPACUrl = "pac"; 108const char* kServer = "server"; 109const char* kBypassRules = "bypass_rules"; 110const char* kRulesNum = "num"; 111const char* kRulesList = "list"; 112 113} // namespace 114 115//---------- ProxyConfigServiceImpl::ProxyConfig::Setting methods -------------- 116 117bool ProxyConfigServiceImpl::ProxyConfig::Setting::CanBeWrittenByUser( 118 bool user_is_owner) { 119 // Setting can only be written by user if user is owner and setting is not 120 // from policy. 121 return user_is_owner && source != ProxyConfig::SOURCE_POLICY; 122} 123 124DictionaryValue* ProxyConfigServiceImpl::ProxyConfig::Setting::Encode() const { 125 DictionaryValue* dict = new DictionaryValue; 126 dict->SetInteger(kSource, source); 127 return dict; 128} 129 130bool ProxyConfigServiceImpl::ProxyConfig::Setting::Decode( 131 DictionaryValue* dict) { 132 int int_source; 133 if (!dict->GetInteger(kSource, &int_source)) 134 return false; 135 source = static_cast<Source>(int_source); 136 return true; 137} 138 139//------- ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy methods ---------- 140 141DictionaryValue* 142 ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy::Encode() const { 143 DictionaryValue* dict = Setting::Encode(); 144 if (!pac_url.is_empty()) 145 dict->SetString(kPACUrl, pac_url.spec()); 146 return dict; 147} 148 149bool ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy::Decode( 150 DictionaryValue* dict, Mode mode) { 151 if (!Setting::Decode(dict)) 152 return false; 153 if (mode == MODE_PAC_SCRIPT) { 154 std::string value; 155 if (!dict->GetString(kPACUrl, &value)) 156 return false; 157 pac_url = GURL(value); 158 } 159 return true; 160} 161 162//--------- ProxyConfigServiceImpl::ProxyConfig::ManualProxy methods ----------- 163 164DictionaryValue* 165 ProxyConfigServiceImpl::ProxyConfig::ManualProxy::Encode() const { 166 DictionaryValue* dict = Setting::Encode(); 167 dict->SetString(kServer, server.ToURI()); 168 return dict; 169} 170 171bool ProxyConfigServiceImpl::ProxyConfig::ManualProxy::Decode( 172 DictionaryValue* dict, net::ProxyServer::Scheme scheme) { 173 if (!Setting::Decode(dict)) 174 return false; 175 std::string value; 176 if (!dict->GetString(kServer, &value)) 177 return false; 178 server = net::ProxyServer::FromURI(value, scheme); 179 return true; 180} 181 182//----------- ProxyConfigServiceImpl::ProxyConfig: public methods -------------- 183 184void ProxyConfigServiceImpl::ProxyConfig::ToNetProxyConfig( 185 net::ProxyConfig* net_config) { 186 switch (mode) { 187 case MODE_DIRECT: 188 *net_config = net::ProxyConfig::CreateDirect(); 189 break; 190 case MODE_AUTO_DETECT: 191 *net_config = net::ProxyConfig::CreateAutoDetect(); 192 break; 193 case MODE_PAC_SCRIPT: 194 *net_config = net::ProxyConfig::CreateFromCustomPacURL( 195 automatic_proxy.pac_url); 196 break; 197 case MODE_SINGLE_PROXY: 198 *net_config = net::ProxyConfig(); 199 net_config->proxy_rules().type = 200 net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY; 201 net_config->proxy_rules().single_proxy = single_proxy.server; 202 net_config->proxy_rules().bypass_rules = bypass_rules; 203 break; 204 case MODE_PROXY_PER_SCHEME: 205 *net_config = net::ProxyConfig(); 206 net_config->proxy_rules().type = 207 net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME; 208 net_config->proxy_rules().proxy_for_http = http_proxy.server; 209 net_config->proxy_rules().proxy_for_https = https_proxy.server; 210 net_config->proxy_rules().proxy_for_ftp = ftp_proxy.server; 211 net_config->proxy_rules().fallback_proxy = socks_proxy.server; 212 net_config->proxy_rules().bypass_rules = bypass_rules; 213 break; 214 default: 215 NOTREACHED() << "Unrecognized proxy config mode"; 216 break; 217 } 218} 219 220bool ProxyConfigServiceImpl::ProxyConfig::Serialize(std::string* output) { 221 scoped_ptr<DictionaryValue> dict(new DictionaryValue); 222 dict->SetInteger(kMode, mode); 223 DictionaryValue* proxy_dict; 224 switch (mode) { 225 case MODE_DIRECT: 226 case MODE_AUTO_DETECT: 227 case MODE_PAC_SCRIPT: 228 proxy_dict = automatic_proxy.Encode(); 229 dict->Set(kAutomaticProxy, proxy_dict); 230 break; 231 case MODE_SINGLE_PROXY: 232 EncodeManualProxy(single_proxy, dict.get(), kSingleProxy); 233 break; 234 case MODE_PROXY_PER_SCHEME: 235 EncodeManualProxy(http_proxy, dict.get(), kHttpProxy); 236 EncodeManualProxy(https_proxy, dict.get(), kHttpsProxy); 237 EncodeManualProxy(ftp_proxy, dict.get(), kFtpProxy); 238 EncodeManualProxy(socks_proxy, dict.get(), kSocksProxy); 239 break; 240 default: 241 NOTREACHED() << "Unrecognized proxy config mode"; 242 break; 243 } 244 net::ProxyBypassRules::RuleList rules = bypass_rules.rules(); 245 if (!rules.empty()) { 246 DictionaryValue* bypass_dict = new DictionaryValue; 247 bypass_dict->SetInteger(kRulesNum, rules.size()); 248 ListValue* list = new ListValue; 249 for (size_t i = 0; i < rules.size(); ++i) { 250 list->Append(Value::CreateStringValue(rules[i]->ToString())); 251 } 252 bypass_dict->Set(kRulesList, list); 253 dict->Set(kBypassRules, bypass_dict); 254 } 255 JSONStringValueSerializer serializer(output); 256 return serializer.Serialize(*dict.get()); 257} 258 259bool ProxyConfigServiceImpl::ProxyConfig::Deserialize( 260 const std::string& input) { 261 JSONStringValueSerializer serializer(input); 262 scoped_ptr<Value> value(serializer.Deserialize(NULL, NULL)); 263 if (!value.get() || value->GetType() != Value::TYPE_DICTIONARY) 264 return false; 265 DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); 266 int int_mode; 267 if (!dict->GetInteger(kMode, &int_mode)) 268 return false; 269 mode = static_cast<Mode>(int_mode); 270 DictionaryValue* proxy_dict = NULL; 271 switch (mode) { 272 case MODE_DIRECT: 273 case MODE_AUTO_DETECT: 274 case MODE_PAC_SCRIPT: 275 if (!dict->GetDictionary(kAutomaticProxy, &proxy_dict) || 276 !automatic_proxy.Decode(proxy_dict, mode)) 277 return false; 278 break; 279 case MODE_SINGLE_PROXY: 280 if (!DecodeManualProxy(dict, kSingleProxy, false, 281 net::ProxyServer::SCHEME_HTTP, &single_proxy)) 282 return false; 283 break; 284 case MODE_PROXY_PER_SCHEME: 285 if (!DecodeManualProxy(dict, kHttpProxy, true, 286 net::ProxyServer::SCHEME_HTTP, &http_proxy)) 287 return false; 288 if (!DecodeManualProxy(dict, kHttpsProxy, true, 289 net::ProxyServer::SCHEME_HTTPS, &https_proxy)) 290 return false; 291 if (!DecodeManualProxy(dict, kFtpProxy, true, 292 net::ProxyServer::SCHEME_HTTP, &ftp_proxy)) 293 return false; 294 if (!DecodeManualProxy(dict, kSocksProxy, true, 295 net::ProxyServer::SCHEME_SOCKS4, &socks_proxy)) 296 return false; 297 // Make sure we have valid server for at least one of the protocols. 298 if (!(http_proxy.server.is_valid() || https_proxy.server.is_valid() || 299 ftp_proxy.server.is_valid() || socks_proxy.server.is_valid())) 300 return false; 301 break; 302 default: 303 NOTREACHED() << "Unrecognized proxy config mode"; 304 break; 305 } 306 DictionaryValue* bypass_dict = NULL; 307 if (dict->GetDictionary(kBypassRules, &bypass_dict)) { 308 int num_rules = 0; 309 if (bypass_dict->GetInteger(kRulesNum, &num_rules) && num_rules > 0) { 310 ListValue* list; 311 if (!bypass_dict->GetList(kRulesList, &list)) 312 return false; 313 for (size_t i = 0; i < list->GetSize(); ++i) { 314 std::string rule; 315 if (!list->GetString(i, &rule)) 316 return false; 317 bypass_rules.AddRuleFromString(rule); 318 } 319 } 320 } 321 return true; 322} 323 324std::string ProxyConfigServiceImpl::ProxyConfig::ToString() const { 325 return ProxyConfigToString(*this); 326} 327 328//----------- ProxyConfigServiceImpl::ProxyConfig: private methods ------------- 329 330void ProxyConfigServiceImpl::ProxyConfig::EncodeManualProxy( 331 const ManualProxy& manual_proxy, DictionaryValue* dict, 332 const char* key_name) { 333 if (!manual_proxy.server.is_valid()) 334 return; 335 DictionaryValue* proxy_dict = manual_proxy.Encode(); 336 dict->Set(key_name, proxy_dict); 337} 338 339bool ProxyConfigServiceImpl::ProxyConfig::DecodeManualProxy( 340 DictionaryValue* dict, const char* key_name, bool ok_if_absent, 341 net::ProxyServer::Scheme scheme, ManualProxy* manual_proxy) { 342 DictionaryValue* proxy_dict; 343 if (!dict->GetDictionary(key_name, &proxy_dict)) 344 return ok_if_absent; 345 return manual_proxy->Decode(proxy_dict, scheme); 346} 347 348//------------------- ProxyConfigServiceImpl: public methods ------------------- 349 350ProxyConfigServiceImpl::ProxyConfigServiceImpl() 351 : can_post_task_(false), 352 has_config_(false), 353 persist_to_device_pending_(false) { 354 // Start async fetch of proxy config from settings persisted on device. 355 // TODO(kuan): retrieve config from policy and owner and merge them 356 bool use_default = true; 357 if (CrosLibrary::Get()->EnsureLoaded()) { 358 retrieve_property_op_ = SignedSettings::CreateRetrievePropertyOp( 359 kSettingProxyEverywhere, this); 360 if (retrieve_property_op_ && retrieve_property_op_->Execute()) { 361 VLOG(1) << "Start retrieving proxy setting from device"; 362 use_default = false; 363 } else { 364 VLOG(1) << "Fail to retrieve proxy setting from device"; 365 } 366 } 367 if (use_default) 368 InitConfigToDefault(false); 369 can_post_task_ = true; 370} 371 372ProxyConfigServiceImpl::ProxyConfigServiceImpl(const ProxyConfig& init_config) 373 : can_post_task_(true), 374 has_config_(true), 375 persist_to_device_pending_(false) { 376 reference_config_ = init_config; 377 // Update the IO-accessible copy in |cached_config_| as well. 378 cached_config_ = reference_config_; 379} 380 381ProxyConfigServiceImpl::~ProxyConfigServiceImpl() { 382} 383 384void ProxyConfigServiceImpl::UIGetProxyConfig(ProxyConfig* config) { 385 // Should be called from UI thread. 386 CheckCurrentlyOnUIThread(); 387 // Simply returns the copy on the UI thread. 388 *config = reference_config_; 389} 390 391bool ProxyConfigServiceImpl::UISetProxyConfigToDirect() { 392 // Should be called from UI thread. 393 CheckCurrentlyOnUIThread(); 394 reference_config_.mode = ProxyConfig::MODE_DIRECT; 395 OnUISetProxyConfig(true); 396 return true; 397} 398 399bool ProxyConfigServiceImpl::UISetProxyConfigToAutoDetect() { 400 // Should be called from UI thread. 401 CheckCurrentlyOnUIThread(); 402 reference_config_.mode = ProxyConfig::MODE_AUTO_DETECT; 403 OnUISetProxyConfig(true); 404 return true; 405} 406 407bool ProxyConfigServiceImpl::UISetProxyConfigToPACScript(const GURL& pac_url) { 408 // Should be called from UI thread. 409 CheckCurrentlyOnUIThread(); 410 reference_config_.mode = ProxyConfig::MODE_PAC_SCRIPT; 411 reference_config_.automatic_proxy.pac_url = pac_url; 412 OnUISetProxyConfig(true); 413 return true; 414} 415 416bool ProxyConfigServiceImpl::UISetProxyConfigToSingleProxy( 417 const net::ProxyServer& server) { 418 // Should be called from UI thread. 419 CheckCurrentlyOnUIThread(); 420 reference_config_.mode = ProxyConfig::MODE_SINGLE_PROXY; 421 reference_config_.single_proxy.server = server; 422 OnUISetProxyConfig(true); 423 return true; 424} 425 426bool ProxyConfigServiceImpl::UISetProxyConfigToProxyPerScheme( 427 const std::string& scheme, const net::ProxyServer& server) { 428 // Should be called from UI thread. 429 CheckCurrentlyOnUIThread(); 430 ProxyConfig::ManualProxy* proxy = NULL; 431 if (scheme == "http") 432 proxy = &reference_config_.http_proxy; 433 else if (scheme == "https") 434 proxy = &reference_config_.https_proxy; 435 else if (scheme == "ftp") 436 proxy = &reference_config_.ftp_proxy; 437 else if (scheme == "socks") 438 proxy = &reference_config_.socks_proxy; 439 if (!proxy) { 440 NOTREACHED() << "Cannot set proxy: invalid scheme [" << scheme << "]"; 441 return false; 442 } 443 reference_config_.mode = ProxyConfig::MODE_PROXY_PER_SCHEME; 444 proxy->server = server; 445 OnUISetProxyConfig(true); 446 return true; 447} 448 449bool ProxyConfigServiceImpl::UISetProxyConfigBypassRules( 450 const net::ProxyBypassRules& bypass_rules) { 451 // Should be called from UI thread. 452 CheckCurrentlyOnUIThread(); 453 DCHECK(reference_config_.mode == ProxyConfig::MODE_SINGLE_PROXY || 454 reference_config_.mode == ProxyConfig::MODE_PROXY_PER_SCHEME); 455 if (reference_config_.mode != ProxyConfig::MODE_SINGLE_PROXY && 456 reference_config_.mode != ProxyConfig::MODE_PROXY_PER_SCHEME) { 457 VLOG(1) << "Cannot set bypass rules for proxy mode [" 458 << reference_config_.mode << "]"; 459 return false; 460 } 461 reference_config_.bypass_rules = bypass_rules; 462 OnUISetProxyConfig(true); 463 return true; 464} 465 466void ProxyConfigServiceImpl::AddObserver( 467 net::ProxyConfigService::Observer* observer) { 468 // Should be called from IO thread. 469 CheckCurrentlyOnIOThread(); 470 observers_.AddObserver(observer); 471} 472 473void ProxyConfigServiceImpl::RemoveObserver( 474 net::ProxyConfigService::Observer* observer) { 475 // Should be called from IO thread. 476 CheckCurrentlyOnIOThread(); 477 observers_.RemoveObserver(observer); 478} 479 480bool ProxyConfigServiceImpl::IOGetProxyConfig(net::ProxyConfig* net_config) { 481 // Should be called from IO thread. 482 CheckCurrentlyOnIOThread(); 483 if (has_config_) { 484 // Simply return the last cached proxy configuration. 485 cached_config_.ToNetProxyConfig(net_config); 486 return true; 487 } 488 return false; 489} 490 491void ProxyConfigServiceImpl::OnSettingsOpSucceeded(bool value) { 492 VLOG(1) << "Stored proxy setting to device"; 493 store_property_op_ = NULL; 494 if (persist_to_device_pending_) 495 PersistConfigToDevice(); 496} 497 498void ProxyConfigServiceImpl::OnSettingsOpSucceeded(std::string value) { 499 VLOG(1) << "Retrieved proxy setting from device, value=[" << value << "]"; 500 if (reference_config_.Deserialize(value)) { 501 OnUISetProxyConfig(false); 502 } else { 503 LOG(WARNING) << "Error deserializing device's proxy setting"; 504 InitConfigToDefault(true); 505 } 506 retrieve_property_op_ = NULL; 507} 508 509void ProxyConfigServiceImpl::OnSettingsOpFailed() { 510 if (retrieve_property_op_) { 511 LOG(WARNING) << "Error retrieving proxy setting from device"; 512 InitConfigToDefault(true); 513 retrieve_property_op_ = NULL; 514 } else { 515 LOG(WARNING) << "Error storing proxy setting to device"; 516 store_property_op_ = NULL; 517 if (persist_to_device_pending_) 518 PersistConfigToDevice(); 519 } 520} 521 522//------------------ ProxyConfigServiceImpl: private methods ------------------- 523 524void ProxyConfigServiceImpl::InitConfigToDefault(bool post_to_io_thread) { 525 VLOG(1) << "Using default proxy config: auto-detect"; 526 reference_config_.mode = ProxyConfig::MODE_AUTO_DETECT; 527 reference_config_.automatic_proxy.source = ProxyConfig::SOURCE_OWNER; 528 if (post_to_io_thread && can_post_task_) { 529 OnUISetProxyConfig(false); 530 } else { 531 // Update the IO-accessible copy in |cached_config_| as well. 532 cached_config_ = reference_config_; 533 has_config_ = true; 534 } 535} 536 537void ProxyConfigServiceImpl::PersistConfigToDevice() { 538 DCHECK(!store_property_op_); 539 persist_to_device_pending_ = false; 540 std::string value; 541 if (!reference_config_.Serialize(&value)) { 542 VLOG(1) << "Error serializing proxy config"; 543 return; 544 } 545 store_property_op_ = SignedSettings::CreateStorePropertyOp( 546 kSettingProxyEverywhere, value, this); 547 bool rc = store_property_op_->Execute(); 548 VLOG(1) << "Start storing proxy setting to device, value=" << value << ", rc=" 549 << rc; 550} 551 552void ProxyConfigServiceImpl::OnUISetProxyConfig(bool persist_to_device) { 553 // Posts a task to IO thread with the new config, so it can update 554 // |cached_config_|. 555 Task* task = NewRunnableMethod(this, 556 &ProxyConfigServiceImpl::IOSetProxyConfig, reference_config_); 557 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task)) { 558 VLOG(1) << "Couldn't post task to IO thread to set new proxy config"; 559 delete task; 560 } 561 562 if (persist_to_device && CrosLibrary::Get()->EnsureLoaded()) { 563 if (store_property_op_) { 564 persist_to_device_pending_ = true; 565 VLOG(1) << "Pending persisting proxy setting to device"; 566 } else { 567 PersistConfigToDevice(); 568 } 569 } 570} 571 572void ProxyConfigServiceImpl::CheckCurrentlyOnIOThread() { 573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 574} 575 576void ProxyConfigServiceImpl::CheckCurrentlyOnUIThread() { 577 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 578} 579 580void ProxyConfigServiceImpl::IOSetProxyConfig(const ProxyConfig& new_config) { 581 // This is called on the IO thread (posted from UI thread). 582 CheckCurrentlyOnIOThread(); 583 VLOG(1) << "Proxy configuration changed"; 584 has_config_ = true; 585 cached_config_ = new_config; 586 // Notify observers of new proxy config. 587 net::ProxyConfig net_config; 588 cached_config_.ToNetProxyConfig(&net_config); 589 FOR_EACH_OBSERVER(net::ProxyConfigService::Observer, observers_, 590 OnProxyConfigChanged(net_config)); 591} 592 593} // namespace chromeos 594