Implementation of ProxyResolver that uses a Mojo service.

BUG=11746

Review URL: https://codereview.chromium.org/917863005

Cr-Commit-Position: refs/heads/master@{#319848}
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 8ebaaa03..79188e9 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -798,12 +798,16 @@
     sources = [
       "dns/mojo_host_resolver_impl.cc",
       "dns/mojo_host_resolver_impl.h",
+      "proxy/mojo_proxy_resolver_factory.h",
+      "proxy/proxy_resolver_mojo.cc",
+      "proxy/proxy_resolver_mojo.h",
     ]
 
     public_deps = [
       ":mojo_type_converters",
       ":net",
       "//base",
+      "//mojo/common",
       "//net/interfaces",
       "//third_party/mojo/src/mojo/public/cpp/bindings",
     ]
@@ -1405,6 +1409,7 @@
         "dns/mojo_host_resolver_impl_unittest.cc",
         "proxy/mojo_proxy_resolver_factory_impl_unittest.cc",
         "proxy/mojo_proxy_resolver_impl_unittest.cc",
+        "proxy/proxy_resolver_mojo_unittest.cc",
       ]
     }
 
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index e1d65e1..8460f6df 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -627,6 +627,9 @@
 // HTTP_1_1_REQUIRED error code received on HTTP/2 session to proxy.
 NET_ERROR(PROXY_HTTP_1_1_REQUIRED, -366)
 
+// The PAC script terminated fatally and must be reloaded.
+NET_ERROR(PAC_SCRIPT_TERMINATED, -367)
+
 // The cache does not have the requested entry.
 NET_ERROR(CACHE_MISS, -400)
 
diff --git a/net/net.gyp b/net/net.gyp
index 0d416c3..f40c1b60 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -722,6 +722,7 @@
               'dns/mojo_host_resolver_impl_unittest.cc',
               'proxy/mojo_proxy_resolver_factory_impl_unittest.cc',
               'proxy/mojo_proxy_resolver_impl_unittest.cc',
+              'proxy/proxy_resolver_mojo_unittest.cc',
             ],
           },
         ],
@@ -1277,12 +1278,17 @@
           'sources': [
             'dns/mojo_host_resolver_impl.cc',
             'dns/mojo_host_resolver_impl.h',
+            'proxy/mojo_proxy_resolver_factory.h',
+            'proxy/proxy_resolver_mojo.cc',
+            'proxy/proxy_resolver_mojo.h',
           ],
           'dependencies': [
             'mojo_type_converters',
             'net',
             'net_interfaces',
+            '../mojo/mojo_base.gyp:mojo_common_lib',
             '../mojo/mojo_base.gyp:mojo_environment_chromium',
+            '../mojo/mojo_base.gyp:mojo_url_type_converters',
             '../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings',
           ],
         },
diff --git a/net/net.gypi b/net/net.gypi
index 796d43d..174ced83 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -1449,6 +1449,7 @@
       'proxy/proxy_config_unittest.cc',
       'proxy/proxy_info_unittest.cc',
       'proxy/proxy_list_unittest.cc',
+      'proxy/proxy_resolver_mojo_unittest.cc',
       'proxy/proxy_resolver_v8_tracing_unittest.cc',
       'proxy/proxy_resolver_v8_unittest.cc',
       'proxy/proxy_script_decider_unittest.cc',
diff --git a/net/proxy/mojo_proxy_resolver_factory.h b/net/proxy/mojo_proxy_resolver_factory.h
new file mode 100644
index 0000000..2387e15
--- /dev/null
+++ b/net/proxy/mojo_proxy_resolver_factory.h
@@ -0,0 +1,26 @@
+// Copyright 2015 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 NET_PROXY_MOJO_PROXY_RESOLVER_FACTORY_H_
+#define NET_PROXY_MOJO_PROXY_RESOLVER_FACTORY_H_
+
+#include "net/interfaces/host_resolver_service.mojom.h"
+#include "net/interfaces/proxy_resolver_service.mojom.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h"
+
+namespace net {
+
+// Factory for connecting to Mojo ProxyResolver services.
+class MojoProxyResolverFactory {
+ public:
+  // Connect to a new ProxyResolver service using request |req|, using
+  // |host_resolver| as the DNS resolver.
+  // Note: The connection request |req| may be resolved asynchronously.
+  virtual void Create(mojo::InterfaceRequest<interfaces::ProxyResolver> req,
+                      interfaces::HostResolverPtr host_resolver) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_PROXY_MOJO_PROXY_RESOLVER_FACTORY_H_
diff --git a/net/proxy/mojo_proxy_type_converters.cc b/net/proxy/mojo_proxy_type_converters.cc
index bfaae12..00aed7ab 100644
--- a/net/proxy/mojo_proxy_type_converters.cc
+++ b/net/proxy/mojo_proxy_type_converters.cc
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #include "net/base/host_port_pair.h"
+#include "net/proxy/proxy_info.h"
 #include "net/proxy/proxy_server.h"
 
 namespace net {
@@ -80,4 +81,17 @@
                           net::HostPortPair(obj->host, obj->port));
 }
 
+// static
+net::ProxyInfo
+TypeConverter<net::ProxyInfo, mojo::Array<net::interfaces::ProxyServerPtr>>::
+    Convert(const mojo::Array<net::interfaces::ProxyServerPtr>& obj) {
+  net::ProxyList proxy_list;
+  for (size_t i = 0; i < obj.size(); i++) {
+    proxy_list.AddProxyServer(obj[i].To<net::ProxyServer>());
+  }
+  net::ProxyInfo info;
+  info.UseProxyList(proxy_list);
+  return info;
+}
+
 }  // namespace mojo
diff --git a/net/proxy/mojo_proxy_type_converters.h b/net/proxy/mojo_proxy_type_converters.h
index f13c1fc..55ea69df 100644
--- a/net/proxy/mojo_proxy_type_converters.h
+++ b/net/proxy/mojo_proxy_type_converters.h
@@ -9,6 +9,7 @@
 #include "third_party/mojo/src/mojo/public/cpp/bindings/type_converter.h"
 
 namespace net {
+class ProxyInfo;
 class ProxyServer;
 }
 
@@ -24,6 +25,13 @@
   static net::ProxyServer Convert(const net::interfaces::ProxyServerPtr& obj);
 };
 
+template <>
+struct TypeConverter<net::ProxyInfo,
+                     mojo::Array<net::interfaces::ProxyServerPtr>> {
+  static net::ProxyInfo Convert(
+      const mojo::Array<net::interfaces::ProxyServerPtr>& obj);
+};
+
 }  // namespace mojo
 
 #endif  // NET_PROXY_MOJO_PROXY_TYPE_CONVERTERS_H_
diff --git a/net/proxy/proxy_resolver_mojo.cc b/net/proxy/proxy_resolver_mojo.cc
new file mode 100644
index 0000000..6150d15
--- /dev/null
+++ b/net/proxy/proxy_resolver_mojo.cc
@@ -0,0 +1,254 @@
+// Copyright 2015 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 "net/proxy/proxy_resolver_mojo.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/common/url_type_converters.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mojo_host_resolver_impl.h"
+#include "net/proxy/mojo_proxy_resolver_factory.h"
+#include "net/proxy/mojo_proxy_type_converters.h"
+#include "net/proxy/proxy_info.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/error_handler.h"
+
+namespace net {
+
+class ProxyResolverMojo::Job : public interfaces::ProxyResolverRequestClient,
+                               public mojo::ErrorHandler {
+ public:
+  Job(ProxyResolverMojo* resolver,
+      const GURL& url,
+      ProxyInfo* results,
+      const net::CompletionCallback& callback);
+  ~Job() override;
+
+  // Cancels the job and prevents the callback from being run.
+  void Cancel();
+
+ private:
+  // Overridden from mojo::ErrorHandler:
+  void OnConnectionError() override;
+
+  // Overridden from interfaces::ProxyResolverRequestClient:
+  void ReportResult(
+      int32_t error,
+      mojo::Array<interfaces::ProxyServerPtr> proxy_servers) override;
+
+  ProxyResolverMojo* resolver_;
+  const GURL url_;
+  ProxyInfo* results_;
+  net::CompletionCallback callback_;
+
+  base::ThreadChecker thread_checker_;
+  mojo::Binding<interfaces::ProxyResolverRequestClient> binding_;
+};
+
+ProxyResolverMojo::Job::Job(ProxyResolverMojo* resolver,
+                            const GURL& url,
+                            ProxyInfo* results,
+                            const net::CompletionCallback& callback)
+    : resolver_(resolver),
+      url_(url),
+      results_(results),
+      callback_(callback),
+      binding_(this) {
+  binding_.set_error_handler(this);
+
+  interfaces::ProxyResolverRequestClientPtr client_ptr;
+  binding_.Bind(mojo::GetProxy(&client_ptr));
+  resolver_->mojo_proxy_resolver_ptr_->GetProxyForUrl(mojo::String::From(url_),
+                                                      client_ptr.Pass());
+}
+
+ProxyResolverMojo::Job::~Job() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!callback_.is_null())
+    callback_.Run(ERR_PAC_SCRIPT_TERMINATED);
+}
+
+void ProxyResolverMojo::Job::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(!callback_.is_null());
+  callback_.Reset();
+}
+
+void ProxyResolverMojo::Job::OnConnectionError() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DVLOG(1) << "ProxyResolverMojo::Job::OnConnectionError";
+  resolver_->RemoveJob(this);
+}
+
+void ProxyResolverMojo::Job::ReportResult(
+    int32_t error,
+    mojo::Array<interfaces::ProxyServerPtr> proxy_servers) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DVLOG(1) << "ProxyResolverMojo::Job::ReportResult: " << error;
+
+  if (error == OK) {
+    *results_ = proxy_servers.To<ProxyInfo>();
+    DVLOG(1) << "Servers: " << results_->ToPacString();
+  }
+
+  callback_.Run(error);
+  callback_.Reset();
+  resolver_->RemoveJob(this);
+}
+
+ProxyResolverMojo::ProxyResolverMojo(
+    MojoProxyResolverFactory* mojo_proxy_resolver_factory,
+    HostResolver* host_resolver)
+    : ProxyResolver(true /* |expects_pac_bytes| */),
+      mojo_proxy_resolver_factory_(mojo_proxy_resolver_factory),
+      host_resolver_(host_resolver) {
+}
+
+ProxyResolverMojo::~ProxyResolverMojo() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // All pending requests should have been cancelled.
+  DCHECK(pending_jobs_.empty());
+  DCHECK(set_pac_script_callback_.IsCancelled());
+}
+
+void ProxyResolverMojo::CancelSetPacScript() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  set_pac_script_callback_.Cancel();
+}
+
+int ProxyResolverMojo::SetPacScript(
+    const scoped_refptr<ProxyResolverScriptData>& pac_script,
+    const net::CompletionCallback& callback) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(set_pac_script_callback_.IsCancelled());
+  DCHECK(!callback.is_null());
+  if (pac_script->type() != ProxyResolverScriptData::TYPE_SCRIPT_CONTENTS ||
+      pac_script->utf16().empty()) {
+    return ERR_PAC_SCRIPT_FAILED;
+  }
+
+  DVLOG(1) << "ProxyResolverMojo::SetPacScript: " << pac_script->utf16();
+  set_pac_script_callback_.Reset(
+      base::Bind(&ProxyResolverMojo::OnSetPacScriptDone, base::Unretained(this),
+                 pac_script, callback));
+
+  if (!mojo_proxy_resolver_ptr_)
+    SetUpServices();
+
+  mojo_proxy_resolver_ptr_->SetPacScript(
+      mojo::String::From(pac_script->utf16()),
+      set_pac_script_callback_.callback());
+
+  return ERR_IO_PENDING;
+}
+
+void ProxyResolverMojo::OnSetPacScriptDone(
+    const scoped_refptr<ProxyResolverScriptData>& pac_script,
+    const net::CompletionCallback& callback,
+    int32_t result) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(!set_pac_script_callback_.IsCancelled());
+  DVLOG(1) << "ProxyResolverMojo::OnSetPacScriptDone: " << result;
+
+  callback.Run(result);
+  set_pac_script_callback_.Cancel();
+}
+
+void ProxyResolverMojo::SetUpServices() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // A Mojo service implementation must outlive its binding.
+  mojo_host_resolver_binding_.reset();
+
+  interfaces::HostResolverPtr mojo_host_resolver_ptr;
+  mojo_host_resolver_.reset(new MojoHostResolverImpl(host_resolver_));
+  mojo_host_resolver_binding_.reset(new mojo::Binding<interfaces::HostResolver>(
+      mojo_host_resolver_.get(), mojo::GetProxy(&mojo_host_resolver_ptr)));
+  mojo_proxy_resolver_ptr_.reset();
+  mojo_proxy_resolver_factory_->Create(
+      mojo::GetProxy(&mojo_proxy_resolver_ptr_), mojo_host_resolver_ptr.Pass());
+  mojo_proxy_resolver_ptr_.set_error_handler(this);
+}
+
+void ProxyResolverMojo::AbortPendingRequests() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!set_pac_script_callback_.IsCancelled()) {
+    set_pac_script_callback_.callback().Run(ERR_PAC_SCRIPT_TERMINATED);
+    set_pac_script_callback_.Cancel();
+  }
+
+  // Need to use this loop because deleting a Job will cause its callback to be
+  // run with a failure error code, which may cause other Jobs to be deleted.
+  while (!pending_jobs_.empty()) {
+    auto it = pending_jobs_.begin();
+    Job* job = *it;
+    pending_jobs_.erase(it);
+
+    // Deleting the job will cause its completion callback to be run with an
+    // ERR_PAC_SCRIPT_TERMINATED error.
+    delete job;
+  }
+}
+
+void ProxyResolverMojo::OnConnectionError() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DVLOG(1) << "ProxyResolverMojo::OnConnectionError";
+
+  // Disconnect from the Mojo proxy resolver service. An attempt to reconnect
+  // will happen on the next |SetPacScript()| request.
+  mojo_proxy_resolver_ptr_.reset();
+
+  // Aborting requests will invoke their callbacks, which may call
+  // |SetPacScript()| and re-create the connection. So disconnect from the Mojo
+  // service (above) before aborting the pending requests.
+  AbortPendingRequests();
+}
+
+void ProxyResolverMojo::RemoveJob(Job* job) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  size_t num_erased = pending_jobs_.erase(job);
+  DCHECK(num_erased);
+  delete job;
+}
+
+int ProxyResolverMojo::GetProxyForURL(const GURL& url,
+                                      ProxyInfo* results,
+                                      const net::CompletionCallback& callback,
+                                      RequestHandle* request,
+                                      const BoundNetLog& net_log) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // If the Mojo service is not connected, fail. The Mojo service is connected
+  // when the script is set, which must be done after construction and after a
+  // previous request returns ERR_PAC_SCRIPT_TERMINATED due to the Mojo proxy
+  // resolver process crashing.
+  if (!mojo_proxy_resolver_ptr_) {
+    DVLOG(1) << "ProxyResolverMojo::GetProxyForURL: Mojo not connected";
+    return ERR_PAC_SCRIPT_TERMINATED;
+  }
+
+  Job* job = new Job(this, url, results, callback);
+  bool inserted = pending_jobs_.insert(job).second;
+  DCHECK(inserted);
+  *request = job;
+
+  return ERR_IO_PENDING;
+}
+
+void ProxyResolverMojo::CancelRequest(RequestHandle request) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  Job* job = static_cast<Job*>(request);
+  DCHECK(job);
+  job->Cancel();
+  RemoveJob(job);
+}
+
+LoadState ProxyResolverMojo::GetLoadState(RequestHandle request) const {
+  // TODO(amistry): Implement real LoadState.
+  return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+}
+
+}  // namespace net
diff --git a/net/proxy/proxy_resolver_mojo.h b/net/proxy/proxy_resolver_mojo.h
new file mode 100644
index 0000000..9d97095
--- /dev/null
+++ b/net/proxy/proxy_resolver_mojo.h
@@ -0,0 +1,104 @@
+// Copyright 2015 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 NET_PROXY_PROXY_RESOLVER_MOJO_H_
+#define NET_PROXY_PROXY_RESOLVER_MOJO_H_
+
+#include <set>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/interfaces/host_resolver_service.mojom.h"
+#include "net/interfaces/proxy_resolver_service.mojom.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_resolver_script_data.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h"
+
+class GURL;
+
+namespace net {
+
+class BoundNetLog;
+class HostResolver;
+class ProxyInfo;
+class MojoProxyResolverFactory;
+
+// Implementation of ProxyResolver that connects to a Mojo service to evaluate
+// PAC scripts. This implementation only knows about Mojo services, and
+// therefore that service may live in or out of process.
+//
+// This implementation handles disconnections from the Mojo service (i.e. if the
+// service is out-of-process and that process crashes) and transparently
+// re-connects to a new service.
+class ProxyResolverMojo : public ProxyResolver, public mojo::ErrorHandler {
+ public:
+  // Constructs a ProxyResolverMojo and connects to a new Mojo proxy resolver
+  // service using |mojo_proxy_resolver_factory|. The new Mojo proxy resolver
+  // uses |host_resolver| as the DNS resolver.  |mojo_proxy_resolver_factory|
+  // and |host_resolver| are not owned and must outlive this.
+  // TODO(amistry): Add ProxyResolverErrorObserver and NetLog.
+  ProxyResolverMojo(MojoProxyResolverFactory* mojo_proxy_resolver_factory,
+                    HostResolver* host_resolver);
+  ~ProxyResolverMojo() override;
+
+  // ProxyResolver implementation:
+  int GetProxyForURL(const GURL& url,
+                     ProxyInfo* results,
+                     const net::CompletionCallback& callback,
+                     RequestHandle* request,
+                     const BoundNetLog& net_log) override;
+  void CancelRequest(RequestHandle request) override;
+  LoadState GetLoadState(RequestHandle request) const override;
+  void CancelSetPacScript() override;
+  int SetPacScript(const scoped_refptr<ProxyResolverScriptData>& pac_script,
+                   const net::CompletionCallback& callback) override;
+
+ private:
+  class Job;
+
+  // Overridden from mojo::ErrorHandler:
+  void OnConnectionError() override;
+
+  // Callback for ProxyResolverService::SetPacScript.
+  void OnSetPacScriptDone(
+      const scoped_refptr<ProxyResolverScriptData>& pac_script,
+      const net::CompletionCallback& callback,
+      int32_t result);
+
+  void SetUpServices();
+
+  void AbortPendingRequests();
+
+  void RemoveJob(Job* job);
+
+  // Connection to the Mojo proxy resolver.
+  interfaces::ProxyResolverPtr mojo_proxy_resolver_ptr_;
+
+  // Mojo host resolver service and binding.
+  scoped_ptr<interfaces::HostResolver> mojo_host_resolver_;
+  scoped_ptr<mojo::Binding<interfaces::HostResolver>>
+      mojo_host_resolver_binding_;
+
+  // Factory for connecting to new Mojo proxy resolvers.
+  // Not owned.
+  MojoProxyResolverFactory* mojo_proxy_resolver_factory_;
+
+  // DNS resolver, saved for creating a new Mojo proxy resolver when the
+  // existing one disconnects (i.e. when utility process crashes).
+  HostResolver* host_resolver_;
+
+  std::set<Job*> pending_jobs_;
+  net::CancelableCompletionCallback set_pac_script_callback_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProxyResolverMojo);
+};
+
+}  // namespace net
+
+#endif  // NET_PROXY_PROXY_RESOLVER_MOJO_H_
diff --git a/net/proxy/proxy_resolver_mojo_unittest.cc b/net/proxy/proxy_resolver_mojo_unittest.cc
new file mode 100644
index 0000000..2933bb3db
--- /dev/null
+++ b/net/proxy/proxy_resolver_mojo_unittest.cc
@@ -0,0 +1,697 @@
+// Copyright 2015 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 "net/proxy/proxy_resolver_mojo.h"
+
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "mojo/common/common_type_converters.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/proxy/mojo_proxy_resolver_factory.h"
+#include "net/proxy/mojo_proxy_type_converters.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver_script_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/error_handler.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+const char kScriptData[] = "FooBarBaz";
+const char kScriptData2[] = "BlahBlahBlah";
+const char kExampleUrl[] = "http://www.example.com";
+
+struct SetPacScriptAction {
+  enum Action {
+    COMPLETE,
+    DISCONNECT,
+  };
+
+  static SetPacScriptAction ReturnResult(Error error) {
+    SetPacScriptAction result;
+    result.error = error;
+    return result;
+  }
+
+  static SetPacScriptAction Disconnect() {
+    SetPacScriptAction result;
+    result.action = DISCONNECT;
+    return result;
+  }
+
+  Action action = COMPLETE;
+  Error error = OK;
+};
+
+struct GetProxyForUrlAction {
+  enum Action {
+    COMPLETE,
+    // Drop the request by closing the reply channel.
+    DROP,
+    // Disconnect the service.
+    DISCONNECT,
+    // Wait for the client pipe to be disconnected.
+    WAIT_FOR_CLIENT_DISCONNECT,
+  };
+
+  GetProxyForUrlAction() {}
+  GetProxyForUrlAction(const GetProxyForUrlAction& old) {
+    action = old.action;
+    error = old.error;
+    expected_url = old.expected_url;
+    proxy_servers = old.proxy_servers.Clone();
+  }
+
+  static GetProxyForUrlAction ReturnError(const GURL& url, Error error) {
+    GetProxyForUrlAction result;
+    result.expected_url = url;
+    result.error = error;
+    return result;
+  }
+
+  static GetProxyForUrlAction ReturnServers(
+      const GURL& url,
+      const mojo::Array<interfaces::ProxyServerPtr>& proxy_servers) {
+    GetProxyForUrlAction result;
+    result.expected_url = url;
+    result.proxy_servers = proxy_servers.Clone();
+    return result;
+  }
+
+  static GetProxyForUrlAction DropRequest(const GURL& url) {
+    GetProxyForUrlAction result;
+    result.expected_url = url;
+    result.action = DROP;
+    return result;
+  }
+
+  static GetProxyForUrlAction Disconnect(const GURL& url) {
+    GetProxyForUrlAction result;
+    result.expected_url = url;
+    result.action = DISCONNECT;
+    return result;
+  }
+
+  static GetProxyForUrlAction WaitForClientDisconnect(const GURL& url) {
+    GetProxyForUrlAction result;
+    result.expected_url = url;
+    result.action = WAIT_FOR_CLIENT_DISCONNECT;
+    return result;
+  }
+
+  Action action = COMPLETE;
+  Error error = OK;
+  mojo::Array<interfaces::ProxyServerPtr> proxy_servers;
+  GURL expected_url;
+};
+
+class MockMojoProxyResolver : public interfaces::ProxyResolver {
+ public:
+  explicit MockMojoProxyResolver(
+      mojo::InterfaceRequest<interfaces::ProxyResolver> req);
+  ~MockMojoProxyResolver() override;
+
+  void AddPacScriptAction(SetPacScriptAction action);
+  // Returned script data is UTF8.
+  std::string pac_script_data() { return pac_script_data_; }
+
+  void AddGetProxyAction(GetProxyForUrlAction action);
+
+  void WaitForNextRequest();
+
+ private:
+  // Overridden from interfaces::ProxyResolver:
+  void SetPacScript(const mojo::String& data,
+                    const mojo::Callback<void(int32_t)>& callback) override;
+  void GetProxyForUrl(
+      const mojo::String& url,
+      interfaces::ProxyResolverRequestClientPtr client) override;
+
+  void WakeWaiter();
+
+  std::string pac_script_data_;
+  std::queue<SetPacScriptAction> pac_script_actions_;
+
+  std::queue<GetProxyForUrlAction> get_proxy_actions_;
+
+  base::Closure quit_closure_;
+
+  mojo::Binding<interfaces::ProxyResolver> binding_;
+};
+
+MockMojoProxyResolver::MockMojoProxyResolver(
+    mojo::InterfaceRequest<interfaces::ProxyResolver> req)
+    : binding_(this, req.Pass()) {
+}
+
+MockMojoProxyResolver::~MockMojoProxyResolver() {
+  EXPECT_TRUE(pac_script_actions_.empty())
+      << "Actions remaining: " << pac_script_actions_.size();
+  EXPECT_TRUE(get_proxy_actions_.empty())
+      << "Actions remaining: " << get_proxy_actions_.size();
+}
+
+void MockMojoProxyResolver::AddPacScriptAction(SetPacScriptAction action) {
+  pac_script_actions_.push(action);
+}
+
+void MockMojoProxyResolver::AddGetProxyAction(GetProxyForUrlAction action) {
+  get_proxy_actions_.push(action);
+}
+
+void MockMojoProxyResolver::WaitForNextRequest() {
+  base::RunLoop run_loop;
+  quit_closure_ = run_loop.QuitClosure();
+  run_loop.Run();
+}
+
+void MockMojoProxyResolver::WakeWaiter() {
+  if (!quit_closure_.is_null())
+    quit_closure_.Run();
+  quit_closure_.Reset();
+}
+
+void MockMojoProxyResolver::SetPacScript(
+    const mojo::String& data,
+    const mojo::Callback<void(int32_t)>& callback) {
+  pac_script_data_ = data.To<std::string>();
+
+  ASSERT_FALSE(pac_script_actions_.empty());
+  SetPacScriptAction action = pac_script_actions_.front();
+  pac_script_actions_.pop();
+
+  switch (action.action) {
+    case SetPacScriptAction::COMPLETE:
+      callback.Run(action.error);
+      break;
+    case SetPacScriptAction::DISCONNECT:
+      binding_.Close();
+      break;
+  }
+  WakeWaiter();
+}
+
+void MockMojoProxyResolver::GetProxyForUrl(
+    const mojo::String& url,
+    interfaces::ProxyResolverRequestClientPtr client) {
+  ASSERT_FALSE(get_proxy_actions_.empty());
+  GetProxyForUrlAction action = get_proxy_actions_.front();
+  get_proxy_actions_.pop();
+
+  EXPECT_EQ(action.expected_url.spec(), url.To<std::string>());
+  switch (action.action) {
+    case GetProxyForUrlAction::COMPLETE:
+      client->ReportResult(action.error, action.proxy_servers.Pass());
+      break;
+    case GetProxyForUrlAction::DROP:
+      client.reset();
+      break;
+    case GetProxyForUrlAction::DISCONNECT:
+      binding_.Close();
+      break;
+    case GetProxyForUrlAction::WAIT_FOR_CLIENT_DISCONNECT:
+      ASSERT_FALSE(client.WaitForIncomingMethodCall());
+      break;
+  }
+  WakeWaiter();
+}
+
+class TestMojoProxyResolverFactory : public MojoProxyResolverFactory {
+ public:
+  TestMojoProxyResolverFactory();
+  ~TestMojoProxyResolverFactory();
+
+  // Overridden from MojoProxyResolverFactory:
+  void Create(mojo::InterfaceRequest<interfaces::ProxyResolver> req,
+              interfaces::HostResolverPtr host_resolver) override;
+
+  MockMojoProxyResolver& GetMockResolver() { return *mock_proxy_resolver_; }
+
+  void AddFuturePacScriptAction(int creation, SetPacScriptAction action);
+  void AddFutureGetProxyAction(int creation, GetProxyForUrlAction action);
+
+  int num_create_calls() const { return num_create_calls_; }
+  void FailNextCreate() { fail_next_create_ = true; }
+
+ private:
+  int num_create_calls_;
+  std::map<int, std::list<SetPacScriptAction>> pac_script_actions_;
+  std::map<int, std::list<GetProxyForUrlAction>> get_proxy_actions_;
+  bool fail_next_create_;
+
+  scoped_ptr<MockMojoProxyResolver> mock_proxy_resolver_;
+};
+
+TestMojoProxyResolverFactory::TestMojoProxyResolverFactory()
+    : num_create_calls_(0), fail_next_create_(false) {
+}
+
+TestMojoProxyResolverFactory::~TestMojoProxyResolverFactory() {
+}
+
+void TestMojoProxyResolverFactory::Create(
+    mojo::InterfaceRequest<interfaces::ProxyResolver> req,
+    interfaces::HostResolverPtr host_resolver) {
+  if (fail_next_create_) {
+    req = nullptr;
+    fail_next_create_ = false;
+  } else {
+    mock_proxy_resolver_.reset(new MockMojoProxyResolver(req.Pass()));
+
+    for (const auto& action : pac_script_actions_[num_create_calls_])
+      mock_proxy_resolver_->AddPacScriptAction(action);
+
+    for (const auto& action : get_proxy_actions_[num_create_calls_])
+      mock_proxy_resolver_->AddGetProxyAction(action);
+  }
+  num_create_calls_++;
+}
+
+void TestMojoProxyResolverFactory::AddFuturePacScriptAction(
+    int creation,
+    SetPacScriptAction action) {
+  pac_script_actions_[creation].push_back(action);
+}
+
+void TestMojoProxyResolverFactory::AddFutureGetProxyAction(
+    int creation,
+    GetProxyForUrlAction action) {
+  get_proxy_actions_[creation].push_back(action);
+}
+
+class Request {
+ public:
+  Request(ProxyResolverMojo* resolver, const GURL& url);
+
+  int Resolve();
+  void Cancel();
+  int WaitForResult();
+
+  int error() const { return error_; }
+  const ProxyInfo& results() const { return results_; }
+
+ private:
+  ProxyResolverMojo* resolver_;
+  const GURL url_;
+  ProxyInfo results_;
+  ProxyResolver::RequestHandle handle_;
+  int error_;
+  TestCompletionCallback callback_;
+};
+
+Request::Request(ProxyResolverMojo* resolver, const GURL& url)
+    : resolver_(resolver), url_(url), error_(0) {
+}
+
+int Request::Resolve() {
+  BoundNetLog net_log;
+  error_ = resolver_->GetProxyForURL(url_, &results_, callback_.callback(),
+                                     &handle_, net_log);
+  return error_;
+}
+
+void Request::Cancel() {
+  resolver_->CancelRequest(handle_);
+}
+
+int Request::WaitForResult() {
+  error_ = callback_.WaitForResult();
+  return error_;
+}
+
+}  // namespace
+
+class ProxyResolverMojoTest : public testing::Test {
+ public:
+  void SetUp() override {
+    proxy_resolver_mojo_.reset(new ProxyResolverMojo(
+        &mojo_proxy_resolver_factory_, &mock_host_resolver_));
+  }
+
+  scoped_ptr<Request> MakeRequest(const GURL& url) {
+    return make_scoped_ptr(new Request(proxy_resolver_mojo_.get(), url));
+  }
+
+  mojo::Array<interfaces::ProxyServerPtr> ProxyServersFromPacString(
+      const std::string& pac_string) {
+    ProxyInfo proxy_info;
+    proxy_info.UsePacString(pac_string);
+
+    return mojo::Array<interfaces::ProxyServerPtr>::From(
+        proxy_info.proxy_list().GetAll());
+  }
+
+  void SetPacScript(int instance) {
+    mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+        instance, SetPacScriptAction::ReturnResult(OK));
+    TestCompletionCallback callback;
+    scoped_refptr<ProxyResolverScriptData> pac_script(
+        ProxyResolverScriptData::FromUTF8(kScriptData));
+    EXPECT_EQ(OK, callback.GetResult(proxy_resolver_mojo_->SetPacScript(
+                      pac_script, callback.callback())));
+  }
+
+  void RunCallbackAndSetPacScript(const net::CompletionCallback& callback,
+                                  const net::CompletionCallback& pac_callback,
+                                  int instance,
+                                  int result) {
+    callback.Run(result);
+    mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+        instance, SetPacScriptAction::ReturnResult(OK));
+    scoped_refptr<ProxyResolverScriptData> pac_script(
+        ProxyResolverScriptData::FromUTF8(kScriptData));
+    EXPECT_EQ(ERR_IO_PENDING,
+              proxy_resolver_mojo_->SetPacScript(pac_script, pac_callback));
+  }
+
+  MockHostResolver mock_host_resolver_;
+  TestMojoProxyResolverFactory mojo_proxy_resolver_factory_;
+  scoped_ptr<ProxyResolverMojo> proxy_resolver_mojo_;
+};
+
+TEST_F(ProxyResolverMojoTest, SetPacScript) {
+  SetPacScript(0);
+  EXPECT_EQ(kScriptData,
+            mojo_proxy_resolver_factory_.GetMockResolver().pac_script_data());
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_Empty) {
+  TestCompletionCallback callback;
+  scoped_refptr<ProxyResolverScriptData> pac_script(
+      ProxyResolverScriptData::FromUTF8(""));
+  EXPECT_EQ(ERR_PAC_SCRIPT_FAILED,
+            callback.GetResult(proxy_resolver_mojo_->SetPacScript(
+                pac_script, callback.callback())));
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_Url) {
+  TestCompletionCallback callback;
+  scoped_refptr<ProxyResolverScriptData> pac_script(
+      ProxyResolverScriptData::FromURL(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_PAC_SCRIPT_FAILED,
+            callback.GetResult(proxy_resolver_mojo_->SetPacScript(
+                pac_script, callback.callback())));
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_Failed) {
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::ReturnResult(ERR_PAC_STATUS_NOT_OK));
+
+  TestCompletionCallback callback;
+  scoped_refptr<ProxyResolverScriptData> pac_script(
+      ProxyResolverScriptData::FromUTF8(kScriptData));
+  EXPECT_EQ(ERR_PAC_STATUS_NOT_OK,
+            callback.GetResult(proxy_resolver_mojo_->SetPacScript(
+                pac_script, callback.callback())));
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_Disconnected) {
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::Disconnect());
+
+  scoped_refptr<ProxyResolverScriptData> pac_script(
+      ProxyResolverScriptData::FromUTF8(kScriptData));
+  TestCompletionCallback callback;
+  EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_mojo_->SetPacScript(
+                                pac_script, callback.callback()));
+  EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, callback.GetResult(ERR_IO_PENDING));
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_SuccessThenDisconnect) {
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::ReturnResult(OK));
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::Disconnect());
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      1, SetPacScriptAction::ReturnResult(ERR_FAILED));
+  {
+    scoped_refptr<ProxyResolverScriptData> pac_script(
+        ProxyResolverScriptData::FromUTF8(kScriptData));
+    TestCompletionCallback callback;
+    EXPECT_EQ(OK, callback.GetResult(proxy_resolver_mojo_->SetPacScript(
+                      pac_script, callback.callback())));
+    EXPECT_EQ(kScriptData,
+              mojo_proxy_resolver_factory_.GetMockResolver().pac_script_data());
+  }
+
+  {
+    scoped_refptr<ProxyResolverScriptData> pac_script(
+        ProxyResolverScriptData::FromUTF8(kScriptData2));
+    TestCompletionCallback callback;
+    EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_mojo_->SetPacScript(
+                                  pac_script, callback.callback()));
+    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, callback.GetResult(ERR_IO_PENDING));
+  }
+
+  {
+    scoped_refptr<ProxyResolverScriptData> pac_script(
+        ProxyResolverScriptData::FromUTF8(kScriptData2));
+    TestCompletionCallback callback;
+    EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_mojo_->SetPacScript(
+                                  pac_script, callback.callback()));
+    EXPECT_EQ(ERR_FAILED, callback.GetResult(ERR_IO_PENDING));
+  }
+
+  // The service should have been recreated on the last SetPacScript call.
+  EXPECT_EQ(2, mojo_proxy_resolver_factory_.num_create_calls());
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_Cancel) {
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::ReturnResult(OK));
+
+  scoped_refptr<ProxyResolverScriptData> pac_script(
+      ProxyResolverScriptData::FromUTF8(kScriptData));
+  TestCompletionCallback callback;
+  EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_mojo_->SetPacScript(
+                                pac_script, callback.callback()));
+  proxy_resolver_mojo_->CancelSetPacScript();
+
+  // The Mojo request is still made.
+  mojo_proxy_resolver_factory_.GetMockResolver().WaitForNextRequest();
+}
+
+TEST_F(ProxyResolverMojoTest, SetPacScript_CancelAndSetAgain) {
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::ReturnResult(ERR_FAILED));
+  mojo_proxy_resolver_factory_.AddFuturePacScriptAction(
+      0, SetPacScriptAction::ReturnResult(ERR_UNEXPECTED));
+
+  scoped_refptr<ProxyResolverScriptData> pac_script(
+      ProxyResolverScriptData::FromUTF8(kScriptData));
+  TestCompletionCallback callback1;
+  EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_mojo_->SetPacScript(
+                                pac_script, callback1.callback()));
+  proxy_resolver_mojo_->CancelSetPacScript();
+
+  TestCompletionCallback callback2;
+  EXPECT_EQ(ERR_IO_PENDING, proxy_resolver_mojo_->SetPacScript(
+                                pac_script, callback2.callback()));
+  EXPECT_EQ(ERR_UNEXPECTED, callback2.GetResult(ERR_IO_PENDING));
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::ReturnServers(
+             GURL(kExampleUrl), ProxyServersFromPacString("DIRECT")));
+  SetPacScript(0);
+
+  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+  EXPECT_EQ(OK, request->WaitForResult());
+
+  EXPECT_EQ("DIRECT", request->results().ToPacString());
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_WithoutSetPacScript) {
+  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request->Resolve());
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_MultipleResults) {
+  static const char kPacString[] =
+      "PROXY foo1:80;DIRECT;SOCKS foo2:1234;"
+      "SOCKS5 foo3:1080;HTTPS foo4:443;QUIC foo6:8888";
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::ReturnServers(
+             GURL(kExampleUrl), ProxyServersFromPacString(kPacString)));
+  SetPacScript(0);
+
+  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+  EXPECT_EQ(OK, request->WaitForResult());
+
+  EXPECT_EQ(kPacString, request->results().ToPacString());
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_Error) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::ReturnError(GURL(kExampleUrl), ERR_UNEXPECTED));
+  SetPacScript(0);
+
+  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+  EXPECT_EQ(ERR_UNEXPECTED, request->WaitForResult());
+
+  EXPECT_TRUE(request->results().is_empty());
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_Cancel) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::WaitForClientDisconnect(GURL(kExampleUrl)));
+  SetPacScript(0);
+
+  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+  request->Cancel();
+
+  // The Mojo request is still made.
+  mojo_proxy_resolver_factory_.GetMockResolver().WaitForNextRequest();
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_MultipleRequests) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::ReturnServers(
+             GURL(kExampleUrl), ProxyServersFromPacString("DIRECT")));
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::ReturnServers(
+             GURL("https://www.chromium.org"),
+             ProxyServersFromPacString("HTTPS foo:443")));
+  SetPacScript(0);
+
+  scoped_ptr<Request> request1(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request1->Resolve());
+  scoped_ptr<Request> request2(MakeRequest(GURL("https://www.chromium.org")));
+  EXPECT_EQ(ERR_IO_PENDING, request2->Resolve());
+
+  EXPECT_EQ(OK, request1->WaitForResult());
+  EXPECT_EQ(OK, request2->WaitForResult());
+
+  EXPECT_EQ("DIRECT", request1->results().ToPacString());
+  EXPECT_EQ("HTTPS foo:443", request2->results().ToPacString());
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_Disconnect) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::Disconnect(GURL(kExampleUrl)));
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      1, GetProxyForUrlAction::ReturnServers(
+             GURL(kExampleUrl), ProxyServersFromPacString("DIRECT")));
+  {
+    SetPacScript(0);
+    scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+    EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request->WaitForResult());
+    EXPECT_TRUE(request->results().is_empty());
+  }
+
+  {
+    // Calling GetProxyForURL without first setting the pac script should fail.
+    scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request->Resolve());
+  }
+
+  {
+    SetPacScript(1);
+    scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+    EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+    EXPECT_EQ(OK, request->WaitForResult());
+    EXPECT_EQ("DIRECT", request->results().ToPacString());
+  }
+
+  EXPECT_EQ(2, mojo_proxy_resolver_factory_.num_create_calls());
+}
+
+TEST_F(ProxyResolverMojoTest, GetProxyForURL_ClientClosed) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::DropRequest(GURL(kExampleUrl)));
+  SetPacScript(0);
+
+  scoped_ptr<Request> request1(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request1->Resolve());
+
+  EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request1->WaitForResult());
+  EXPECT_EQ(1, mojo_proxy_resolver_factory_.num_create_calls());
+}
+
+TEST_F(ProxyResolverMojoTest, DisconnectAndFailCreate) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::Disconnect(GURL(kExampleUrl)));
+
+  {
+    SetPacScript(0);
+    scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+    EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, request->WaitForResult());
+    EXPECT_TRUE(request->results().is_empty());
+  }
+
+  // The service should attempt to create a new connection, but that should
+  // fail.
+  {
+    scoped_refptr<ProxyResolverScriptData> pac_script(
+        ProxyResolverScriptData::FromUTF8(kScriptData));
+    TestCompletionCallback callback;
+    mojo_proxy_resolver_factory_.FailNextCreate();
+    EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED,
+              callback.GetResult(proxy_resolver_mojo_->SetPacScript(
+                  pac_script, callback.callback())));
+  }
+
+  // A third attempt should succeed.
+  SetPacScript(2);
+
+  EXPECT_EQ(3, mojo_proxy_resolver_factory_.num_create_calls());
+}
+
+TEST_F(ProxyResolverMojoTest, DisconnectAndReconnectInCallback) {
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      0, GetProxyForUrlAction::Disconnect(GURL(kExampleUrl)));
+  mojo_proxy_resolver_factory_.AddFutureGetProxyAction(
+      1, GetProxyForUrlAction::ReturnServers(
+             GURL(kExampleUrl), ProxyServersFromPacString("DIRECT")));
+
+  // In this first resolve request, the Mojo service is disconnected causing the
+  // request to return ERR_PAC_SCRIPT_TERMINATED. In the request callback, form
+  // a new connection to a Mojo resolver service by calling SetPacScript().
+  SetPacScript(0);
+  ProxyInfo results;
+  TestCompletionCallback resolve_callback;
+  TestCompletionCallback pac_callback;
+  ProxyResolver::RequestHandle handle;
+  BoundNetLog net_log;
+  int error = proxy_resolver_mojo_->GetProxyForURL(
+      GURL(kExampleUrl), &results,
+      base::Bind(&ProxyResolverMojoTest::RunCallbackAndSetPacScript,
+                 base::Unretained(this), resolve_callback.callback(),
+                 pac_callback.callback(), 1),
+      &handle, net_log);
+  EXPECT_EQ(ERR_IO_PENDING, error);
+  EXPECT_EQ(ERR_PAC_SCRIPT_TERMINATED, resolve_callback.WaitForResult());
+  EXPECT_EQ(OK, pac_callback.WaitForResult());
+
+  // Setting the PAC script above should have been successful and let us send a
+  // resolve request.
+  scoped_ptr<Request> request(MakeRequest(GURL(kExampleUrl)));
+  EXPECT_EQ(ERR_IO_PENDING, request->Resolve());
+  EXPECT_EQ(OK, request->WaitForResult());
+  EXPECT_EQ("DIRECT", request->results().ToPacString());
+
+  EXPECT_EQ(2, mojo_proxy_resolver_factory_.num_create_calls());
+}
+
+}  // namespace net