summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/dom_ui/net_internals_ui.cc97
-rw-r--r--chrome/browser/importer/firefox_proxy_settings.cc70
-rw-r--r--chrome/browser/importer/firefox_proxy_settings.h12
-rw-r--r--chrome/browser/importer/firefox_proxy_settings_unittest.cc29
-rw-r--r--chrome/browser/net/connection_tester.cc403
-rw-r--r--chrome/browser/net/connection_tester.h175
-rw-r--r--chrome/browser/net/connection_tester_unittest.cc138
-rw-r--r--chrome/browser/resources/net_internals/index.html13
-rw-r--r--chrome/browser/resources/net_internals/main.css10
-rw-r--r--chrome/browser/resources/net_internals/main.js54
-rw-r--r--chrome/browser/resources/net_internals/testview.js132
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--net/proxy/proxy_config.h18
-rw-r--r--net/proxy/proxy_config_service_fixed.h1
-rw-r--r--net/url_request/url_request_context.h5
16 files changed, 1144 insertions, 17 deletions
diff --git a/chrome/browser/dom_ui/net_internals_ui.cc b/chrome/browser/dom_ui/net_internals_ui.cc
index f7bf423..5f4139a 100644
--- a/chrome/browser/dom_ui/net_internals_ui.cc
+++ b/chrome/browser/dom_ui/net_internals_ui.cc
@@ -22,6 +22,7 @@
#include "chrome/browser/dom_ui/chrome_url_data_manager.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/net/chrome_net_log.h"
+#include "chrome/browser/net/connection_tester.h"
#include "chrome/browser/net/passive_log_collector.h"
#include "chrome/browser/net/url_request_context_getter.h"
#include "chrome/browser/profile.h"
@@ -83,6 +84,21 @@ Value* EntryToDictionaryValue(net::NetLog::EventType type,
return entry_dict;
}
+Value* ExperimentToValue(const ConnectionTester::Experiment& experiment) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString(L"url", experiment.url.spec());
+
+ dict->SetStringFromUTF16(
+ L"proxy_settings_experiment",
+ ConnectionTester::ProxySettingsExperimentDescription(
+ experiment.proxy_settings_experiment));
+ dict->SetStringFromUTF16(
+ L"host_resolver_experiment",
+ ConnectionTester::HostResolverExperimentDescription(
+ experiment.host_resolver_experiment));
+ return dict;
+}
+
class NetInternalsHTMLSource : public ChromeURLDataManager::DataSource {
public:
NetInternalsHTMLSource();
@@ -122,7 +138,7 @@ class NetInternalsMessageHandler
// Executes the javascript function |function_name| in the renderer, passing
// it the argument |value|.
void CallJavascriptFunction(const std::wstring& function_name,
- const Value& value);
+ const Value* value);
private:
class IOThreadImpl;
@@ -140,7 +156,8 @@ class NetInternalsMessageHandler::IOThreadImpl
: public base::RefCountedThreadSafe<
NetInternalsMessageHandler::IOThreadImpl,
ChromeThread::DeleteOnUIThread>,
- public ChromeNetLog::Observer {
+ public ChromeNetLog::Observer,
+ public ConnectionTester::Delegate {
public:
// Type for methods that can be used as MessageHandler callbacks.
typedef void (IOThreadImpl::*MessageHandler)(const Value*);
@@ -182,6 +199,7 @@ class NetInternalsMessageHandler::IOThreadImpl
void OnGetHostResolverCache(const Value* value);
void OnClearHostResolverCache(const Value* value);
void OnGetPassiveLogEntries(const Value* value);
+ void OnStartConnectionTests(const Value* value);
// ChromeNetLog::Observer implementation:
virtual void OnAddEntry(net::NetLog::EventType type,
@@ -190,6 +208,15 @@ class NetInternalsMessageHandler::IOThreadImpl
net::NetLog::EventPhase phase,
net::NetLog::EventParameters* extra_parameters);
+ // ConnectionTester::Delegate implementation:
+ virtual void OnStartConnectionTestSuite();
+ virtual void OnStartConnectionTestExperiment(
+ const ConnectionTester::Experiment& experiment);
+ virtual void OnCompletedConnectionTestExperiment(
+ const ConnectionTester::Experiment& experiment,
+ int result);
+ virtual void OnCompletedConnectionTestSuite();
+
private:
class CallbackHelper;
@@ -210,6 +237,9 @@ class NetInternalsMessageHandler::IOThreadImpl
scoped_refptr<URLRequestContextGetter> context_getter_;
+ // Helper that runs the suite of connection tests.
+ scoped_ptr<ConnectionTester> connection_tester_;
+
// True if we have attached an observer to the NetLog already.
bool is_observing_log_;
friend class base::RefCountedThreadSafe<IOThreadImpl>;
@@ -347,13 +377,20 @@ void NetInternalsMessageHandler::RegisterMessages() {
dom_ui_->RegisterMessageCallback(
"getPassiveLogEntries",
proxy_->CreateCallback(&IOThreadImpl::OnGetPassiveLogEntries));
+ dom_ui_->RegisterMessageCallback(
+ "startConnectionTests",
+ proxy_->CreateCallback(&IOThreadImpl::OnStartConnectionTests));
}
void NetInternalsMessageHandler::CallJavascriptFunction(
const std::wstring& function_name,
- const Value& value) {
+ const Value* value) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
- dom_ui_->CallJavascriptFunction(function_name, value);
+ if (value) {
+ dom_ui_->CallJavascriptFunction(function_name, *value);
+ } else {
+ dom_ui_->CallJavascriptFunction(function_name);
+ }
}
////////////////////////////////////////////////////////////////////////////////
@@ -389,6 +426,9 @@ void NetInternalsMessageHandler::IOThreadImpl::Detach() {
// Unregister with network stack to observe events.
if (is_observing_log_)
io_thread_->globals()->net_log->RemoveObserver(this);
+
+ // Cancel any in-progress connection tests.
+ connection_tester_.reset();
}
void NetInternalsMessageHandler::IOThreadImpl::OnRendererReady(
@@ -630,6 +670,21 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetPassiveLogEntries(
CallJavascriptFunction(L"g_browser.receivedPassiveLogEntries", list);
}
+void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTests(
+ const Value* value) {
+ // |value| should be: [<URL to test>].
+ string16 url_str;
+ if (value && value->GetType() == Value::TYPE_LIST) {
+ const ListValue* list = static_cast<const ListValue*>(value);
+ list->GetStringAsUTF16(0, &url_str);
+ }
+
+ GURL url(url_str);
+
+ connection_tester_.reset(new ConnectionTester(this));
+ connection_tester_->RunAllTests(url);
+}
+
void NetInternalsMessageHandler::IOThreadImpl::OnAddEntry(
net::NetLog::EventType type,
const base::TimeTicks& time,
@@ -643,6 +698,38 @@ void NetInternalsMessageHandler::IOThreadImpl::OnAddEntry(
EntryToDictionaryValue(type, time, source, phase, extra_parameters));
}
+void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTestSuite() {
+ CallJavascriptFunction(L"g_browser.receivedStartConnectionTestSuite", NULL);
+}
+
+void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTestExperiment(
+ const ConnectionTester::Experiment& experiment) {
+ CallJavascriptFunction(
+ L"g_browser.receivedStartConnectionTestExperiment",
+ ExperimentToValue(experiment));
+}
+
+void
+NetInternalsMessageHandler::IOThreadImpl::OnCompletedConnectionTestExperiment(
+ const ConnectionTester::Experiment& experiment,
+ int result) {
+ DictionaryValue* dict = new DictionaryValue();
+
+ dict->Set(L"experiment", ExperimentToValue(experiment));
+ dict->SetInteger(L"result", result);
+
+ CallJavascriptFunction(
+ L"g_browser.receivedCompletedConnectionTestExperiment",
+ dict);
+}
+
+void
+NetInternalsMessageHandler::IOThreadImpl::OnCompletedConnectionTestSuite() {
+ CallJavascriptFunction(
+ L"g_browser.receivedCompletedConnectionTestSuite",
+ NULL);
+}
+
void NetInternalsMessageHandler::IOThreadImpl::DispatchToMessageHandler(
Value* arg, MessageHandler method) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
@@ -657,7 +744,7 @@ void NetInternalsMessageHandler::IOThreadImpl::CallJavascriptFunction(
if (handler_) {
// We check |handler_| in case it was deleted on the UI thread earlier
// while we were running on the IO thread.
- handler_->CallJavascriptFunction(function_name, *arg);
+ handler_->CallJavascriptFunction(function_name, arg);
}
delete arg;
return;
diff --git a/chrome/browser/importer/firefox_proxy_settings.cc b/chrome/browser/importer/firefox_proxy_settings.cc
index 493beff..d4d0d88 100644
--- a/chrome/browser/importer/firefox_proxy_settings.cc
+++ b/chrome/browser/importer/firefox_proxy_settings.cc
@@ -8,6 +8,7 @@
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "net/proxy/proxy_config.h"
namespace {
@@ -82,7 +83,7 @@ void FirefoxProxySettings::Reset() {
}
// static
- bool FirefoxProxySettings::GetSettings(FirefoxProxySettings* settings) {
+bool FirefoxProxySettings::GetSettings(FirefoxProxySettings* settings) {
DCHECK(settings);
settings->Reset();
@@ -93,6 +94,73 @@ void FirefoxProxySettings::Reset() {
return GetSettingsFromFile(pref_file, settings);
}
+bool FirefoxProxySettings::ToProxyConfig(net::ProxyConfig* config) {
+ switch (config_type()) {
+ case NO_PROXY:
+ *config = net::ProxyConfig::CreateDirect();
+ return true;
+ case AUTO_DETECT:
+ *config = net::ProxyConfig::CreateAutoDetect();
+ return true;
+ case AUTO_FROM_URL:
+ *config = net::ProxyConfig::CreateFromCustomPacURL(
+ GURL(autoconfig_url()));
+ return true;
+ case SYSTEM:
+ // Can't convert this directly to a ProxyConfig.
+ return false;
+ case MANUAL:
+ // Handled outside of the switch (since it is a lot of code.)
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ // The rest of this funciton is for handling the MANUAL case.
+ DCHECK_EQ(MANUAL, config_type());
+
+ *config = net::ProxyConfig();
+ config->proxy_rules().type =
+ net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+
+ if (!http_proxy().empty()) {
+ config->proxy_rules().proxy_for_http = net::ProxyServer(
+ net::ProxyServer::SCHEME_HTTP,
+ http_proxy(),
+ http_proxy_port());
+ }
+
+ if (!ftp_proxy().empty()) {
+ config->proxy_rules().proxy_for_ftp = net::ProxyServer(
+ net::ProxyServer::SCHEME_HTTP,
+ ftp_proxy(),
+ ftp_proxy_port());
+ }
+
+ if (!ssl_proxy().empty()) {
+ config->proxy_rules().proxy_for_https = net::ProxyServer(
+ net::ProxyServer::SCHEME_HTTP,
+ ssl_proxy(),
+ ssl_proxy_port());
+ }
+
+ if (!socks_host().empty()) {
+ net::ProxyServer::Scheme proxy_scheme = V5 == socks_version() ?
+ net::ProxyServer::SCHEME_SOCKS5 : net::ProxyServer::SCHEME_SOCKS4;
+
+ config->proxy_rules().socks_proxy = net::ProxyServer(
+ proxy_scheme,
+ socks_host(),
+ socks_port());
+ }
+
+ config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching(
+ JoinString(proxy_bypass_list_, ';'));
+
+ return true;
+}
+
// static
bool FirefoxProxySettings::GetSettingsFromFile(const FilePath& pref_file,
FirefoxProxySettings* settings) {
diff --git a/chrome/browser/importer/firefox_proxy_settings.h b/chrome/browser/importer/firefox_proxy_settings.h
index eb624bd..80e1e24 100644
--- a/chrome/browser/importer/firefox_proxy_settings.h
+++ b/chrome/browser/importer/firefox_proxy_settings.h
@@ -12,6 +12,10 @@
class FilePath;
+namespace net {
+class ProxyConfig;
+}
+
class FirefoxProxySettings {
public:
enum ProxyConfig {
@@ -62,6 +66,14 @@ class FirefoxProxySettings {
return proxy_bypass_list_;
}
+ const std::string autoconfig_url() const {
+ return autoconfig_url_;
+ }
+
+ // Converts a FirefoxProxySettings object to a net::ProxyConfig.
+ // On success returns true and fills |config| with the result.
+ bool ToProxyConfig(net::ProxyConfig* config);
+
protected:
// Gets the settings from the passed prefs.js file and returns true if
// successful.
diff --git a/chrome/browser/importer/firefox_proxy_settings_unittest.cc b/chrome/browser/importer/firefox_proxy_settings_unittest.cc
index 8c854f4..8926f47 100644
--- a/chrome/browser/importer/firefox_proxy_settings_unittest.cc
+++ b/chrome/browser/importer/firefox_proxy_settings_unittest.cc
@@ -2,12 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <sstream>
+
#include "testing/gtest/include/gtest/gtest.h"
#include "base/file_path.h"
#include "base/path_service.h"
#include "chrome/browser/importer/firefox_proxy_settings.h"
#include "chrome/common/chrome_paths.h"
+#include "net/proxy/proxy_config.h"
class FirefoxProxySettingsTest : public testing::Test {
};
@@ -47,5 +50,29 @@ TEST_F(FirefoxProxySettingsTest, TestParse) {
EXPECT_EQ("localhost", settings.proxy_bypass_list()[0]);
EXPECT_EQ("127.0.0.1", settings.proxy_bypass_list()[1]);
EXPECT_EQ("noproxy.com", settings.proxy_bypass_list()[2]);
-}
+ // Test that ToProxyConfig() properly translates into a net::ProxyConfig.
+ net::ProxyConfig config;
+ EXPECT_TRUE(settings.ToProxyConfig(&config));
+
+ // Pretty-print |config| to a string (easy way to define the expectations).
+ std::ostringstream stream;
+ stream << config;
+ std::string pretty_printed_config = stream.str();
+
+ EXPECT_EQ(
+ "Automatic settings:\n"
+ " Auto-detect: No\n"
+ " Custom PAC script: [None]\n"
+ "Manual settings:\n"
+ " Proxy server: \n"
+ " HTTP: http_proxy:1111\n"
+ " HTTPS: ssl_proxy:2222\n"
+ " FTP: ftp_proxy:3333\n"
+ " SOCKS: socks4://socks_host:5555\n"
+ " Bypass list: \n"
+ " *localhost\n"
+ " 127.0.0.1\n"
+ " *noproxy.com",
+ pretty_printed_config);
+}
diff --git a/chrome/browser/net/connection_tester.cc b/chrome/browser/net/connection_tester.cc
new file mode 100644
index 0000000..c441f1d
--- /dev/null
+++ b/chrome/browser/net/connection_tester.cc
@@ -0,0 +1,403 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/connection_tester.h"
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "chrome/browser/importer/firefox_proxy_settings.h"
+#include "chrome/common/chrome_switches.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/host_resolver_impl.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace {
+
+// ExperimentURLRequestContext ------------------------------------------------
+
+// An instance of ExperimentURLRequestContext is created for each experiment
+// run by ConnectionTester. The class initializes network dependencies according
+// to the specified "experiment".
+class ExperimentURLRequestContext : public URLRequestContext {
+ public:
+ int Init(const ConnectionTester::Experiment& experiment) {
+ int rv;
+
+ // Create a custom HostResolver for this experiment.
+ rv = CreateHostResolver(experiment.host_resolver_experiment,
+ &host_resolver_);
+ if (rv != net::OK)
+ return rv; // Failure.
+
+ // Create a custom ProxyService for this this experiment.
+ rv = CreateProxyService(experiment.proxy_settings_experiment,
+ &proxy_service_);
+ if (rv != net::OK)
+ return rv; // Failure.
+
+ // The rest of the dependencies are standard, and don't depend on the
+ // experiment being run.
+ ftp_transaction_factory_ = new net::FtpNetworkLayer(host_resolver_);
+ ssl_config_service_ = new net::SSLConfigServiceDefaults;
+ http_auth_handler_factory_ = net::HttpAuthHandlerFactory::CreateDefault();
+ http_transaction_factory_ =
+ new net::HttpCache(
+ net::HttpNetworkLayer::CreateFactory(NULL, host_resolver_,
+ proxy_service_,
+ ssl_config_service_,
+ http_auth_handler_factory_),
+ disk_cache::CreateInMemoryCacheBackend(0));
+ // In-memory cookie store.
+ cookie_store_ = new net::CookieMonster(NULL, NULL);
+
+ return net::OK;
+ }
+
+ protected:
+ virtual ~ExperimentURLRequestContext() {
+ delete ftp_transaction_factory_;
+ delete http_transaction_factory_;
+ delete http_auth_handler_factory_;
+ }
+
+ private:
+ // Creates a host resolver for |experiment|. On success returns net::OK and
+ // fills |host_resolver| with a new pointer. Otherwise returns a network
+ // error code.
+ int CreateHostResolver(
+ ConnectionTester::HostResolverExperiment experiment,
+ scoped_refptr<net::HostResolver>* host_resolver) {
+ // Create a vanilla HostResolver that disables caching.
+ const size_t kMaxJobs = 50u;
+ scoped_refptr<net::HostResolverImpl> impl = new net::HostResolverImpl(
+ NULL, NULL, NULL, kMaxJobs);
+
+ *host_resolver = impl;
+
+ // Modify it slightly based on the experiment being run.
+ switch (experiment) {
+ case ConnectionTester::HOST_RESOLVER_EXPERIMENT_PLAIN:
+ return net::OK;
+ case ConnectionTester::HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
+ impl->SetDefaultAddressFamily(net::ADDRESS_FAMILY_IPV4);
+ return net::OK;
+ case ConnectionTester::HOST_RESOLVER_EXPERIMENT_IPV6_PROBE: {
+ // Note that we don't use impl->ProbeIPv6Support() since that finishes
+ // asynchronously and may not take effect in time for the test.
+ // So instead we will probe synchronously (might take 100-200 ms).
+ net::AddressFamily family = net::IPv6Supported() ?
+ net::ADDRESS_FAMILY_UNSPECIFIED : net::ADDRESS_FAMILY_IPV4;
+ impl->SetDefaultAddressFamily(family);
+ return net::OK;
+ }
+ default:
+ NOTREACHED();
+ return net::ERR_UNEXPECTED;
+ }
+ }
+
+ // Creates a proxy config service for |experiment|. On success returns net::OK
+ // and fills |config_service| with a new pointer. Otherwise returns a network
+ // error code.
+ int CreateProxyConfigService(
+ ConnectionTester::ProxySettingsExperiment experiment,
+ scoped_ptr<net::ProxyConfigService>* config_service) {
+ switch (experiment) {
+ case ConnectionTester::PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
+ return CreateSystemProxyConfigService(config_service);
+ case ConnectionTester::PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
+ return CreateFirefoxProxyConfigService(config_service);
+ case ConnectionTester::PROXY_EXPERIMENT_USE_AUTO_DETECT:
+ config_service->reset(new net::ProxyConfigServiceFixed(
+ net::ProxyConfig::CreateAutoDetect()));
+ return net::OK;
+ case ConnectionTester::PROXY_EXPERIMENT_USE_DIRECT:
+ config_service->reset(new net::ProxyConfigServiceFixed(
+ net::ProxyConfig::CreateDirect()));
+ return net::OK;
+ default:
+ NOTREACHED();
+ return net::ERR_UNEXPECTED;
+ }
+ }
+
+ // Creates a proxy service for |experiment|. On success returns net::OK
+ // and fills |config_service| with a new pointer. Otherwise returns a network
+ // error code.
+ int CreateProxyService(
+ ConnectionTester::ProxySettingsExperiment experiment,
+ scoped_refptr<net::ProxyService>* proxy_service) {
+ // Create an appropriate proxy config service.
+ scoped_ptr<net::ProxyConfigService> config_service;
+ int rv = CreateProxyConfigService(experiment, &config_service);
+ if (rv != net::OK)
+ return rv; // Failure.
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSingleProcess)) {
+ // We can't create a standard proxy resolver in single-process mode.
+ // Rather than falling-back to some other implementation, fail.
+ return net::ERR_NOT_IMPLEMENTED;
+ }
+
+ *proxy_service = net::ProxyService::Create(
+ config_service.release(),
+ true, this, NULL, NULL, MessageLoop::current());
+
+ return net::OK;
+ }
+
+ // Creates a proxy config service that pulls from the system proxy settings.
+ // On success returns net::OK and fills |config_service| with a new pointer.
+ // Otherwise returns a network error code.
+ int CreateSystemProxyConfigService(
+ scoped_ptr<net::ProxyConfigService>* config_service) {
+#if defined(OS_LINUX)
+ // TODO(eroman): This is not supported on Linux yet, because of how
+ // construction needs ot happen on the UI thread.
+ return net::ERR_NOT_IMPLEMENTED;
+#else
+ config_service->reset(
+ net::ProxyService::CreateSystemProxyConfigService(NULL, NULL));
+ return net::OK;
+#endif
+ }
+
+ // Creates a fixed proxy config service that is initialized using Firefox's
+ // current proxy settings. On success returns net::OK and fills
+ // |config_service| with a new pointer. Otherwise returns a network error
+ // code.
+ int CreateFirefoxProxyConfigService(
+ scoped_ptr<net::ProxyConfigService>* config_service) {
+ // Fetch Firefox's proxy settings (can fail if Firefox is not installed).
+ FirefoxProxySettings firefox_settings;
+ if (!FirefoxProxySettings::GetSettings(&firefox_settings))
+ return net::ERR_FILE_NOT_FOUND;
+
+ if (FirefoxProxySettings::SYSTEM == firefox_settings.config_type())
+ return CreateSystemProxyConfigService(config_service);
+
+ net::ProxyConfig config;
+ if (firefox_settings.ToProxyConfig(&config)) {
+ config_service->reset(new net::ProxyConfigServiceFixed(config));
+ return net::OK;
+ }
+
+ return net::ERR_FAILED;
+ }
+};
+
+} // namespace
+
+// ConnectionTester::TestRunner ----------------------------------------------
+
+// TestRunner is a helper class for running an individual experiment. It can
+// be deleted any time after it is started, and this will abort the request.
+class ConnectionTester::TestRunner : public URLRequest::Delegate {
+ public:
+ // |tester| must remain alive throughout the TestRunner's lifetime.
+ // |tester| will be notified of completion.
+ explicit TestRunner(ConnectionTester* tester) : tester_(tester) {}
+
+ // Starts running |experiment|. Notifies tester->OnExperimentCompleted() when
+ // it is done.
+ void Run(const Experiment& experiment);
+
+ // URLRequest::Delegate implementation.
+ virtual void OnResponseStarted(URLRequest* request);
+ virtual void OnReadCompleted(URLRequest* request, int bytes_read);
+ // TODO(eroman): handle cases requiring authentication.
+
+ private:
+ // The number of bytes to read each response body chunk.
+ static const int kReadBufferSize = 1024;
+
+ // Starts reading the response's body (and keeps reading until an error or
+ // end of stream).
+ void ReadBody(URLRequest* request);
+
+ // Called when the request has completed (for both success and failure).
+ void OnResponseCompleted(URLRequest* request);
+
+ ConnectionTester* tester_;
+ scoped_ptr<URLRequest> request_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestRunner);
+};
+
+void ConnectionTester::TestRunner::OnResponseStarted(URLRequest* request) {
+ if (!request->status().is_success()) {
+ OnResponseCompleted(request);
+ return;
+ }
+
+ // Start reading the body.
+ ReadBody(request);
+}
+
+void ConnectionTester::TestRunner::OnReadCompleted(URLRequest* request,
+ int bytes_read) {
+ if (bytes_read <= 0) {
+ OnResponseCompleted(request);
+ return;
+ }
+
+ // Keep reading until the stream is closed. Throw the data read away.
+ ReadBody(request);
+}
+
+void ConnectionTester::TestRunner::ReadBody(URLRequest* request) {
+ // Read the response body |kReadBufferSize| bytes at a time.
+ scoped_refptr<net::IOBuffer> unused_buffer =
+ new net::IOBuffer(kReadBufferSize);
+ int num_bytes;
+ if (request->Read(unused_buffer, kReadBufferSize, &num_bytes)) {
+ OnReadCompleted(request, num_bytes);
+ } else if (!request->status().is_io_pending()) {
+ // Read failed synchronously.
+ OnResponseCompleted(request);
+ }
+}
+
+void ConnectionTester::TestRunner::OnResponseCompleted(URLRequest* request) {
+ int result = net::OK;
+ if (!request->status().is_success()) {
+ DCHECK_NE(net::ERR_IO_PENDING, request->status().os_error());
+ result = request->status().os_error();
+ }
+ tester_->OnExperimentCompleted(result);
+}
+
+void ConnectionTester::TestRunner::Run(const Experiment& experiment) {
+ // Try to create a URLRequestContext for this experiment.
+ scoped_refptr<ExperimentURLRequestContext> context =
+ new ExperimentURLRequestContext();
+ int rv = context->Init(experiment);
+ if (rv != net::OK) {
+ // Complete the experiment with a failure.
+ tester_->OnExperimentCompleted(rv);
+ return;
+ }
+
+ // Fetch a request using the experimental context.
+ request_.reset(new URLRequest(experiment.url, this));
+ request_->set_context(context);
+ request_->Start();
+}
+
+// ConnectionTester ----------------------------------------------------------
+
+ConnectionTester::ConnectionTester(Delegate* delegate)
+ : delegate_(delegate) {
+ DCHECK(delegate);
+}
+
+ConnectionTester::~ConnectionTester() {
+ // Cancellation happens automatically by deleting test_runner_.
+}
+
+void ConnectionTester::RunAllTests(const GURL& url) {
+ // Select all possible experiments to run. (In no particular order).
+ // It is possible that some of these experiments are actually duplicates.
+ GetAllPossibleExperimentCombinations(url, &remaining_experiments_);
+
+ delegate_->OnStartConnectionTestSuite();
+ StartNextExperiment();
+}
+
+// static
+string16 ConnectionTester::ProxySettingsExperimentDescription(
+ ProxySettingsExperiment experiment) {
+ // TODO(eroman): Use proper string resources.
+ switch (experiment) {
+ case PROXY_EXPERIMENT_USE_DIRECT:
+ return ASCIIToUTF16("Don't use any proxy");
+ case PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS:
+ return ASCIIToUTF16("Use system proxy settings");
+ case PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS:
+ return ASCIIToUTF16("Use Firefox's proxy settings");
+ case PROXY_EXPERIMENT_USE_AUTO_DETECT:
+ return ASCIIToUTF16("Auto-detect proxy settings");
+ default:
+ NOTREACHED();
+ return string16();
+ }
+}
+
+// static
+string16 ConnectionTester::HostResolverExperimentDescription(
+ HostResolverExperiment experiment) {
+ // TODO(eroman): Use proper string resources.
+ switch (experiment) {
+ case HOST_RESOLVER_EXPERIMENT_PLAIN:
+ return string16();
+ case HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6:
+ return ASCIIToUTF16("Disable IPv6 host resolving");
+ case HOST_RESOLVER_EXPERIMENT_IPV6_PROBE:
+ return ASCIIToUTF16("Probe for IPv6 host resolving");
+ default:
+ NOTREACHED();
+ return string16();
+ }
+}
+
+// static
+void ConnectionTester::GetAllPossibleExperimentCombinations(
+ const GURL& url,
+ ConnectionTester::ExperimentList* list) {
+ list->clear();
+ for (size_t resolver_experiment = 0;
+ resolver_experiment < HOST_RESOLVER_EXPERIMENT_COUNT;
+ ++resolver_experiment) {
+ for (size_t proxy_experiment = 0;
+ proxy_experiment < PROXY_EXPERIMENT_COUNT;
+ ++proxy_experiment) {
+ Experiment experiment(
+ url,
+ static_cast<ProxySettingsExperiment>(proxy_experiment),
+ static_cast<HostResolverExperiment>(resolver_experiment));
+ list->push_back(experiment);
+ }
+ }
+}
+
+void ConnectionTester::StartNextExperiment() {
+ DCHECK(!remaining_experiments_.empty());
+ DCHECK(!current_test_runner_.get());
+
+ delegate_->OnStartConnectionTestExperiment(current_experiment());
+
+ current_test_runner_.reset(new TestRunner(this));
+ current_test_runner_->Run(current_experiment());
+}
+
+void ConnectionTester::OnExperimentCompleted(int result) {
+ Experiment current = current_experiment();
+
+ // Advance to the next experiment.
+ remaining_experiments_.erase(remaining_experiments_.begin());
+ current_test_runner_.reset();
+
+ // Notify the delegate of completion.
+ delegate_->OnCompletedConnectionTestExperiment(current, result);
+
+ if (remaining_experiments_.empty()) {
+ delegate_->OnCompletedConnectionTestSuite();
+ } else {
+ StartNextExperiment();
+ }
+}
+
diff --git a/chrome/browser/net/connection_tester.h b/chrome/browser/net/connection_tester.h
new file mode 100644
index 0000000..10534f5
--- /dev/null
+++ b/chrome/browser/net/connection_tester.h
@@ -0,0 +1,175 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_CONNECTION_TESTER_H_
+#define CHROME_BROWSER_NET_CONNECTION_TESTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/completion_callback.h"
+
+// ConnectionTester runs a suite of tests (also called "experiments"),
+// to try and discover why loading a particular URL is failing with an error
+// code.
+//
+// For example, one reason why the URL might have failed, is that the
+// network requires the URL to be routed through a proxy, however chrome is
+// not configured for that.
+//
+// The above issue might be detected by running test that fetches the URL using
+// auto-detect and seeing if it works this time. Or even by retrieving the
+// settings from another installed browser and trying with those.
+//
+// USAGE:
+//
+// To run the test suite, create an instance of ConnectionTester and then call
+// RunAllTests().
+//
+// This starts a sequence of tests, which will complete asynchronously.
+// The ConnectionTester object can be deleted at any time, and it will abort
+// any of the in-progress tests.
+//
+// As tests are started or completed, notification will be sent through the
+// "Delegate" object.
+
+class ConnectionTester {
+ public:
+ // This enum lists the possible proxy settings configurations.
+ enum ProxySettingsExperiment {
+ // Do not use any proxy.
+ PROXY_EXPERIMENT_USE_DIRECT = 0,
+
+ // Use the system proxy settings.
+ PROXY_EXPERIMENT_USE_SYSTEM_SETTINGS,
+
+ // Use Firefox's proxy settings if they are available.
+ PROXY_EXPERIMENT_USE_FIREFOX_SETTINGS,
+
+ // Use proxy auto-detect.
+ PROXY_EXPERIMENT_USE_AUTO_DETECT,
+
+ PROXY_EXPERIMENT_COUNT,
+ };
+
+ // This enum lists the possible host resolving configurations.
+ enum HostResolverExperiment {
+ // Use a default host resolver implementation.
+ HOST_RESOLVER_EXPERIMENT_PLAIN = 0,
+
+ // Disable IPv6 host resolving.
+ HOST_RESOLVER_EXPERIMENT_DISABLE_IPV6,
+
+ // Probe for IPv6 support.
+ HOST_RESOLVER_EXPERIMENT_IPV6_PROBE,
+
+ HOST_RESOLVER_EXPERIMENT_COUNT,
+ };
+
+ // The "Experiment" structure describes an individual test to run.
+ struct Experiment {
+ Experiment(const GURL& url,
+ ProxySettingsExperiment proxy_settings_experiment,
+ HostResolverExperiment host_resolver_experiment)
+ : url(url),
+ proxy_settings_experiment(proxy_settings_experiment),
+ host_resolver_experiment(host_resolver_experiment) {
+ }
+
+ // The URL to try and fetch.
+ GURL url;
+
+ // The proxy settings to use.
+ ProxySettingsExperiment proxy_settings_experiment;
+
+ // The host resolver settings to use.
+ HostResolverExperiment host_resolver_experiment;
+ };
+
+ typedef std::vector<Experiment> ExperimentList;
+
+ // "Delegate" is an interface for receiving start and completion notification
+ // of individual tests that are run by the ConnectionTester.
+ //
+ // NOTE: do not delete the ConnectionTester when executing within one of the
+ // delegate methods.
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called once the test suite is about to start.
+ virtual void OnStartConnectionTestSuite() = 0;
+
+ // Called when an individual experiment is about to be started.
+ virtual void OnStartConnectionTestExperiment(
+ const Experiment& experiment) = 0;
+
+ // Called when an individual experiment has completed.
+ // |experiment| - the experiment that has completed.
+ // |result| - the net error that the experiment completed with
+ // (or net::OK if it was success).
+ virtual void OnCompletedConnectionTestExperiment(
+ const Experiment& experiment,
+ int result) = 0;
+
+ // Called once ALL tests have completed.
+ virtual void OnCompletedConnectionTestSuite() = 0;
+ };
+
+ // Constructs a ConnectionTester that notifies test progress to |delegate|.
+ // |delegate| is owned by the caller, and must remain valid for the lifetime
+ // of ConnectionTester.
+ explicit ConnectionTester(Delegate* delegate);
+
+ // Note that destruction cancels any in-progress tests.
+ ~ConnectionTester();
+
+ // Starts running the test suite on |url|. Notification of progress is sent to
+ // |delegate_|.
+ void RunAllTests(const GURL& url);
+
+ // Returns a text string explaining what |experiment| is testing.
+ static string16 ProxySettingsExperimentDescription(
+ ProxySettingsExperiment experiment);
+ static string16 HostResolverExperimentDescription(
+ HostResolverExperiment experiment);
+
+ private:
+ // Internally each experiment run by ConnectionTester is handled by a
+ // "TestRunner" instance.
+ class TestRunner;
+ friend class TestRunner;
+
+ // Fills |list| with the set of all possible experiments for |url|.
+ static void GetAllPossibleExperimentCombinations(const GURL& url,
+ ExperimentList* list);
+
+ // Starts the next experiment from |remaining_experiments_|.
+ void StartNextExperiment();
+
+ // Callback for when |current_test_runner_| finishes.
+ void OnExperimentCompleted(int result);
+
+ // Returns the experiment at the front of our list.
+ const Experiment& current_experiment() const {
+ return remaining_experiments_.front();
+ }
+
+ // The object to notify test progress to.
+ Delegate* delegate_;
+
+ // The current in-progress test, or NULL if there is no active test.
+ scoped_ptr<TestRunner> current_test_runner_;
+
+ // The ordered list of experiments to try next. The experiment at the front
+ // of the list is the one currently in progress.
+ ExperimentList remaining_experiments_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionTester);
+};
+
+#endif // CHROME_BROWSER_NET_CONNECTION_TESTER_H_
+
diff --git a/chrome/browser/net/connection_tester_unittest.cc b/chrome/browser/net/connection_tester_unittest.cc
new file mode 100644
index 0000000..b9cf2ca
--- /dev/null
+++ b/chrome/browser/net/connection_tester_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/connection_tester.h"
+
+#include "net/base/mock_host_resolver.h"
+#include "net/url_request/url_request_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+// This is a testing delegate which simply counts how many times each of
+// the delegate's methods were invoked.
+class ConnectionTesterDelegate : public ConnectionTester::Delegate {
+ public:
+ ConnectionTesterDelegate()
+ : start_connection_test_suite_count_(0),
+ start_connection_test_experiment_count_(0),
+ completed_connection_test_experiment_count_(0),
+ completed_connection_test_suite_count_(0) {
+ }
+
+ virtual void OnStartConnectionTestSuite() {
+ start_connection_test_suite_count_++;
+ }
+
+ virtual void OnStartConnectionTestExperiment(
+ const ConnectionTester::Experiment& experiment) {
+ start_connection_test_experiment_count_++;
+ }
+
+ virtual void OnCompletedConnectionTestExperiment(
+ const ConnectionTester::Experiment& experiment,
+ int result) {
+ completed_connection_test_experiment_count_++;
+ }
+
+ virtual void OnCompletedConnectionTestSuite() {
+ completed_connection_test_suite_count_++;
+ MessageLoop::current()->Quit();
+ }
+
+ int start_connection_test_suite_count() const {
+ return start_connection_test_suite_count_;
+ }
+
+ int start_connection_test_experiment_count() const {
+ return start_connection_test_experiment_count_;
+ }
+
+ int completed_connection_test_experiment_count() const {
+ return completed_connection_test_experiment_count_;
+ }
+
+ int completed_connection_test_suite_count() const {
+ return completed_connection_test_suite_count_;
+ }
+
+ private:
+ int start_connection_test_suite_count_;
+ int start_connection_test_experiment_count_;
+ int completed_connection_test_experiment_count_;
+ int completed_connection_test_suite_count_;
+};
+
+// The test fixture is responsible for:
+// - Making sure each test has an IO loop running
+// - Catching any host resolve requests and mapping them to localhost
+// (so the test doesn't use any external network dependencies).
+class ConnectionTesterTest : public PlatformTest {
+ public:
+ ConnectionTesterTest() : message_loop_(MessageLoop::TYPE_IO) {
+ scoped_refptr<net::RuleBasedHostResolverProc> catchall_resolver =
+ new net::RuleBasedHostResolverProc(NULL);
+
+ catchall_resolver->AddRule("*", "127.0.0.1");
+
+ scoped_host_resolver_proc_.Init(catchall_resolver);
+ }
+
+ protected:
+ net::ScopedDefaultHostResolverProc scoped_host_resolver_proc_;
+ ConnectionTesterDelegate test_delegate_;
+ MessageLoop message_loop_;
+};
+
+TEST_F(ConnectionTesterTest, RunAllTests) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateForkingServer(L"net/data/url_request_unittest/");
+
+ ConnectionTester tester(&test_delegate_);
+
+ // Start the test suite on URL "echoall".
+ // TODO(eroman): Is this URL right?
+ GURL url = server->TestServerPage("echoall");
+ tester.RunAllTests(url);
+
+ // Wait for all the tests to complete.
+ MessageLoop::current()->Run();
+
+ const int kNumExperiments =
+ ConnectionTester::PROXY_EXPERIMENT_COUNT *
+ ConnectionTester::HOST_RESOLVER_EXPERIMENT_COUNT;
+
+ EXPECT_EQ(1, test_delegate_.start_connection_test_suite_count());
+ EXPECT_EQ(kNumExperiments,
+ test_delegate_.start_connection_test_experiment_count());
+ EXPECT_EQ(kNumExperiments,
+ test_delegate_.completed_connection_test_experiment_count());
+ EXPECT_EQ(1, test_delegate_.completed_connection_test_suite_count());
+}
+
+TEST_F(ConnectionTesterTest, DeleteWhileInProgress) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateForkingServer(L"net/data/url_request_unittest/");
+
+ ConnectionTester tester(&test_delegate_);
+
+ // Start the test suite on URL "echoall".
+ // TODO(eroman): Is this URL right?
+ GURL url = server->TestServerPage("echoall");
+ tester.RunAllTests(url);
+
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_EQ(1, test_delegate_.start_connection_test_suite_count());
+ EXPECT_EQ(1, test_delegate_.start_connection_test_experiment_count());
+ EXPECT_EQ(0, test_delegate_.completed_connection_test_experiment_count());
+ EXPECT_EQ(0, test_delegate_.completed_connection_test_suite_count());
+
+ // Note here that we don't wait for the tests to complete
+ // (so |tester| will be deleted upon leaving this scope.
+}
+
+} // namespace
+
diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html
index be62e42..648333c 100644
--- a/chrome/browser/resources/net_internals/index.html
+++ b/chrome/browser/resources/net_internals/index.html
@@ -10,6 +10,7 @@ found in the LICENSE file.
<script src="view.js"></script>
<script src="tabswitcherview.js"></script>
<script src="dataview.js"></script>
+ <script src="testview.js"></script>
<script src="main.js"></script>
<script src="dnsview.js"></script>
<script src="requestsview.js"></script>
@@ -32,6 +33,7 @@ found in the LICENSE file.
<li><a href="#sockets" id=socketsTab>Sockets</a></li>
<li><a href="#httpCache" id=httpCacheTab>HTTP Cache</a></li>
<li><a href="#data" id=dataTab>Data</a></li>
+ <li><a href="#tests" id=testTab>Tests</a></li>
</ul>
<div style="clear: both;"></div>
</div>
@@ -89,12 +91,19 @@ found in the LICENSE file.
<div id=httpCacheTabContent>TODO: display http cache information (disk cache navigator)</div>
<!-- Import/Export data -->
<div id=dataTabContent>
- <br/>
- <button id=exportToText><b>Click here to generate bug report data</b><br/>(Copy paste the result and attach to bug)</button>
+ <button id=exportToText><b>Click here to generate bug report data</b><br/>(Copy paste the result and attach to bug)</button>
<br/>
<br/>
<button id=exportToJson>Export data to JSON</button>
</div>
+ <!-- Connection tests -->
+ <div id=testTabContent>
+ <p>Input a URL which failed to load, and then click the button to run some
+ tests for why it failed.</p>
+ URL: <input type=text id=testUrlInput />
+ <input type=button id=testStart value="Start tests" />
+ <div id=testSummary></div>
+ </div>
<!-- ================= Requests view =================== -->
diff --git a/chrome/browser/resources/net_internals/main.css b/chrome/browser/resources/net_internals/main.css
index 76c608d..4da22a0 100644
--- a/chrome/browser/resources/net_internals/main.css
+++ b/chrome/browser/resources/net_internals/main.css
@@ -181,12 +181,12 @@ body {
#detailsLogBox,
#detailsTimelineBox,
-#proxyTabContent {
+#proxyTabContent,
+#dataTabContent,
+#dnsTabContent,
+#testTabContent {
overflow: auto;
-}
-
-#proxyTabContent {
- padding-left: 20px;
+ padding: 10px;
}
#proxyTabContent td,
diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js
index d06e5e9..b05cbdd 100644
--- a/chrome/browser/resources/net_internals/main.js
+++ b/chrome/browser/resources/net_internals/main.js
@@ -61,6 +61,11 @@ function onLoaded() {
// captured data.
var dataView = new DataView("dataTabContent", "exportToJson", "exportToText");
+ // Create a view which will display the results and controls for connection
+ // tests.
+ var testView = new TestView("testTabContent", "testUrlInput", "testStart",
+ "testSummary");
+
// Create a view which lets you tab between the different sub-views.
var categoryTabSwitcher =
new TabSwitcherView(new DivView('categoryTabHandles'));
@@ -74,6 +79,7 @@ function onLoaded() {
categoryTabSwitcher.addTab('httpCacheTab',
new DivView('httpCacheTabContent'), false);
categoryTabSwitcher.addTab('dataTab', dataView, false);
+ categoryTabSwitcher.addTab('testTab', testView, false);
// Build a map from the anchor name of each tab handle to its "tab ID".
// We will consider navigations to the #hash as a switch tab request.
@@ -114,9 +120,11 @@ function onLoaded() {
function BrowserBridge() {
// List of observers for various bits of browser state.
this.logObservers_ = [];
+ this.connectionTestsObservers_ = [];
this.proxySettings_ = new PollableDataHelper('onProxySettingsChanged');
this.badProxies_ = new PollableDataHelper('onBadProxiesChanged');
- this.hostResolverCache_ = new PollableDataHelper('onHostResolverCacheChanged');
+ this.hostResolverCache_ =
+ new PollableDataHelper('onHostResolverCacheChanged');
// Cache of the data received.
// TODO(eroman): the controls to clear data in the "Requests" tab should be
@@ -171,6 +179,10 @@ BrowserBridge.prototype.sendClearHostResolverCache = function() {
chrome.send('clearHostResolverCache');
};
+BrowserBridge.prototype.sendStartConnectionTests = function(url) {
+ chrome.send('startConnectionTests', [url]);
+};
+
//------------------------------------------------------------------------------
// Messages received from the browser
//------------------------------------------------------------------------------
@@ -222,6 +234,33 @@ BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) {
this.receivedLogEntry(entries[i], true);
};
+
+BrowserBridge.prototype.receivedStartConnectionTestSuite = function() {
+ for (var i = 0; i < this.connectionTestsObservers_.length; ++i)
+ this.connectionTestsObservers_[i].onStartedConnectionTestSuite();
+};
+
+BrowserBridge.prototype.receivedStartConnectionTestExperiment = function(
+ experiment) {
+ for (var i = 0; i < this.connectionTestsObservers_.length; ++i) {
+ this.connectionTestsObservers_[i].onStartedConnectionTestExperiment(
+ experiment);
+ }
+};
+
+BrowserBridge.prototype.receivedCompletedConnectionTestExperiment =
+function(info) {
+ for (var i = 0; i < this.connectionTestsObservers_.length; ++i) {
+ this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment(
+ info.experiment, info.result);
+ }
+};
+
+BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() {
+ for (var i = 0; i < this.connectionTestsObservers_.length; ++i)
+ this.connectionTestsObservers_[i].onCompletedConnectionTestSuite();
+};
+
//------------------------------------------------------------------------------
/**
@@ -273,6 +312,19 @@ BrowserBridge.prototype.addHostResolverCacheObserver = function(observer) {
};
/**
+ * Adds a listener for the progress of the connection tests.
+ * The observer will be called back with:
+ *
+ * observer.onStartedConnectionTestSuite();
+ * observer.onStartedConnectionTestExperiment(experiment);
+ * observer.onCompletedConnectionTestExperiment(experiment, result);
+ * observer.onCompletedConnectionTestSuite();
+ */
+BrowserBridge.prototype.addConnectionTestsObserver = function(observer) {
+ this.connectionTestsObservers_.push(observer);
+};
+
+/**
* The browser gives us times in terms of "time ticks" in milliseconds.
* This function converts the tick count to a Date() object.
*
diff --git a/chrome/browser/resources/net_internals/testview.js b/chrome/browser/resources/net_internals/testview.js
new file mode 100644
index 0000000..e721606
--- /dev/null
+++ b/chrome/browser/resources/net_internals/testview.js
@@ -0,0 +1,132 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * This view displays the progress and results from the "connection tester".
+ *
+ * - Has an input box to specify the URL.
+ * - Has a button to start running the tests.
+ * - Shows the set of experiments that have been run so far, and their
+ * result.
+ *
+ * @constructor
+ */
+function TestView(mainBoxId, urlInputId, startButtonId, summaryDivId) {
+ DivView.call(this, mainBoxId);
+
+ this.urlInput_ = document.getElementById(urlInputId);
+ var startButton = document.getElementById(startButtonId);
+ this.summaryDiv_ = document.getElementById(summaryDivId);
+
+ startButton.onclick = this.startTests_.bind(this);
+
+ g_browser.addConnectionTestsObserver(this);
+}
+
+inherits(TestView, DivView);
+
+TestView.prototype.startTests_ = function() {
+ g_browser.sendStartConnectionTests(this.urlInput_.value);
+};
+
+/**
+ * Callback for when the connection tests have begun.
+ */
+TestView.prototype.onStartedConnectionTestSuite = function() {
+ this.summaryDiv_.innerHTML = '';
+
+ var p = addNode(this.summaryDiv_, 'p');
+ addTextNode(p, 'Started connection test suite suite on ' +
+ (new Date()).toLocaleString());
+
+ // Add a table that will hold the individual test results.
+ var table = addNode(this.summaryDiv_, 'table');
+ table.border = 1;
+ var thead = addNode(table, 'thead');
+ thead.innerHTML = '<tr><th>Result</th><th>Experiment</th>' +
+ '<th>Error</th><th>Time (ms)</th></tr>';
+
+ this.tbody_ = addNode(table, 'tbody');
+};
+
+/**
+ * Callback for when an individual test in the suite has begun.
+ */
+TestView.prototype.onStartedConnectionTestExperiment = function(experiment) {
+ var tr = addNode(this.tbody_, 'tr');
+
+ var passFailCell = addNode(tr, 'td');
+
+ var experimentCell = addNode(tr, 'td');
+
+ var resultCell = addNode(tr, 'td');
+ addTextNode(resultCell, '?');
+
+ var dtCell = addNode(tr, 'td');
+ addTextNode(dtCell, '?');
+
+ // We will fill in result cells with actual values (to replace the
+ // placeholder '?') once the test has completed. For now we just
+ // save references to these cells.
+ this.currentExperimentRow_ = {
+ experimentCell: experimentCell,
+ dtCell: dtCell,
+ resultCell: resultCell,
+ passFailCell: passFailCell,
+ startTime: (new Date()).getTime()
+ };
+
+ addTextNode(experimentCell, 'Fetch ' + experiment.url);
+
+ if (experiment.proxy_settings_experiment ||
+ experiment.host_resolver_experiment) {
+ var ul = addNode(experimentCell, 'ul');
+
+ if (experiment.proxy_settings_experiment) {
+ var li = addNode(ul, 'li');
+ addTextNode(li, experiment.proxy_settings_experiment);
+ }
+
+ if (experiment.host_resolver_experiment) {
+ var li = addNode(ul, 'li');
+ addTextNode(li, experiment.host_resolver_experiment);
+ }
+ }
+};
+
+/**
+ * Callback for when an individual test in the suite has finished.
+ */
+TestView.prototype.onCompletedConnectionTestExperiment = function(
+ experiment, result) {
+ var r = this.currentExperimentRow_;
+
+ var endTime = (new Date()).getTime();
+
+ r.dtCell.innerHTML = '';
+ addTextNode(r.dtCell, (endTime - r.startTime));
+
+ r.resultCell.innerHTML = '';
+
+ if (result == 0) {
+ r.passFailCell.style.color = 'green';
+ addTextNode(r.passFailCell, 'PASS');
+ } else {
+ // TODO(eroman): stringize the error code.
+ addTextNode(r.resultCell, result);
+ r.passFailCell.style.color = 'red';
+ addTextNode(r.passFailCell, 'FAIL');
+ }
+
+ this.currentExperimentRow_ = null;
+};
+
+/**
+ * Callback for when the last test in the suite has finished.
+ */
+TestView.prototype.onCompletedConnectionTestSuite = function() {
+ var p = addNode(this.summaryDiv_, 'p');
+ addTextNode(p, 'Completed connection test suite suite');
+};
+
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 91b147e..d8e42d8 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1589,6 +1589,8 @@
'browser/net/chrome_net_log.h',
'browser/net/chrome_url_request_context.cc',
'browser/net/chrome_url_request_context.h',
+ 'browser/net/connection_tester.cc',
+ 'browser/net/connection_tester.h',
'browser/net/url_request_context_getter.cc',
'browser/net/url_request_context_getter.h',
'browser/net/dns_global.cc',
@@ -3401,6 +3403,7 @@
'browser/resources/net_internals/resizableverticalsplitview.js',
'browser/resources/net_internals/sourceentry.js',
'browser/resources/net_internals/tabswitcherview.js',
+ 'browser/resources/net_internals/testview.js',
'browser/resources/net_internals/timelineviewpainter.js',
'browser/resources/net_internals/topmidbottomview.js',
'browser/resources/net_internals/util.js',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 40b2316..79c783c 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -843,6 +843,7 @@
'browser/metrics/metrics_log_unittest.cc',
'browser/metrics/metrics_response_unittest.cc',
'browser/metrics/metrics_service_unittest.cc',
+ 'browser/net/connection_tester_unittest.cc',
'browser/net/chrome_url_request_context_unittest.cc',
'browser/net/dns_host_info_unittest.cc',
'browser/net/dns_master_unittest.cc',
diff --git a/net/proxy/proxy_config.h b/net/proxy/proxy_config.h
index c641bbf..58e70e3 100644
--- a/net/proxy/proxy_config.h
+++ b/net/proxy/proxy_config.h
@@ -148,6 +148,24 @@ class ProxyConfig {
return auto_detect_;
}
+ // Helpers to construct some common proxy configurations.
+
+ static ProxyConfig CreateDirect() {
+ return ProxyConfig();
+ }
+
+ static ProxyConfig CreateAutoDetect() {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ return config;
+ }
+
+ static ProxyConfig CreateFromCustomPacURL(const GURL& pac_url) {
+ ProxyConfig config;
+ config.set_pac_url(pac_url);
+ return config;
+ }
+
private:
// True if the proxy configuration should be auto-detected.
bool auto_detect_;
diff --git a/net/proxy/proxy_config_service_fixed.h b/net/proxy/proxy_config_service_fixed.h
index 54fd9ac..b677eb4 100644
--- a/net/proxy/proxy_config_service_fixed.h
+++ b/net/proxy/proxy_config_service_fixed.h
@@ -6,6 +6,7 @@
#define NET_PROXY_PROXY_CONFIG_SERVICE_FIXED_H_
#include "net/base/net_errors.h"
+#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service.h"
namespace net {
diff --git a/net/url_request/url_request_context.h b/net/url_request/url_request_context.h
index 070942f..cd1736b 100644
--- a/net/url_request/url_request_context.h
+++ b/net/url_request/url_request_context.h
@@ -30,13 +30,14 @@ class SocketStream;
class URLRequest;
// Subclass to provide application-specific context for URLRequest instances.
-class URLRequestContext :
- public base::RefCountedThreadSafe<URLRequestContext> {
+class URLRequestContext
+ : public base::RefCountedThreadSafe<URLRequestContext> {
public:
URLRequestContext()
: net_log_(NULL),
http_transaction_factory_(NULL),
ftp_transaction_factory_(NULL),
+ http_auth_handler_factory_(NULL),
cookie_policy_(NULL),
transport_security_state_(NULL) {
}