diff options
author | creis@chromium.org <creis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-01 20:52:26 +0000 |
---|---|---|
committer | creis@chromium.org <creis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-01 20:52:26 +0000 |
commit | f70fe9218f1f3bb44e7c7c0b9b20aabbf4420d4b (patch) | |
tree | 572440765686472d7b064de9b3ad820cf0af6b3b | |
parent | 716db0f70a724707946e625fa8d450d42a0deaf7 (diff) | |
download | chromium_src-f70fe9218f1f3bb44e7c7c0b9b20aabbf4420d4b.zip chromium_src-f70fe9218f1f3bb44e7c7c0b9b20aabbf4420d4b.tar.gz chromium_src-f70fe9218f1f3bb44e7c7c0b9b20aabbf4420d4b.tar.bz2 |
Revert 226284 "Move TransferNavigationResourceThrottle into Cros..."
Memory errors in TransferNavigation unit tests.
> Move TransferNavigationResourceThrottle into CrossSiteResourceHandler.
>
> We now transfer requests to a new process when they are ready to commit, rather than each time a redirect occurs. This simplifies the ResourceDispatcherHost logic, and it prepares for a future CL to intercept all navigations in the browser process.
>
> BUG=238331
> TEST=No more than one process swap for repeated redirects.
>
> Review URL: https://codereview.chromium.org/15476003
TBR=creis@chromium.org
Review URL: https://codereview.chromium.org/25372005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@226293 0039d316-1c4b-4281-b951-d872f2087c98
13 files changed, 280 insertions, 345 deletions
diff --git a/content/browser/loader/cross_site_resource_handler.cc b/content/browser/loader/cross_site_resource_handler.cc index 6bac75a..90fe84a 100644 --- a/content/browser/loader/cross_site_resource_handler.cc +++ b/content/browser/loader/cross_site_resource_handler.cc @@ -9,17 +9,14 @@ #include "base/bind.h" #include "base/logging.h" #include "content/browser/cross_site_request_manager.h" -#include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/renderer_host/render_view_host_delegate.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/content_browser_client.h" #include "content/public/browser/global_request_id.h" #include "content/public/browser/resource_controller.h" #include "content/public/common/resource_response.h" #include "net/http/http_response_headers.h" -#include "net/url_request/url_request.h" namespace content { @@ -27,21 +24,13 @@ namespace { void OnCrossSiteResponseHelper(int render_process_id, int render_view_id, - const GlobalRequestID& global_request_id, - bool is_transfer, - const GURL& transfer_url, - const Referrer& referrer, - int64 frame_id) { + int request_id) { RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(render_process_id, render_view_id); - if (!rvh) - return; - RenderViewHostDelegate* delegate = rvh->GetDelegate(); - if (!delegate || !delegate->GetRendererManagementDelegate()) - return; - - delegate->GetRendererManagementDelegate()->OnCrossSiteResponse( - rvh, global_request_id, is_transfer, transfer_url, referrer, frame_id); + if (rvh && rvh->GetDelegate()->GetRendererManagementDelegate()) { + rvh->GetDelegate()->GetRendererManagementDelegate()->OnCrossSiteResponse( + rvh, GlobalRequestID(render_process_id, request_id)); + } } } // namespace @@ -88,16 +77,10 @@ bool CrossSiteResourceHandler::OnResponseStarted( ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_); - // We will need to swap processes if either (1) a redirect that requires a - // transfer occurred before we got here, or (2) a pending cross-site request - // was already in progress. Note that a swap may no longer be needed if we - // transferred back into the original process due to a redirect. - bool should_transfer = - GetContentClient()->browser()->ShouldSwapProcessesForRedirect( - info->GetContext(), request_->original_url(), request_->url()); - bool swap_needed = should_transfer || - CrossSiteRequestManager::GetInstance()-> - HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID()); + // A swap may no longer be needed if we transferred back into the original + // process due to a redirect. + bool swap_needed = CrossSiteRequestManager::GetInstance()-> + HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID()); // If this is a download, just pass the response through without doing a // cross-site check. The renderer will see it is a download and abort the @@ -107,9 +90,8 @@ bool CrossSiteResourceHandler::OnResponseStarted( // page. We should allow the navigation to finish without running the unload // handler or swapping in the pending RenderViewHost. // - // In both cases, any pending RenderViewHost (if one was created for this - // navigation) will stick around until the next cross-site navigation, since - // we are unable to tell when to destroy it. + // In both cases, the pending RenderViewHost will stick around until the next + // cross-site navigation, since we are unable to tell when to destroy it. // See RenderViewHostManager::RendererAbortedProvisionalLoad. if (!swap_needed || info->is_download() || (response->head.headers.get() && @@ -117,10 +99,8 @@ bool CrossSiteResourceHandler::OnResponseStarted( return next_handler_->OnResponseStarted(request_id, response, defer); } - // Now that we know a swap is needed and we have something to commit, we - // pause to let the UI thread run the unload handler of the previous page - // and set up a transfer if needed. - StartCrossSiteTransition(request_id, response, should_transfer); + // Tell the renderer to run the onunload event handler. + StartCrossSiteTransition(request_id, response); // Defer loading until after the onunload event handler has run. did_defer_ = *defer = true; @@ -139,24 +119,19 @@ bool CrossSiteResourceHandler::OnResponseCompleted( const net::URLRequestStatus& status, const std::string& security_info) { if (!in_cross_site_transition_) { - ResourceRequestInfoImpl* info = - ResourceRequestInfoImpl::ForRequest(request_); - // If we've already completed the transition, or we're canceling the - // request, or an error occurred with no cross-process navigation in - // progress, then we should just pass this through. if (has_started_response_ || - status.status() != net::URLRequestStatus::FAILED || - !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest( - info->GetChildID(), info->GetRouteID())) { + status.status() != net::URLRequestStatus::FAILED) { + // We've already completed the transition or we're canceling the request, + // so just pass it through. return next_handler_->OnResponseCompleted(request_id, status, security_info); } - // An error occurred. We should wait now for the cross-process transition, + // An error occured, we should wait now for the cross-site transition, // so that the error message (e.g., 404) can be displayed to the user. // Also continue with the logic below to remember that we completed // during the cross-site transition. - StartCrossSiteTransition(request_id, NULL, false); + StartCrossSiteTransition(request_id, NULL); } // We have to buffer the call until after the transition completes. @@ -211,35 +186,19 @@ void CrossSiteResourceHandler::ResumeResponse() { // telling the old RenderViewHost to run its onunload handler. void CrossSiteResourceHandler::StartCrossSiteTransition( int request_id, - ResourceResponse* response, - bool should_transfer) { + ResourceResponse* response) { in_cross_site_transition_ = true; response_ = response; // Store this handler on the ExtraRequestInfo, so that RDH can call our - // ResumeResponse method when we are ready to resume. + // ResumeResponse method when the close ACK is received. ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_); info->set_cross_site_handler(this); - DCHECK_EQ(request_id, info->GetRequestID()); - GlobalRequestID global_id(info->GetChildID(), info->GetRequestID()); - // Tell the contents responsible for this request that a cross-site response // is starting, so that it can tell its old renderer to run its onunload - // handler now. We will wait until the unload is finished and (if a transfer - // is needed) for the new renderer's request to arrive. - GURL transfer_url; - Referrer referrer; - int frame_id = -1; - if (should_transfer) { - transfer_url = request_->url(); - referrer = Referrer(GURL(request_->referrer()), info->GetReferrerPolicy()); - frame_id = info->GetFrameID(); - - ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation( - global_id, transfer_url); - } + // handler now. We will wait to hear the corresponding ClosePage_ACK. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -247,11 +206,10 @@ void CrossSiteResourceHandler::StartCrossSiteTransition( &OnCrossSiteResponseHelper, info->GetChildID(), info->GetRouteID(), - global_id, - should_transfer, - transfer_url, - referrer, - frame_id)); + request_id)); + + // TODO(creis): If the above call should fail, then we need to notify the IO + // thread to proceed anyway, using ResourceDispatcherHost::OnClosePageACK. } void CrossSiteResourceHandler::ResumeIfDeferred() { diff --git a/content/browser/loader/cross_site_resource_handler.h b/content/browser/loader/cross_site_resource_handler.h index 5c08f7e..39c3a14 100644 --- a/content/browser/loader/cross_site_resource_handler.h +++ b/content/browser/loader/cross_site_resource_handler.h @@ -47,9 +47,9 @@ class CrossSiteResourceHandler : public LayeredResourceHandler { private: // Prepare to render the cross-site response in a new RenderViewHost, by // telling the old RenderViewHost to run its onunload handler. - void StartCrossSiteTransition(int request_id, - ResourceResponse* response, - bool should_transfer); + void StartCrossSiteTransition( + int request_id, + ResourceResponse* response); void ResumeIfDeferred(); diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc index 839678e..a44715d 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/content/browser/loader/resource_dispatcher_host_impl.cc @@ -40,6 +40,7 @@ #include "content/browser/loader/stream_resource_handler.h" #include "content/browser/loader/sync_resource_handler.h" #include "content/browser/loader/throttling_resource_handler.h" +#include "content/browser/loader/transfer_navigation_resource_throttle.h" #include "content/browser/loader/upload_data_stream_builder.h" #include "content/browser/plugin_service_impl.h" #include "content/browser/renderer_host/render_view_host_delegate.h" @@ -937,9 +938,6 @@ void ResourceDispatcherHostImpl::UpdateRequestForTransfer( route_id, request_id); } - - // We should have a CrossSiteResourceHandler to finish the transfer. - DCHECK(info->cross_site_handler()); } void ResourceDispatcherHostImpl::BeginRequest( @@ -1109,11 +1107,16 @@ void ResourceDispatcherHostImpl::BeginRequest( new RedirectToFileResourceHandler(handler.Pass(), request, this)); } - // Install a CrossSiteResourceHandler for all main frame requests. This will - // let us check whether a transfer is required and pause for the unload - // handler either if so or if a cross-process navigation is already under way. - if (request_data.resource_type == ResourceType::MAIN_FRAME && - process_type == PROCESS_TYPE_RENDERER) { + // Install a CrossSiteResourceHandler if this request is coming from a + // RenderViewHost with a pending cross-site request. We only check this for + // MAIN_FRAME requests. Unblock requests only come from a blocked page, do + // not count as cross-site, otherwise it gets blocked indefinitely. + if (request_data.resource_type == ResourceType::MAIN_FRAME && + process_type == PROCESS_TYPE_RENDERER && + CrossSiteRequestManager::GetInstance()-> + HasPendingCrossSiteRequest(child_id, route_id)) { + // Wrap the event handler to be sure the current page's onunload handler + // has a chance to run before we render the new page. handler.reset(new CrossSiteResourceHandler(handler.Pass(), request)); } @@ -1137,6 +1140,12 @@ void ResourceDispatcherHostImpl::BeginRequest( throttles.push_back(new PowerSaveBlockResourceThrottle()); } + if (request_data.resource_type == ResourceType::MAIN_FRAME) { + throttles.insert( + throttles.begin(), + new TransferNavigationResourceThrottle(request)); + } + throttles.push_back( scheduler_->ScheduleRequest(child_id, route_id, request).release()); diff --git a/content/browser/loader/resource_dispatcher_host_unittest.cc b/content/browser/loader/resource_dispatcher_host_unittest.cc index 4a9e275..75f3c7b 100644 --- a/content/browser/loader/resource_dispatcher_host_unittest.cc +++ b/content/browser/loader/resource_dispatcher_host_unittest.cc @@ -9,7 +9,6 @@ #include "base/memory/scoped_vector.h" #include "base/message_loop/message_loop.h" #include "base/pickle.h" -#include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "content/browser/browser_thread_impl.h" @@ -1699,9 +1698,7 @@ TEST_F(ResourceDispatcherHostTest, CancelRequestsForContextTransferred) { EXPECT_EQ(0, host_.pending_requests()); } -// Test transferred navigations with text/html, which doesn't trigger any -// content sniffing. -TEST_F(ResourceDispatcherHostTest, TransferNavigationHtml) { +TEST_F(ResourceDispatcherHostTest, TransferNavigation) { EXPECT_EQ(0, host_.pending_requests()); int render_view_id = 0; @@ -1721,22 +1718,7 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationHtml) { MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); - // Now that we're blocked on the redirect, update the response and unblock by - // telling the AsyncResourceHandler to follow the redirect. - const std::string kResponseBody = "hello world"; - SetResponse("HTTP/1.1 200 OK\n" - "Content-Type: text/html\n\n", - kResponseBody); - ResourceHostMsg_FollowRedirect redirect_msg(request_id, false, GURL()); - bool msg_was_ok; - host_.OnMessageReceived(redirect_msg, filter_.get(), &msg_was_ok); - base::MessageLoop::current()->RunUntilIdle(); - - // Flush all the pending requests to get the response through the - // BufferedResourceHandler. - while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} - - // Restore, now that we've set up a transfer. + // Restore. SetBrowserClientForTesting(old_client); // This second filter is used to emulate a second process. @@ -1746,78 +1728,10 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationHtml) { int new_render_view_id = 1; int new_request_id = 2; - ResourceHostMsg_Request request = - CreateResourceRequest("GET", ResourceType::MAIN_FRAME, - GURL("http://other.com/blech")); - request.transferred_request_child_id = filter_->child_id(); - request.transferred_request_request_id = request_id; - - // For cleanup. - child_ids_.insert(second_filter->child_id()); - ResourceHostMsg_RequestResource transfer_request_msg( - new_render_view_id, new_request_id, request); - host_.OnMessageReceived( - transfer_request_msg, second_filter.get(), &msg_was_ok); - base::MessageLoop::current()->RunUntilIdle(); - - // Check generated messages. - ResourceIPCAccumulator::ClassifiedMessages msgs; - accum_.GetClassifiedMessages(&msgs); - - ASSERT_EQ(2U, msgs.size()); - EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); - CheckSuccessfulRequest(msgs[1], kResponseBody); -} - -// Test transferred navigations with text/plain, which causes -// BufferedResourceHandler to buffer the response to sniff the content -// before the transfer occurs. -TEST_F(ResourceDispatcherHostTest, TransferNavigationText) { - EXPECT_EQ(0, host_.pending_requests()); - - int render_view_id = 0; - int request_id = 1; - - // Configure initial request. - 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 - // transfer navigation code paths. - TransfersAllNavigationsContentBrowserClient new_client; - ContentBrowserClient* old_client = SetBrowserClientForTesting(&new_client); - - MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); - - // 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 - // MIME type, which causes BufferedResourceHandler to buffer it before the - // transfer occurs. const std::string kResponseBody = "hello world"; SetResponse("HTTP/1.1 200 OK\n" "Content-Type: text/plain\n\n", kResponseBody); - ResourceHostMsg_FollowRedirect redirect_msg(request_id, false, GURL()); - bool msg_was_ok; - host_.OnMessageReceived(redirect_msg, filter_.get(), &msg_was_ok); - base::MessageLoop::current()->RunUntilIdle(); - - // Flush all the pending requests to get the response through the - // BufferedResourceHandler. - while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} - - // Restore, now that we've set up a transfer. - SetBrowserClientForTesting(old_client); - - // This second filter is used to emulate a second process. - scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter( - this, browser_context_->GetResourceContext()); - - int new_render_view_id = 1; - int new_request_id = 2; ResourceHostMsg_Request request = CreateResourceRequest("GET", ResourceType::MAIN_FRAME, @@ -1829,17 +1743,20 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationText) { child_ids_.insert(second_filter->child_id()); ResourceHostMsg_RequestResource transfer_request_msg( new_render_view_id, new_request_id, request); + bool msg_was_ok; host_.OnMessageReceived( transfer_request_msg, second_filter.get(), &msg_was_ok); base::MessageLoop::current()->RunUntilIdle(); + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + // Check generated messages. ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - ASSERT_EQ(2U, msgs.size()); - EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); - CheckSuccessfulRequest(msgs[1], kResponseBody); + ASSERT_EQ(1U, msgs.size()); + CheckSuccessfulRequest(msgs[0], kResponseBody); } TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { @@ -1852,7 +1769,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { // Configure initial request. SetResponse("HTTP/1.1 302 Found\n" "Location: http://other.com/blech\n\n"); - const std::string kResponseBody = "hello world"; SetResourceType(ResourceType::MAIN_FRAME); HandleScheme("http"); @@ -1880,19 +1796,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { host_.OnMessageReceived( first_request_msg, first_filter.get(), &msg_was_ok); base::MessageLoop::current()->RunUntilIdle(); - - // Now that we're blocked on the redirect, update the response and unblock - // by telling the AsyncResourceHandler to follow the redirect. - SetResponse("HTTP/1.1 200 OK\n" - "Content-Type: text/html\n\n", - kResponseBody); - ResourceHostMsg_FollowRedirect redirect_msg(request_id, false, GURL()); - host_.OnMessageReceived(redirect_msg, first_filter.get(), &msg_was_ok); - base::MessageLoop::current()->RunUntilIdle(); - - // Flush all the pending requests to get the response through the - // BufferedResourceHandler. - while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} } // The first filter is now deleted, as if the child process died. @@ -1909,6 +1812,11 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { int new_render_view_id = 1; int new_request_id = 2; + const std::string kResponseBody = "hello world"; + SetResponse("HTTP/1.1 200 OK\n" + "Content-Type: text/plain\n\n", + kResponseBody); + ResourceHostMsg_Request request = CreateResourceRequest("GET", ResourceType::MAIN_FRAME, GURL("http://other.com/blech")); @@ -1924,16 +1832,18 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { transfer_request_msg, second_filter.get(), &msg_was_ok); base::MessageLoop::current()->RunUntilIdle(); + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + // Check generated messages. ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - ASSERT_EQ(2U, msgs.size()); - EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); - CheckSuccessfulRequest(msgs[1], kResponseBody); + ASSERT_EQ(1U, msgs.size()); + CheckSuccessfulRequest(msgs[0], kResponseBody); } -TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { +TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) { EXPECT_EQ(0, host_.pending_requests()); int render_view_id = 0; @@ -1953,30 +1863,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); - // Now that we're blocked on the redirect, simulate hitting another redirect. - SetResponse("HTTP/1.1 302 Found\n" - "Location: http://other.com/blerg\n\n"); - ResourceHostMsg_FollowRedirect redirect_msg(request_id, false, GURL()); - bool msg_was_ok; - host_.OnMessageReceived(redirect_msg, filter_.get(), &msg_was_ok); - base::MessageLoop::current()->RunUntilIdle(); - - // Now that we're blocked on the second redirect, update the response and - // unblock by telling the AsyncResourceHandler to follow the redirect. - // Again, use text/plain to force BufferedResourceHandler to buffer before - // the transfer. - const std::string kResponseBody = "hello world"; - SetResponse("HTTP/1.1 200 OK\n" - "Content-Type: text/plain\n\n", - kResponseBody); - ResourceHostMsg_FollowRedirect redirect_msg2(request_id, false, GURL()); - host_.OnMessageReceived(redirect_msg2, filter_.get(), &msg_was_ok); - base::MessageLoop::current()->RunUntilIdle(); - - // Flush all the pending requests to get the response through the - // BufferedResourceHandler. - while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} - // Restore. SetBrowserClientForTesting(old_client); @@ -1987,6 +1873,13 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { int new_render_view_id = 1; int new_request_id = 2; + // Delay the start of the next request so that we can setup the response for + // the next URL. + SetDelayedStartJobGeneration(true); + + SetResponse("HTTP/1.1 302 Found\n" + "Location: http://other.com/blerg\n\n"); + ResourceHostMsg_Request request = CreateResourceRequest("GET", ResourceType::MAIN_FRAME, GURL("http://other.com/blech")); @@ -1997,8 +1890,24 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { child_ids_.insert(second_filter->child_id()); ResourceHostMsg_RequestResource transfer_request_msg( new_render_view_id, new_request_id, request); + bool msg_was_ok; host_.OnMessageReceived( transfer_request_msg, second_filter.get(), &msg_was_ok); + base::MessageLoop::current()->RunUntilIdle(); + + // Response data for "http://other.com/blerg": + const std::string kResponseBody = "hello world"; + SetResponse("HTTP/1.1 200 OK\n" + "Content-Type: text/plain\n\n", + kResponseBody); + + // OK, let the redirect happen. + SetDelayedStartJobGeneration(false); + CompleteStartRequest(second_filter.get(), new_request_id); + base::MessageLoop::current()->RunUntilIdle(); + + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} // Verify that we update the ResourceRequestInfo. GlobalRequestID global_request_id(second_filter->child_id(), new_request_id); @@ -2009,16 +1918,25 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { EXPECT_EQ(new_request_id, info->GetRequestID()); EXPECT_EQ(second_filter, info->filter()); - // Let request complete. + // Now, simulate the renderer choosing to follow the redirect. + ResourceHostMsg_FollowRedirect redirect_msg( + new_request_id, false, GURL()); + host_.OnMessageReceived(redirect_msg, second_filter.get(), &msg_was_ok); base::MessageLoop::current()->RunUntilIdle(); + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + // Check generated messages. ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - ASSERT_EQ(2U, msgs.size()); + ASSERT_EQ(1U, msgs.size()); + + // We should have received a redirect followed by a "normal" payload. EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); - CheckSuccessfulRequest(msgs[1], kResponseBody); + msgs[0].erase(msgs[0].begin()); + CheckSuccessfulRequest(msgs[0], kResponseBody); } TEST_F(ResourceDispatcherHostTest, UnknownURLScheme) { diff --git a/content/browser/loader/resource_loader.cc b/content/browser/loader/resource_loader.cc index 8f70ab5..c99fe5b 100644 --- a/content/browser/loader/resource_loader.cc +++ b/content/browser/loader/resource_loader.cc @@ -9,7 +9,6 @@ #include "base/metrics/histogram.h" #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/resource_loader_delegate.h" #include "content/browser/loader/resource_request_info_impl.h" #include "content/browser/ssl/ssl_client_auth_handler.h" @@ -166,10 +165,10 @@ void ResourceLoader::MarkAsTransferring(const GURL& target_url) { } void ResourceLoader::CompleteTransfer() { - DCHECK_EQ(DEFERRED_READ, deferred_stage_); + DCHECK_EQ(DEFERRED_REDIRECT, deferred_stage_); is_transferring_ = false; - GetRequestInfo()->cross_site_handler()->ResumeResponse(); + Resume(); } ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() { diff --git a/content/browser/loader/transfer_navigation_resource_throttle.cc b/content/browser/loader/transfer_navigation_resource_throttle.cc new file mode 100644 index 0000000..8021bce --- /dev/null +++ b/content/browser/loader/transfer_navigation_resource_throttle.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2012 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/transfer_navigation_resource_throttle.h" + +#include "base/bind.h" +#include "content/browser/loader/resource_dispatcher_host_impl.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/global_request_id.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/common/referrer.h" +#include "net/url_request/url_request.h" + +namespace content { + +namespace { + +void RequestTransferURLOnUIThread(int render_process_id, + int render_view_id, + const GURL& new_url, + const Referrer& referrer, + WindowOpenDisposition window_open_disposition, + int64 frame_id, + const GlobalRequestID& global_request_id) { + RenderViewHost* rvh = + RenderViewHost::FromID(render_process_id, render_view_id); + if (!rvh) + return; + + RenderViewHostDelegate* delegate = rvh->GetDelegate(); + if (!delegate) + return; + + // We don't know whether the original request had |user_action| set to true. + // However, since we force the navigation to be in the current tab, it doesn't + // matter. + delegate->RequestTransferURL( + new_url, referrer, window_open_disposition, + frame_id, global_request_id, false, true); +} + +} // namespace + +TransferNavigationResourceThrottle::TransferNavigationResourceThrottle( + net::URLRequest* request) + : request_(request) { +} + +TransferNavigationResourceThrottle::~TransferNavigationResourceThrottle() { +} + +void TransferNavigationResourceThrottle::WillRedirectRequest( + const GURL& new_url, + bool* defer) { + const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_); + + // If a toplevel request is redirecting across extension extents, we want to + // switch processes. We do this by deferring the redirect and resuming the + // request once the navigation controller properly assigns the right process + // to host the new URL. + // TODO(mpcomplete): handle for cases other than extensions (e.g. WebUI). + ResourceContext* resource_context = info->GetContext(); + if (GetContentClient()->browser()->ShouldSwapProcessesForRedirect( + resource_context, request_->url(), new_url)) { + int render_process_id, render_view_id; + if (info->GetAssociatedRenderView(&render_process_id, &render_view_id)) { + GlobalRequestID global_id(info->GetChildID(), info->GetRequestID()); + + ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id, + new_url); + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&RequestTransferURLOnUIThread, + render_process_id, + render_view_id, + new_url, + Referrer(GURL(request_->referrer()), info->GetReferrerPolicy()), + CURRENT_TAB, + info->GetFrameID(), + global_id)); + + *defer = true; + } + } +} + +} // namespace content diff --git a/content/browser/loader/transfer_navigation_resource_throttle.h b/content/browser/loader/transfer_navigation_resource_throttle.h new file mode 100644 index 0000000..6d08c2a --- /dev/null +++ b/content/browser/loader/transfer_navigation_resource_throttle.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 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_TRANSFER_NAVIGATION_RESOURCE_THROTTLE_H_ +#define CONTENT_BROWSER_LOADER_TRANSFER_NAVIGATION_RESOURCE_THROTTLE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/resource_throttle.h" + +namespace net { +class URLRequest; +} + +namespace content { + +// This ResourceThrottle checks whether a navigation redirect will cause a +// renderer process swap. When that happens, we remember the request so +// that we can transfer it to be handled by the new renderer. This fixes +// http://crbug.com/79520 +class TransferNavigationResourceThrottle : public ResourceThrottle { + public: + explicit TransferNavigationResourceThrottle(net::URLRequest* request); + virtual ~TransferNavigationResourceThrottle(); + + // ResourceThrottle implementation: + virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE; + + private: + net::URLRequest* request_; + + DISALLOW_COPY_AND_ASSIGN(TransferNavigationResourceThrottle); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_LOADER_TRANSFER_NAVIGATION_RESOURCE_THROTTLE_H_ diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h index aac79cf..997452a 100644 --- a/content/browser/renderer_host/render_view_host_delegate.h +++ b/content/browser/renderer_host/render_view_host_delegate.h @@ -93,15 +93,10 @@ class CONTENT_EXPORT RenderViewHostDelegate { const base::TimeTicks& proceed_time) = 0; // The |pending_render_view_host| is ready to commit a page. The delegate - // should ensure that the old RenderViewHost runs its unload handler first - // and determine whether a RenderViewHost transfer is needed. + // should ensure that the old RenderViewHost runs its unload handler first. virtual void OnCrossSiteResponse( RenderViewHost* pending_render_view_host, - const GlobalRequestID& global_request_id, - bool is_transfer, - const GURL& transfer_url, - const Referrer& referrer, - int64 frame_id) = 0; + const GlobalRequestID& global_request_id) = 0; protected: virtual ~RendererManagement() {} diff --git a/content/browser/web_contents/render_view_host_manager.cc b/content/browser/web_contents/render_view_host_manager.cc index 53d7681..c72e165 100644 --- a/content/browser/web_contents/render_view_host_manager.cc +++ b/content/browser/web_contents/render_view_host_manager.cc @@ -33,21 +33,12 @@ namespace content { -RenderViewHostManager::PendingNavigationParams::PendingNavigationParams() - : is_transfer(false), frame_id(-1) { +RenderViewHostManager::PendingNavigationParams::PendingNavigationParams() { } RenderViewHostManager::PendingNavigationParams::PendingNavigationParams( - const GlobalRequestID& global_request_id, - bool is_transfer, - const GURL& transfer_url, - Referrer referrer, - int64 frame_id) - : global_request_id(global_request_id), - is_transfer(is_transfer), - transfer_url(transfer_url), - referrer(referrer), - frame_id(frame_id) { + const GlobalRequestID& global_request_id) + : global_request_id(global_request_id) { } RenderViewHostManager::RenderViewHostManager( @@ -248,24 +239,8 @@ void RenderViewHostManager::SwappedOut(RenderViewHost* render_view_host) { return; } - // Now that the unload handler has run, we need to either initiate the - // pending transfer (if there is one) or resume the paused response (if not). - // TODO(creis): The blank swapped out page is visible during this time, but - // we can shorten this by delivering the response directly, rather than - // forcing an identical request to be made. - if (pending_nav_params_->is_transfer) { - // We don't know whether the original request had |user_action| set to true. - // However, since we force the navigation to be in the current tab, it - // doesn't matter. - render_view_host->GetDelegate()->RequestTransferURL( - pending_nav_params_->transfer_url, - pending_nav_params_->referrer, - CURRENT_TAB, - pending_nav_params_->frame_id, - pending_nav_params_->global_request_id, - false, - true); - } else if (pending_render_view_host_) { + // Now that the unload handler has run, we need to resume the paused response. + if (pending_render_view_host_) { RenderProcessHostImpl* pending_process = static_cast<RenderProcessHostImpl*>( pending_render_view_host_->GetProcess()); @@ -403,32 +378,22 @@ void RenderViewHostManager::ShouldClosePage( void RenderViewHostManager::OnCrossSiteResponse( RenderViewHost* pending_render_view_host, - const GlobalRequestID& global_request_id, - bool is_transfer, - const GURL& transfer_url, - const Referrer& referrer, - int64 frame_id) { - // This should be called either when the pending RVH is ready to commit or - // when we realize that the current RVH's request requires a transfer. - DCHECK( - pending_render_view_host == pending_render_view_host_ || - pending_render_view_host == render_view_host_); - - // TODO(creis): Eventually we will want to check all navigation responses - // here, but currently we pass information for a transfer if - // ShouldSwapProcessesForRedirect returned true in the network stack. - // In that case, we should set up a transfer after the unload handler runs. - // If is_transfer is false, we will just run the unload handler and resume. - pending_nav_params_.reset(new PendingNavigationParams( - global_request_id, is_transfer, transfer_url, referrer, frame_id)); + const GlobalRequestID& global_request_id) { + // This should be called when the pending RVH is ready to commit. + DCHECK_EQ(pending_render_view_host_, pending_render_view_host); + + // Remember the request ID until the unload handler has run. + pending_nav_params_.reset(new PendingNavigationParams(global_request_id)); // Run the unload handler of the current page. SwapOutOldPage(); } void RenderViewHostManager::SwapOutOldPage() { - // Should only see this while we have a pending renderer or transfer. - CHECK(cross_navigation_pending_ || pending_nav_params_.get()); + // Should only see this while we have a pending renderer. + if (!cross_navigation_pending_) + return; + DCHECK(pending_render_view_host_); // Tell the old renderer it is being swapped out. This will fire the unload // handler (without firing the beforeunload handler a second time). When the @@ -441,8 +406,7 @@ void RenderViewHostManager::SwapOutOldPage() { // means it is not a download or unsafe page, and we are going to perform the // navigation. Thus, we no longer need to remember that the RenderViewHost // is part of a pending cross-site request. - if (pending_render_view_host_) - pending_render_view_host_->SetHasPendingCrossSiteRequest(false); + pending_render_view_host_->SetHasPendingCrossSiteRequest(false); } void RenderViewHostManager::Observe( @@ -960,12 +924,7 @@ RenderViewHostImpl* RenderViewHostManager::UpdateRendererStateForNavigate( DCHECK(!pending_render_view_host_->are_navigations_suspended()); bool is_transfer = entry.transferred_global_request_id() != GlobalRequestID(); - if (is_transfer) { - // We don't need to stop the old renderer or run beforeunload/unload - // handlers, because those have already been done. - DCHECK(pending_nav_params_->global_request_id == - entry.transferred_global_request_id()); - } else { + if (!is_transfer) { // Also make sure the old render view stops, in case a load is in // progress. (We don't want to do this for transfers, since it will // interrupt the transfer with an unexpected DidStopLoading.) @@ -974,13 +933,13 @@ RenderViewHostImpl* RenderViewHostManager::UpdateRendererStateForNavigate( pending_render_view_host_->SetNavigationsSuspended(true, base::TimeTicks()); - - // Tell the CrossSiteRequestManager that this RVH has a pending cross-site - // request, so that ResourceDispatcherHost will know to tell us to run the - // old page's unload handler before it sends the response. - pending_render_view_host_->SetHasPendingCrossSiteRequest(true); } + // Tell the CrossSiteRequestManager that this RVH has a pending cross-site + // request, so that ResourceDispatcherHost will know to tell us to run the + // old page's unload handler before it sends the response. + pending_render_view_host_->SetHasPendingCrossSiteRequest(true); + // We now have a pending RVH. DCHECK(!cross_navigation_pending_); cross_navigation_pending_ = true; diff --git a/content/browser/web_contents/render_view_host_manager.h b/content/browser/web_contents/render_view_host_manager.h index aef6383..aacc90b 100644 --- a/content/browser/web_contents/render_view_host_manager.h +++ b/content/browser/web_contents/render_view_host_manager.h @@ -14,7 +14,6 @@ #include "content/common/content_export.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" -#include "content/public/common/referrer.h" namespace content { @@ -218,11 +217,7 @@ class CONTENT_EXPORT RenderViewHostManager const base::TimeTicks& proceed_time) OVERRIDE; virtual void OnCrossSiteResponse( RenderViewHost* pending_render_view_host, - const GlobalRequestID& global_request_id, - bool is_transfer, - const GURL& transfer_url, - const Referrer& referrer, - int64 frame_id) OVERRIDE; + const GlobalRequestID& global_request_id) OVERRIDE; // NotificationObserver implementation. virtual void Observe(int type, @@ -240,8 +235,7 @@ class CONTENT_EXPORT RenderViewHostManager RenderViewHostImpl* GetSwappedOutRenderViewHost(SiteInstance* instance); // Runs the unload handler in the current page, when we know that a pending - // cross-process navigation is going to commit. We may initiate a transfer - // to a new process after this completes or times out. + // cross-process navigation is going to commit. void SwapOutOldPage(); private: @@ -249,34 +243,13 @@ class CONTENT_EXPORT RenderViewHostManager friend class TestWebContents; // Tracks information about a navigation while a cross-process transition is - // in progress, in case we need to transfer it to a new RenderViewHost. + // in progress. + // TODO(creis): Add transfer navigation params for http://crbug.com/238331. struct PendingNavigationParams { PendingNavigationParams(); - PendingNavigationParams(const GlobalRequestID& global_request_id, - bool is_transfer, - const GURL& transfer_url, - Referrer referrer, - int64 frame_id); - - // The child ID and request ID for the pending navigation. Present whether - // |is_transfer| is true or false. - GlobalRequestID global_request_id; - - // Whether this pending navigation needs to be transferred to another - // process than the one it was going to commit in. If so, the - // |transfer_url|, |referrer|, and |frame_id| parameters will be set. - bool is_transfer; - - // If |is_transfer|, this is the destination URL to request in the new - // process. - GURL transfer_url; + explicit PendingNavigationParams(const GlobalRequestID& global_request_id); - // If |is_transfer|, this is the referrer to use for the request in the new - // process. - Referrer referrer; - - // If |is_transfer|, this is the frame ID to use in RequestTransferURL. - int64 frame_id; + GlobalRequestID global_request_id; }; // Returns whether this tab should transition to a new renderer for diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index e0c955f..2ebe303 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -113,43 +113,34 @@ // a new RenderViewHost dedicated to the new SiteInstance. This works as // follows: // -// - RVHM::Navigate determines whether the destination is cross-site, and if so, +// - Navigate determines whether the destination is cross-site, and if so, // it creates a pending_render_view_host_. // - The pending RVH is "suspended," so that no navigation messages are sent to -// its renderer until the beforeunload JavaScript handler has a chance to +// its renderer until the onbeforeunload JavaScript handler has a chance to // run in the current RVH. // - The pending RVH tells CrossSiteRequestManager (a thread-safe singleton) -// that it has a pending cross-site request. We will check this on the IO -// thread when deciding how to handle the response. -// - The current RVH runs its beforeunload handler. If it returns false, we +// that it has a pending cross-site request. ResourceDispatcherHost will +// check for this when the response arrives. +// - The current RVH runs its onbeforeunload handler. If it returns false, we // cancel all the pending logic. Otherwise we allow the pending RVH to send // the navigation request to its renderer. // - ResourceDispatcherHost receives a ResourceRequest on the IO thread for the -// main resource load on the pending RVH. It creates a -// CrossSiteResourceHandler to check whether a process swap is needed when -// the request is ready to commit. +// main resource load on the pending RVH. It checks CrossSiteRequestManager +// to see that it is a cross-site request, and installs a +// CrossSiteResourceHandler. // - When RDH receives a response, the BufferedResourceHandler determines // whether it is a download. If so, it sends a message to the new renderer // causing it to cancel the request, and the download proceeds. For now, the // pending RVH remains until the next DidNavigate event for this // WebContentsImpl. This isn't ideal, but it doesn't affect any functionality. // - After RDH receives a response and determines that it is safe and not a -// download, the CrossSiteResourceHandler checks whether a process swap is -// needed (either because CrossSiteRequestManager has state for it or because -// a transfer was needed for a redirect). -// - If so, CrossSiteResourceHandler pauses the response to first run the old -// page's unload handler. It does this by asynchronously calling the -// OnCrossSiteResponse method of RenderViewHostManager on the UI thread, which -// sends a SwapOut message to the current RVH. -// - Once the unload handler is finished, RVHM::SwappedOut checks if a transfer -// to a new process is needed, based on the stored pending_nav_params_. (This -// is independent of whether we started out with a cross-process navigation.) -// - If not, it just tells the ResourceDispatcherHost to resume the response -// to its current RenderViewHost. -// - If so, it cancels the current pending RenderViewHost and sets up a new -// navigation using RequestTransferURL. When the transferred request -// arrives in the ResourceDispatcherHost, we transfer the response and -// resume it. +// download, it pauses the response to first run the old page's onunload +// handler. It does this by asynchronously calling the OnCrossSiteResponse +// method of WebContentsImpl on the UI thread, which sends a SwapOut message +// to the current RVH. +// - Once the onunload handler is finished, a SwapOut_ACK message is sent to +// the ResourceDispatcherHost, who unpauses the response. Data is then sent +// to the pending RVH. // - The pending renderer sends a FrameNavigate message that invokes the // DidNavigate method. This replaces the current RVH with the // pending RVH. diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc index c11cb51..aded7ae 100644 --- a/content/browser/web_contents/web_contents_impl_unittest.cc +++ b/content/browser/web_contents/web_contents_impl_unittest.cc @@ -1062,7 +1062,7 @@ TEST_F(WebContentsImplTest, CrossSiteCantPreemptAfterUnload) { // Simulate the pending renderer's response, which leads to an unload request // being sent to orig_rvh. contents()->GetRenderManagerForTesting()->OnCrossSiteResponse( - pending_rvh, GlobalRequestID(0, 0), false, GURL(), Referrer(), 1); + pending_rvh, GlobalRequestID(0, 0)); // Suppose the original renderer navigates now, while the unload request is in // flight. We should ignore it, wait for the unload ack, and let the pending diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 7835950..29e22da 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -690,6 +690,8 @@ 'browser/loader/sync_resource_handler.h', 'browser/loader/throttling_resource_handler.cc', 'browser/loader/throttling_resource_handler.h', + 'browser/loader/transfer_navigation_resource_throttle.cc', + 'browser/loader/transfer_navigation_resource_throttle.h', 'browser/loader/upload_data_stream_builder.cc', 'browser/loader/upload_data_stream_builder.h', 'browser/mach_broker_mac.h', |