diff options
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/loader/detachable_resource_handler.cc | 209 | ||||
-rw-r--r-- | content/browser/loader/detachable_resource_handler.h | 94 | ||||
-rw-r--r-- | content/browser/loader/resource_dispatcher_host_impl.cc | 63 | ||||
-rw-r--r-- | content/browser/loader/resource_dispatcher_host_impl.h | 4 | ||||
-rw-r--r-- | content/browser/loader/resource_dispatcher_host_unittest.cc | 708 | ||||
-rw-r--r-- | content/browser/loader/resource_loader.cc | 8 | ||||
-rw-r--r-- | content/browser/loader/resource_request_info_impl.cc | 1 | ||||
-rw-r--r-- | content/browser/loader/resource_request_info_impl.h | 15 | ||||
-rw-r--r-- | content/content_browser.gypi | 2 |
9 files changed, 942 insertions, 162 deletions
diff --git a/content/browser/loader/detachable_resource_handler.cc b/content/browser/loader/detachable_resource_handler.cc new file mode 100644 index 0000000..7cc7b09 --- /dev/null +++ b/content/browser/loader/detachable_resource_handler.cc @@ -0,0 +1,209 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/loader/detachable_resource_handler.h" + +#include "base/logging.h" +#include "base/time/time.h" +#include "content/browser/loader/resource_request_info_impl.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request_status.h" + +namespace { +// This matches the maximum allocation size of AsyncResourceHandler. +const int kReadBufSize = 32 * 1024; +} + +namespace content { + +DetachableResourceHandler::DetachableResourceHandler( + net::URLRequest* request, + base::TimeDelta cancel_delay, + scoped_ptr<ResourceHandler> next_handler) + : ResourceHandler(request), + next_handler_(next_handler.Pass()), + cancel_delay_(cancel_delay), + is_deferred_(false), + is_finished_(false) { + GetRequestInfo()->set_detachable_handler(this); +} + +DetachableResourceHandler::~DetachableResourceHandler() { + // Cleanup back-pointer stored on the request info. + GetRequestInfo()->set_detachable_handler(NULL); +} + +void DetachableResourceHandler::Detach() { + if (is_detached()) + return; + + if (!is_finished_) { + // Simulate a cancel on the next handler before destroying it. + net::URLRequestStatus status(net::URLRequestStatus::CANCELED, + net::ERR_ABORTED); + bool defer_ignored = false; + next_handler_->OnResponseCompleted(GetRequestID(), status, std::string(), + &defer_ignored); + DCHECK(!defer_ignored); + // If |next_handler_| were to defer its shutdown in OnResponseCompleted, + // this would destroy it anyway. Fortunately, AsyncResourceHandler never + // does this anyway, so DCHECK it. BufferedResourceHandler and RVH shutdown + // already ignore deferred ResourceHandler shutdown, but + // DetachableResourceHandler and the detach-on-renderer-cancel logic + // introduces a case where this occurs when the renderer cancels a resource. + } + // A OnWillRead / OnReadCompleted pair may still be in progress, but + // OnWillRead passes back a scoped_refptr, so downstream handler's buffer will + // survive long enough to complete that read. From there, future reads will + // drain into |read_buffer_|. (If |next_handler_| is an AsyncResourceHandler, + // the net::IOBuffer takes a reference to the ResourceBuffer which owns the + // shared memory.) + next_handler_.reset(); + + // Time the request out if it takes too long. + detached_timer_.reset(new base::OneShotTimer<DetachableResourceHandler>()); + detached_timer_->Start( + FROM_HERE, cancel_delay_, this, &DetachableResourceHandler::Cancel); + + // Resume if necessary. The request may have been deferred, say, waiting on a + // full buffer in AsyncResourceHandler. Now that it has been detached, resume + // and drain it. + if (is_deferred_) + Resume(); +} + +void DetachableResourceHandler::SetController(ResourceController* controller) { + ResourceHandler::SetController(controller); + + // Intercept the ResourceController for downstream handlers to keep track of + // whether the request is deferred. + if (next_handler_) + next_handler_->SetController(this); +} + +bool DetachableResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + if (!next_handler_) + return true; + + return next_handler_->OnUploadProgress(request_id, position, size); +} + +bool DetachableResourceHandler::OnRequestRedirected(int request_id, + const GURL& url, + ResourceResponse* response, + bool* defer) { + DCHECK(!is_deferred_); + + if (!next_handler_) + return true; + + bool ret = next_handler_->OnRequestRedirected(request_id, url, response, + &is_deferred_); + *defer = is_deferred_; + return ret; +} + +bool DetachableResourceHandler::OnResponseStarted(int request_id, + ResourceResponse* response, + bool* defer) { + DCHECK(!is_deferred_); + + if (!next_handler_) + return true; + + bool ret = + next_handler_->OnResponseStarted(request_id, response, &is_deferred_); + *defer = is_deferred_; + return ret; +} + +bool DetachableResourceHandler::OnWillStart(int request_id, const GURL& url, + bool* defer) { + DCHECK(!is_deferred_); + + if (!next_handler_) + return true; + + bool ret = next_handler_->OnWillStart(request_id, url, &is_deferred_); + *defer = is_deferred_; + return ret; +} + +bool DetachableResourceHandler::OnWillRead(int request_id, + scoped_refptr<net::IOBuffer>* buf, + int* buf_size, + int min_size) { + if (!next_handler_) { + DCHECK_EQ(-1, min_size); + if (!read_buffer_) + read_buffer_ = new net::IOBuffer(kReadBufSize); + *buf = read_buffer_; + *buf_size = kReadBufSize; + return true; + } + + return next_handler_->OnWillRead(request_id, buf, buf_size, min_size); +} + +bool DetachableResourceHandler::OnReadCompleted(int request_id, int bytes_read, + bool* defer) { + DCHECK(!is_deferred_); + + if (!next_handler_) + return true; + + bool ret = + next_handler_->OnReadCompleted(request_id, bytes_read, &is_deferred_); + *defer = is_deferred_; + return ret; +} + +void DetachableResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& security_info, + bool* defer) { + // No DCHECK(!is_deferred_) as the request may have been cancelled while + // deferred. + + if (!next_handler_) + return; + + is_finished_ = true; + + next_handler_->OnResponseCompleted(request_id, status, security_info, + &is_deferred_); + *defer = is_deferred_; +} + +void DetachableResourceHandler::OnDataDownloaded(int request_id, + int bytes_downloaded) { + if (!next_handler_) + return; + + next_handler_->OnDataDownloaded(request_id, bytes_downloaded); +} + +void DetachableResourceHandler::Resume() { + DCHECK(is_deferred_); + is_deferred_ = false; + controller()->Resume(); +} + +void DetachableResourceHandler::Cancel() { + controller()->Cancel(); +} + +void DetachableResourceHandler::CancelAndIgnore() { + controller()->CancelAndIgnore(); +} + +void DetachableResourceHandler::CancelWithError(int error_code) { + controller()->CancelWithError(error_code); +} + +} // namespace content diff --git a/content/browser/loader/detachable_resource_handler.h b/content/browser/loader/detachable_resource_handler.h new file mode 100644 index 0000000..990084d --- /dev/null +++ b/content/browser/loader/detachable_resource_handler.h @@ -0,0 +1,94 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_LOADER_DETACHABLE_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_LOADER_DETACHABLE_RESOURCE_HANDLER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "content/browser/loader/resource_handler.h" +#include "content/public/browser/resource_controller.h" + +namespace net { +class IOBuffer; +class URLRequest; +} // namespace net + +namespace content { + +// A ResourceHandler which delegates all calls to the next handler, unless +// detached. Once detached, it drives the request to completion itself. This is +// used for requests which outlive the owning renderer, such as <link +// rel=prefetch> and <a ping>. Requests do not start out detached so, e.g., +// prefetches appear in DevTools and get placed in the renderer's local +// cache. If the request does not complete after a timeout on detach, it is +// cancelled. +// +// Note that, once detached, the request continues without the original next +// handler, so any policy decisions in that handler are skipped. +class DetachableResourceHandler : public ResourceHandler, + public ResourceController { + public: + DetachableResourceHandler(net::URLRequest* request, + base::TimeDelta cancel_delay, + scoped_ptr<ResourceHandler> next_handler); + virtual ~DetachableResourceHandler(); + + bool is_detached() const { return next_handler_ == NULL; } + void Detach(); + + void set_cancel_delay(base::TimeDelta cancel_delay) { + cancel_delay_ = cancel_delay; + } + + // ResourceHandler implementation: + virtual void SetController(ResourceController* controller) OVERRIDE; + virtual bool OnUploadProgress(int request_id, uint64 position, + uint64 size) OVERRIDE; + virtual bool OnRequestRedirected(int request_id, const GURL& url, + ResourceResponse* response, + bool* defer) OVERRIDE; + virtual bool OnResponseStarted(int request_id, + ResourceResponse* response, + bool* defer) OVERRIDE; + virtual bool OnWillStart(int request_id, const GURL& url, + bool* defer) OVERRIDE; + virtual bool OnWillRead(int request_id, + scoped_refptr<net::IOBuffer>* buf, + int* buf_size, + int min_size) OVERRIDE; + virtual bool OnReadCompleted(int request_id, int bytes_read, + bool* defer) OVERRIDE; + virtual void OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info, + bool* defer) OVERRIDE; + virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE; + + // ResourceController implementation: + virtual void Resume() OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual void CancelAndIgnore() OVERRIDE; + virtual void CancelWithError(int error_code) OVERRIDE; + + private: + scoped_ptr<ResourceHandler> next_handler_; + scoped_refptr<net::IOBuffer> read_buffer_; + + scoped_ptr<base::OneShotTimer<DetachableResourceHandler> > detached_timer_; + base::TimeDelta cancel_delay_; + + bool is_deferred_; + bool is_finished_; + + DISALLOW_COPY_AND_ASSIGN(DetachableResourceHandler); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_LOADER_DETACHABLE_RESOURCE_HANDLER_H_ diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc index 6da6d49..5681255 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/content/browser/loader/resource_dispatcher_host_impl.cc @@ -33,6 +33,7 @@ #include "content/browser/loader/async_resource_handler.h" #include "content/browser/loader/buffered_resource_handler.h" #include "content/browser/loader/cross_site_resource_handler.h" +#include "content/browser/loader/detachable_resource_handler.h" #include "content/browser/loader/power_save_block_resource_throttle.h" #include "content/browser/loader/redirect_to_file_resource_handler.h" #include "content/browser/loader/resource_message_filter.h" @@ -127,6 +128,22 @@ const int kUserGestureWindowMs = 3500; // use. Arbitrarily chosen. const double kMaxRequestsPerProcessRatio = 0.45; +// TODO(jkarlin): The value is high to reduce the chance of the detachable +// request timing out, forcing a blocked second request to open a new connection +// and start over. Reduce this value once we have a better idea of what it +// should be and once we stop blocking multiple simultaneous requests for the +// same resource (see bugs 46104 and 31014). +const int kDefaultDetachableCancelDelayMs = 30000; + +bool IsDetachableResourceType(ResourceType::Type type) { + switch (type) { + case ResourceType::PREFETCH: + return true; + default: + return false; + } +} + // Aborts a request before an URLRequest has actually been created. void AbortRequestBeforeItStarts(ResourceMessageFilter* filter, IPC::Message* sync_result, @@ -406,12 +423,15 @@ void ResourceDispatcherHostImpl::CancelRequestsForContext( for (LoaderList::iterator i = loaders_to_cancel.begin(); i != loaders_to_cancel.end(); ++i) { // There is no strict requirement that this be the case, but currently - // downloads, streams and transferred requests are the only requests that - // aren't cancelled when the associated processes go away. It may be OK for - // this invariant to change in the future, but if this assertion fires - // without the invariant changing, then it's indicative of a leak. + // downloads, streams, detachable requests, and transferred requests are the + // only requests that aren't cancelled when the associated processes go + // away. It may be OK for this invariant to change in the future, but if + // this assertion fires without the invariant changing, then it's indicative + // of a leak. DCHECK((*i)->GetRequestInfo()->is_download() || (*i)->GetRequestInfo()->is_stream() || + ((*i)->GetRequestInfo()->detachable_handler() && + (*i)->GetRequestInfo()->detachable_handler()->is_detached()) || (*i)->is_transferring()); } #endif @@ -694,18 +714,20 @@ void ResourceDispatcherHostImpl::DidReceiveRedirect(ResourceLoader* loader, void ResourceDispatcherHostImpl::DidReceiveResponse(ResourceLoader* loader) { ResourceRequestInfoImpl* info = loader->GetRequestInfo(); + // There should be an entry in the map created when we dispatched the - // request. + // request unless it's been detached and the renderer has died. OfflineMap::iterator policy_it( offline_policy_map_.find(info->GetGlobalRoutingID())); if (offline_policy_map_.end() != policy_it) { policy_it->second->UpdateStateForSuccessfullyStartedRequest( loader->request()->response_info()); } else { - // We should always have an entry in offline_policy_map_ from when - // this request traversed Begin{Download,SaveFile,Request}. + // Unless detached, we should have an entry in offline_policy_map_ from + // when this request traversed Begin{Download,SaveFile,Request}. // TODO(rdsmith): This isn't currently true; see http://crbug.com/241176. - NOTREACHED(); + DCHECK(info->detachable_handler() && + info->detachable_handler()->is_detached()); } int render_process_id, render_view_id; @@ -1114,6 +1136,12 @@ void ResourceDispatcherHostImpl::BeginRequest( handler.reset(new SyncResourceHandler(request, sync_result, this)); } else { handler.reset(new AsyncResourceHandler(request, this)); + if (IsDetachableResourceType(request_data.resource_type)) { + handler.reset(new DetachableResourceHandler( + request, + base::TimeDelta::FromMilliseconds(kDefaultDetachableCancelDelayMs), + handler.Pass())); + } } // The RedirectToFileResourceHandler depends on being next in the chain. @@ -1233,7 +1261,7 @@ ResourceRequestInfoImpl* ResourceDispatcherHostImpl::CreateRequestInfo( PAGE_TRANSITION_LINK, false, // should_replace_current_entry download, // is_download - false, // is_stream + false, // is_stream download, // allow_download false, // has_user_gesture blink::WebReferrerPolicyDefault, @@ -1329,8 +1357,8 @@ void ResourceDispatcherHostImpl::ResumeDeferredNavigation( } // The object died, so cancel and detach all requests associated with it except -// for downloads, which belong to the browser process even if initiated via a -// renderer. +// for downloads and detachable resources, which belong to the browser process +// even if initiated via a renderer. void ResourceDispatcherHostImpl::CancelRequestsForProcess(int child_id) { CancelRequestsForRoute(child_id, -1 /* cancel all */); registered_temp_files_.erase(child_id); @@ -1355,14 +1383,15 @@ void ResourceDispatcherHostImpl::CancelRequestsForRoute(int child_id, GlobalRequestID id(child_id, i->first.request_id); DCHECK(id == i->first); - - // Don't cancel navigations that are transferring to another process, - // since they belong to another process now. + // Don't cancel navigations that are expected to live beyond this process. if (IsTransferredNavigation(id)) any_requests_transferring = true; - if (!info->is_download() && !info->is_stream() && - !IsTransferredNavigation(id) && - (route_id == -1 || route_id == info->GetRouteID())) { + + if (info->detachable_handler()) { + info->detachable_handler()->Detach(); + } else if (!info->is_download() && !info->is_stream() && + !IsTransferredNavigation(id) && + (route_id == -1 || route_id == info->GetRouteID())) { matching_requests.push_back(id); } } diff --git a/content/browser/loader/resource_dispatcher_host_impl.h b/content/browser/loader/resource_dispatcher_host_impl.h index 851463e..ef0e4cf 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.h +++ b/content/browser/loader/resource_dispatcher_host_impl.h @@ -238,6 +238,10 @@ class CONTENT_EXPORT ResourceDispatcherHostImpl TestBlockedRequestsProcessDies); FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, CalculateApproximateMemoryCost); + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + DetachableResourceTimesOut); + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + TestProcessCancelDetachableTimesOut); class ShutdownTask; diff --git a/content/browser/loader/resource_dispatcher_host_unittest.cc b/content/browser/loader/resource_dispatcher_host_unittest.cc index 07062ef..81e0c88 100644 --- a/content/browser/loader/resource_dispatcher_host_unittest.cc +++ b/content/browser/loader/resource_dispatcher_host_unittest.cc @@ -14,7 +14,9 @@ #include "base/strings/string_split.h" #include "content/browser/browser_thread_impl.h" #include "content/browser/child_process_security_policy_impl.h" +#include "content/browser/loader/detachable_resource_handler.h" #include "content/browser/loader/resource_dispatcher_host_impl.h" +#include "content/browser/loader/resource_loader.h" #include "content/browser/loader/resource_message_filter.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/worker_host/worker_service_impl.h" @@ -40,6 +42,7 @@ #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_simple_job.h" #include "net/url_request/url_request_test_job.h" +#include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/common/appcache/appcache_interfaces.h" @@ -524,13 +527,14 @@ class ResourceDispatcherHostTest : public testing::Test, cache_thread_(BrowserThread::CACHE, &message_loop_), io_thread_(BrowserThread::IO, &message_loop_), old_factory_(NULL), - resource_type_(ResourceType::SUB_RESOURCE), send_data_received_acks_(false) { browser_context_.reset(new TestBrowserContext()); BrowserContext::EnsureResourceContextInitialized(browser_context_.get()); message_loop_.RunUntilIdle(); - filter_ = new ForwardingFilter( - this, browser_context_->GetResourceContext()); + ResourceContext* resource_context = browser_context_->GetResourceContext(); + filter_ = new ForwardingFilter(this, resource_context); + resource_context->GetRequestContext()->set_network_delegate( + &network_delegate_); } virtual ~ResourceDispatcherHostTest() { @@ -595,23 +599,25 @@ class ResourceDispatcherHostTest : public testing::Test, message_loop_.RunUntilIdle(); } - // Creates a request using the current test object as the filter. + // Creates a request using the current test object as the filter and + // SubResource as the resource type. void MakeTestRequest(int render_view_id, int request_id, const GURL& url); - // Generates a request using the given filter. This will probably be a - // ForwardingFilter. - void MakeTestRequest(ResourceMessageFilter* filter, - int render_view_id, - int request_id, - const GURL& url); + // Generates a request using the given filter and resource type. + void MakeTestRequestWithResourceType(ResourceMessageFilter* filter, + int render_view_id, int request_id, + const GURL& url, + ResourceType::Type type); void CancelRequest(int request_id); void CompleteStartRequest(int request_id); void CompleteStartRequest(ResourceMessageFilter* filter, int request_id); + net::TestNetworkDelegate* network_delegate() { return &network_delegate_; } + void EnsureSchemeIsAllowed(const std::string& scheme) { ChildProcessSecurityPolicyImpl* policy = ChildProcessSecurityPolicyImpl::GetInstance(); @@ -635,11 +641,6 @@ class ResourceDispatcherHostTest : public testing::Test, SetResponse(headers, std::string()); } - // Sets a particular resource type for any request from now on. - void SetResourceType(ResourceType::Type type) { - resource_type_ = type; - } - void SendDataReceivedACKs(bool send_acks) { send_data_received_acks_ = send_acks; } @@ -719,13 +720,13 @@ class ResourceDispatcherHostTest : public testing::Test, BrowserThreadImpl io_thread_; scoped_ptr<TestBrowserContext> browser_context_; scoped_refptr<ForwardingFilter> filter_; + net::TestNetworkDelegate network_delegate_; ResourceDispatcherHostImpl host_; ResourceIPCAccumulator accum_; std::string response_headers_; std::string response_data_; std::string scheme_; net::URLRequest::ProtocolFactory* old_factory_; - ResourceType::Type resource_type_; bool send_data_received_acks_; std::set<int> child_ids_; static ResourceDispatcherHostTest* test_fixture_; @@ -742,19 +743,21 @@ int ResourceDispatcherHostTest::url_request_jobs_created_count_ = 0; void ResourceDispatcherHostTest::MakeTestRequest(int render_view_id, int request_id, const GURL& url) { - MakeTestRequest(filter_.get(), render_view_id, request_id, url); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + url, ResourceType::SUB_RESOURCE); } -void ResourceDispatcherHostTest::MakeTestRequest( +void ResourceDispatcherHostTest::MakeTestRequestWithResourceType( ResourceMessageFilter* filter, int render_view_id, int request_id, - const GURL& url) { + const GURL& url, + ResourceType::Type type) { // If it's already there, this'll be dropped on the floor, which is fine. child_ids_.insert(filter->child_id()); ResourceHostMsg_Request request = - CreateResourceRequest("GET", resource_type_, url); + CreateResourceRequest("GET", type, url); ResourceHostMsg_RequestResource msg(render_view_id, request_id, request); bool msg_was_ok; host_.OnMessageReceived(msg, filter, &msg_was_ok); @@ -779,16 +782,47 @@ void ResourceDispatcherHostTest::CompleteStartRequest( URLRequestTestDelayedStartJob::CompleteStart(req); } -void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, - const std::string& reference_data) { +void CheckRequestCompleteErrorCode(const IPC::Message& message, + int expected_error_code) { + // Verify the expected error code was received. + int request_id; + int error_code; + + ASSERT_EQ(ResourceMsg_RequestComplete::ID, message.type()); + + PickleIterator iter(message); + ASSERT_TRUE(IPC::ReadParam(&message, &iter, &request_id)); + ASSERT_TRUE(IPC::ReadParam(&message, &iter, &error_code)); + ASSERT_EQ(expected_error_code, error_code); +} + +testing::AssertionResult ExtractDataOffsetAndLength(const IPC::Message& message, + int* data_offset, + int* data_length) { + PickleIterator iter(message); + int request_id; + if (!IPC::ReadParam(&message, &iter, &request_id)) + return testing::AssertionFailure() << "Could not read request_id"; + if (!IPC::ReadParam(&message, &iter, data_offset)) + return testing::AssertionFailure() << "Could not read data_offset"; + if (!IPC::ReadParam(&message, &iter, data_length)) + return testing::AssertionFailure() << "Could not read data_length"; + return testing::AssertionSuccess(); +} + +void CheckSuccessfulRequestWithErrorCode( + const std::vector<IPC::Message>& messages, + const std::string& reference_data, + int expected_error) { // A successful request will have received 4 messages: // ReceivedResponse (indicates headers received) // SetDataBuffer (contains shared memory handle) // DataReceived (data offset and length into shared memory) // RequestComplete (request is done) // - // This function verifies that we received 4 messages and that they - // are appropriate. + // This function verifies that we received 4 messages and that they are + // appropriate. It allows for an error code other than net::OK if the request + // should successfully receive data and then abort, e.g., on cancel. ASSERT_EQ(4U, messages.size()); // The first messages should be received response @@ -808,12 +842,10 @@ void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, // should probably test multiple chunks later ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2].type()); - PickleIterator iter2(messages[2]); - ASSERT_TRUE(IPC::ReadParam(&messages[2], &iter2, &request_id)); int data_offset; - ASSERT_TRUE(IPC::ReadParam(&messages[2], &iter2, &data_offset)); int data_length; - ASSERT_TRUE(IPC::ReadParam(&messages[2], &iter2, &data_length)); + ASSERT_TRUE( + ExtractDataOffsetAndLength(messages[2], &data_offset, &data_length)); ASSERT_EQ(reference_data.size(), static_cast<size_t>(data_length)); ASSERT_GE(shm_size, data_length); @@ -824,7 +856,22 @@ void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, ASSERT_EQ(0, memcmp(reference_data.c_str(), data, data_length)); // The last message should be all data received. - ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[3].type()); + CheckRequestCompleteErrorCode(messages[3], expected_error); +} + +void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, + const std::string& reference_data) { + CheckSuccessfulRequestWithErrorCode(messages, reference_data, net::OK); +} + +void CheckSuccessfulRedirect(const std::vector<IPC::Message>& messages, + const std::string& reference_data) { + ASSERT_EQ(5U, messages.size()); + ASSERT_EQ(ResourceMsg_ReceivedRedirect::ID, messages[0].type()); + + const std::vector<IPC::Message> second_req_msgs = + std::vector<IPC::Message>(messages.begin() + 1, messages.end()); + CheckSuccessfulRequest(second_req_msgs, reference_data); } void CheckFailedRequest(const std::vector<IPC::Message>& messages, @@ -837,15 +884,8 @@ void CheckFailedRequest(const std::vector<IPC::Message>& messages, if (messages.size() == 2) { EXPECT_EQ(ResourceMsg_ReceivedResponse::ID, messages[0].type()); } - EXPECT_EQ(ResourceMsg_RequestComplete::ID, messages[failure_index].type()); - int request_id; - int error_code; - - PickleIterator iter(messages[failure_index]); - EXPECT_TRUE(IPC::ReadParam(&messages[failure_index], &iter, &request_id)); - EXPECT_TRUE(IPC::ReadParam(&messages[failure_index], &iter, &error_code)); - EXPECT_EQ(expected_error, error_code); + CheckRequestCompleteErrorCode(messages[failure_index], expected_error); } // Tests whether many messages get dispatched properly. @@ -853,6 +893,16 @@ TEST_F(ResourceDispatcherHostTest, TestMany) { MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); + MakeTestRequestWithResourceType(filter_.get(), 0, 4, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type + MakeTestRequest(0, 5, net::URLRequestTestJob::test_url_redirect_to_url_2()); + + // Finish the redirection + ResourceHostMsg_FollowRedirect redirect_msg(5, false, GURL()); + bool msg_was_ok; + host_.OnMessageReceived(redirect_msg, filter_.get(), &msg_was_ok); + base::MessageLoop::current()->RunUntilIdle(); // flush all the pending requests while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -861,52 +911,203 @@ TEST_F(ResourceDispatcherHostTest, TestMany) { ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - // there are three requests, so we should have gotten them classified as such - ASSERT_EQ(3U, msgs.size()); + // there are five requests, so we should have gotten them classified as such + ASSERT_EQ(5U, msgs.size()); CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_2()); CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3()); + CheckSuccessfulRequest(msgs[3], net::URLRequestTestJob::test_data_4()); + CheckSuccessfulRedirect(msgs[4], net::URLRequestTestJob::test_data_2()); } -void CheckCancelledRequestCompleteMessage(const IPC::Message& message) { - ASSERT_EQ(ResourceMsg_RequestComplete::ID, message.type()); - - int request_id; - int error_code; - - PickleIterator iter(message); - ASSERT_TRUE(IPC::ReadParam(&message, &iter, &request_id)); - ASSERT_TRUE(IPC::ReadParam(&message, &iter, &error_code)); - - EXPECT_EQ(net::ERR_ABORTED, error_code); -} - -// Tests whether messages get canceled properly. We issue three requests, -// cancel one of them, and make sure that each sent the proper notifications. +// Tests whether messages get canceled properly. We issue four requests, +// cancel two of them, and make sure that each sent the proper notifications. TEST_F(ResourceDispatcherHostTest, Cancel) { MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); + + MakeTestRequestWithResourceType(filter_.get(), 0, 4, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type + CancelRequest(2); + // Cancel request must come from the renderer for a detachable resource to + // delay. + host_.CancelRequest(filter_->child_id(), 4, true); + + // The handler should have been detached now. + GlobalRequestID global_request_id(filter_->child_id(), 4); + ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( + host_.GetURLRequest(global_request_id)); + ASSERT_TRUE(info->detachable_handler()->is_detached()); + // flush all the pending requests while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} base::MessageLoop::current()->RunUntilIdle(); + // Everything should be out now. + EXPECT_EQ(0, host_.pending_requests()); + ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - // there are three requests, so we should have gotten them classified as such - ASSERT_EQ(3U, msgs.size()); + // there are four requests, so we should have gotten them classified as such + ASSERT_EQ(4U, msgs.size()); CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3()); - // Check that request 2 got canceled. + // Check that request 2 and 4 got canceled, as far as the renderer is + // concerned. ASSERT_EQ(2U, msgs[1].size()); ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[1][0].type()); - CheckCancelledRequestCompleteMessage(msgs[1][1]); + CheckRequestCompleteErrorCode(msgs[1][1], net::ERR_ABORTED); + + ASSERT_EQ(2U, msgs[3].size()); + ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[3][0].type()); + CheckRequestCompleteErrorCode(msgs[1][1], net::ERR_ABORTED); + + // However, request 4 should have actually gone to completion. (Only request 2 + // was canceled.) + EXPECT_EQ(4, network_delegate()->completed_requests()); + EXPECT_EQ(1, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); +} + +// Shows that detachable requests will timeout if the request takes too long to +// complete. +TEST_F(ResourceDispatcherHostTest, DetachedResourceTimesOut) { + MakeTestRequestWithResourceType(filter_.get(), 0, 1, + net::URLRequestTestJob::test_url_2(), + ResourceType::PREFETCH); // detachable type + GlobalRequestID global_request_id(filter_->child_id(), 1); + ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( + host_.GetURLRequest(global_request_id)); + ASSERT_TRUE(info->detachable_handler()); + info->detachable_handler()->set_cancel_delay( + base::TimeDelta::FromMilliseconds(200)); + base::MessageLoop::current()->RunUntilIdle(); + host_.CancelRequest(filter_->child_id(), 1, true); + + // From the renderer's perspective, the request was cancelled. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + ASSERT_EQ(2U, msgs[0].size()); + ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[0][0].type()); + CheckRequestCompleteErrorCode(msgs[0][1], net::ERR_ABORTED); + + // But it continues detached. + EXPECT_EQ(1, host_.pending_requests()); + EXPECT_TRUE(info->detachable_handler()->is_detached()); + + // Wait until after the delay timer times out before we start processing any + // messages. + base::OneShotTimer<base::MessageLoop> timer; + timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(210), + base::MessageLoop::current(), &base::MessageLoop::QuitWhenIdle); + base::MessageLoop::current()->Run(); + + // The prefetch should be cancelled by now. + EXPECT_EQ(0, host_.pending_requests()); + EXPECT_EQ(1, network_delegate()->completed_requests()); + EXPECT_EQ(1, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); +} + +// If the filter has disappeared then detachable resources should continue to +// load. +TEST_F(ResourceDispatcherHostTest, DeletedFilterDetached) { + ResourceHostMsg_Request request = CreateResourceRequest( + "GET", ResourceType::PREFETCH, net::URLRequestTestJob::test_url_4()); + + ResourceHostMsg_RequestResource msg(0, 1, request); + bool msg_was_ok; + host_.OnMessageReceived(msg, filter_, &msg_was_ok); + + // Remove the filter before processing the request by simulating channel + // closure. + GlobalRequestID global_request_id(filter_->child_id(), 1); + ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( + host_.GetURLRequest(global_request_id)); + info->filter_->OnChannelClosing(); + info->filter_.reset(); + + // From the renderer's perspective, the request was cancelled. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + CheckRequestCompleteErrorCode(msgs[0][0], net::ERR_ABORTED); + + // But it continues detached. + EXPECT_EQ(1, host_.pending_requests()); + EXPECT_TRUE(info->detachable_handler()->is_detached()); + + KickOffRequest(); + + // Make sure the request wasn't canceled early. + EXPECT_EQ(1, host_.pending_requests()); + + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(0, host_.pending_requests()); + EXPECT_EQ(1, network_delegate()->completed_requests()); + EXPECT_EQ(0, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); +} + +// If the filter has disappeared (original process dies) then detachable +// resources should continue to load, even when redirected. +TEST_F(ResourceDispatcherHostTest, DeletedFilterDetachedRedirect) { + ResourceHostMsg_Request request = CreateResourceRequest( + "GET", ResourceType::PREFETCH, + net::URLRequestTestJob::test_url_redirect_to_url_2()); + + ResourceHostMsg_RequestResource msg(0, 1, request); + bool msg_was_ok; + host_.OnMessageReceived(msg, filter_, &msg_was_ok); + + // Remove the filter before processing the request by simulating channel + // closure. + GlobalRequestID global_request_id(filter_->child_id(), 1); + ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( + host_.GetURLRequest(global_request_id)); + info->filter_->OnChannelClosing(); + info->filter_.reset(); + + // From the renderer's perspective, the request was cancelled. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + CheckRequestCompleteErrorCode(msgs[0][0], net::ERR_ABORTED); + + // But it continues detached. + EXPECT_EQ(1, host_.pending_requests()); + EXPECT_TRUE(info->detachable_handler()->is_detached()); + + // Verify no redirects before resetting the filter. + net::URLRequest* url_request = host_.GetURLRequest(global_request_id); + EXPECT_EQ(1u, url_request->url_chain().size()); + KickOffRequest(); + + // Verify that a redirect was followed. + EXPECT_EQ(2u, url_request->url_chain().size()); + + // Make sure the request wasn't canceled early. + EXPECT_EQ(1, host_.pending_requests()); + + // Finish up the request. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(0, host_.pending_requests()); + EXPECT_EQ(1, network_delegate()->completed_requests()); + EXPECT_EQ(0, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); } TEST_F(ResourceDispatcherHostTest, CancelWhileStartIsDeferred) { @@ -926,11 +1127,49 @@ TEST_F(ResourceDispatcherHostTest, CancelWhileStartIsDeferred) { // calling CancelRequest. EXPECT_FALSE(was_deleted); - // flush all the pending requests + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_TRUE(was_deleted); +} + +TEST_F(ResourceDispatcherHostTest, DetachWhileStartIsDeferred) { + bool was_deleted = false; + + // Arrange to have requests deferred before starting. + TestResourceDispatcherHostDelegate delegate; + delegate.set_flags(DEFER_STARTING_REQUEST); + delegate.set_url_request_user_data(new TestUserData(&was_deleted)); + host_.SetDelegate(&delegate); + + MakeTestRequestWithResourceType(filter_.get(), 0, 1, + net::URLRequestTestJob::test_url_1(), + ResourceType::PREFETCH); // detachable type + // Cancel request must come from the renderer for a detachable resource to + // detach. + host_.CancelRequest(filter_->child_id(), 1, true); + + // Even after driving the event loop, the request has not been deleted. + EXPECT_FALSE(was_deleted); + + // However, it is still throttled because the defer happened above the + // DetachableResourceHandler. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} base::MessageLoop::current()->RunUntilIdle(); + EXPECT_FALSE(was_deleted); + + // Resume the request. + GenericResourceThrottle* throttle = + GenericResourceThrottle::active_throttle(); + ASSERT_TRUE(throttle); + throttle->Resume(); + // Now, the request completes. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + base::MessageLoop::current()->RunUntilIdle(); EXPECT_TRUE(was_deleted); + EXPECT_EQ(1, network_delegate()->completed_requests()); + EXPECT_EQ(0, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); } // Tests if cancel is called in ResourceThrottle::WillStartRequest, then the @@ -951,7 +1190,7 @@ TEST_F(ResourceDispatcherHostTest, CancelInResourceThrottleWillStartRequest) { // Check that request got canceled. ASSERT_EQ(1U, msgs[0].size()); - CheckCancelledRequestCompleteMessage(msgs[0][0]); + CheckRequestCompleteErrorCode(msgs[0][0], net::ERR_ABORTED); // Make sure URLRequest is never started. EXPECT_EQ(0, url_request_jobs_created_count_); @@ -1031,16 +1270,8 @@ TEST_F(ResourceDispatcherHostTest, CancelInDelegate) { // Check the cancellation ASSERT_EQ(1U, msgs.size()); ASSERT_EQ(1U, msgs[0].size()); - ASSERT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][0].type()); - - int request_id; - int error_code; - PickleIterator iter(msgs[0][0]); - ASSERT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id)); - ASSERT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &error_code)); - - EXPECT_EQ(net::ERR_ACCESS_DENIED, error_code); + CheckRequestCompleteErrorCode(msgs[0][0], net::ERR_ACCESS_DENIED); } // The host delegate acts as a second one so we can have some requests @@ -1078,15 +1309,23 @@ TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { ResourceHostMsg_Request request = CreateResourceRequest( "GET", ResourceType::SUB_RESOURCE, net::URLRequestTestJob::test_url_1()); - MakeTestRequest(test_filter.get(), 0, 1, - net::URLRequestTestJob::test_url_1()); + MakeTestRequestWithResourceType(test_filter.get(), 0, 1, + net::URLRequestTestJob::test_url_1(), + ResourceType::SUB_RESOURCE); // request 2 goes to us MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); // request 3 goes to the test delegate - MakeTestRequest(test_filter.get(), 0, 3, - net::URLRequestTestJob::test_url_3()); + MakeTestRequestWithResourceType(test_filter.get(), 0, 3, + net::URLRequestTestJob::test_url_3(), + ResourceType::SUB_RESOURCE); + + // request 4 goes to us + MakeTestRequestWithResourceType(filter_.get(), 0, 4, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type + // Make sure all requests have finished stage one. test_url_1 will have // finished. @@ -1098,7 +1337,9 @@ TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { // breaks the whole test. //EXPECT_EQ(3, host_.pending_requests()); - // Process each request for one level so one callback is called. + // Process test_url_2 and test_url_3 for one level so one callback is called. + // We'll cancel test_url_4 (detachable) before processing it to verify that it + // delays the cancel. for (int i = 0; i < 2; i++) EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); @@ -1106,6 +1347,13 @@ TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { host_.CancelRequestsForProcess(filter_->child_id()); test_filter->has_canceled_ = true; + // The requests should all be cancelled, except request 4, which is detached. + EXPECT_EQ(1, host_.pending_requests()); + GlobalRequestID global_request_id(filter_->child_id(), 4); + ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( + host_.GetURLRequest(global_request_id)); + ASSERT_TRUE(info->detachable_handler()->is_detached()); + // Flush all the pending requests. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -1114,11 +1362,65 @@ TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { // The test delegate should not have gotten any messages after being canceled. ASSERT_EQ(0, test_filter->received_after_canceled_); - // We should have gotten exactly one result. + // There should be two results. ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - ASSERT_EQ(1U, msgs.size()); + ASSERT_EQ(2U, msgs.size()); CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2()); + // The detachable request was cancelled by the renderer before it + // finished. From the perspective of the renderer, it should have cancelled. + ASSERT_EQ(2U, msgs[1].size()); + ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[1][0].type()); + CheckRequestCompleteErrorCode(msgs[1][1], net::ERR_ABORTED); + // But it completed anyway. For the network stack, no requests were canceled. + EXPECT_EQ(4, network_delegate()->completed_requests()); + EXPECT_EQ(0, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); +} + +TEST_F(ResourceDispatcherHostTest, TestProcessCancelDetachedTimesOut) { + MakeTestRequestWithResourceType(filter_.get(), 0, 1, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type + GlobalRequestID global_request_id(filter_->child_id(), 1); + ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( + host_.GetURLRequest(global_request_id)); + ASSERT_TRUE(info->detachable_handler()); + info->detachable_handler()->set_cancel_delay( + base::TimeDelta::FromMilliseconds(200)); + base::MessageLoop::current()->RunUntilIdle(); + + // Cancel the requests to the test process. + host_.CancelRequestsForProcess(filter_->child_id()); + EXPECT_EQ(1, host_.pending_requests()); + + // Wait until after the delay timer times out before we start processing any + // messages. + base::OneShotTimer<base::MessageLoop> timer; + timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(210), + base::MessageLoop::current(), &base::MessageLoop::QuitWhenIdle); + base::MessageLoop::current()->Run(); + + // The prefetch should be cancelled by now. + EXPECT_EQ(0, host_.pending_requests()); + + // In case any messages are still to be processed. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + base::MessageLoop::current()->RunUntilIdle(); + + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + ASSERT_EQ(1U, msgs.size()); + + // The request should have cancelled. + ASSERT_EQ(2U, msgs[0].size()); + ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[0][0].type()); + CheckRequestCompleteErrorCode(msgs[0][1], net::ERR_ABORTED); + // And not run to completion. + EXPECT_EQ(1, network_delegate()->completed_requests()); + EXPECT_EQ(1, network_delegate()->canceled_requests()); + EXPECT_EQ(0, network_delegate()->error_count()); } // Tests blocking and resuming requests. @@ -1187,6 +1489,10 @@ TEST_F(ResourceDispatcherHostTest, TestBlockingCancelingRequests) { MakeTestRequest(1, 2, net::URLRequestTestJob::test_url_2()); MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); MakeTestRequest(1, 4, net::URLRequestTestJob::test_url_1()); + // Blocked detachable resources should not delay cancellation. + MakeTestRequestWithResourceType(filter_.get(), 1, 5, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type // Flush all the pending requests. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -1219,12 +1525,21 @@ TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsProcessDies) { host_.BlockRequestsForRoute(second_filter->child_id(), 0); - MakeTestRequest(filter_.get(), 0, 1, net::URLRequestTestJob::test_url_1()); - MakeTestRequest(second_filter.get(), 0, 2, - net::URLRequestTestJob::test_url_2()); - MakeTestRequest(filter_.get(), 0, 3, net::URLRequestTestJob::test_url_3()); - MakeTestRequest(second_filter.get(), 0, 4, - net::URLRequestTestJob::test_url_1()); + MakeTestRequestWithResourceType(filter_.get(), 0, 1, + net::URLRequestTestJob::test_url_1(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(second_filter.get(), 0, 2, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 0, 3, + net::URLRequestTestJob::test_url_3(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(second_filter.get(), 0, 4, + net::URLRequestTestJob::test_url_1(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(second_filter.get(), 0, 5, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type // Simulate process death. host_.CancelRequestsForProcess(second_filter->child_id()); @@ -1236,7 +1551,8 @@ TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsProcessDies) { ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - // The 2 requests for the RVH 0 should have been processed. + // The 2 requests for the RVH 0 should have been processed. Note that + // blocked detachable requests are canceled without delay. ASSERT_EQ(2U, msgs.size()); CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); @@ -1258,13 +1574,30 @@ TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsDontLeak) { host_.BlockRequestsForRoute(filter_->child_id(), 2); host_.BlockRequestsForRoute(second_filter->child_id(), 1); - MakeTestRequest(filter_.get(), 0, 1, net::URLRequestTestJob::test_url_1()); - MakeTestRequest(filter_.get(), 1, 2, net::URLRequestTestJob::test_url_2()); - MakeTestRequest(filter_.get(), 0, 3, net::URLRequestTestJob::test_url_3()); - MakeTestRequest(second_filter.get(), 1, 4, - net::URLRequestTestJob::test_url_1()); - MakeTestRequest(filter_.get(), 2, 5, net::URLRequestTestJob::test_url_2()); - MakeTestRequest(filter_.get(), 2, 6, net::URLRequestTestJob::test_url_3()); + MakeTestRequestWithResourceType(filter_.get(), 0, 1, + net::URLRequestTestJob::test_url_1(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 1, 2, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 0, 3, + net::URLRequestTestJob::test_url_3(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(second_filter.get(), 1, 4, + net::URLRequestTestJob::test_url_1(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 2, 5, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 2, 6, + net::URLRequestTestJob::test_url_3(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 0, 7, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type + MakeTestRequestWithResourceType(second_filter.get(), 1, 8, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type host_.CancelRequestsForProcess(filter_->child_id()); host_.CancelRequestsForProcess(second_filter->child_id()); @@ -1324,22 +1657,27 @@ TEST_F(ResourceDispatcherHostTest, TooMuchOutstandingRequestsMemory) { // Saturate the number of outstanding requests for our process. for (size_t i = 0; i < kMaxRequests; ++i) { - MakeTestRequest(filter_.get(), 0, i + 1, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType(filter_.get(), 0, i + 1, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); } // Issue two more requests for our process -- these should fail immediately. - MakeTestRequest(filter_.get(), 0, kMaxRequests + 1, - net::URLRequestTestJob::test_url_2()); - MakeTestRequest(filter_.get(), 0, kMaxRequests + 2, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType(filter_.get(), 0, kMaxRequests + 1, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(filter_.get(), 0, kMaxRequests + 2, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); // Issue two requests for the second process -- these should succeed since // it is just process 0 that is saturated. - MakeTestRequest(second_filter.get(), 0, kMaxRequests + 3, - net::URLRequestTestJob::test_url_2()); - MakeTestRequest(second_filter.get(), 0, kMaxRequests + 4, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType(second_filter.get(), 0, kMaxRequests + 3, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); + MakeTestRequestWithResourceType(second_filter.get(), 0, kMaxRequests + 4, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); // Flush all the pending requests. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -1390,23 +1728,27 @@ TEST_F(ResourceDispatcherHostTest, TooManyOutstandingRequests) { // Saturate the number of outstanding requests for our process. for (size_t i = 0; i < kMaxRequestsPerProcess; ++i) { - MakeTestRequest(filter_.get(), 0, i + 1, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType(filter_.get(), 0, i + 1, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); } // Issue another request for our process -- this should fail immediately. - MakeTestRequest(filter_.get(), 0, kMaxRequestsPerProcess + 1, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType(filter_.get(), 0, kMaxRequestsPerProcess + 1, + net::URLRequestTestJob::test_url_2(), + ResourceType::SUB_RESOURCE); // Issue a request for the second process -- this should succeed, because it // is just process 0 that is saturated. - MakeTestRequest(second_filter.get(), 0, kMaxRequestsPerProcess + 2, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType( + second_filter.get(), 0, kMaxRequestsPerProcess + 2, + net::URLRequestTestJob::test_url_2(), ResourceType::SUB_RESOURCE); // Issue a request for the third process -- this should fail, because the // global limit has been reached. - MakeTestRequest(third_filter.get(), 0, kMaxRequestsPerProcess + 3, - net::URLRequestTestJob::test_url_2()); + MakeTestRequestWithResourceType( + third_filter.get(), 0, kMaxRequestsPerProcess + 3, + net::URLRequestTestJob::test_url_2(), ResourceType::SUB_RESOURCE); // Flush all the pending requests. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -1546,11 +1888,11 @@ TEST_F(ResourceDispatcherHostTest, ForbiddenDownload) { std::string response_data("<html><title>Test One</title></html>"); SetResponse(raw_headers, response_data); - // Only MAIN_FRAMEs can trigger a download. - SetResourceType(ResourceType::MAIN_FRAME); - HandleScheme("http"); - MakeTestRequest(0, 1, GURL("http:bla")); + + // Only MAIN_FRAMEs can trigger a download. + MakeTestRequestWithResourceType(filter_.get(), 0, 1, GURL("http:bla"), + ResourceType::MAIN_FRAME); // Flush all pending requests. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -1565,15 +1907,7 @@ TEST_F(ResourceDispatcherHostTest, ForbiddenDownload) { // The RequestComplete message should have had the error code of // ERR_FILE_NOT_FOUND. - int request_id; - int error_code; - - PickleIterator iter(msgs[0][0]); - EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id)); - EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &error_code)); - - EXPECT_EQ(1, request_id); - EXPECT_EQ(net::ERR_FILE_NOT_FOUND, error_code); + CheckRequestCompleteErrorCode(msgs[0][0], net::ERR_FILE_NOT_FOUND); } // Test for http://crbug.com/76202 . We don't want to destroy a @@ -1597,11 +1931,12 @@ TEST_F(ResourceDispatcherHostTest, IgnoreCancelForDownloads) { response_data.resize(1025, ' '); SetResponse(raw_headers, response_data); - SetResourceType(ResourceType::MAIN_FRAME); SetDelayedCompleteJobGeneration(true); HandleScheme("http"); - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::MAIN_FRAME); // Return some data so that the request is identified as a download // and the proper resource handlers are created. EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); @@ -1632,11 +1967,12 @@ TEST_F(ResourceDispatcherHostTest, CancelRequestsForContext) { response_data.resize(1025, ' '); SetResponse(raw_headers, response_data); - SetResourceType(ResourceType::MAIN_FRAME); SetDelayedCompleteJobGeneration(true); HandleScheme("http"); - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::MAIN_FRAME); // Return some data so that the request is identified as a download // and the proper resource handlers are created. EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); @@ -1660,6 +1996,33 @@ TEST_F(ResourceDispatcherHostTest, CancelRequestsForContext) { EXPECT_EQ(0, host_.pending_requests()); } +TEST_F(ResourceDispatcherHostTest, CancelRequestsForContextDetached) { + EXPECT_EQ(0, host_.pending_requests()); + + int render_view_id = 0; + int request_id = 1; + + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + net::URLRequestTestJob::test_url_4(), + ResourceType::PREFETCH); // detachable type + + // Simulate a cancel coming from the renderer. + host_.CancelRequest(filter_->child_id(), request_id, true); + + // Since the request had already started processing as detachable, + // the cancellation above should have been ignored and the request + // should have been detached. + EXPECT_EQ(1, host_.pending_requests()); + + // Cancelling by other methods should also leave it detached. + host_.CancelRequestsForProcess(render_view_id); + EXPECT_EQ(1, host_.pending_requests()); + + // Cancelling by context should work. + host_.CancelRequestsForContext(filter_->resource_context()); + EXPECT_EQ(0, host_.pending_requests()); +} + // Test the cancelling of requests that are being transferred to a new renderer // due to a redirection. TEST_F(ResourceDispatcherHostTest, CancelRequestsForContextTransferred) { @@ -1673,10 +2036,12 @@ TEST_F(ResourceDispatcherHostTest, CancelRequestsForContextTransferred) { std::string response_data("<html>foobar</html>"); SetResponse(raw_headers, response_data); - SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::MAIN_FRAME); + GlobalRequestID global_request_id(filter_->child_id(), request_id); host_.MarkAsTransferredNavigation(global_request_id, @@ -1713,7 +2078,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationHtml) { SetResponse("HTTP/1.1 302 Found\n" "Location: http://other.com/blech\n\n"); - SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); // Temporarily replace ContentBrowserClient with one that will trigger the @@ -1721,7 +2085,9 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationHtml) { TransfersAllNavigationsContentBrowserClient new_client; ContentBrowserClient* old_client = SetBrowserClientForTesting(&new_client); - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::MAIN_FRAME); // Now that we're blocked on the redirect, update the response and unblock by // telling the AsyncResourceHandler to follow the redirect. @@ -1784,7 +2150,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationText) { SetResponse("HTTP/1.1 302 Found\n" "Location: http://other.com/blech\n\n"); - SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); // Temporarily replace ContentBrowserClient with one that will trigger the @@ -1792,7 +2157,9 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationText) { TransfersAllNavigationsContentBrowserClient new_client; ContentBrowserClient* old_client = SetBrowserClientForTesting(&new_client); - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::MAIN_FRAME); // Now that we're blocked on the redirect, update the response and unblock by // telling the AsyncResourceHandler to follow the redirect. Use a text/plain @@ -1856,7 +2223,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { "Location: http://other.com/blech\n\n"); const std::string kResponseBody = "hello world"; - SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); // Temporarily replace ContentBrowserClient with one that will trigger the @@ -1945,7 +2311,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { SetResponse("HTTP/1.1 302 Found\n" "Location: http://other.com/blech\n\n"); - SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); // Temporarily replace ContentBrowserClient with one that will trigger the @@ -1953,7 +2318,9 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { TransfersAllNavigationsContentBrowserClient new_client; ContentBrowserClient* old_client = SetBrowserClientForTesting(&new_client); - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::MAIN_FRAME); // Now that we're blocked on the redirect, simulate hitting another redirect. SetResponse("HTTP/1.1 302 Found\n" @@ -2026,10 +2393,10 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { TEST_F(ResourceDispatcherHostTest, UnknownURLScheme) { EXPECT_EQ(0, host_.pending_requests()); - SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); - MakeTestRequest(0, 1, GURL("foo://bar")); + MakeTestRequestWithResourceType(filter_.get(), 0, 1, GURL("foo://bar"), + ResourceType::MAIN_FRAME); // Flush all pending requests. while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} @@ -2044,15 +2411,7 @@ TEST_F(ResourceDispatcherHostTest, UnknownURLScheme) { // The RequestComplete message should have the error code of // ERR_UNKNOWN_URL_SCHEME. - int request_id; - int error_code; - - PickleIterator iter(msgs[0][0]); - EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id)); - EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &error_code)); - - EXPECT_EQ(1, request_id); - EXPECT_EQ(net::ERR_UNKNOWN_URL_SCHEME, error_code); + CheckRequestCompleteErrorCode(msgs[0][0], net::ERR_UNKNOWN_URL_SCHEME); } TEST_F(ResourceDispatcherHostTest, DataReceivedACKs) { @@ -2076,6 +2435,65 @@ TEST_F(ResourceDispatcherHostTest, DataReceivedACKs) { EXPECT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][size - 1].type()); } +// Request a very large detachable resource and cancel part way. Some of the +// data should have been sent to the renderer, but not all. +TEST_F(ResourceDispatcherHostTest, DataSentBeforeDetach) { + EXPECT_EQ(0, host_.pending_requests()); + + int render_view_id = 0; + int request_id = 1; + + std::string raw_headers("HTTP\n" + "Content-type: image/jpeg\n\n"); + std::string response_data("01234567890123456789\x01foobar"); + + // Create a response larger than kMaxAllocationSize (currently 32K). Note + // that if this increase beyond 512K we'll need to make the response longer. + const int kAllocSize = 1024*512; + response_data.resize(kAllocSize, ' '); + + SetResponse(raw_headers, response_data); + SetDelayedCompleteJobGeneration(true); + HandleScheme("http"); + + MakeTestRequestWithResourceType(filter_.get(), render_view_id, request_id, + GURL("http://example.com/blah"), + ResourceType::PREFETCH); + + // Get a bit of data before cancelling. + EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); + + // Simulate a cancellation coming from the renderer. + ResourceHostMsg_CancelRequest msg(request_id); + bool msg_was_ok; + host_.OnMessageReceived(msg, filter_.get(), &msg_was_ok); + + EXPECT_EQ(1, host_.pending_requests()); + + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + // Sort all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + EXPECT_EQ(4U, msgs[0].size()); + + // Figure out how many bytes were received by the renderer. + int data_offset; + int data_length; + ASSERT_TRUE( + ExtractDataOffsetAndLength(msgs[0][2], &data_offset, &data_length)); + EXPECT_LT(0, data_length); + EXPECT_GT(kAllocSize, data_length); + + // Verify the data that was received before cancellation. The request should + // have appeared to cancel, however. + CheckSuccessfulRequestWithErrorCode( + msgs[0], + std::string(response_data.begin(), response_data.begin() + data_length), + net::ERR_ABORTED); +} + TEST_F(ResourceDispatcherHostTest, DelayedDataReceivedACKs) { EXPECT_EQ(0, host_.pending_requests()); diff --git a/content/browser/loader/resource_loader.cc b/content/browser/loader/resource_loader.cc index 94561ba..4993c82 100644 --- a/content/browser/loader/resource_loader.cc +++ b/content/browser/loader/resource_loader.cc @@ -10,6 +10,7 @@ #include "base/time/time.h" #include "content/browser/child_process_security_policy_impl.h" #include "content/browser/loader/cross_site_resource_handler.h" +#include "content/browser/loader/detachable_resource_handler.h" #include "content/browser/loader/resource_loader_delegate.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/ssl/ssl_client_auth_handler.h" @@ -439,6 +440,13 @@ void ResourceLoader::CancelRequestInternal(int error, bool from_renderer) { if (from_renderer && (info->is_download() || info->is_stream())) return; + if (from_renderer && info->detachable_handler()) { + // TODO(davidben): Fix Blink handling of prefetches so they are not + // cancelled on navigate away and end up in the local cache. + info->detachable_handler()->Detach(); + return; + } + // TODO(darin): Perhaps we should really be looking to see if the status is // IO_PENDING? bool was_pending = request_->is_pending(); diff --git a/content/browser/loader/resource_request_info_impl.cc b/content/browser/loader/resource_request_info_impl.cc index c7eed3b..ddd4f5e 100644 --- a/content/browser/loader/resource_request_info_impl.cc +++ b/content/browser/loader/resource_request_info_impl.cc @@ -106,6 +106,7 @@ ResourceRequestInfoImpl::ResourceRequestInfoImpl( base::WeakPtr<ResourceMessageFilter> filter, bool is_async) : cross_site_handler_(NULL), + detachable_handler_(NULL), process_type_(process_type), child_id_(child_id), route_id_(route_id), diff --git a/content/browser/loader/resource_request_info_impl.h b/content/browser/loader/resource_request_info_impl.h index b6f8563..0db6259 100644 --- a/content/browser/loader/resource_request_info_impl.h +++ b/content/browser/loader/resource_request_info_impl.h @@ -8,6 +8,7 @@ #include <string> #include "base/basictypes.h" +#include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -19,6 +20,7 @@ namespace content { class CrossSiteResourceHandler; +class DetachableResourceHandler; class ResourceContext; class ResourceMessageFilter; struct GlobalRequestID; @@ -117,6 +119,14 @@ class ResourceRequestInfoImpl : public ResourceRequestInfo, return should_replace_current_entry_; } + // DetachableResourceHandler for this request. May be NULL. + DetachableResourceHandler* detachable_handler() const { + return detachable_handler_; + } + void set_detachable_handler(DetachableResourceHandler* h) { + detachable_handler_ = h; + } + // Identifies the type of process (renderer, plugin, etc.) making the request. int process_type() const { return process_type_; } @@ -141,8 +151,13 @@ class ResourceRequestInfoImpl : public ResourceRequestInfo, void set_memory_cost(int cost) { memory_cost_ = cost; } private: + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + DeletedFilterDetached); + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + DeletedFilterDetachedRedirect); // Non-owning, may be NULL. CrossSiteResourceHandler* cross_site_handler_; + DetachableResourceHandler* detachable_handler_; int process_type_; int child_id_; diff --git a/content/content_browser.gypi b/content/content_browser.gypi index ff24f75..e303df1 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -661,6 +661,8 @@ 'browser/loader/certificate_resource_handler.h', 'browser/loader/cross_site_resource_handler.cc', 'browser/loader/cross_site_resource_handler.h', + 'browser/loader/detachable_resource_handler.cc', + 'browser/loader/detachable_resource_handler.h', 'browser/loader/global_routing_id.h', 'browser/loader/layered_resource_handler.cc', 'browser/loader/layered_resource_handler.h', |