Commit 60c5dad3 authored by eustas@chromium.org's avatar eustas@chromium.org

DevTools: make network conditions emulation scoped (browser)

Currently network conditions are applied browser-wide.

To restrict conditions to one tab requests are marked with specific
headers.

Those specific headers are removed before sending headers to network.

BUG=245436

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278109 0039d316-1c4b-4281-b951-d872f2087c98
parent 01db472d
......@@ -61,9 +61,15 @@ base::DictionaryValue* ChromeDevToolsManagerDelegate::HandleCommand(
DevToolsProtocol::ParseCommand(command_dict));
if (!command)
return NULL;
const std::string method = command->method();
scoped_ptr<DevToolsProtocol::Response> response;
if (method == chrome::devtools::Network::emulateNetworkConditions::kName)
return EmulateNetworkConditions(agent_host, command.get())->Serialize();
response = EmulateNetworkConditions(agent_host, command.get());
if (response)
return response->Serialize();
return NULL;
}
......@@ -81,41 +87,35 @@ ChromeDevToolsManagerDelegate::EmulateNetworkConditions(
content::DevToolsAgentHost* agent_host,
DevToolsProtocol::Command* command) {
base::DictionaryValue* params = command->params();
std::vector<std::string> domains;
base::ListValue* domain_list = NULL;
const char* domains_param =
chrome::devtools::Network::emulateNetworkConditions::kParamDomains;
if (!params || !params->GetList(domains_param, &domain_list))
return command->InvalidParamResponse(domains_param);
size_t size = domain_list->GetSize();
for (size_t i = 0; i < size; ++i) {
std::string domain;
if (!domain_list->GetString(i, &domain))
return command->InvalidParamResponse(domains_param);
domains.push_back(domain);
}
namespace names = ::chrome::devtools::Network::emulateNetworkConditions;
bool offline = false;
const char* offline_param =
chrome::devtools::Network::emulateNetworkConditions::kParamOffline;
if (!params->GetBoolean(offline_param, &offline))
return command->InvalidParamResponse(offline_param);
double maximal_throughput = 0.0;
const char* maximal_throughput_param =
chrome::devtools::Network::emulateNetworkConditions::kParamMaximalThroughput;
if (!params->GetDouble(maximal_throughput_param, &maximal_throughput))
return command->InvalidParamResponse(maximal_throughput_param);
if (maximal_throughput < 0.0)
maximal_throughput = 0.0;
if (!params || !params->GetBoolean(names::kParamOffline, &offline))
return command->InvalidParamResponse(names::kParamOffline);
double latency = 0.0;
if (!params->GetDouble(names::kParamLatency, &latency))
return command->InvalidParamResponse(names::kParamLatency);
if (latency < 0.0)
latency = 0.0;
double download_throughput = 0.0;
if (!params->GetDouble(names::kParamDownloadThroughput, &download_throughput))
return command->InvalidParamResponse(names::kParamDownloadThroughput);
if (download_throughput < 0.0)
download_throughput = 0.0;
double upload_throughput = 0.0;
if (!params->GetDouble(names::kParamUploadThroughput, &upload_throughput))
return command->InvalidParamResponse(names::kParamUploadThroughput);
if (upload_throughput < 0.0)
upload_throughput = 0.0;
EnsureDevtoolsCallbackRegistered();
scoped_refptr<DevToolsNetworkConditions> conditions;
if (offline || maximal_throughput)
conditions = new DevToolsNetworkConditions(domains, maximal_throughput);
scoped_refptr<DevToolsNetworkConditions> conditions(
new DevToolsNetworkConditions(
offline, latency, download_throughput, upload_throughput));
emulate_network_conditions_client_id_ = agent_host->GetId();
UpdateNetworkState(agent_host, conditions);
return command->SuccessResponse(NULL);
}
......@@ -126,14 +126,15 @@ void ChromeDevToolsManagerDelegate::UpdateNetworkState(
Profile* profile = GetProfile(agent_host);
if (!profile)
return;
profile->GetDevToolsNetworkController()->SetNetworkState(conditions);
profile->GetDevToolsNetworkController()->SetNetworkState(
agent_host->GetId(), conditions);
}
void ChromeDevToolsManagerDelegate::OnDevToolsStateChanged(
content::DevToolsAgentHost* agent_host,
bool attached) {
if (agent_host->GetId() == emulate_network_conditions_client_id_) {
emulate_network_conditions_client_id_ = std::string();
UpdateNetworkState(agent_host, scoped_refptr<DevToolsNetworkConditions>());
}
scoped_refptr<DevToolsNetworkConditions> conditions;
if (attached)
conditions = new DevToolsNetworkConditions();
UpdateNetworkState(agent_host, conditions);
}
......@@ -37,7 +37,6 @@ class ChromeDevToolsManagerDelegate : public content::DevToolsManagerDelegate {
scoped_ptr<DevToolsProtocol::Response> EmulateNetworkConditions(
content::DevToolsAgentHost* agent_host,
DevToolsProtocol::Command* command);
std::string emulate_network_conditions_client_id_;
void UpdateNetworkState(
content::DevToolsAgentHost* agent_host,
......
......@@ -6,31 +6,35 @@
#include "url/gurl.h"
DevToolsNetworkConditions::DevToolsNetworkConditions(
const std::vector<std::string>& domains,
double maximal_throughput)
: domains_(domains),
maximal_throughput_(maximal_throughput) {
DevToolsNetworkConditions::DevToolsNetworkConditions()
: offline_(false),
latency_(0),
download_throughput_(0),
upload_throughput_(0) {
}
DevToolsNetworkConditions::~DevToolsNetworkConditions() {
DevToolsNetworkConditions::DevToolsNetworkConditions(bool offline)
: offline_(offline),
latency_(0),
download_throughput_(0),
upload_throughput_(0) {
}
bool DevToolsNetworkConditions::HasMatchingDomain(const GURL& url) const {
Domains::const_iterator domain = domains_.begin();
if (domain == domains_.end())
return true;
for (; domain != domains_.end(); ++domain) {
if (url.DomainIs(domain->data()))
return true;
}
return false;
DevToolsNetworkConditions::DevToolsNetworkConditions(
bool offline,
double latency,
double download_throughput,
double upload_throughput)
: offline_(offline),
latency_(latency),
download_throughput_(download_throughput),
upload_throughput_(upload_throughput) {
}
bool DevToolsNetworkConditions::IsOffline() const {
return maximal_throughput_ == 0.0;
DevToolsNetworkConditions::~DevToolsNetworkConditions() {
}
bool DevToolsNetworkConditions::IsThrottling() const {
return maximal_throughput_ != 0.0;
return (latency_ != 0) || (download_throughput_ != 0.0) ||
(upload_throughput_ != 0);
}
......@@ -16,24 +16,29 @@ class GURL;
class DevToolsNetworkConditions
: public base::RefCounted<DevToolsNetworkConditions> {
public:
DevToolsNetworkConditions(const std::vector<std::string>& domains,
double maximal_throughput);
DevToolsNetworkConditions();
explicit DevToolsNetworkConditions(bool offline);
DevToolsNetworkConditions(bool offline,
double latency,
double download_throughput,
double upload_throughput);
bool HasMatchingDomain(const GURL& url) const;
bool IsOffline() const;
bool IsThrottling() const;
double maximal_throughput() const { return maximal_throughput_; }
bool offline() const { return offline_; }
double latency() const { return latency_; }
double download_throughput() const { return download_throughput_; }
double upload_throughput() const { return upload_throughput_; }
private:
friend class base::RefCounted<DevToolsNetworkConditions>;
virtual ~DevToolsNetworkConditions();
// List of domains that will be affected by network conditions.
typedef std::vector<std::string> Domains;
const Domains domains_;
const double maximal_throughput_;
const bool offline_;
const double latency_;
const double download_throughput_;
const double upload_throughput_;
DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkConditions);
};
......
......@@ -4,52 +4,52 @@
#include "chrome/browser/devtools/devtools_network_controller.h"
#include "base/time/time.h"
#include "chrome/browser/devtools/devtools_network_conditions.h"
#include "chrome/browser/devtools/devtools_network_interceptor.h"
#include "chrome/browser/devtools/devtools_network_transaction.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_context.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_info.h"
using content::BrowserThread;
namespace {
const char kDevToolsRequestInitiator[] = "X-DevTools-Request-Initiator";
int64_t kPacketSize = 1500;
} // namespace
DevToolsNetworkController::DevToolsNetworkController()
: weak_ptr_factory_(this) {
: default_interceptor_(new DevToolsNetworkInterceptor()),
appcache_interceptor_(new DevToolsNetworkInterceptor()),
weak_ptr_factory_(this) {
}
DevToolsNetworkController::~DevToolsNetworkController() {
}
void DevToolsNetworkController::AddTransaction(
base::WeakPtr<DevToolsNetworkInterceptor>
DevToolsNetworkController::GetInterceptor(
DevToolsNetworkTransaction* transaction) {
DCHECK(thread_checker_.CalledOnValidThread());
transactions_.insert(transaction);
}
DCHECK(transaction->request());
void DevToolsNetworkController::RemoveTransaction(
DevToolsNetworkTransaction* transaction) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(transactions_.find(transaction) != transactions_.end());
transactions_.erase(transaction);
if (conditions_ && conditions_->IsThrottling()) {
UpdateThrottles();
throttled_transactions_.erase(std::remove(throttled_transactions_.begin(),
throttled_transactions_.end(), transaction),
throttled_transactions_.end());
ArmTimer();
}
if (!interceptors_.size())
return default_interceptor_->GetWeakPtr();
if (transaction->request()->load_flags & net::LOAD_DISABLE_INTERCEPT)
return appcache_interceptor_->GetWeakPtr();
transaction->ProcessRequest();
const std::string& client_id = transaction->client_id();
if (client_id.empty())
return default_interceptor_->GetWeakPtr();
DevToolsNetworkInterceptor* interceptor = interceptors_.get(client_id);
DCHECK(interceptor);
if (!interceptor)
return default_interceptor_->GetWeakPtr();
return interceptor->GetWeakPtr();
}
void DevToolsNetworkController::SetNetworkState(
const std::string& client_id,
const scoped_refptr<DevToolsNetworkConditions> conditions) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
......@@ -58,146 +58,47 @@ void DevToolsNetworkController::SetNetworkState(
base::Bind(
&DevToolsNetworkController::SetNetworkStateOnIO,
weak_ptr_factory_.GetWeakPtr(),
client_id,
conditions));
}
void DevToolsNetworkController::SetNetworkStateOnIO(
const std::string& client_id,
const scoped_refptr<DevToolsNetworkConditions> conditions) {
DCHECK(thread_checker_.CalledOnValidThread());
if (conditions_ && conditions_->IsThrottling())
UpdateThrottles();
conditions_ = conditions;
if (!conditions || !(conditions->IsThrottling() || conditions->IsOffline())) {
timer_.Stop();
int64_t length = throttled_transactions_.size();
for (int64_t i = 0; i < length; ++i)
throttled_transactions_[i]->FireThrottledCallback();
throttled_transactions_.clear();
}
if (!conditions)
return;
if (conditions_->IsOffline()) {
// Iterate over a copy of set, because failing of transaction could
// result in creating a new one, or (theoretically) destroying one.
Transactions old_transactions(transactions_);
for (Transactions::iterator it = old_transactions.begin();
it != old_transactions.end(); ++it) {
if (transactions_.find(*it) == transactions_.end())
continue;
if (!(*it)->request() || (*it)->failed())
continue;
if (ShouldFail((*it)->request()))
(*it)->Fail();
DevToolsNetworkInterceptor* interceptor = interceptors_.get(client_id);
if (!interceptor) {
DCHECK(conditions);
if (!conditions)
return;
Interceptor new_interceptor = Interceptor(new DevToolsNetworkInterceptor());
new_interceptor->UpdateConditions(conditions);
interceptors_.set(client_id, new_interceptor.Pass());
} else {
if (!conditions) {
scoped_refptr<DevToolsNetworkConditions> online_conditions(
new DevToolsNetworkConditions());
interceptor->UpdateConditions(online_conditions);
interceptors_.erase(client_id);
} else {
interceptor->UpdateConditions(conditions);
}
}
if (conditions_->IsThrottling()) {
DCHECK(conditions_->maximal_throughput() != 0);
offset_ = base::TimeTicks::Now();
last_tick_ = 0;
int64_t us_tick_length =
(1000000L * kPacketSize) / conditions_->maximal_throughput();
DCHECK(us_tick_length != 0);
if (us_tick_length == 0)
us_tick_length = 1;
tick_length_ = base::TimeDelta::FromMicroseconds(us_tick_length);
ArmTimer();
}
}
bool DevToolsNetworkController::ShouldFail(
const net::HttpRequestInfo* request) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(request);
if (!conditions_ || !conditions_->IsOffline())
return false;
if (!conditions_->HasMatchingDomain(request->url))
return false;
return !request->extra_headers.HasHeader(kDevToolsRequestInitiator);
}
bool DevToolsNetworkController::ShouldThrottle(
const net::HttpRequestInfo* request) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(request);
if (!conditions_ || !conditions_->IsThrottling())
return false;
if (!conditions_->HasMatchingDomain(request->url))
return false;
return !request->extra_headers.HasHeader(kDevToolsRequestInitiator);
}
void DevToolsNetworkController::UpdateThrottles() {
int64_t last_tick = (base::TimeTicks::Now() - offset_) / tick_length_;
int64_t ticks = last_tick - last_tick_;
last_tick_ = last_tick;
int64_t length = throttled_transactions_.size();
if (!length)
return;
int64_t shift = ticks % length;
for (int64_t i = 0; i < length; ++i) {
throttled_transactions_[i]->DecreaseThrottledByteCount(
(ticks / length) * kPacketSize + (i < shift ? kPacketSize : 0));
}
std::rotate(throttled_transactions_.begin(),
throttled_transactions_.begin() + shift, throttled_transactions_.end());
}
void DevToolsNetworkController::OnTimer() {
UpdateThrottles();
std::vector<DevToolsNetworkTransaction*> active_transactions;
std::vector<DevToolsNetworkTransaction*> finished_transactions;
size_t length = throttled_transactions_.size();
for (size_t i = 0; i < length; ++i) {
if (throttled_transactions_[i]->throttled_byte_count() < 0)
finished_transactions.push_back(throttled_transactions_[i]);
else
active_transactions.push_back(throttled_transactions_[i]);
bool has_offline_interceptors = false;
Interceptors::iterator it = interceptors_.begin();
for (; it != interceptors_.end(); ++it) {
if (it->second->conditions()->offline()) {
has_offline_interceptors = true;
break;
}
}
throttled_transactions_.swap(active_transactions);
length = finished_transactions.size();
for (size_t i = 0; i < length; ++i)
finished_transactions[i]->FireThrottledCallback();
ArmTimer();
}
void DevToolsNetworkController::ArmTimer() {
size_t length = throttled_transactions_.size();
if (!length)
return;
int64_t min_ticks_left = 0x10000L;
for (size_t i = 0; i < length; ++i) {
int64_t packets_left = (throttled_transactions_[i]->throttled_byte_count() +
kPacketSize - 1) / kPacketSize;
int64_t ticks_left = (i + 1) + length * (packets_left - 1);
if (i == 0 || ticks_left < min_ticks_left)
min_ticks_left = ticks_left;
bool is_appcache_offline = appcache_interceptor_->conditions()->offline();
if (is_appcache_offline != has_offline_interceptors) {
scoped_refptr<DevToolsNetworkConditions> appcache_conditions(
new DevToolsNetworkConditions(has_offline_interceptors));
appcache_interceptor_->UpdateConditions(appcache_conditions);
}
base::TimeTicks desired_time =
offset_ + tick_length_ * (last_tick_ + min_ticks_left);
timer_.Start(
FROM_HERE,
desired_time - base::TimeTicks::Now(),
base::Bind(
&DevToolsNetworkController::OnTimer,
weak_ptr_factory_.GetWeakPtr()));
}
void DevToolsNetworkController::ThrottleTransaction(
DevToolsNetworkTransaction* transaction) {
UpdateThrottles();
throttled_transactions_.push_back(transaction);
ArmTimer();
}
......@@ -5,34 +5,18 @@
#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_H_
#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_CONTROLLER_H_
#include <set>
#include <string>
#include <vector>
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
class DevToolsNetworkConditions;
class DevToolsNetworkInterceptor;
class DevToolsNetworkTransaction;
class GURL;
class Profile;
namespace base {
class TimeDelta;
class TimeTicks;
}
namespace content {
class ResourceContext;
}
namespace net {
struct HttpRequestInfo;
}
namespace test {
class DevToolsNetworkControllerHelper;
......@@ -45,17 +29,13 @@ class DevToolsNetworkController {
DevToolsNetworkController();
virtual ~DevToolsNetworkController();
void AddTransaction(DevToolsNetworkTransaction* transaction);
void RemoveTransaction(DevToolsNetworkTransaction* transaction);
// Applies network emulation configuration.
void SetNetworkState(
const std::string& client_id,
const scoped_refptr<DevToolsNetworkConditions> conditions);
bool ShouldFail(const net::HttpRequestInfo* request);
bool ShouldThrottle(const net::HttpRequestInfo* request);
void ThrottleTransaction(DevToolsNetworkTransaction* transaction);
base::WeakPtr<DevToolsNetworkInterceptor> GetInterceptor(
DevToolsNetworkTransaction* transaction);
protected:
friend class test::DevToolsNetworkControllerHelper;
......@@ -64,24 +44,16 @@ class DevToolsNetworkController {
// Controller must be constructed on IO thread.
base::ThreadChecker thread_checker_;
typedef scoped_refptr<DevToolsNetworkConditions> Conditions;
void SetNetworkStateOnIO(const Conditions conditions);
typedef std::set<DevToolsNetworkTransaction*> Transactions;
Transactions transactions_;
Conditions conditions_;
void UpdateThrottles();
void ArmTimer();
void OnTimer();
void SetNetworkStateOnIO(
const std::string& client_id,
const scoped_refptr<DevToolsNetworkConditions> conditions);
std::vector<DevToolsNetworkTransaction*> throttled_transactions_;
base::OneShotTimer<DevToolsNetworkController> timer_;
base::TimeTicks offset_;
base::TimeDelta tick_length_;
uint64_t last_tick_;
typedef scoped_ptr<DevToolsNetworkInterceptor> Interceptor;
Interceptor default_interceptor_;
Interceptor appcache_interceptor_;
typedef base::ScopedPtrHashMap<std::string, DevToolsNetworkInterceptor>
Interceptors;
Interceptors interceptors_;
base::WeakPtrFactory<DevToolsNetworkController> weak_ptr_factory_;
......
......@@ -10,6 +10,7 @@
#include "base/run_loop.h"
#include "chrome/browser/devtools/devtools_network_conditions.h"
#include "chrome/browser/devtools/devtools_network_controller.h"
#include "chrome/browser/devtools/devtools_network_interceptor.h"
#include "chrome/browser/devtools/devtools_network_transaction.h"
#include "net/http/http_transaction_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -17,9 +18,8 @@
namespace test {
const char kHttpDotCom[] = "http://dot.com";
const char kHttpDotOrg[] = "http://dot.org";
const char kCom[] = "com";
const char kClientId[] = "42";
const char kAnotherClientId[] = "24";
class TestCallback {
public:
......@@ -44,7 +44,9 @@ class DevToolsNetworkControllerHelper {
mock_transaction_(kSimpleGET_Transaction),
buffer_(new net::IOBuffer(64)) {
mock_transaction_.test_mode = TEST_MODE_SYNC_NET_START;
mock_transaction_.url = kHttpDotCom;
mock_transaction_.url = "http://dot.com";
mock_transaction_.request_headers =
"X-DevTools-Emulate-Network-Conditions-Client-Id: 42\r\n";
AddMockTransaction(&mock_transaction_);
scoped_ptr<net::HttpTransaction> network_transaction;
......@@ -60,13 +62,10 @@ class DevToolsNetworkControllerHelper {
return request_.get();
}
void SetNetworkState(bool offline) {
std::vector<std::string> domains;
domains.push_back(kCom);
scoped_refptr<DevToolsNetworkConditions> conditions;
if (offline)
conditions = new DevToolsNetworkConditions(domains, 0.0);
controller_.SetNetworkStateOnIO(conditions);
void SetNetworkState(const std::string id, bool offline) {
scoped_refptr<DevToolsNetworkConditions> conditions(
new DevToolsNetworkConditions(offline));
controller_.SetNetworkStateOnIO(id, conditions);
}
int Start() {
......@@ -78,6 +77,10 @@ class DevToolsNetworkControllerHelper {
return transaction_->Read(buffer_.get(), 64, completion_callback_);
}
bool ShouldFail() {
return transaction_->interceptor_->ShouldFail(transaction_.get());
}
~DevToolsNetworkControllerHelper() {
RemoveMockTransaction(&mock_transaction_);
}
......@@ -101,19 +104,37 @@ class DevToolsNetworkControllerHelper {
TEST(DevToolsNetworkControllerTest, SingleDisableEnable) {
DevToolsNetworkControllerHelper helper;
DevToolsNetworkController* controller = helper.controller();
net::HttpRequestInfo* request = helper.GetRequest();
EXPECT_FALSE(controller->ShouldFail(request));
helper.SetNetworkState(true);
EXPECT_TRUE(controller->ShouldFail(request));
helper.SetNetworkState(false);
EXPECT_FALSE(controller->ShouldFail(request));
helper.SetNetworkState(kClientId, false);
helper.Start();
EXPECT_FALSE(helper.ShouldFail());
helper.SetNetworkState(kClientId, true);
EXPECT_TRUE(helper.ShouldFail());
helper.SetNetworkState(kClientId, false);
EXPECT_FALSE(helper.ShouldFail());
base::RunLoop().RunUntilIdle();
}
TEST(DevToolsNetworkControllerTest, InterceptorIsolation) {
DevToolsNetworkControllerHelper helper;
helper.SetNetworkState(kClientId, false);
helper.Start();
EXPECT_FALSE(helper.ShouldFail());
helper.SetNetworkState(kAnotherClientId, true);
EXPECT_FALSE(helper.ShouldFail());
helper.SetNetworkState(kClientId, true);
EXPECT_TRUE(helper.ShouldFail());
helper.SetNetworkState(kAnotherClientId, false);
helper.SetNetworkState(kClientId, false);
base::RunLoop().RunUntilIdle();
}
TEST(DevToolsNetworkControllerTest, FailOnStart) {
DevToolsNetworkControllerHelper helper;
helper.SetNetworkState(true);
helper.SetNetworkState(kClientId, true);
int rv = helper.Start();
EXPECT_EQ(rv, net::ERR_INTERNET_DISCONNECTED);
......@@ -124,6 +145,7 @@ TEST(DevToolsNetworkControllerTest, FailOnStart) {
TEST(DevToolsNetworkControllerTest, FailRunningTransaction) {
DevToolsNetworkControllerHelper helper;
helper.SetNetworkState(kClientId, false);
TestCallback* callback = helper.callback();
int rv = helper.Start();
......@@ -134,7 +156,7 @@ TEST(DevToolsNetworkControllerTest, FailRunningTransaction) {
EXPECT_EQ(rv, net::ERR_IO_PENDING);
EXPECT_EQ(callback->run_count(), 0);
helper.SetNetworkState(true);
helper.SetNetworkState(kClientId, true);
EXPECT_EQ(callback->run_count(), 1);
EXPECT_EQ(callback->value(), net::ERR_INTERNET_DISCONNECTED);
......@@ -145,19 +167,20 @@ TEST(DevToolsNetworkControllerTest, FailRunningTransaction) {
EXPECT_EQ(callback->run_count(), 1);
// Check that transaction in not failed second time.
helper.SetNetworkState(false);
helper.SetNetworkState(true);
helper.SetNetworkState(kClientId, false);
helper.SetNetworkState(kClientId, true);
EXPECT_EQ(callback->run_count(), 1);
}
TEST(DevToolsNetworkControllerTest, ReadAfterFail) {
DevToolsNetworkControllerHelper helper;
helper.SetNetworkState(kClientId, false);
int rv = helper.Start();
EXPECT_EQ(rv, net::OK);
EXPECT_TRUE(helper.transaction()->request());
helper.SetNetworkState(true);
helper.SetNetworkState(kClientId, true);
EXPECT_TRUE(helper.transaction()->failed());
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(64));
......@@ -171,25 +194,15 @@ TEST(DevToolsNetworkControllerTest, ReadAfterFail) {
TEST(DevToolsNetworkControllerTest, AllowsDevToolsRequests) {
DevToolsNetworkControllerHelper helper;
helper.SetNetworkState(kClientId, false);
helper.mock_transaction()->request_headers =
"X-DevTools-Emulate-Network-Conditions-Client-Id: 42\r\n"
"X-DevTools-Request-Initiator: frontend\r\n";
DevToolsNetworkController* controller = helper.controller();
net::HttpRequestInfo* request = helper.GetRequest();
EXPECT_FALSE(controller->ShouldFail(request));
helper.SetNetworkState(true);
EXPECT_FALSE(controller->ShouldFail(request));
}
TEST(DevToolsNetworkControllerTest, AllowsNotMatchingRequests) {
DevToolsNetworkControllerHelper helper;
helper.mock_transaction()->url = kHttpDotOrg;
DevToolsNetworkController* controller = helper.controller();
net::HttpRequestInfo* request = helper.GetRequest();
helper.Start();
EXPECT_FALSE(controller->ShouldFail(request));
helper.SetNetworkState(true);
EXPECT_FALSE(controller->ShouldFail(request));
EXPECT_FALSE(helper.ShouldFail());
helper.SetNetworkState(kClientId, true);
EXPECT_FALSE(helper.ShouldFail());
}
} // namespace test
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/devtools/devtools_network_interceptor.h"
#include "base/time/time.h"
#include "chrome/browser/devtools/devtools_network_conditions.h"
#include "chrome/browser/devtools/devtools_network_transaction.h"
namespace {
int64_t kPacketSize = 1500;
} // namespace
DevToolsNetworkInterceptor::DevToolsNetworkInterceptor()
: conditions_(new DevToolsNetworkConditions()),
weak_ptr_factory_(this) {
}
DevToolsNetworkInterceptor::~DevToolsNetworkInterceptor() {
}
base::WeakPtr<DevToolsNetworkInterceptor>
DevToolsNetworkInterceptor::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void DevToolsNetworkInterceptor::AddTransaction(
DevToolsNetworkTransaction* transaction) {
DCHECK(transactions_.find(transaction) == transactions_.end());
transactions_.insert(transaction);
}
void DevToolsNetworkInterceptor::RemoveTransaction(
DevToolsNetworkTransaction* transaction) {
DCHECK(transactions_.find(transaction) != transactions_.end());
transactions_.erase(transaction);
if (!conditions_->IsThrottling())
return;
UpdateThrottles();
throttled_transactions_.erase(std::remove(throttled_transactions_.begin(),
throttled_transactions_.end(), transaction),
throttled_transactions_.end());
ArmTimer();
}
void DevToolsNetworkInterceptor::UpdateConditions(
const scoped_refptr<DevToolsNetworkConditions> conditions) {
DCHECK(conditions);
if (conditions_->IsThrottling())
UpdateThrottles();
conditions_ = conditions;
if (conditions->offline()) {
timer_.Stop();
throttled_transactions_.clear();
Transactions old_transactions(transactions_);
Transactions::iterator it = old_transactions.begin();
for (;it != old_transactions.end(); ++it) {
if (transactions_.find(*it) == transactions_.end())
continue;
if (!(*it)->request() || (*it)->failed())
continue;
if (ShouldFail(*it))
(*it)->Fail();
}
return;
}
if (conditions->IsThrottling()) {
DCHECK(conditions->download_throughput() != 0);
offset_ = base::TimeTicks::Now();
last_tick_ = 0;
int64_t us_tick_length =
(1000000L * kPacketSize) / conditions->download_throughput();
DCHECK(us_tick_length != 0);
if (us_tick_length == 0)
us_tick_length = 1;
tick_length_ = base::TimeDelta::FromMicroseconds(us_tick_length);
ArmTimer();
} else {
timer_.Stop();
int64_t length = throttled_transactions_.size();
for (int64_t i = 0; i < length; ++i)
throttled_transactions_[i]->FireThrottledCallback();
throttled_transactions_.clear();
}
}
void DevToolsNetworkInterceptor::UpdateThrottles() {
int64_t last_tick = (base::TimeTicks::Now() - offset_) / tick_length_;
int64_t ticks = last_tick - last_tick_;
last_tick_ = last_tick;
int64_t length = throttled_transactions_.size();
if (!length)
return;
int64_t shift = ticks % length;
for (int64_t i = 0; i < length; ++i) {
throttled_transactions_[i]->DecreaseThrottledByteCount(
(ticks / length) * kPacketSize + (i < shift ? kPacketSize : 0));
}
std::rotate(throttled_transactions_.begin(),
throttled_transactions_.begin() + shift, throttled_transactions_.end());
}
void DevToolsNetworkInterceptor::OnTimer() {
UpdateThrottles();
std::vector<DevToolsNetworkTransaction*> active_transactions;
std::vector<DevToolsNetworkTransaction*> finished_transactions;
size_t length = throttled_transactions_.size();
for (size_t i = 0; i < length; ++i) {
if (throttled_transactions_[i]->throttled_byte_count() < 0)
finished_transactions.push_back(throttled_transactions_[i]);
else
active_transactions.push_back(throttled_transactions_[i]);
}
throttled_transactions_.swap(active_transactions);
length = finished_transactions.size();
for (size_t i = 0; i < length; ++i)
finished_transactions[i]->FireThrottledCallback();
ArmTimer();
}
void DevToolsNetworkInterceptor::ArmTimer() {
size_t length = throttled_transactions_.size();
if (!length)
return;
int64_t min_ticks_left = 0x10000L;
for (size_t i = 0; i < length; ++i) {
int64_t packets_left = (throttled_transactions_[i]->throttled_byte_count() +
kPacketSize - 1) / kPacketSize;
int64_t ticks_left = (i + 1) + length * (packets_left - 1);
if (i == 0 || ticks_left < min_ticks_left)
min_ticks_left = ticks_left;
}
base::TimeTicks desired_time =
offset_ + tick_length_ * (last_tick_ + min_ticks_left);
timer_.Start(
FROM_HERE,
desired_time - base::TimeTicks::Now(),
base::Bind(
&DevToolsNetworkInterceptor::OnTimer,
base::Unretained(this)));
}
void DevToolsNetworkInterceptor::ThrottleTransaction(
DevToolsNetworkTransaction* transaction) {
UpdateThrottles();
throttled_transactions_.push_back(transaction);
ArmTimer();
}
bool DevToolsNetworkInterceptor::ShouldFail(
const DevToolsNetworkTransaction* transaction) {
if (!conditions_->offline())
return false;
if (!transaction->request_initiator().empty())
return false;
return true;
}
bool DevToolsNetworkInterceptor::ShouldThrottle(
const DevToolsNetworkTransaction* transaction) {
if (!conditions_->IsThrottling())
return false;
if (!transaction->request_initiator().empty())
return false;
return true;
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_INTERCEPTOR_H_
#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_INTERCEPTOR_H_
#include <set>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/timer/timer.h"
class DevToolsNetworkConditions;
class DevToolsNetworkTransaction;
namespace base {
class TimeDelta;
class TimeTicks;
}
// DevToolsNetworkInterceptor emulates network conditions for transactions with
// specific client id.
class DevToolsNetworkInterceptor {
public:
DevToolsNetworkInterceptor();
virtual ~DevToolsNetworkInterceptor();
base::WeakPtr<DevToolsNetworkInterceptor> GetWeakPtr();
// Applies network emulation configuration.
void UpdateConditions(
const scoped_refptr<DevToolsNetworkConditions> conditions);
void AddTransaction(DevToolsNetworkTransaction* transaction);
void RemoveTransaction(DevToolsNetworkTransaction* transaction);
bool ShouldFail(const DevToolsNetworkTransaction* transaction);
bool ShouldThrottle(const DevToolsNetworkTransaction* transaction);
void ThrottleTransaction(DevToolsNetworkTransaction* transaction);
const DevToolsNetworkConditions* conditions() const {
return conditions_.get();
}
private:
scoped_refptr<DevToolsNetworkConditions> conditions_;
void UpdateThrottles();
void ArmTimer();
void OnTimer();
typedef std::set<DevToolsNetworkTransaction*> Transactions;
Transactions transactions_;
std::vector<DevToolsNetworkTransaction*> throttled_transactions_;
base::OneShotTimer<DevToolsNetworkInterceptor> timer_;
base::TimeTicks offset_;
base::TimeDelta tick_length_;
uint64_t last_tick_;
base::WeakPtrFactory<DevToolsNetworkInterceptor> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkInterceptor);
};
#endif // CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_INTERCEPTOR_H_
......@@ -5,11 +5,20 @@
#include "chrome/browser/devtools/devtools_network_transaction.h"
#include "chrome/browser/devtools/devtools_network_controller.h"
#include "chrome/browser/devtools/devtools_network_interceptor.h"
#include "net/base/net_errors.h"
#include "net/base/upload_progress.h"
#include "net/http/http_network_transaction.h"
#include "net/http/http_request_info.h"
namespace {
const char kDevToolsRequestInitiator[] = "X-DevTools-Request-Initiator";
const char kDevToolsEmulateNetworkConditionsClientId[] =
"X-DevTools-Emulate-Network-Conditions-Client-Id";
} // namespace
DevToolsNetworkTransaction::DevToolsNetworkTransaction(
DevToolsNetworkController* controller,
scoped_ptr<net::HttpTransaction> network_transaction)
......@@ -22,11 +31,11 @@ DevToolsNetworkTransaction::DevToolsNetworkTransaction(
proxy_callback_(base::Bind(&DevToolsNetworkTransaction::OnCallback,
base::Unretained(this))) {
DCHECK(controller);
controller->AddTransaction(this);
}
DevToolsNetworkTransaction::~DevToolsNetworkTransaction() {
controller_->RemoveTransaction(this);
if (interceptor_)
interceptor_->RemoveTransaction(this);
}
void DevToolsNetworkTransaction::Throttle(int result) {
......@@ -37,7 +46,8 @@ void DevToolsNetworkTransaction::Throttle(int result) {
if (result > 0)
throttled_byte_count_ += result;
controller_->ThrottleTransaction(this);
if (interceptor_)
interceptor_->ThrottleTransaction(this);
}
void DevToolsNetworkTransaction::OnCallback(int rv) {
......@@ -45,7 +55,7 @@ void DevToolsNetworkTransaction::OnCallback(int rv) {
return;
DCHECK(!callback_.is_null());
if (callback_type_ == START || callback_type_ == READ) {
if (controller_->ShouldThrottle(request_)) {
if (interceptor_ && interceptor_->ShouldThrottle(this)) {
Throttle(rv);
return;
}
......@@ -68,7 +78,7 @@ int DevToolsNetworkTransaction::SetupCallback(
return result;
}
if (!controller_->ShouldThrottle(request_))
if (!interceptor_ || !interceptor_->ShouldThrottle(this))
return result;
// Only START and READ operation throttling is supported.
......@@ -109,17 +119,46 @@ int DevToolsNetworkTransaction::Start(
const net::BoundNetLog& net_log) {
DCHECK(request);
request_ = request;
interceptor_ = controller_->GetInterceptor(this);
interceptor_->AddTransaction(this);
if (controller_->ShouldFail(request_)) {
if (interceptor_->ShouldFail(this)) {
failed_ = true;
network_transaction_->SetBeforeNetworkStartCallback(
BeforeNetworkStartCallback());
return net::ERR_INTERNET_DISCONNECTED;
}
int rv = network_transaction_->Start(request, proxy_callback_, net_log);
int rv = network_transaction_->Start(request_, proxy_callback_, net_log);
return SetupCallback(callback, rv, START);
}
void DevToolsNetworkTransaction::ProcessRequest() {
DCHECK(request_);
bool has_devtools_client_id = request_->extra_headers.HasHeader(
kDevToolsEmulateNetworkConditionsClientId);
bool has_devtools_request_initiator = request_->extra_headers.HasHeader(
kDevToolsRequestInitiator);
if (!has_devtools_client_id && !has_devtools_request_initiator)
return;
custom_request_.reset(new net::HttpRequestInfo(*request_));
if (has_devtools_client_id) {
custom_request_->extra_headers.GetHeader(
kDevToolsEmulateNetworkConditionsClientId, &client_id_);
custom_request_->extra_headers.RemoveHeader(
kDevToolsEmulateNetworkConditionsClientId);
}
if (has_devtools_request_initiator) {
custom_request_->extra_headers.GetHeader(
kDevToolsRequestInitiator, &request_initiator_);
custom_request_->extra_headers.RemoveHeader(kDevToolsRequestInitiator);
}
request_ = custom_request_.get();
}
int DevToolsNetworkTransaction::RestartIgnoringLastError(
const net::CompletionCallback& callback) {
if (failed_)
......
......@@ -6,6 +6,7 @@
#define CHROME_BROWSER_DEVTOOLS_DEVTOOLS_NETWORK_TRANSACTION_H_
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "net/base/completion_callback.h"
#include "net/base/load_states.h"
#include "net/base/request_priority.h"
......@@ -13,6 +14,7 @@
#include "net/websockets/websocket_handshake_stream_base.h"
class DevToolsNetworkController;
class DevToolsNetworkInterceptor;
class GURL;
namespace net {
......@@ -26,6 +28,10 @@ class IOBuffer;
struct LoadTimingInfo;
class UploadProgress;
class X509Certificate;
} // namespace net
namespace test {
class DevToolsNetworkControllerHelper;
}
// DevToolsNetworkTransaction is a wrapper for network transaction. All
......@@ -42,6 +48,11 @@ class DevToolsNetworkTransaction : public net::HttpTransaction {
virtual ~DevToolsNetworkTransaction();
const net::HttpRequestInfo* request() const { return request_; }
// Checks if request contains DevTools specific headers. Found values are
// remembered and corresponding keys are removed from headers.
void ProcessRequest();
bool failed() const { return failed_; }
// Runs callback (if any) with net::ERR_INTERNET_DISCONNECTED result value.
......@@ -52,6 +63,12 @@ class DevToolsNetworkTransaction : public net::HttpTransaction {
throttled_byte_count_ -= delta;
}
const std::string& request_initiator() const { return request_initiator_; }
const std::string& client_id() const {
return client_id_;
}
void FireThrottledCallback();
// HttpTransaction methods:
......@@ -92,11 +109,15 @@ class DevToolsNetworkTransaction : public net::HttpTransaction {
const BeforeNetworkStartCallback& callback) OVERRIDE;
virtual int ResumeNetworkStart() OVERRIDE;
protected:
friend class test::DevToolsNetworkControllerHelper;
private:
// Proxy callback handler. Runs saved callback.
void OnCallback(int result);
DevToolsNetworkController* controller_;
base::WeakPtr<DevToolsNetworkInterceptor> interceptor_;
// Real network transaction.
scoped_ptr<net::HttpTransaction> network_transaction_;
......@@ -109,6 +130,14 @@ class DevToolsNetworkTransaction : public net::HttpTransaction {
// True if Fail was already invoked.
bool failed_;
// Value of "X-DevTools-Request-Initiator" request header.
std::string request_initiator_;
// Value of "X-DevTools-Emulate-Network-Conditions-Client-Id" request header.
std::string client_id_;
scoped_ptr<net::HttpRequestInfo> custom_request_;
enum CallbackType {
NONE,
READ,
......
......@@ -68,6 +68,8 @@
'browser/devtools/devtools_network_conditions.h',
'browser/devtools/devtools_network_controller.cc',
'browser/devtools/devtools_network_controller.h',
'browser/devtools/devtools_network_interceptor.cc',
'browser/devtools/devtools_network_interceptor.h',
'browser/devtools/devtools_network_transaction.cc',
'browser/devtools/devtools_network_transaction.h',
'browser/devtools/devtools_network_transaction_factory.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment