[go: nahoru, domu]

[network service] Add a NetworkChangeManager interface

This CL adds an interface to allow consumers to subscribe/unsubscribe
to network change events.

- mojom::NetworkChangeManager
  Listens to net::NetworkChangeNotifier and propagate notifications to
  mojom::NetworkChangeManagerClient.
  NetworkService will have a pointer to the impl of this interface.

- mojom::NetworkChangeManagerClient
  The implementation of this interface(content::NetworkConnectionTracker)
  receives notifications from mojom::NetworkChangeManager, and then sends
  those down to its observers.

Design doc:
https://docs.google.com/document/d/1kBp_vTIH-1Jx4M9DN7mnqRRyUmeHe3BZ1NqTbsJ4_S8/edit

Bug: 754709
Change-Id: Ia98dc41da9bb618fd00adfd6412d70d839cd13bb
Reviewed-on: https://chromium-review.googlesource.com/644352
Commit-Queue: Helen Li <xunjieli@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: Matt Menke <mmenke@chromium.org>
Reviewed-by: Paul Jensen <pauljensen@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#510117}
diff --git a/content/network/BUILD.gn b/content/network/BUILD.gn
index a3e60b9..e01ab8b 100644
--- a/content/network/BUILD.gn
+++ b/content/network/BUILD.gn
@@ -39,6 +39,8 @@
     "cookie_manager_impl.h",
     "http_server_properties_pref_delegate.cc",
     "http_server_properties_pref_delegate.h",
+    "network_change_manager_impl.cc",
+    "network_change_manager_impl.h",
     "network_context.cc",
     "network_context.h",
     "network_service_impl.cc",
diff --git a/content/network/DEPS b/content/network/DEPS
index 7241411..c460224c 100644
--- a/content/network/DEPS
+++ b/content/network/DEPS
@@ -9,6 +9,7 @@
   "+content/public/common/appcache_info.h",
   "+content/public/common/content_client.h",
   "+content/public/common/content_switches.h",
+  "+content/public/common/network_change_manager.mojom.h",
   "+content/public/common/network_service.mojom.h",
   "+content/public/common/referrer.h",
   "+content/public/common/resource_request.h",
diff --git a/content/network/network_change_manager_impl.cc b/content/network/network_change_manager_impl.cc
new file mode 100644
index 0000000..0b431a5
--- /dev/null
+++ b/content/network/network_change_manager_impl.cc
@@ -0,0 +1,67 @@
+// Copyright 2017 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 "content/network/network_change_manager_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/logging.h"
+#include "net/base/network_change_notifier.h"
+
+namespace content {
+
+NetworkChangeManagerImpl::NetworkChangeManagerImpl(
+    std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier)
+    : network_change_notifier_(std::move(network_change_notifier)) {
+  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+  connection_type_ =
+      mojom::ConnectionType(net::NetworkChangeNotifier::GetConnectionType());
+}
+
+NetworkChangeManagerImpl::~NetworkChangeManagerImpl() {
+  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+}
+
+void NetworkChangeManagerImpl::AddRequest(
+    mojom::NetworkChangeManagerRequest request) {
+  bindings_.AddBinding(this, std::move(request));
+}
+
+void NetworkChangeManagerImpl::RequestNotifications(
+    mojom::NetworkChangeManagerClientPtr client_ptr) {
+  client_ptr.set_connection_error_handler(
+      base::Bind(&NetworkChangeManagerImpl::NotificationPipeBroken,
+                 // base::Unretained is safe as destruction of the
+                 // NetworkChangeManagerImpl will also destroy the
+                 // |clients_| list (which this object will be
+                 // inserted into, below), which will destroy the
+                 // client_ptr, rendering this callback moot.
+                 base::Unretained(this), base::Unretained(client_ptr.get())));
+  client_ptr->OnInitialConnectionType(connection_type_);
+  clients_.push_back(std::move(client_ptr));
+}
+
+size_t NetworkChangeManagerImpl::GetNumClientsForTesting() const {
+  return clients_.size();
+}
+
+void NetworkChangeManagerImpl::NotificationPipeBroken(
+    mojom::NetworkChangeManagerClient* client) {
+  clients_.erase(
+      std::find_if(clients_.begin(), clients_.end(),
+                   [client](mojom::NetworkChangeManagerClientPtr& ptr) {
+                     return ptr.get() == client;
+                   }));
+}
+
+void NetworkChangeManagerImpl::OnNetworkChanged(
+    net::NetworkChangeNotifier::ConnectionType type) {
+  connection_type_ = mojom::ConnectionType(type);
+  for (const auto& client : clients_) {
+    client->OnNetworkChanged(connection_type_);
+  }
+}
+
+}  // namespace content
diff --git a/content/network/network_change_manager_impl.h b/content/network/network_change_manager_impl.h
new file mode 100644
index 0000000..139ce16
--- /dev/null
+++ b/content/network/network_change_manager_impl.h
@@ -0,0 +1,64 @@
+// Copyright 2017 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 CONTENT_NETWORK_NETWORK_CHANGE_MANAGER_IMPL_H_
+#define CONTENT_NETWORK_NETWORK_CHANGE_MANAGER_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "content/common/content_export.h"
+#include "content/public/common/network_change_manager.mojom.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "net/base/network_change_notifier.h"
+
+namespace content {
+
+// Implementation of mojom::NetworkChangeManager. All accesses to this class are
+// done through mojo on the main thread. This registers itself to receive
+// broadcasts from net::NetworkChangeNotifier and rebroadcasts the notifications
+// to mojom::NetworkChangeManagerClients through mojo pipes.
+class CONTENT_EXPORT NetworkChangeManagerImpl
+    : public mojom::NetworkChangeManager,
+      public net::NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+  // If |network_change_notifier| is not null, |this| will take ownership of it.
+  // Otherwise, the global net::NetworkChangeNotifier will be used.
+  explicit NetworkChangeManagerImpl(
+      std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier);
+
+  ~NetworkChangeManagerImpl() override;
+
+  // Binds a NetworkChangeManager request to this object. Mojo messages
+  // coming through the associated pipe will be served by this object.
+  void AddRequest(mojom::NetworkChangeManagerRequest request);
+
+  // mojom::NetworkChangeManager implementation:
+  void RequestNotifications(
+      mojom::NetworkChangeManagerClientPtr client_ptr) override;
+
+  size_t GetNumClientsForTesting() const;
+
+ private:
+  // Handles connection errors on notification pipes.
+  void NotificationPipeBroken(mojom::NetworkChangeManagerClient* client);
+
+  // net::NetworkChangeNotifier::NetworkChangeObserver implementation:
+  void OnNetworkChanged(
+      net::NetworkChangeNotifier::ConnectionType type) override;
+
+  std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
+  mojo::BindingSet<mojom::NetworkChangeManager> bindings_;
+  std::vector<mojom::NetworkChangeManagerClientPtr> clients_;
+  mojom::ConnectionType connection_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkChangeManagerImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_NETWORK_NETWORK_CHANGE_MANAGER_IMPL_H_
diff --git a/content/network/network_change_manager_impl_unittest.cc b/content/network/network_change_manager_impl_unittest.cc
new file mode 100644
index 0000000..36681555
--- /dev/null
+++ b/content/network/network_change_manager_impl_unittest.cc
@@ -0,0 +1,220 @@
+// Copyright 2017 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 "content/network/network_change_manager_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "content/public/common/network_change_manager.mojom.h"
+#include "net/base/network_change_notifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+// Type of notification expected in test.
+enum NotificationType {
+  // Default value.
+  NONE,
+  // OnInitialConnectionType() notification.
+  INITIAL,
+  // OnNetworkChanged() notification.
+  NETWORK_CHANGED,
+};
+
+class TestNetworkChangeManagerClient
+    : public mojom::NetworkChangeManagerClient {
+ public:
+  explicit TestNetworkChangeManagerClient(
+      content::NetworkChangeManagerImpl* network_change_manager)
+      : num_network_changed_(0),
+        run_loop_(std::make_unique<base::RunLoop>()),
+        notification_type_to_wait_(NONE),
+        connection_type_(mojom::ConnectionType::CONNECTION_UNKNOWN),
+        binding_(this) {
+    mojom::NetworkChangeManagerPtr manager_ptr;
+    mojom::NetworkChangeManagerRequest request(mojo::MakeRequest(&manager_ptr));
+    network_change_manager->AddRequest(std::move(request));
+
+    mojom::NetworkChangeManagerClientPtr client_ptr;
+    mojom::NetworkChangeManagerClientRequest client_request(
+        mojo::MakeRequest(&client_ptr));
+    binding_.Bind(std::move(client_request));
+    manager_ptr->RequestNotifications(std::move(client_ptr));
+  }
+
+  ~TestNetworkChangeManagerClient() override {}
+
+  // NetworkChangeManagerClient implementation:
+  void OnInitialConnectionType(mojom::ConnectionType type) override {
+    connection_type_ = type;
+    if (notification_type_to_wait_ == INITIAL)
+      run_loop_->Quit();
+  }
+
+  void OnNetworkChanged(mojom::ConnectionType type) override {
+    num_network_changed_++;
+    connection_type_ = type;
+    if (notification_type_to_wait_ == NETWORK_CHANGED)
+      run_loop_->Quit();
+  }
+
+  // Returns the number of OnNetworkChanged() notifications.
+  size_t num_network_changed() const { return num_network_changed_; }
+
+  void WaitForNotification(NotificationType notification_type) {
+    notification_type_to_wait_ = notification_type;
+    run_loop_->Run();
+    run_loop_.reset(new base::RunLoop());
+  }
+
+  mojom::ConnectionType connection_type() const { return connection_type_; }
+
+ private:
+  size_t num_network_changed_;
+  std::unique_ptr<base::RunLoop> run_loop_;
+  NotificationType notification_type_to_wait_;
+  mojom::ConnectionType connection_type_;
+  mojo::Binding<mojom::NetworkChangeManagerClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeManagerClient);
+};
+
+}  // namespace
+
+class NetworkChangeManagerImplTest : public testing::Test {
+ public:
+  NetworkChangeManagerImplTest()
+      : network_change_manager_(new NetworkChangeManagerImpl(
+            base::WrapUnique(net::NetworkChangeNotifier::CreateMock()))) {
+    network_change_manager_client_ =
+        std::make_unique<TestNetworkChangeManagerClient>(
+            network_change_manager_.get());
+  }
+
+  ~NetworkChangeManagerImplTest() override {}
+
+  TestNetworkChangeManagerClient* network_change_manager_client() {
+    return network_change_manager_client_.get();
+  }
+
+  NetworkChangeManagerImpl* network_change_manager() const {
+    return network_change_manager_.get();
+  }
+
+  void SimulateNetworkChange(net::NetworkChangeNotifier::ConnectionType type) {
+    net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(type);
+  }
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  std::unique_ptr<NetworkChangeManagerImpl> network_change_manager_;
+  std::unique_ptr<TestNetworkChangeManagerClient>
+      network_change_manager_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkChangeManagerImplTest);
+};
+
+TEST_F(NetworkChangeManagerImplTest, ClientNotified) {
+  // Simulate a new network change.
+  SimulateNetworkChange(net::NetworkChangeNotifier::CONNECTION_3G);
+  network_change_manager_client()->WaitForNotification(NETWORK_CHANGED);
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_3G,
+            network_change_manager_client()->connection_type());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, network_change_manager_client()->num_network_changed());
+}
+
+TEST_F(NetworkChangeManagerImplTest, OneClientPipeBroken) {
+  auto network_change_manager_client2 =
+      std::make_unique<TestNetworkChangeManagerClient>(
+          network_change_manager());
+
+  // Simulate a network change.
+  SimulateNetworkChange(net::NetworkChangeNotifier::CONNECTION_WIFI);
+
+  network_change_manager_client()->WaitForNotification(NETWORK_CHANGED);
+  network_change_manager_client2->WaitForNotification(NETWORK_CHANGED);
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_WIFI,
+            network_change_manager_client2->connection_type());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(2u, network_change_manager()->GetNumClientsForTesting());
+
+  EXPECT_EQ(1u, network_change_manager_client()->num_network_changed());
+  EXPECT_EQ(1u, network_change_manager_client2->num_network_changed());
+  network_change_manager_client2.reset();
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, network_change_manager()->GetNumClientsForTesting());
+
+  // Simulate a second network change, and the remaining client should be
+  // notified.
+  SimulateNetworkChange(net::NetworkChangeNotifier::CONNECTION_2G);
+
+  network_change_manager_client()->WaitForNotification(NETWORK_CHANGED);
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_2G,
+            network_change_manager_client()->connection_type());
+  EXPECT_EQ(2u, network_change_manager_client()->num_network_changed());
+}
+
+TEST_F(NetworkChangeManagerImplTest, NewClientReceivesCurrentType) {
+  // Simulate a network change.
+  SimulateNetworkChange(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH);
+
+  network_change_manager_client()->WaitForNotification(NETWORK_CHANGED);
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_BLUETOOTH,
+            network_change_manager_client()->connection_type());
+  base::RunLoop().RunUntilIdle();
+
+  // Register a new client after the network change and it should receive the
+  // up-to-date connection type.
+  TestNetworkChangeManagerClient network_change_manager_client2(
+      network_change_manager());
+  network_change_manager_client2.WaitForNotification(INITIAL);
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_BLUETOOTH,
+            network_change_manager_client2.connection_type());
+}
+
+TEST(NetworkChangeConnectionTypeTest, ConnectionTypeEnumMatch) {
+  for (int typeInt = net::NetworkChangeNotifier::CONNECTION_UNKNOWN;
+       typeInt != net::NetworkChangeNotifier::CONNECTION_LAST; typeInt++) {
+    mojom::ConnectionType mojoType = mojom::ConnectionType(typeInt);
+    switch (typeInt) {
+      case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_UNKNOWN, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_ETHERNET, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_WIFI:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_WIFI, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_2G:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_2G, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_3G:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_3G, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_4G:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_4G, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_NONE:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_NONE, mojoType);
+        break;
+      case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_BLUETOOTH, mojoType);
+        EXPECT_EQ(mojom::ConnectionType::CONNECTION_LAST, mojoType);
+        break;
+    }
+  }
+}
+
+}  // namespace content
diff --git a/content/network/network_service_impl.cc b/content/network/network_service_impl.cc
index 021b6bb1..3198d3f 100644
--- a/content/network/network_service_impl.cc
+++ b/content/network/network_service_impl.cc
@@ -4,20 +4,26 @@
 
 #include "content/network/network_service_impl.h"
 
+#include <utility>
+
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/values.h"
-#include "build/build_config.h"
 #include "content/network/network_context.h"
 #include "content/public/common/content_switches.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "net/base/logging_network_change_observer.h"
+#include "net/base/network_change_notifier.h"
 #include "net/log/file_net_log_observer.h"
 #include "net/log/net_log.h"
 #include "net/log/net_log_util.h"
 #include "net/url_request/url_request_context_builder.h"
 
+#if defined(OS_ANDROID)
+#include "net/android/network_change_notifier_factory_android.h"
+#endif
+
 namespace content {
 
 std::unique_ptr<NetworkService> NetworkService::Create(net::NetLog* net_log) {
@@ -62,11 +68,33 @@
     : registry_(std::move(registry)), binding_(this) {
   // |registry_| is nullptr when an in-process NetworkService is
   // created directly. The latter is done in concert with using
-  // CreateNetworkContextWithBuilder to ease the transition to using the network
-  // service.
+  // CreateNetworkContextWithBuilder to ease the transition to using the
+  // network service.
   if (registry_) {
     registry_->AddInterface<mojom::NetworkService>(
         base::Bind(&NetworkServiceImpl::Create, base::Unretained(this)));
+
+    // Set up net::NetworkChangeNotifier to watch for network change events.
+    std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier;
+#if defined(OS_ANDROID)
+    network_change_notifier_factory_ =
+        std::make_unique<net::NetworkChangeNotifierFactoryAndroid>();
+    network_change_notifier =
+        base::WrapUnique(network_change_notifier_factory_->CreateInstance());
+#elif defined(OS_CHROMEOS) || defined(OS_IOS)
+    // ChromeOS has its own implementation of NetworkChangeNotifier that lives
+    // outside of //net and iOS doesn't embed //content.
+    // TODO(xunjieli): Figure out what to do for these two platforms.
+    NOTIMPLEMENTED();
+#else
+    network_change_notifier =
+        base::WrapUnique(net::NetworkChangeNotifier::Create());
+#endif
+    network_change_manager_ = std::make_unique<NetworkChangeManagerImpl>(
+        std::move(network_change_notifier));
+  } else {
+    network_change_manager_ =
+        std::make_unique<NetworkChangeManagerImpl>(nullptr);
   }
 
   if (net_log) {
@@ -161,6 +189,11 @@
   return net_log_;
 }
 
+void NetworkServiceImpl::GetNetworkChangeManager(
+    mojom::NetworkChangeManagerRequest request) {
+  network_change_manager_->AddRequest(std::move(request));
+}
+
 void NetworkServiceImpl::OnBindInterface(
     const service_manager::BindSourceInfo& source_info,
     const std::string& interface_name,
diff --git a/content/network/network_service_impl.h b/content/network/network_service_impl.h
index 746a2f5..39b5690 100644
--- a/content/network/network_service_impl.h
+++ b/content/network/network_service_impl.h
@@ -6,9 +6,13 @@
 #define CONTENT_NETWORK_NETWORK_SERVICE_IMPL_H_
 
 #include <memory>
+#include <set>
+#include <string>
 
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "content/common/content_export.h"
+#include "content/network/network_change_manager_impl.h"
 #include "content/public/common/network_service.mojom.h"
 #include "content/public/network/network_service.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -20,6 +24,9 @@
 class LoggingNetworkChangeObserver;
 class URLRequestContext;
 class URLRequestContextBuilder;
+#if defined(OS_ANDROID)
+class NetworkChangeNotifierFactoryAndroid;
+#endif
 }  // namespace net
 
 namespace content {
@@ -57,6 +64,8 @@
                             mojom::NetworkContextParamsPtr params) override;
   void DisableQuic() override;
   void SetRawHeadersAccess(uint32_t process_id, bool allow) override;
+  void GetNetworkChangeManager(
+      mojom::NetworkChangeManagerRequest request) override;
 
   bool quic_disabled() const { return quic_disabled_; }
   bool HasRawHeadersAccess(uint32_t process_id) const;
@@ -85,6 +94,12 @@
   // destroyed before them.
   std::unique_ptr<net::LoggingNetworkChangeObserver> network_change_observer_;
 
+#if defined(OS_ANDROID)
+  std::unique_ptr<net::NetworkChangeNotifierFactoryAndroid>
+      network_change_notifier_factory_;
+#endif
+  std::unique_ptr<NetworkChangeManagerImpl> network_change_manager_;
+
   std::unique_ptr<service_manager::BinderRegistry> registry_;
 
   mojo::Binding<mojom::NetworkService> binding_;
diff --git a/content/network/network_service_unittest.cc b/content/network/network_service_unittest.cc
index 9dc0e3a..33bf305 100644
--- a/content/network/network_service_unittest.cc
+++ b/content/network/network_service_unittest.cc
@@ -106,11 +106,12 @@
     service_factory_bindings_.AddBinding(this, std::move(request));
   }
 
+  std::unique_ptr<service_manager::ServiceContext> service_context_;
+
  private:
   service_manager::BinderRegistry registry_;
   mojo::BindingSet<service_manager::mojom::ServiceFactory>
       service_factory_bindings_;
-  std::unique_ptr<service_manager::ServiceContext> service_context_;
 };
 
 }  // namespace
@@ -313,6 +314,118 @@
   EXPECT_EQ(net::OK, client()->completion_status().error_code);
 }
 
+class TestNetworkChangeManagerClient
+    : public mojom::NetworkChangeManagerClient {
+ public:
+  explicit TestNetworkChangeManagerClient(
+      mojom::NetworkService* network_service)
+      : connection_type_(mojom::ConnectionType::CONNECTION_UNKNOWN),
+        binding_(this) {
+    mojom::NetworkChangeManagerPtr manager_ptr;
+    mojom::NetworkChangeManagerRequest request(mojo::MakeRequest(&manager_ptr));
+    network_service->GetNetworkChangeManager(std::move(request));
+
+    mojom::NetworkChangeManagerClientPtr client_ptr;
+    mojom::NetworkChangeManagerClientRequest client_request(
+        mojo::MakeRequest(&client_ptr));
+    binding_.Bind(std::move(client_request));
+    manager_ptr->RequestNotifications(std::move(client_ptr));
+  }
+
+  ~TestNetworkChangeManagerClient() override {}
+
+  // NetworkChangeManagerClient implementation:
+  void OnInitialConnectionType(mojom::ConnectionType type) override {
+    if (type == connection_type_)
+      run_loop_.Quit();
+  }
+
+  void OnNetworkChanged(mojom::ConnectionType type) override {
+    if (type == connection_type_)
+      run_loop_.Quit();
+  }
+
+  // Waits for the desired |connection_type| notification.
+  void WaitForNotification(mojom::ConnectionType type) {
+    connection_type_ = type;
+    run_loop_.Run();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  mojom::ConnectionType connection_type_;
+  mojo::Binding<mojom::NetworkChangeManagerClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeManagerClient);
+};
+
+// mojom:NetworkChangeManager currently doesn't support ChromeOS, which has a
+// different code path to set up net::NetworkChangeNotifier.
+#if defined(OS_CHROMEOS)
+#define MAYBE_NetworkChangeManagerRequest DISABLED_NetworkChangeManagerRequest
+#else
+#define MAYBE_NetworkChangeManagerRequest NetworkChangeManagerRequest
+#endif
+TEST_F(NetworkServiceTest, MAYBE_NetworkChangeManagerRequest) {
+  TestNetworkChangeManagerClient manager_client(service());
+  net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+      net::NetworkChangeNotifier::CONNECTION_3G);
+  manager_client.WaitForNotification(mojom::ConnectionType::CONNECTION_3G);
+}
+
+class NetworkServiceNetworkChangeTest
+    : public service_manager::test::ServiceTest {
+ public:
+  NetworkServiceNetworkChangeTest()
+      : ServiceTest("content_unittests",
+                    false,
+                    base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
+  ~NetworkServiceNetworkChangeTest() override {}
+
+  mojom::NetworkService* service() { return network_service_.get(); }
+
+ private:
+  // A ServiceTestClient that broadcasts a network change notification in the
+  // network service's process.
+  class ServiceTestClientWithNetworkChange : public ServiceTestClient {
+   public:
+    explicit ServiceTestClientWithNetworkChange(
+        service_manager::test::ServiceTest* test)
+        : ServiceTestClient(test) {}
+    ~ServiceTestClientWithNetworkChange() override {}
+
+   protected:
+    void CreateService(service_manager::mojom::ServiceRequest request,
+                       const std::string& name) override {
+      if (name == mojom::kNetworkServiceName) {
+        service_context_.reset(new service_manager::ServiceContext(
+            NetworkServiceImpl::CreateForTesting(), std::move(request)));
+        // Send a broadcast after NetworkServiceImpl is actually created.
+        // Otherwise, this NotifyObservers is a no-op.
+        net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests(
+            net::NetworkChangeNotifier::CONNECTION_3G);
+      }
+    }
+  };
+  std::unique_ptr<service_manager::Service> CreateService() override {
+    return std::make_unique<ServiceTestClientWithNetworkChange>(this);
+  }
+
+  void SetUp() override {
+    service_manager::test::ServiceTest::SetUp();
+    connector()->BindInterface(mojom::kNetworkServiceName, &network_service_);
+  }
+
+  mojom::NetworkServicePtr network_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkServiceNetworkChangeTest);
+};
+
+TEST_F(NetworkServiceNetworkChangeTest, MAYBE_NetworkChangeManagerRequest) {
+  TestNetworkChangeManagerClient manager_client(service());
+  manager_client.WaitForNotification(mojom::ConnectionType::CONNECTION_3G);
+}
+
 }  // namespace
 
 }  // namespace content
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 6fd09e0..1e0c7bc 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -171,6 +171,8 @@
     "mojo_channel_switches.cc",
     "mojo_channel_switches.h",
     "mutable_network_traffic_annotation_tag_struct_traits.h",
+    "network_connection_tracker.cc",
+    "network_connection_tracker.h",
     "notification_resources.cc",
     "notification_resources.h",
     "origin_trial_policy.cc",
@@ -355,6 +357,7 @@
   sources = [
     "download_stream.mojom",
     "mutable_network_traffic_annotation_tag.mojom",
+    "network_change_manager.mojom",
     "network_service.mojom",
     "network_service_test.mojom",
     "push_messaging_status.mojom",
diff --git a/content/public/common/DEPS b/content/public/common/DEPS
index ced58cb..33a09d5 100644
--- a/content/public/common/DEPS
+++ b/content/public/common/DEPS
@@ -14,4 +14,7 @@
   "simple_url_loader_unittest\.cc": [
     "+content/public/network",
   ],
+  "network_connection_tracker_unittest\.cc": [
+    "+content/public/network",
+  ],
 }
diff --git a/content/public/common/network_change_manager.mojom b/content/public/common/network_change_manager.mojom
new file mode 100644
index 0000000..96170c01
--- /dev/null
+++ b/content/public/common/network_change_manager.mojom
@@ -0,0 +1,50 @@
+// Copyright 2017 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.
+
+module content.mojom;
+
+// This needs to match the definition of net::ConnectionType.
+enum ConnectionType {
+  CONNECTION_UNKNOWN = 0,  // A connection exists, but its type is unknown.
+                           // Also used as a default value.
+  CONNECTION_ETHERNET = 1,
+  CONNECTION_WIFI = 2,
+  CONNECTION_2G = 3,
+  CONNECTION_3G = 4,
+  CONNECTION_4G = 5,
+  CONNECTION_NONE = 6,     // No connection.
+  CONNECTION_BLUETOOTH = 7,
+  CONNECTION_LAST = CONNECTION_BLUETOOTH
+};
+
+// A client interface that subscribes to network change events from
+// NetworkChangeManager.
+interface NetworkChangeManagerClient {
+  // Invoked when the initial connection type is ready.
+  OnInitialConnectionType(ConnectionType type);
+
+  // OnNetworkChanged will be called when a change occurs to the host
+  // computer's hardware or software that affects the route network packets
+  // take to any network server. Some examples:
+  //   1. A network connection becoming available or going away. For example
+  //      plugging or unplugging an Ethernet cable, WiFi or cellular modem
+  //      connecting or disconnecting from a network, or a VPN tunnel being
+  //      established or taken down.
+  //   2. An active network connection's IP address changes.
+  //   3. A change to the local IP routing tables.
+  // The signal is produced when the change is complete. For example if a new
+  // network connection has become available, the signal is issued after OS has
+  // finished establishing the connection (i.e. DHCP is done) to the point where
+  // the new connection is usable. |type| indicates the type of the active
+  // primary network connection after the change.  OnNetworkChanged will always
+  // be called with CONNECTION_NONE immediately prior to being called with an
+  // online state.
+  OnNetworkChanged(ConnectionType type);
+};
+
+// An interface that broadcasts network change events.
+interface NetworkChangeManager {
+  // Requests to receive notification when there is a network change.
+  RequestNotifications(NetworkChangeManagerClient client_ptr);
+};
diff --git a/content/public/common/network_connection_tracker.cc b/content/public/common/network_connection_tracker.cc
new file mode 100644
index 0000000..a725a6b
--- /dev/null
+++ b/content/public/common/network_connection_tracker.cc
@@ -0,0 +1,139 @@
+// Copyright 2017 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 "content/public/common/network_connection_tracker.h"
+
+#include <utility>
+
+#include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/public/common/network_service.mojom.h"
+
+namespace content {
+
+namespace {
+
+// Wraps a |user_callback| when GetConnectionType() is called on a different
+// thread than NetworkConnectionTracker's thread.
+void OnGetConnectionType(
+    scoped_refptr<base::TaskRunner> task_runner,
+    const NetworkConnectionTracker::ConnectionTypeCallback& user_callback,
+    mojom::ConnectionType connection_type) {
+  task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](NetworkConnectionTracker::ConnectionTypeCallback callback,
+             mojom::ConnectionType type) { callback.Run(type); },
+          user_callback, connection_type));
+}
+
+static const int32_t kConnectionTypeInvalid = -1;
+
+}  // namespace
+
+NetworkConnectionTracker::NetworkConnectionTracker(
+    mojom::NetworkService* network_service)
+    : task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      connection_type_(kConnectionTypeInvalid),
+      network_change_observer_list_(
+          new base::ObserverListThreadSafe<NetworkConnectionObserver>(
+              base::ObserverListBase<
+                  NetworkConnectionObserver>::NOTIFY_EXISTING_ONLY)),
+      binding_(this) {
+  // Get NetworkChangeManagerPtr.
+  mojom::NetworkChangeManagerPtr manager_ptr;
+  mojom::NetworkChangeManagerRequest request(mojo::MakeRequest(&manager_ptr));
+  network_service->GetNetworkChangeManager(std::move(request));
+
+  // Request notification from NetworkChangeManagerPtr.
+  mojom::NetworkChangeManagerClientPtr client_ptr;
+  mojom::NetworkChangeManagerClientRequest client_request(
+      mojo::MakeRequest(&client_ptr));
+  binding_.Bind(std::move(client_request));
+  manager_ptr->RequestNotifications(std::move(client_ptr));
+}
+
+NetworkConnectionTracker::~NetworkConnectionTracker() {
+  network_change_observer_list_->AssertEmpty();
+}
+
+bool NetworkConnectionTracker::GetConnectionType(
+    mojom::ConnectionType* type,
+    ConnectionTypeCallback callback) {
+  // |connection_type_| is initialized when NetworkService starts up. In most
+  // cases, it won't be kConnectionTypeInvalid and code will return early.
+  base::subtle::Atomic32 type_value =
+      base::subtle::NoBarrier_Load(&connection_type_);
+  if (type_value != kConnectionTypeInvalid) {
+    *type = static_cast<mojom::ConnectionType>(type_value);
+    return true;
+  }
+  base::AutoLock lock(lock_);
+  // Check again after getting the lock, and return early if
+  // OnInitialConnectionType() is called after first NoBarrier_Load.
+  type_value = base::subtle::NoBarrier_Load(&connection_type_);
+  if (type_value != kConnectionTypeInvalid) {
+    *type = static_cast<mojom::ConnectionType>(type_value);
+    return true;
+  }
+  if (!task_runner_->RunsTasksInCurrentSequence()) {
+    connection_type_callbacks_.push_back(
+        base::Bind(&OnGetConnectionType, base::ThreadTaskRunnerHandle::Get(),
+                   std::move(callback)));
+  } else {
+    connection_type_callbacks_.push_back(std::move(callback));
+  }
+  return false;
+}
+
+// static
+bool NetworkConnectionTracker::IsConnectionCellular(
+    mojom::ConnectionType type) {
+  bool is_cellular = false;
+  switch (type) {
+    case mojom::ConnectionType::CONNECTION_2G:
+    case mojom::ConnectionType::CONNECTION_3G:
+    case mojom::ConnectionType::CONNECTION_4G:
+      is_cellular = true;
+      break;
+    case mojom::ConnectionType::CONNECTION_UNKNOWN:
+    case mojom::ConnectionType::CONNECTION_ETHERNET:
+    case mojom::ConnectionType::CONNECTION_WIFI:
+    case mojom::ConnectionType::CONNECTION_NONE:
+    case mojom::ConnectionType::CONNECTION_BLUETOOTH:
+      is_cellular = false;
+      break;
+  }
+  return is_cellular;
+}
+
+void NetworkConnectionTracker::AddNetworkConnectionObserver(
+    NetworkConnectionObserver* observer) {
+  network_change_observer_list_->AddObserver(observer);
+}
+
+void NetworkConnectionTracker::RemoveNetworkConnectionObserver(
+    NetworkConnectionObserver* observer) {
+  network_change_observer_list_->RemoveObserver(observer);
+}
+
+void NetworkConnectionTracker::OnInitialConnectionType(
+    mojom::ConnectionType type) {
+  base::AutoLock lock(lock_);
+  base::subtle::NoBarrier_Store(&connection_type_,
+                                static_cast<base::subtle::Atomic32>(type));
+  while (!connection_type_callbacks_.empty()) {
+    connection_type_callbacks_.front().Run(type);
+    connection_type_callbacks_.pop_front();
+  }
+}
+
+void NetworkConnectionTracker::OnNetworkChanged(mojom::ConnectionType type) {
+  base::subtle::NoBarrier_Store(&connection_type_,
+                                static_cast<base::subtle::Atomic32>(type));
+  network_change_observer_list_->Notify(
+      FROM_HERE, &NetworkConnectionObserver::OnConnectionChanged, type);
+}
+
+}  // namespace content
diff --git a/content/public/common/network_connection_tracker.h b/content/public/common/network_connection_tracker.h
new file mode 100644
index 0000000..206428c
--- /dev/null
+++ b/content/public/common/network_connection_tracker.h
@@ -0,0 +1,106 @@
+// Copyright 2017 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 CONTENT_PUBLIC_COMMON_NETWORK_CONNECTION_TRACKER_H_
+#define CONTENT_PUBLIC_COMMON_NETWORK_CONNECTION_TRACKER_H_
+
+#include <list>
+#include <memory>
+
+#include "base/atomicops.h"
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/synchronization/lock.h"
+#include "content/common/content_export.h"
+#include "content/public/common/network_change_manager.mojom.h"
+#include "content/public/common/network_service.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+
+namespace content {
+
+// This class subscribes to network change events from
+// mojom::NetworkChangeManager and propogates these notifications to its
+// NetworkConnectionObservers registered through AddObserver()/RemoveObserver().
+class CONTENT_EXPORT NetworkConnectionTracker
+    : public mojom::NetworkChangeManagerClient {
+ public:
+  typedef base::Callback<void(mojom::ConnectionType)> ConnectionTypeCallback;
+
+  class CONTENT_EXPORT NetworkConnectionObserver {
+   public:
+    // Please refer to NetworkChangeManagerClient::OnNetworkChanged for when
+    // this method is invoked.
+    virtual void OnConnectionChanged(mojom::ConnectionType type) = 0;
+
+   protected:
+    NetworkConnectionObserver() {}
+    virtual ~NetworkConnectionObserver() {}
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(NetworkConnectionObserver);
+  };
+
+  explicit NetworkConnectionTracker(mojom::NetworkService* network_service);
+
+  ~NetworkConnectionTracker() override;
+
+  // If connection type can be retrieved synchronously, returns true and |type|
+  // will contain the current connection type; Otherwise, returns false, in
+  // which case, |callback| will be called on the calling thread when connection
+  // type is ready. This method is thread safe. Please also refer to
+  // net::NetworkChangeNotifier::GetConnectionType() for documentation.
+  bool GetConnectionType(mojom::ConnectionType* type,
+                         ConnectionTypeCallback callback);
+
+  // Returns true if |type| is a cellular connection.
+  // Returns false if |type| is CONNECTION_UNKNOWN, and thus, depending on the
+  // implementation of GetConnectionType(), it is possible that
+  // IsConnectionCellular(GetConnectionType()) returns false even if the
+  // current connection is cellular.
+  static bool IsConnectionCellular(mojom::ConnectionType type);
+
+  // Registers |observer| to receive notifications of network changes. The
+  // thread on which this is called is the thread on which |observer| will be
+  // called back with notifications.
+  void AddNetworkConnectionObserver(NetworkConnectionObserver* observer);
+
+  // Unregisters |observer| from receiving notifications.  This must be called
+  // on the same thread on which AddObserver() was called.
+  // All observers must be unregistred before |this| is destroyed.
+  void RemoveNetworkConnectionObserver(NetworkConnectionObserver* observer);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(NetworkGetConnectionTest,
+                           GetConnectionTypeOnDifferentThread);
+  // NetworkChangeManagerClient implementation:
+  void OnInitialConnectionType(mojom::ConnectionType type) override;
+  void OnNetworkChanged(mojom::ConnectionType type) override;
+
+  // The task runner that |this| lives on.
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+  // Protect access to |connection_type_callbacks_|.
+  base::Lock lock_;
+
+  // Saves user callback if GetConnectionType() cannot complete synchronously.
+  std::list<ConnectionTypeCallback> connection_type_callbacks_;
+
+  // |connection_type_| is set on one thread but read on many threads.
+  // The default value is -1 before OnInitialConnectionType().
+  base::subtle::Atomic32 connection_type_;
+
+  const scoped_refptr<base::ObserverListThreadSafe<NetworkConnectionObserver>>
+      network_change_observer_list_;
+
+  mojo::Binding<mojom::NetworkChangeManagerClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkConnectionTracker);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_COMMON_NETWORK_CONNECTION_TRACKER_H_
diff --git a/content/public/common/network_connection_tracker_unittest.cc b/content/public/common/network_connection_tracker_unittest.cc
new file mode 100644
index 0000000..11907e2
--- /dev/null
+++ b/content/public/common/network_connection_tracker_unittest.cc
@@ -0,0 +1,292 @@
+// Copyright 2017 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 "content/public/common/network_connection_tracker.h"
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "content/public/common/network_change_manager.mojom.h"
+#include "content/public/network/network_service.h"
+#include "net/base/mock_network_change_notifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+class TestNetworkConnectionObserver
+    : public NetworkConnectionTracker::NetworkConnectionObserver {
+ public:
+  explicit TestNetworkConnectionObserver(NetworkConnectionTracker* tracker)
+      : num_notifications_(0),
+        tracker_(tracker),
+        run_loop_(std::make_unique<base::RunLoop>()),
+        connection_type_(mojom::ConnectionType::CONNECTION_UNKNOWN) {
+    tracker_->AddNetworkConnectionObserver(this);
+  }
+
+  ~TestNetworkConnectionObserver() override {
+    tracker_->RemoveNetworkConnectionObserver(this);
+  }
+
+  // Helper to synchronously get connection type from NetworkConnectionTracker.
+  mojom::ConnectionType GetConnectionTypeSync() {
+    mojom::ConnectionType type;
+    base::RunLoop run_loop;
+    bool sync = tracker_->GetConnectionType(
+        &type,
+        base::Bind(&TestNetworkConnectionObserver::GetConnectionTypeCallback,
+                   &run_loop, &type));
+    if (!sync)
+      run_loop.Run();
+    return type;
+  }
+
+  // NetworkConnectionObserver implementation:
+  void OnConnectionChanged(mojom::ConnectionType type) override {
+    EXPECT_EQ(type, GetConnectionTypeSync());
+
+    num_notifications_++;
+    connection_type_ = type;
+    run_loop_->Quit();
+  }
+
+  size_t num_notifications() const { return num_notifications_; }
+  void WaitForNotification() {
+    run_loop_->Run();
+    run_loop_.reset(new base::RunLoop());
+  }
+
+  mojom::ConnectionType connection_type() const { return connection_type_; }
+
+ private:
+  static void GetConnectionTypeCallback(base::RunLoop* run_loop,
+                                        mojom::ConnectionType* out,
+                                        mojom::ConnectionType type) {
+    *out = type;
+    run_loop->Quit();
+  }
+
+  size_t num_notifications_;
+  NetworkConnectionTracker* tracker_;
+  std::unique_ptr<base::RunLoop> run_loop_;
+  mojom::ConnectionType connection_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestNetworkConnectionObserver);
+};
+
+// A helper class to call NetworkConnectionTracker::GetConnectionType().
+class ConnectionTypeGetter {
+ public:
+  explicit ConnectionTypeGetter(NetworkConnectionTracker* tracker)
+      : tracker_(tracker),
+        connection_type_(mojom::ConnectionType::CONNECTION_UNKNOWN) {}
+  ~ConnectionTypeGetter() {}
+
+  bool GetConnectionType() {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return tracker_->GetConnectionType(
+        &connection_type_,
+        base::Bind(&ConnectionTypeGetter::OnGetConnectionType,
+                   base::Unretained(this)));
+  }
+
+  void WaitForConnectionType(mojom::ConnectionType expected_connection_type) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    run_loop_.Run();
+    EXPECT_EQ(expected_connection_type, connection_type_);
+  }
+
+  mojom::ConnectionType connection_type() const { return connection_type_; }
+
+ private:
+  void OnGetConnectionType(mojom::ConnectionType type) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    connection_type_ = type;
+    run_loop_.Quit();
+  }
+
+  base::RunLoop run_loop_;
+  NetworkConnectionTracker* tracker_;
+  mojom::ConnectionType connection_type_;
+  THREAD_CHECKER(thread_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectionTypeGetter);
+};
+
+}  // namespace
+
+class NetworkConnectionTrackerTest : public testing::Test {
+ public:
+  NetworkConnectionTrackerTest()
+      : network_service_(NetworkService::Create()),
+        network_connection_tracker_(network_service_.get()),
+        network_connection_observer_(&network_connection_tracker_) {}
+
+  ~NetworkConnectionTrackerTest() override {}
+
+  NetworkConnectionTracker* network_connection_tracker() {
+    return &network_connection_tracker_;
+  }
+
+  TestNetworkConnectionObserver* network_connection_observer() {
+    return &network_connection_observer_;
+  }
+
+  // Simulates a connection type change and broadcast it to observers.
+  void SimulateConnectionTypeChange(
+      net::NetworkChangeNotifier::ConnectionType type) {
+    mock_network_change_notifier_.NotifyObserversOfNetworkChangeForTests(type);
+  }
+
+  // Sets the current connection type of the mock network change notifier.
+  void SetConnectionType(net::NetworkChangeNotifier::ConnectionType type) {
+    mock_network_change_notifier_.SetConnectionType(type);
+  }
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  net::test::MockNetworkChangeNotifier mock_network_change_notifier_;
+  std::unique_ptr<NetworkService> network_service_;
+  NetworkConnectionTracker network_connection_tracker_;
+  TestNetworkConnectionObserver network_connection_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkConnectionTrackerTest);
+};
+
+TEST_F(NetworkConnectionTrackerTest, ObserverNotified) {
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_UNKNOWN,
+            network_connection_observer()->connection_type());
+
+  // Simulate a network change.
+  SimulateConnectionTypeChange(
+      net::NetworkChangeNotifier::ConnectionType::CONNECTION_3G);
+
+  network_connection_observer()->WaitForNotification();
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_3G,
+            network_connection_observer()->connection_type());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, network_connection_observer()->num_notifications());
+}
+
+TEST_F(NetworkConnectionTrackerTest, UnregisteredObserverNotNotified) {
+  auto network_connection_observer2 =
+      std::make_unique<TestNetworkConnectionObserver>(
+          network_connection_tracker());
+
+  // Simulate a network change.
+  SimulateConnectionTypeChange(
+      net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI);
+
+  network_connection_observer2->WaitForNotification();
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_WIFI,
+            network_connection_observer2->connection_type());
+  network_connection_observer()->WaitForNotification();
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_WIFI,
+            network_connection_observer()->connection_type());
+  base::RunLoop().RunUntilIdle();
+
+  network_connection_observer2.reset();
+
+  // Simulate an another network change.
+  SimulateConnectionTypeChange(
+      net::NetworkChangeNotifier::ConnectionType::CONNECTION_2G);
+  network_connection_observer()->WaitForNotification();
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_2G,
+            network_connection_observer()->connection_type());
+  EXPECT_EQ(2u, network_connection_observer()->num_notifications());
+}
+
+TEST_F(NetworkConnectionTrackerTest, GetConnectionType) {
+  SetConnectionType(net::NetworkChangeNotifier::ConnectionType::CONNECTION_3G);
+  std::unique_ptr<NetworkService> network_service = NetworkService::Create();
+  NetworkConnectionTracker tracker(network_service.get());
+
+  ConnectionTypeGetter getter1(&tracker), getter2(&tracker);
+  // These two GetConnectionType() will finish asynchonously because network
+  // service is not yet set up.
+  EXPECT_FALSE(getter1.GetConnectionType());
+  EXPECT_FALSE(getter2.GetConnectionType());
+
+  getter1.WaitForConnectionType(
+      /*expected_connection_type=*/mojom::ConnectionType::CONNECTION_3G);
+  getter2.WaitForConnectionType(
+      /*expected_connection_type=*/mojom::ConnectionType::CONNECTION_3G);
+
+  ConnectionTypeGetter getter3(&tracker);
+  // This GetConnectionType() should finish synchronously.
+  EXPECT_TRUE(getter3.GetConnectionType());
+  EXPECT_EQ(mojom::ConnectionType::CONNECTION_3G, getter3.connection_type());
+}
+
+// Tests GetConnectionType() on a different thread.
+class NetworkGetConnectionTest : public NetworkConnectionTrackerTest {
+ public:
+  NetworkGetConnectionTest()
+      : getter_thread_("NetworkGetConnectionTestThread") {
+    getter_thread_.Start();
+  }
+
+  ~NetworkGetConnectionTest() override {}
+
+  void GetConnectionType() {
+    DCHECK(getter_thread_.task_runner()->RunsTasksInCurrentSequence());
+    getter_ =
+        std::make_unique<ConnectionTypeGetter>(network_connection_tracker());
+    EXPECT_FALSE(getter_->GetConnectionType());
+  }
+
+  void WaitForConnectionType(mojom::ConnectionType expected_connection_type) {
+    DCHECK(getter_thread_.task_runner()->RunsTasksInCurrentSequence());
+    getter_->WaitForConnectionType(expected_connection_type);
+  }
+
+  base::Thread* getter_thread() { return &getter_thread_; }
+
+ private:
+  base::Thread getter_thread_;
+
+  // Accessed on |getter_thread_|.
+  std::unique_ptr<ConnectionTypeGetter> getter_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkGetConnectionTest);
+};
+
+TEST_F(NetworkGetConnectionTest, GetConnectionTypeOnDifferentThread) {
+  // Flush pending OnInitialConnectionType() notification and force |tracker| to
+  // use async for GetConnectionType() calls.
+  base::RunLoop().RunUntilIdle();
+  base::subtle::NoBarrier_Store(&network_connection_tracker()->connection_type_,
+                                -1);
+  {
+    base::RunLoop run_loop;
+    getter_thread()->task_runner()->PostTaskAndReply(
+        FROM_HERE,
+        base::BindOnce(&NetworkGetConnectionTest::GetConnectionType,
+                       base::Unretained(this)),
+        base::BindOnce([](base::RunLoop* run_loop) { run_loop->Quit(); },
+                       base::Unretained(&run_loop)));
+    run_loop.Run();
+  }
+
+  network_connection_tracker()->OnInitialConnectionType(
+      mojom::ConnectionType::CONNECTION_3G);
+  {
+    base::RunLoop run_loop;
+    getter_thread()->task_runner()->PostTaskAndReply(
+        FROM_HERE,
+        base::BindOnce(
+            &NetworkGetConnectionTest::WaitForConnectionType,
+            base::Unretained(this),
+            /*expected_connection_type=*/mojom::ConnectionType::CONNECTION_3G),
+        base::BindOnce([](base::RunLoop* run_loop) { run_loop->Quit(); },
+                       base::Unretained(&run_loop)));
+    run_loop.Run();
+  }
+}
+
+}  // namespace content
diff --git a/content/public/common/network_service.mojom b/content/public/common/network_service.mojom
index b46ed10..9ef6d6a 100644
--- a/content/public/common/network_service.mojom
+++ b/content/public/common/network_service.mojom
@@ -4,6 +4,7 @@
 
 module content.mojom;
 
+import "network_change_manager.mojom";
 import "mojo/common/file_path.mojom";
 import "mojo/common/time.mojom";
 import "url_loader.mojom";
@@ -81,12 +82,12 @@
   CreateURLLoaderFactory(URLLoaderFactory& url_loader_factory,
                          uint32 process_id);
 
-  // Handle a request to display cache data to the user. |url| is parsed to
+  // Handles a request to display cache data to the user. |url| is parsed to
   // display different parts of the cache.
   HandleViewCacheRequest(url.mojom.Url url,
                          URLLoaderClient client);
 
-  // Get the CookieManager associated with this network context.
+  // Gets the CookieManager associated with this network context.
   GetCookieManager(network.mojom.CookieManager& cookie_manager);
 
   // TODO(crbug.com/729800): Switch from {process,frame}_id to the network
@@ -128,4 +129,7 @@
   // permission increases risks in case the child process becomes compromised,
   // so this should be done only in specific cases (e.g. DevTools attached).
   SetRawHeadersAccess(uint32 process_id, bool allow);
+
+  // Gets the NetworkChangeManager.
+  GetNetworkChangeManager(NetworkChangeManager& network_change_manager);
 };
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index c0bfc0a..b88b17f 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1509,6 +1509,7 @@
     "../common/unique_name_helper_unittest.cc",
     "../common/webplugininfo_unittest.cc",
     "../network/cookie_manager_impl_unittest.cc",
+    "../network/network_change_manager_impl_unittest.cc",
     "../network/network_context_unittest.cc",
     "../network/network_service_unittest.cc",
     "../network/proxy_resolver_factory_mojo_unittest.cc",
@@ -1516,6 +1517,7 @@
     "../network/throttling/throttling_controller_unittest.cc",
     "../network/url_loader_unittest.cc",
     "../public/common/drop_data_unittest.cc",
+    "../public/common/network_connection_tracker_unittest.cc",
     "../public/common/simple_url_loader_unittest.cc",
     "../public/common/url_utils_unittest.cc",
     "../public/test/referrer_unittest.cc",
diff --git a/net/BUILD.gn b/net/BUILD.gn
index a1fc7125..810a9e93 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2491,6 +2491,8 @@
     "base/load_timing_info_test_util.h",
     "base/mock_file_stream.cc",
     "base/mock_file_stream.h",
+    "base/mock_network_change_notifier.cc",
+    "base/mock_network_change_notifier.h",
     "base/test_completion_callback.cc",
     "base/test_completion_callback.h",
     "cert/mock_cert_verifier.cc",
@@ -4631,8 +4633,6 @@
     "base/lookup_string_in_fixed_set_unittest.cc",
     "base/mime_sniffer_unittest.cc",
     "base/mime_util_unittest.cc",
-    "base/mock_network_change_notifier.cc",
-    "base/mock_network_change_notifier.h",
     "base/net_string_util_unittest.cc",
     "base/network_activity_monitor_unittest.cc",
     "base/network_change_notifier_unittest.cc",