diff options
author | eustas@chromium.org <eustas@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-18 17:05:36 +0000 |
---|---|---|
committer | eustas@chromium.org <eustas@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-18 17:05:36 +0000 |
commit | 60c5dad3c730ceb8cf227e63cd9877712d11a5d5 (patch) | |
tree | 068f9d1a1cf0bd60fb8f9b0d7f708679f884296c | |
parent | 01db472d7320e72ea8464b92f6c725644d7b88b0 (diff) | |
download | chromium_src-60c5dad3c730ceb8cf227e63cd9877712d11a5d5.zip chromium_src-60c5dad3c730ceb8cf227e63cd9877712d11a5d5.tar.gz chromium_src-60c5dad3c730ceb8cf227e63cd9877712d11a5d5.tar.bz2 |
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
12 files changed, 528 insertions, 310 deletions
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc index 473309d..3dd74ac 100644 --- a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc +++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc @@ -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); } diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.h b/chrome/browser/devtools/chrome_devtools_manager_delegate.h index bdbbbbf..6385c0c 100644 --- a/chrome/browser/devtools/chrome_devtools_manager_delegate.h +++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.h @@ -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, diff --git a/chrome/browser/devtools/devtools_network_conditions.cc b/chrome/browser/devtools/devtools_network_conditions.cc index 59e06b8..534e787 100644 --- a/chrome/browser/devtools/devtools_network_conditions.cc +++ b/chrome/browser/devtools/devtools_network_conditions.cc @@ -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); } diff --git a/chrome/browser/devtools/devtools_network_conditions.h b/chrome/browser/devtools/devtools_network_conditions.h index 7c9d477..e19f77e 100644 --- a/chrome/browser/devtools/devtools_network_conditions.h +++ b/chrome/browser/devtools/devtools_network_conditions.h @@ -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); }; diff --git a/chrome/browser/devtools/devtools_network_controller.cc b/chrome/browser/devtools/devtools_network_controller.cc index 176ac66..566a2108 100644 --- a/chrome/browser/devtools/devtools_network_controller.cc +++ b/chrome/browser/devtools/devtools_network_controller.cc @@ -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(); } diff --git a/chrome/browser/devtools/devtools_network_controller.h b/chrome/browser/devtools/devtools_network_controller.h index a9edd0f..6550da4 100644 --- a/chrome/browser/devtools/devtools_network_controller.h +++ b/chrome/browser/devtools/devtools_network_controller.h @@ -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_; diff --git a/chrome/browser/devtools/devtools_network_controller_unittest.cc b/chrome/browser/devtools/devtools_network_controller_unittest.cc index 708b9e1..07bba5e 100644 --- a/chrome/browser/devtools/devtools_network_controller_unittest.cc +++ b/chrome/browser/devtools/devtools_network_controller_unittest.cc @@ -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 diff --git a/chrome/browser/devtools/devtools_network_interceptor.cc b/chrome/browser/devtools/devtools_network_interceptor.cc new file mode 100644 index 0000000..0399b8d --- /dev/null +++ b/chrome/browser/devtools/devtools_network_interceptor.cc @@ -0,0 +1,183 @@ +// 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; +} diff --git a/chrome/browser/devtools/devtools_network_interceptor.h b/chrome/browser/devtools/devtools_network_interceptor.h new file mode 100644 index 0000000..564bdf6 --- /dev/null +++ b/chrome/browser/devtools/devtools_network_interceptor.h @@ -0,0 +1,70 @@ +// 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_ diff --git a/chrome/browser/devtools/devtools_network_transaction.cc b/chrome/browser/devtools/devtools_network_transaction.cc index 8a72e01..b2afc98 100644 --- a/chrome/browser/devtools/devtools_network_transaction.cc +++ b/chrome/browser/devtools/devtools_network_transaction.cc @@ -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_) diff --git a/chrome/browser/devtools/devtools_network_transaction.h b/chrome/browser/devtools/devtools_network_transaction.h index 37e84ed..77ae9217 100644 --- a/chrome/browser/devtools/devtools_network_transaction.h +++ b/chrome/browser/devtools/devtools_network_transaction.h @@ -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, diff --git a/chrome/chrome_debugger.gypi b/chrome/chrome_debugger.gypi index fd5627f..fcd4cb1 100644 --- a/chrome/chrome_debugger.gypi +++ b/chrome/chrome_debugger.gypi @@ -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', |