Add Network Conditions Override Manager and tests

BUG=https://code.google.com/p/chromedriver/issues/detail?id=984

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

Cr-Commit-Position: refs/heads/master@{#319668}
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 4960e11..5f164b34 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1143,6 +1143,8 @@
       'test/chromedriver/chrome/mobile_emulation_override_manager.h',
       'test/chromedriver/chrome/navigation_tracker.cc',
       'test/chromedriver/chrome/navigation_tracker.h',
+      'test/chromedriver/chrome/network_conditions_override_manager.cc',
+      'test/chromedriver/chrome/network_conditions_override_manager.h',
       'test/chromedriver/chrome/status.cc',
       'test/chromedriver/chrome/status.h',
       'test/chromedriver/chrome/ui_events.cc',
@@ -1256,6 +1258,9 @@
       'test/chromedriver/chrome/javascript_dialog_manager_unittest.cc',
       'test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc',
       'test/chromedriver/chrome/navigation_tracker_unittest.cc',
+      'test/chromedriver/chrome/network_conditions_override_manager_unittest.cc',
+      'test/chromedriver/chrome/recorder_devtools_client.cc',
+      'test/chromedriver/chrome/recorder_devtools_client.h',
       'test/chromedriver/chrome/status_unittest.cc',
       'test/chromedriver/chrome/stub_chrome.cc',
       'test/chromedriver/chrome/stub_chrome.h',
diff --git a/chrome/test/chromedriver/chrome/geolocation_override_manager_unittest.cc b/chrome/test/chromedriver/chrome/geolocation_override_manager_unittest.cc
index 955ced7..574d861a 100644
--- a/chrome/test/chromedriver/chrome/geolocation_override_manager_unittest.cc
+++ b/chrome/test/chromedriver/chrome/geolocation_override_manager_unittest.cc
@@ -2,57 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <string>
-#include <vector>
-
-#include "base/compiler_specific.h"
 #include "base/values.h"
 #include "chrome/test/chromedriver/chrome/geolocation_override_manager.h"
 #include "chrome/test/chromedriver/chrome/geoposition.h"
+#include "chrome/test/chromedriver/chrome/recorder_devtools_client.h"
 #include "chrome/test/chromedriver/chrome/status.h"
-#include "chrome/test/chromedriver/chrome/stub_devtools_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
-struct Command {
-  Command() {}
-  Command(const std::string& method, const base::DictionaryValue& params)
-      : method(method) {
-    this->params.MergeDictionary(&params);
-  }
-  Command(const Command& command) {
-    *this = command;
-  }
-  Command& operator=(const Command& command) {
-    method = command.method;
-    params.Clear();
-    params.MergeDictionary(&command.params);
-    return *this;
-  }
-  ~Command() {}
-
-  std::string method;
-  base::DictionaryValue params;
-};
-
-class RecorderDevToolsClient : public StubDevToolsClient {
- public:
-  RecorderDevToolsClient() {}
-  ~RecorderDevToolsClient() override {}
-
-  // Overridden from StubDevToolsClient:
-  Status SendCommandAndGetResult(
-      const std::string& method,
-      const base::DictionaryValue& params,
-      scoped_ptr<base::DictionaryValue>* result) override {
-    commands_.push_back(Command(method, params));
-    return Status(kOk);
-  }
-
-  std::vector<Command> commands_;
-};
-
 void AssertGeolocationCommand(const Command& command,
                               const Geoposition& geoposition) {
   ASSERT_EQ("Page.setGeolocationOverride", command.method);
diff --git a/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc b/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc
index a016511..8b2c6b5 100644
--- a/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc
+++ b/chrome/test/chromedriver/chrome/javascript_dialog_manager_unittest.cc
@@ -8,8 +8,8 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/values.h"
 #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
+#include "chrome/test/chromedriver/chrome/recorder_devtools_client.h"
 #include "chrome/test/chromedriver/chrome/status.h"
-#include "chrome/test/chromedriver/chrome/stub_devtools_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 TEST(JavaScriptDialogManager, NoDialog) {
@@ -22,30 +22,6 @@
   ASSERT_EQ(kNoAlertOpen, manager.HandleDialog(false, NULL).code());
 }
 
-namespace {
-
-class RecorderDevToolsClient : public StubDevToolsClient {
- public:
-  RecorderDevToolsClient() {}
-  ~RecorderDevToolsClient() override {}
-
-  // Overridden from StubDevToolsClient:
-  Status SendCommandAndGetResult(
-      const std::string& method,
-      const base::DictionaryValue& params,
-      scoped_ptr<base::DictionaryValue>* result) override {
-    method_ = method;
-    params_.Clear();
-    params_.MergeDictionary(&params);
-    return Status(kOk);
-  }
-
-  std::string method_;
-  base::DictionaryValue params_;
-};
-
-}  // namespace
-
 TEST(JavaScriptDialogManager, HandleDialogPassesParams) {
   RecorderDevToolsClient client;
   JavaScriptDialogManager manager(&client);
@@ -57,9 +33,9 @@
   std::string given_text("text");
   ASSERT_EQ(kOk, manager.HandleDialog(false, &given_text).code());
   std::string text;
-  client.params_.GetString("promptText", &text);
+  ASSERT_TRUE(client.commands_[0].params.GetString("promptText", &text));
   ASSERT_EQ(given_text, text);
-  ASSERT_TRUE(client.params_.HasKey("accept"));
+  ASSERT_TRUE(client.commands_[0].params.HasKey("accept"));
 }
 
 TEST(JavaScriptDialogManager, HandleDialogNullPrompt) {
@@ -71,8 +47,8 @@
       kOk,
       manager.OnEvent(&client, "Page.javascriptDialogOpening", params).code());
   ASSERT_EQ(kOk, manager.HandleDialog(false, NULL).code());
-  ASSERT_FALSE(client.params_.HasKey("promptText"));
-  ASSERT_TRUE(client.params_.HasKey("accept"));
+  ASSERT_FALSE(client.commands_[0].params.HasKey("promptText"));
+  ASSERT_TRUE(client.commands_[0].params.HasKey("accept"));
 }
 
 TEST(JavaScriptDialogManager, ReconnectClearsStateAndSendsEnable) {
@@ -88,7 +64,7 @@
   ASSERT_EQ(kOk, manager.GetDialogMessage(&message).code());
 
   ASSERT_TRUE(manager.OnConnected(&client).IsOk());
-  ASSERT_EQ("Page.enable", client.method_);
+  ASSERT_EQ("Page.enable", client.commands_[0].method);
   ASSERT_FALSE(manager.IsDialogOpen());
   ASSERT_EQ(kNoAlertOpen, manager.GetDialogMessage(&message).code());
   ASSERT_EQ(kNoAlertOpen, manager.HandleDialog(false, NULL).code());
diff --git a/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc
index 099442f..c8cf20c 100644
--- a/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc
+++ b/chrome/test/chromedriver/chrome/mobile_emulation_override_manager_unittest.cc
@@ -2,59 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <string>
-#include <vector>
-
-#include "base/compiler_specific.h"
 #include "base/values.h"
 #include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h"
+#include "chrome/test/chromedriver/chrome/recorder_devtools_client.h"
 #include "chrome/test/chromedriver/chrome/status.h"
-#include "chrome/test/chromedriver/chrome/stub_devtools_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
-struct Command {
-  Command() {}
-  Command(const std::string& method, const base::DictionaryValue& params)
-      : method(method) {
-    this->params.MergeDictionary(&params);
-  }
-  Command(const Command& command) {
-    *this = command;
-  }
-  Command& operator=(const Command& command) {
-    method = command.method;
-    params.Clear();
-    params.MergeDictionary(&command.params);
-    return *this;
-  }
-  ~Command() {}
-
-  std::string method;
-  base::DictionaryValue params;
-};
-
-class RecorderDevToolsClient : public StubDevToolsClient {
- public:
-  RecorderDevToolsClient() {}
-  ~RecorderDevToolsClient() override {}
-
-  // Overridden from StubDevToolsClient:
-  Status SendCommandAndGetResult(
-      const std::string& method,
-      const base::DictionaryValue& params,
-      scoped_ptr<base::DictionaryValue>* result) override {
-    commands_.push_back(Command(method, params));
-    return Status(kOk);
-  }
-
-  std::vector<Command> commands_;
-};
-
 void AssertDeviceMetricsCommand(const Command& command,
-                              const DeviceMetrics& device_metrics) {
+                                const DeviceMetrics& device_metrics) {
   ASSERT_EQ("Page.setDeviceMetricsOverride", command.method);
   int width, height;
   double device_scale_factor, font_scale_factor;
diff --git a/chrome/test/chromedriver/chrome/network_conditions.h b/chrome/test/chromedriver/chrome/network_conditions.h
new file mode 100644
index 0000000..2233655
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/network_conditions.h
@@ -0,0 +1,15 @@
+// Copyright (c) 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 CHROME_TEST_CHROMEDRIVER_CHROME_NETWORK_CONDITIONS_H_
+#define CHROME_TEST_CHROMEDRIVER_CHROME_NETWORK_CONDITIONS_H_
+
+struct NetworkConditions {
+  bool offline;
+  double latency;
+  double download_throughput;
+  double upload_throughput;
+};
+
+#endif  // CHROME_TEST_CHROMEDRIVER_CHROME_NETWORK_CONDITIONS_H_
diff --git a/chrome/test/chromedriver/chrome/network_conditions_override_manager.cc b/chrome/test/chromedriver/chrome/network_conditions_override_manager.cc
new file mode 100644
index 0000000..18c9018
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/network_conditions_override_manager.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 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 "chrome/test/chromedriver/chrome/network_conditions_override_manager.h"
+
+#include "base/values.h"
+#include "chrome/test/chromedriver/chrome/devtools_client.h"
+#include "chrome/test/chromedriver/chrome/network_conditions.h"
+#include "chrome/test/chromedriver/chrome/status.h"
+
+NetworkConditionsOverrideManager::NetworkConditionsOverrideManager(
+    DevToolsClient* client)
+    : client_(client),
+      overridden_network_conditions_(NULL) {
+  client_->AddListener(this);
+}
+
+NetworkConditionsOverrideManager::~NetworkConditionsOverrideManager() {
+}
+
+Status NetworkConditionsOverrideManager::OverrideNetworkConditions(
+    const NetworkConditions& network_conditions) {
+  Status status = ApplyOverride(&network_conditions);
+  if (status.IsOk())
+    overridden_network_conditions_ = &network_conditions;
+  return status;
+}
+
+Status NetworkConditionsOverrideManager::OnConnected(DevToolsClient* client) {
+  return ApplyOverrideIfNeeded();
+}
+
+Status NetworkConditionsOverrideManager::OnEvent(
+    DevToolsClient* client,
+    const std::string& method,
+    const base::DictionaryValue& params) {
+  if (method == "Page.frameNavigated") {
+    const base::Value* unused_value;
+    if (!params.Get("frame.parentId", &unused_value))
+      return ApplyOverrideIfNeeded();
+  }
+  return Status(kOk);
+}
+
+Status NetworkConditionsOverrideManager::ApplyOverrideIfNeeded() {
+  if (overridden_network_conditions_)
+    return ApplyOverride(overridden_network_conditions_);
+  return Status(kOk);
+}
+
+Status NetworkConditionsOverrideManager::ApplyOverride(
+    const NetworkConditions* network_conditions) {
+  base::DictionaryValue params, empty_params;
+  params.SetBoolean("offline", network_conditions->offline);
+  params.SetDouble("latency", network_conditions->latency);
+  params.SetDouble("downloadThroughput",
+                    network_conditions->download_throughput);
+  params.SetDouble("uploadThroughput", network_conditions->upload_throughput);
+
+  Status status = client_->SendCommand("Network.enable", empty_params);
+  if (status.IsError())
+    return status;
+
+  scoped_ptr<base::DictionaryValue> result;
+  bool can = false;
+  status = client_->SendCommandAndGetResult(
+      "Network.canEmulateNetworkConditions", empty_params, &result);
+  if (status.IsError() || !result->GetBoolean("result", &can))
+    return Status(kUnknownError,
+        "unable to detect if chrome can emulate network conditions", status);
+  if (!can)
+    return Status(kUnknownError, "Cannot emulate network conditions");
+
+  return client_->SendCommand("Network.emulateNetworkConditions", params);
+}
diff --git a/chrome/test/chromedriver/chrome/network_conditions_override_manager.h b/chrome/test/chromedriver/chrome/network_conditions_override_manager.h
new file mode 100644
index 0000000..2f55a3ba
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/network_conditions_override_manager.h
@@ -0,0 +1,48 @@
+// Copyright (c) 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 CHROME_TEST_CHROMEDRIVER_CHROME_NETWORK_CONDITIONS_OVERRIDE_MANAGER_H_
+#define CHROME_TEST_CHROMEDRIVER_CHROME_NETWORK_CONDITIONS_OVERRIDE_MANAGER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class DevToolsClient;
+struct NetworkConditions;
+class Status;
+
+// Overrides the network conditions, if requested, for the duration of the
+// given |DevToolsClient|'s lifetime.
+class NetworkConditionsOverrideManager : public DevToolsEventListener {
+ public:
+  explicit NetworkConditionsOverrideManager(DevToolsClient* client);
+  ~NetworkConditionsOverrideManager() override;
+
+  Status OverrideNetworkConditions(const NetworkConditions& network_conditions);
+
+  // Overridden from DevToolsEventListener:
+  Status OnConnected(DevToolsClient* client) override;
+  Status OnEvent(DevToolsClient* client,
+                 const std::string& method,
+                 const base::DictionaryValue& params) override;
+
+ private:
+  Status ApplyOverrideIfNeeded();
+  Status ApplyOverride(const NetworkConditions* network_conditions);
+
+  DevToolsClient* client_;
+  const NetworkConditions* overridden_network_conditions_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkConditionsOverrideManager);
+};
+
+#endif  // CHROME_TEST_CHROMEDRIVER_CHROME_NETWORK_CONDITIONS_OVERRIDE_MANAGER_H_
diff --git a/chrome/test/chromedriver/chrome/network_conditions_override_manager_unittest.cc b/chrome/test/chromedriver/chrome/network_conditions_override_manager_unittest.cc
new file mode 100644
index 0000000..85171b0
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/network_conditions_override_manager_unittest.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 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 "base/values.h"
+#include "chrome/test/chromedriver/chrome/network_conditions.h"
+#include "chrome/test/chromedriver/chrome/network_conditions_override_manager.h"
+#include "chrome/test/chromedriver/chrome/recorder_devtools_client.h"
+#include "chrome/test/chromedriver/chrome/status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void AssertNetworkConditionsCommand(
+    const Command& command,
+    const NetworkConditions& network_conditions) {
+  ASSERT_EQ("Network.emulateNetworkConditions", command.method);
+  bool offline;
+  double latency, download_throughput, upload_throughput;
+  ASSERT_TRUE(command.params.GetBoolean("offline", &offline));
+  ASSERT_TRUE(command.params.GetDouble("latency", &latency));
+  ASSERT_TRUE(command.params.GetDouble("downloadThroughput",
+                                       &download_throughput));
+  ASSERT_TRUE(command.params.GetDouble("uploadThroughput",
+                                       &upload_throughput));
+  ASSERT_EQ(network_conditions.offline, offline);
+  ASSERT_EQ(network_conditions.latency, latency);
+  ASSERT_EQ(network_conditions.download_throughput, download_throughput);
+  ASSERT_EQ(network_conditions.upload_throughput, upload_throughput);
+}
+
+}  // namespace
+
+TEST(NetworkConditionsOverrideManager, OverrideSendsCommand) {
+  RecorderDevToolsClient client;
+  NetworkConditionsOverrideManager manager(&client);
+  NetworkConditions network_conditions = {false, 100, 750*1024, 750*1024};
+  manager.OverrideNetworkConditions(network_conditions);
+  ASSERT_EQ(3u, client.commands_.size());
+  ASSERT_NO_FATAL_FAILURE(
+     AssertNetworkConditionsCommand(client.commands_[2], network_conditions));
+
+  network_conditions.latency = 200;
+  manager.OverrideNetworkConditions(network_conditions);
+  ASSERT_EQ(6u, client.commands_.size());
+  ASSERT_NO_FATAL_FAILURE(
+      AssertNetworkConditionsCommand(client.commands_[5], network_conditions));
+}
+
+TEST(NetworkConditionsOverrideManager, SendsCommandOnConnect) {
+  RecorderDevToolsClient client;
+  NetworkConditionsOverrideManager manager(&client);
+  ASSERT_EQ(0u, client.commands_.size());
+  ASSERT_EQ(kOk, manager.OnConnected(&client).code());
+
+  NetworkConditions network_conditions = {false, 100, 750*1024, 750*1024};
+  manager.OverrideNetworkConditions(network_conditions);
+  ASSERT_EQ(3u, client.commands_.size());
+  ASSERT_EQ(kOk, manager.OnConnected(&client).code());
+  ASSERT_EQ(6u, client.commands_.size());
+  ASSERT_NO_FATAL_FAILURE(
+      AssertNetworkConditionsCommand(client.commands_[5], network_conditions));
+}
+
+TEST(NetworkConditionsOverrideManager, SendsCommandOnNavigation) {
+  RecorderDevToolsClient client;
+  NetworkConditionsOverrideManager manager(&client);
+  base::DictionaryValue main_frame_params;
+  ASSERT_EQ(kOk,
+            manager.OnEvent(&client, "Page.frameNavigated", main_frame_params)
+                .code());
+  ASSERT_EQ(0u, client.commands_.size());
+
+  NetworkConditions network_conditions = {false, 100, 750*1024, 750*1024};
+  manager.OverrideNetworkConditions(network_conditions);
+  ASSERT_EQ(3u, client.commands_.size());
+  ASSERT_EQ(kOk,
+            manager.OnEvent(&client, "Page.frameNavigated", main_frame_params)
+                .code());
+  ASSERT_EQ(6u, client.commands_.size());
+  ASSERT_NO_FATAL_FAILURE(
+      AssertNetworkConditionsCommand(client.commands_[2], network_conditions));
+
+  base::DictionaryValue sub_frame_params;
+  sub_frame_params.SetString("frame.parentId", "id");
+  ASSERT_EQ(
+      kOk,
+      manager.OnEvent(&client, "Page.frameNavigated", sub_frame_params).code());
+  ASSERT_EQ(6u, client.commands_.size());
+  ASSERT_NO_FATAL_FAILURE(
+      AssertNetworkConditionsCommand(client.commands_[5], network_conditions));
+}
diff --git a/chrome/test/chromedriver/chrome/recorder_devtools_client.cc b/chrome/test/chromedriver/chrome/recorder_devtools_client.cc
new file mode 100644
index 0000000..2a7b0bd
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/recorder_devtools_client.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 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 "chrome/test/chromedriver/chrome/recorder_devtools_client.h"
+#include "chrome/test/chromedriver/chrome/status.h"
+
+RecorderDevToolsClient::RecorderDevToolsClient() {}
+
+RecorderDevToolsClient::~RecorderDevToolsClient() {}
+
+Status RecorderDevToolsClient::SendCommandAndGetResult(
+    const std::string& method,
+    const base::DictionaryValue& params,
+    scoped_ptr<base::DictionaryValue>* result) {
+  commands_.push_back(Command(method, params));
+
+  // For any tests that directly call SendCommandAndGetResults, we'll just
+  // always return { "result": true }. Currently only used when testing
+  // "canEmulateNetworkConditions".
+  (*result).reset(new base::DictionaryValue);
+  (*result)->SetBoolean("result", true);
+  return Status(kOk);
+}
diff --git a/chrome/test/chromedriver/chrome/recorder_devtools_client.h b/chrome/test/chromedriver/chrome/recorder_devtools_client.h
new file mode 100644
index 0000000..f7e49d2
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/recorder_devtools_client.h
@@ -0,0 +1,54 @@
+// Copyright (c) 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 CHROME_TEST_CHROMEDRIVER_CHROME_RECORDER_DEVTOOLS_CLIENT_H_
+#define CHROME_TEST_CHROMEDRIVER_CHROME_RECORDER_DEVTOOLS_CLIENT_H_
+
+#include <vector>
+
+#include "base/values.h"
+#include "chrome/test/chromedriver/chrome/stub_devtools_client.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class Status;
+
+struct Command {
+  Command() {}
+  Command(const std::string& method, const base::DictionaryValue& params)
+      : method(method) {
+    this->params.MergeDictionary(&params);
+  }
+  Command(const Command& command) {
+    *this = command;
+  }
+  Command& operator=(const Command& command) {
+    method = command.method;
+    params.Clear();
+    params.MergeDictionary(&command.params);
+    return *this;
+  }
+  ~Command() {}
+
+  std::string method;
+  base::DictionaryValue params;
+};
+
+class RecorderDevToolsClient : public StubDevToolsClient {
+ public:
+  RecorderDevToolsClient();
+  ~RecorderDevToolsClient() override;
+
+  // Overridden from StubDevToolsClient:
+  Status SendCommandAndGetResult(
+      const std::string& method,
+      const base::DictionaryValue& params,
+      scoped_ptr<base::DictionaryValue>* result) override;
+
+  std::vector<Command> commands_;
+};
+
+#endif  // CHROME_TEST_CHROMEDRIVER_CHROME_RECORDER_DEVTOOLS_CLIENT_H_
diff --git a/chrome/test/chromedriver/chrome/stub_web_view.cc b/chrome/test/chromedriver/chrome/stub_web_view.cc
index 8ba6c7c5..12c6eeea 100644
--- a/chrome/test/chromedriver/chrome/stub_web_view.cc
+++ b/chrome/test/chromedriver/chrome/stub_web_view.cc
@@ -121,6 +121,11 @@
   return Status(kOk);
 }
 
+Status StubWebView::OverrideNetworkConditions(
+    const NetworkConditions& network_conditions) {
+  return Status(kOk);
+}
+
 Status StubWebView::CaptureScreenshot(std::string* screenshot) {
   return Status(kOk);
 }
diff --git a/chrome/test/chromedriver/chrome/stub_web_view.h b/chrome/test/chromedriver/chrome/stub_web_view.h
index 0f59e6d6a..4b23879e 100644
--- a/chrome/test/chromedriver/chrome/stub_web_view.h
+++ b/chrome/test/chromedriver/chrome/stub_web_view.h
@@ -60,6 +60,8 @@
                              bool* is_pending) override;
   JavaScriptDialogManager* GetJavaScriptDialogManager() override;
   Status OverrideGeolocation(const Geoposition& geoposition) override;
+  Status OverrideNetworkConditions(
+      const NetworkConditions& network_conditions) override;
   Status CaptureScreenshot(std::string* screenshot) override;
   Status SetFileInputFiles(const std::string& frame,
                            const base::DictionaryValue& element,
diff --git a/chrome/test/chromedriver/chrome/web_view.h b/chrome/test/chromedriver/chrome/web_view.h
index 718900e..47c0cca7 100644
--- a/chrome/test/chromedriver/chrome/web_view.h
+++ b/chrome/test/chromedriver/chrome/web_view.h
@@ -24,6 +24,7 @@
 class JavaScriptDialogManager;
 struct KeyEvent;
 struct MouseEvent;
+struct NetworkConditions;
 struct TouchEvent;
 class Status;
 
@@ -144,6 +145,10 @@
   // Overrides normal geolocation with a given geoposition.
   virtual Status OverrideGeolocation(const Geoposition& geoposition) = 0;
 
+  // Overrides normal network conditions with given conditions.
+  virtual Status OverrideNetworkConditions(
+      const NetworkConditions& network_conditions) = 0;
+
   // Captures the visible portions of the web view as a base64-encoded PNG.
   virtual Status CaptureScreenshot(std::string* screenshot) = 0;
 
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc
index d703b4e3..712a36cd 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl.cc
@@ -24,6 +24,7 @@
 #include "chrome/test/chromedriver/chrome/js.h"
 #include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h"
 #include "chrome/test/chromedriver/chrome/navigation_tracker.h"
+#include "chrome/test/chromedriver/chrome/network_conditions_override_manager.h"
 #include "chrome/test/chromedriver/chrome/status.h"
 #include "chrome/test/chromedriver/chrome/ui_events.h"
 
@@ -127,6 +128,8 @@
           new MobileEmulationOverrideManager(client.get(), device_metrics)),
       geolocation_override_manager_(
           new GeolocationOverrideManager(client.get())),
+      network_conditions_override_manager_(
+          new NetworkConditionsOverrideManager(client.get())),
       heap_snapshot_taker_(new HeapSnapshotTaker(client.get())),
       debugger_(new DebuggerTracker(client.get())),
       client_(client.release()) {}
@@ -410,6 +413,12 @@
   return geolocation_override_manager_->OverrideGeolocation(geoposition);
 }
 
+Status WebViewImpl::OverrideNetworkConditions(
+    const NetworkConditions& network_conditions) {
+  return network_conditions_override_manager_->OverrideNetworkConditions(
+      network_conditions);
+}
+
 Status WebViewImpl::CaptureScreenshot(std::string* screenshot) {
   base::DictionaryValue params;
   scoped_ptr<base::DictionaryValue> result;
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.h b/chrome/test/chromedriver/chrome/web_view_impl.h
index 19660dc..9fcadaf 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.h
+++ b/chrome/test/chromedriver/chrome/web_view_impl.h
@@ -27,6 +27,7 @@
 class FrameTracker;
 class GeolocationOverrideManager;
 class MobileEmulationOverrideManager;
+class NetworkConditionsOverrideManager;
 class HeapSnapshotTaker;
 struct KeyEvent;
 struct MouseEvent;
@@ -87,6 +88,8 @@
                              bool* is_pending) override;
   JavaScriptDialogManager* GetJavaScriptDialogManager() override;
   Status OverrideGeolocation(const Geoposition& geoposition) override;
+  Status OverrideNetworkConditions(
+      const NetworkConditions& network_conditions) override;
   Status CaptureScreenshot(std::string* screenshot) override;
   Status SetFileInputFiles(const std::string& frame,
                            const base::DictionaryValue& element,
@@ -117,6 +120,8 @@
   scoped_ptr<JavaScriptDialogManager> dialog_manager_;
   scoped_ptr<MobileEmulationOverrideManager> mobile_emulation_override_manager_;
   scoped_ptr<GeolocationOverrideManager> geolocation_override_manager_;
+  scoped_ptr<NetworkConditionsOverrideManager>
+      network_conditions_override_manager_;
   scoped_ptr<HeapSnapshotTaker> heap_snapshot_taker_;
   scoped_ptr<DebuggerTracker> debugger_;
   scoped_ptr<DevToolsClient> client_;
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index a4389f97..3700a73a 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -357,3 +357,26 @@
 
   def SetAutoReporting(self, enabled):
     self.ExecuteCommand(Command.SET_AUTO_REPORTING, {'enabled': enabled})
+
+  def SetNetworkConditions(self, latency, download_throughput,
+                           upload_throughput):
+    # Until http://crbug.com/456324 is resolved, we'll always set 'offline' to
+    # False, as going "offline" will sever Chromedriver's connection to Chrome.
+    params = {
+        'network_conditions': {
+            'offline': False,
+            'latency': latency,
+            'download_throughput': download_throughput,
+            'upload_throughput': upload_throughput
+        }
+    }
+    self.ExecuteCommand(Command.SET_NETWORK_CONDITIONS, params)
+
+  def GetNetworkConditions(self):
+    conditions = self.ExecuteCommand(Command.GET_NETWORK_CONDITIONS)
+    return {
+        'latency': conditions['latency'],
+        'download_throughput': conditions['download_throughput'],
+        'upload_throughput': conditions['upload_throughput'],
+        'offline': conditions['offline']
+    }
diff --git a/chrome/test/chromedriver/client/command_executor.py b/chrome/test/chromedriver/client/command_executor.py
index 4b59881..f3d6465 100644
--- a/chrome/test/chromedriver/client/command_executor.py
+++ b/chrome/test/chromedriver/client/command_executor.py
@@ -95,6 +95,10 @@
   EXECUTE_SQL = (_Method.POST, '/session/:sessionId/execute_sql')
   GET_LOCATION = (_Method.GET, '/session/:sessionId/location')
   SET_LOCATION = (_Method.POST, '/session/:sessionId/location')
+  GET_NETWORK_CONDITIONS = (
+      _Method.GET, '/session/:sessionId/chromium/network_conditions')
+  SET_NETWORK_CONDITIONS = (
+      _Method.POST, '/session/:sessionId/chromium/network_conditions')
   GET_STATUS = (_Method.GET, '/session/:sessionId/application_cache/status')
   IS_BROWSER_ONLINE = (_Method.GET, '/session/:sessionId/browser_connection')
   SET_BROWSER_ONLINE = (_Method.POST, '/session/:sessionId/browser_connection')
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index 6e452fe..3ae9299 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -370,6 +370,16 @@
           kPost,
           "session/:sessionId/location",
           WrapToCommand("SetGeolocation", base::Bind(&ExecuteSetLocation))),
+      CommandMapping(
+          kGet,
+          "session/:sessionId/chromium/network_conditions",
+          WrapToCommand("GetNetworkConditions",
+                        base::Bind(&ExecuteGetNetworkConditions))),
+      CommandMapping(
+          kPost,
+          "session/:sessionId/chromium/network_conditions",
+          WrapToCommand("SetNetworkConditions",
+                        base::Bind(&ExecuteSetNetworkConditions))),
       CommandMapping(kGet,
                      "session/:sessionId/application_cache/status",
                      base::Bind(&ExecuteGetStatus)),
diff --git a/chrome/test/chromedriver/session.h b/chrome/test/chromedriver/session.h
index 2df16d7..fa0a82d 100644
--- a/chrome/test/chromedriver/session.h
+++ b/chrome/test/chromedriver/session.h
@@ -16,6 +16,7 @@
 #include "chrome/test/chromedriver/basic_types.h"
 #include "chrome/test/chromedriver/chrome/device_metrics.h"
 #include "chrome/test/chromedriver/chrome/geoposition.h"
+#include "chrome/test/chromedriver/chrome/network_conditions.h"
 #include "chrome/test/chromedriver/command_listener.h"
 
 namespace base {
@@ -72,6 +73,7 @@
   scoped_ptr<std::string> prompt_text;
   scoped_ptr<Geoposition> overridden_geoposition;
   scoped_ptr<DeviceMetrics> overridden_device_metrics;
+  scoped_ptr<NetworkConditions> overridden_network_conditions;
   // Logs that populate from DevTools events.
   ScopedVector<WebDriverLog> devtools_logs;
   scoped_ptr<WebDriverLog> driver_log;
diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc
index 9af15c3a..b403124 100644
--- a/chrome/test/chromedriver/session_commands.cc
+++ b/chrome/test/chromedriver/session_commands.cc
@@ -356,6 +356,20 @@
       return status;
   }
 
+  if (session->overridden_network_conditions) {
+    WebView* web_view;
+    status = session->chrome->GetWebViewById(web_view_id, &web_view);
+    if (status.IsError())
+      return status;
+    status = web_view->ConnectIfNecessary();
+    if (status.IsError())
+      return status;
+    status = web_view->OverrideNetworkConditions(
+        *session->overridden_network_conditions);
+    if (status.IsError())
+      return status;
+  }
+
   session->window = web_view_id;
   session->SwitchToTopFrame();
   session->mouse_position = WebPoint(0, 0);
@@ -456,6 +470,29 @@
   return Status(kOk);
 }
 
+Status ExecuteGetNetworkConditions(
+    Session* session,
+    const base::DictionaryValue& params,
+    scoped_ptr<base::Value>* value) {
+  if (!session->overridden_network_conditions) {
+    return Status(kUnknownError,
+                  "network conditions must be set before it can be retrieved");
+  }
+  base::DictionaryValue conditions;
+  conditions.SetBoolean("offline",
+                        session->overridden_network_conditions->offline);
+  conditions.SetInteger("latency",
+                        session->overridden_network_conditions->latency);
+  conditions.SetInteger(
+      "download_throughput",
+      session->overridden_network_conditions->download_throughput);
+  conditions.SetInteger(
+      "upload_throughput",
+      session->overridden_network_conditions->upload_throughput);
+  value->reset(conditions.DeepCopy());
+  return Status(kOk);
+}
+
 Status ExecuteGetWindowPosition(
     Session* session,
     const base::DictionaryValue& params,
diff --git a/chrome/test/chromedriver/session_commands.h b/chrome/test/chromedriver/session_commands.h
index c30ceff..204e3ee 100644
--- a/chrome/test/chromedriver/session_commands.h
+++ b/chrome/test/chromedriver/session_commands.h
@@ -118,6 +118,11 @@
     const base::DictionaryValue& params,
     scoped_ptr<base::Value>* value);
 
+Status ExecuteGetNetworkConditions(
+    Session* session,
+    const base::DictionaryValue& params,
+    scoped_ptr<base::Value>* value);
+
 Status ExecuteGetWindowPosition(
     Session* session,
     const base::DictionaryValue& params,
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index a56389d..556227a4 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -50,6 +50,8 @@
     # This test is flaky since it uses setTimeout.
     # Re-enable once crbug.com/177511 is fixed and we can remove setTimeout.
     'ChromeDriverTest.testAlert',
+    # Enable per-browser when http://crbug.com/456324 is fixed.
+    'ChromeDriverTest.testEmulateNetworkConditionsOffline',
 ]
 
 _VERSION_SPECIFIC_FILTER = {}
@@ -787,6 +789,47 @@
     lots_of_data = self._driver.ExecuteScript(script)
     self.assertEquals('0'.zfill(int(10e6)), lots_of_data)
 
+  def testEmulateNetworkConditions(self):
+    # DSL: 2Mbps throughput, 5ms RTT
+    latency = 5
+    throughput = 2048 * 1024
+    self._driver.SetNetworkConditions(latency, throughput, throughput)
+
+    network = self._driver.GetNetworkConditions()
+    self.assertEquals(latency, network['latency']);
+    self.assertEquals(throughput, network['download_throughput']);
+    self.assertEquals(throughput, network['upload_throughput']);
+
+  def testEmulateNetworkConditionsOffline(self):
+    self._driver.SetNetworkConditions(5, 2048, 2048, offline=True)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/page_test.html'))
+    self.assertIn('is not available', self._driver.GetTitle())
+
+  def testEmulateNetworkConditionsSpeed(self):
+    # Warm up the browser.
+    self._http_server.SetDataForPath(
+        '/', "<html><body>blank</body></html>")
+    self._driver.Load(self._http_server.GetUrl() + '/')
+
+    # DSL: 2Mbps throughput, 5ms RTT
+    latency = 5
+    throughput_kbps = 2048
+    throughput = throughput_kbps * 1024
+    self._driver.SetNetworkConditions(latency, throughput, throughput)
+
+    _32_bytes = " 0 1 2 3 4 5 6 7 8 9 A B C D E F"
+    _1_megabyte = _32_bytes * 32768
+    self._http_server.SetDataForPath(
+        '/1MB',
+        "<html><body>%s</body></html>" % _1_megabyte)
+    start = time.time()
+    self._driver.Load(self._http_server.GetUrl() + '/1MB')
+    finish = time.time()
+    duration = finish - start
+    actual_throughput_kbps = 1024 / duration
+    self.assertLessEqual(actual_throughput_kbps, throughput_kbps * 1.5)
+    self.assertGreaterEqual(actual_throughput_kbps, throughput_kbps / 1.5)
+
   def testShadowDomFindElementWithSlashDeep(self):
     """Checks that chromedriver can find elements in a shadow DOM using /deep/
     css selectors."""
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 9a3c405..70273317 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -869,6 +869,53 @@
   return status;
 }
 
+Status ExecuteSetNetworkConditions(
+    Session* session,
+    WebView* web_view,
+    const base::DictionaryValue& params,
+    scoped_ptr<base::Value>* value) {
+  const base::DictionaryValue* conditions = NULL;
+  NetworkConditions network_conditions;
+  // |latency| is required.
+  if (!params.GetDictionary("network_conditions", &conditions) ||
+      !conditions->GetDouble("latency", &network_conditions.latency))
+    return Status(kUnknownError, "missing or invalid 'network_conditions'");
+
+  // Either |throughput| or the pair |download_throughput| and
+  // |upload_throughput| is required.
+  if (conditions->HasKey("throughput")) {
+    if (!conditions->GetDouble("throughput",
+                                &network_conditions.download_throughput))
+      return Status(kUnknownError, "invalid 'throughput'");
+    conditions->GetDouble("throughput", &network_conditions.upload_throughput);
+  } else if (conditions->HasKey("download_throughput") &&
+             conditions->HasKey("upload_throughput")) {
+    if (!conditions->GetDouble("download_throughput",
+                                &network_conditions.download_throughput) ||
+        !conditions->GetDouble("upload_throughput",
+                                &network_conditions.upload_throughput))
+      return Status(kUnknownError,
+                    "invalid 'download_throughput' or 'upload_throughput'");
+  } else {
+    return Status(kUnknownError,
+                  "invalid 'network_conditions' is missing 'throughput' or "
+                  "'download_throughput'/'upload_throughput' pair");
+  }
+
+  // |offline| is optional.
+  if (conditions->HasKey("offline")) {
+    if (!conditions->GetBoolean("offline", &network_conditions.offline))
+      return Status(kUnknownError, "invalid 'offline'");
+  } else {
+    network_conditions.offline = false;
+  }
+
+  session->overridden_network_conditions.reset(
+      new NetworkConditions(network_conditions));
+  return web_view->OverrideNetworkConditions(
+      *session->overridden_network_conditions);
+}
+
 Status ExecuteTakeHeapSnapshot(
     Session* session,
     WebView* web_view,
diff --git a/chrome/test/chromedriver/window_commands.h b/chrome/test/chromedriver/window_commands.h
index 612906e..67d9ff88 100644
--- a/chrome/test/chromedriver/window_commands.h
+++ b/chrome/test/chromedriver/window_commands.h
@@ -290,6 +290,12 @@
     const base::DictionaryValue& params,
     scoped_ptr<base::Value>* value);
 
+Status ExecuteSetNetworkConditions(
+    Session* session,
+    WebView* web_view,
+    const base::DictionaryValue& params,
+    scoped_ptr<base::Value>* value);
+
 Status ExecuteTakeHeapSnapshot(
     Session* session,
     WebView* web_view,