diff options
author | creis@chromium.org <creis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-01 20:12:54 +0000 |
---|---|---|
committer | creis@chromium.org <creis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-01 20:12:54 +0000 |
commit | 37403f36a6984400c226fda2afd139be51043a1c (patch) | |
tree | 72b6ca06acd47399f37a784a8bb2c96d8dde44fc /content | |
parent | 584a1aaa6645e49fc05fb7a43c7d6693c329f01e (diff) | |
download | chromium_src-37403f36a6984400c226fda2afd139be51043a1c.zip chromium_src-37403f36a6984400c226fda2afd139be51043a1c.tar.gz chromium_src-37403f36a6984400c226fda2afd139be51043a1c.tar.bz2 |
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
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@226284 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
13 files changed, 345 insertions, 280 deletions
diff --git a/content/browser/loader/cross_site_resource_handler.cc b/content/browser/loader/cross_site_resource_handler.cc index 90fe84a..6bac75a 100644 --- a/content/browser/loader/cross_site_resource_handler.cc +++ b/content/browser/loader/cross_site_resource_handler.cc @@ -9,14 +9,17 @@ #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 { @@ -24,13 +27,21 @@ namespace { void OnCrossSiteResponseHelper(int render_process_id, int render_view_id, - int request_id) { + const GlobalRequestID& global_request_id, + bool is_transfer, + const GURL& transfer_url, + const Referrer& referrer, + int64 frame_id) { RenderViewHostImpl* rvh = RenderViewHostImpl::FromID(render_process_id, render_view_id); - if (rvh && rvh->GetDelegate()->GetRendererManagementDelegate()) { - rvh->GetDelegate()->GetRendererManagementDelegate()->OnCrossSiteResponse( - rvh, GlobalRequestID(render_process_id, request_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); } } // namespace @@ -77,10 +88,16 @@ bool CrossSiteResourceHandler::OnResponseStarted( ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(request_); - // 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()); + // 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()); // 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 @@ -90,8 +107,9 @@ 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, the pending RenderViewHost will stick around until the next - // cross-site navigation, since we are unable to tell when to destroy it. + // 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. // See RenderViewHostManager::RendererAbortedProvisionalLoad. if (!swap_needed || info->is_download() || (response->head.headers.get() && @@ -99,8 +117,10 @@ bool CrossSiteResourceHandler::OnResponseStarted( return next_handler_->OnResponseStarted(request_id, response, defer); } - // Tell the renderer to run the onunload event handler. - StartCrossSiteTransition(request_id, response); + // 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); // Defer loading until after the onunload event handler has run. did_defer_ = *defer = true; @@ -119,19 +139,24 @@ 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) { - // We've already completed the transition or we're canceling the request, - // so just pass it through. + status.status() != net::URLRequestStatus::FAILED || + !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest( + info->GetChildID(), info->GetRouteID())) { return next_handler_->OnResponseCompleted(request_id, status, security_info); } - // An error occured, we should wait now for the cross-site transition, + // An error occurred. We should wait now for the cross-process 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); + StartCrossSiteTransition(request_id, NULL, false); } // We have to buffer the call until after the transition completes. @@ -186,19 +211,35 @@ void CrossSiteResourceHandler::ResumeResponse() { // telling the old RenderViewHost to run its onunload handler. void CrossSiteResourceHandler::StartCrossSiteTransition( int request_id, - ResourceResponse* response) { + ResourceResponse* response, + bool should_transfer) { in_cross_site_transition_ = true; response_ = response; // Store this handler on the ExtraRequestInfo, so that RDH can call our - // ResumeResponse method when the close ACK is received. + // ResumeResponse method when we are ready to resume. 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 to hear the corresponding ClosePage_ACK. + // 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); + } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -206,10 +247,11 @@ void CrossSiteResourceHandler::StartCrossSiteTransition( &OnCrossSiteResponseHelper, info->GetChildID(), info->GetRouteID(), - request_id)); - - // TODO(creis): If the above call should fail, then we need to notify the IO - // thread to proceed anyway, using ResourceDispatcherHost::OnClosePageACK. + global_id, + should_transfer, + transfer_url, + referrer, + frame_id)); } void CrossSiteResourceHandler::ResumeIfDeferred() { diff --git a/content/browser/loader/cross_site_resource_handler.h b/content/browser/loader/cross_site_resource_handler.h index 39c3a14..5c08f7e 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); + void StartCrossSiteTransition(int request_id, + ResourceResponse* response, + bool should_transfer); void ResumeIfDeferred(); diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc index a44715d..839678e 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/content/browser/loader/resource_dispatcher_host_impl.cc @@ -40,7 +40,6 @@ #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" @@ -938,6 +937,9 @@ void ResourceDispatcherHostImpl::UpdateRequestForTransfer( route_id, request_id); } + + // We should have a CrossSiteResourceHandler to finish the transfer. + DCHECK(info->cross_site_handler()); } void ResourceDispatcherHostImpl::BeginRequest( @@ -1107,16 +1109,11 @@ void ResourceDispatcherHostImpl::BeginRequest( new RedirectToFileResourceHandler(handler.Pass(), request, this)); } - // 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. + // 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) { handler.reset(new CrossSiteResourceHandler(handler.Pass(), request)); } @@ -1140,12 +1137,6 @@ 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 75f3c7b..4a9e275 100644 --- a/content/browser/loader/resource_dispatcher_host_unittest.cc +++ b/content/browser/loader/resource_dispatcher_host_unittest.cc @@ -9,6 +9,7 @@ #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" @@ -1698,7 +1699,9 @@ TEST_F(ResourceDispatcherHostTest, CancelRequestsForContextTransferred) { EXPECT_EQ(0, host_.pending_requests()); } -TEST_F(ResourceDispatcherHostTest, TransferNavigation) { +// Test transferred navigations with text/html, which doesn't trigger any +// content sniffing. +TEST_F(ResourceDispatcherHostTest, TransferNavigationHtml) { EXPECT_EQ(0, host_.pending_requests()); int render_view_id = 0; @@ -1718,7 +1721,22 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigation) { MakeTestRequest(render_view_id, request_id, GURL("http://example.com/blah")); - // Restore. + // 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. SetBrowserClientForTesting(old_client); // This second filter is used to emulate a second process. @@ -1728,10 +1746,78 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigation) { 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, @@ -1743,20 +1829,17 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigation) { 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(1U, msgs.size()); - CheckSuccessfulRequest(msgs[0], kResponseBody); + ASSERT_EQ(2U, msgs.size()); + EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); + CheckSuccessfulRequest(msgs[1], kResponseBody); } TEST_F(ResourceDispatcherHostTest, TransferNavigationWithProcessCrash) { @@ -1769,6 +1852,7 @@ 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"); @@ -1796,6 +1880,19 @@ 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. @@ -1812,11 +1909,6 @@ 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")); @@ -1832,18 +1924,16 @@ 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(1U, msgs.size()); - CheckSuccessfulRequest(msgs[0], kResponseBody); + ASSERT_EQ(2U, msgs.size()); + EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); + CheckSuccessfulRequest(msgs[1], kResponseBody); } -TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) { +TEST_F(ResourceDispatcherHostTest, TransferNavigationWithTwoRedirects) { EXPECT_EQ(0, host_.pending_requests()); int render_view_id = 0; @@ -1863,6 +1953,30 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) { 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); @@ -1873,13 +1987,6 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) { 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")); @@ -1890,24 +1997,8 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) { 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); @@ -1918,25 +2009,16 @@ TEST_F(ResourceDispatcherHostTest, TransferNavigationAndThenRedirect) { EXPECT_EQ(new_request_id, info->GetRequestID()); EXPECT_EQ(second_filter, info->filter()); - // 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); + // Let request complete. base::MessageLoop::current()->RunUntilIdle(); - // Flush all the pending requests. - while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} - // Check generated messages. ResourceIPCAccumulator::ClassifiedMessages msgs; accum_.GetClassifiedMessages(&msgs); - ASSERT_EQ(1U, msgs.size()); - - // We should have received a redirect followed by a "normal" payload. + ASSERT_EQ(2U, msgs.size()); EXPECT_EQ(ResourceMsg_ReceivedRedirect::ID, msgs[0][0].type()); - msgs[0].erase(msgs[0].begin()); - CheckSuccessfulRequest(msgs[0], kResponseBody); + CheckSuccessfulRequest(msgs[1], kResponseBody); } TEST_F(ResourceDispatcherHostTest, UnknownURLScheme) { diff --git a/content/browser/loader/resource_loader.cc b/content/browser/loader/resource_loader.cc index c99fe5b..8f70ab5 100644 --- a/content/browser/loader/resource_loader.cc +++ b/content/browser/loader/resource_loader.cc @@ -9,6 +9,7 @@ #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" @@ -165,10 +166,10 @@ void ResourceLoader::MarkAsTransferring(const GURL& target_url) { } void ResourceLoader::CompleteTransfer() { - DCHECK_EQ(DEFERRED_REDIRECT, deferred_stage_); + DCHECK_EQ(DEFERRED_READ, deferred_stage_); is_transferring_ = false; - Resume(); + GetRequestInfo()->cross_site_handler()->ResumeResponse(); } ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo() { diff --git a/content/browser/loader/transfer_navigation_resource_throttle.cc b/content/browser/loader/transfer_navigation_resource_throttle.cc deleted file mode 100644 index 8021bce..0000000 --- a/content/browser/loader/transfer_navigation_resource_throttle.cc +++ /dev/null @@ -1,93 +0,0 @@ -// 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 deleted file mode 100644 index 6d08c2a..0000000 --- a/content/browser/loader/transfer_navigation_resource_throttle.h +++ /dev/null @@ -1,38 +0,0 @@ -// 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 997452a..aac79cf 100644 --- a/content/browser/renderer_host/render_view_host_delegate.h +++ b/content/browser/renderer_host/render_view_host_delegate.h @@ -93,10 +93,15 @@ 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. + // should ensure that the old RenderViewHost runs its unload handler first + // and determine whether a RenderViewHost transfer is needed. virtual void OnCrossSiteResponse( RenderViewHost* pending_render_view_host, - const GlobalRequestID& global_request_id) = 0; + const GlobalRequestID& global_request_id, + bool is_transfer, + const GURL& transfer_url, + const Referrer& referrer, + int64 frame_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 c72e165..53d7681 100644 --- a/content/browser/web_contents/render_view_host_manager.cc +++ b/content/browser/web_contents/render_view_host_manager.cc @@ -33,12 +33,21 @@ namespace content { -RenderViewHostManager::PendingNavigationParams::PendingNavigationParams() { +RenderViewHostManager::PendingNavigationParams::PendingNavigationParams() + : is_transfer(false), frame_id(-1) { } RenderViewHostManager::PendingNavigationParams::PendingNavigationParams( - const GlobalRequestID& global_request_id) - : global_request_id(global_request_id) { + 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) { } RenderViewHostManager::RenderViewHostManager( @@ -239,8 +248,24 @@ void RenderViewHostManager::SwappedOut(RenderViewHost* render_view_host) { return; } - // Now that the unload handler has run, we need to resume the paused response. - if (pending_render_view_host_) { + // 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_) { RenderProcessHostImpl* pending_process = static_cast<RenderProcessHostImpl*>( pending_render_view_host_->GetProcess()); @@ -378,22 +403,32 @@ void RenderViewHostManager::ShouldClosePage( void RenderViewHostManager::OnCrossSiteResponse( RenderViewHost* pending_render_view_host, - 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)); + 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)); // Run the unload handler of the current page. SwapOutOldPage(); } void RenderViewHostManager::SwapOutOldPage() { - // Should only see this while we have a pending renderer. - if (!cross_navigation_pending_) - return; - DCHECK(pending_render_view_host_); + // Should only see this while we have a pending renderer or transfer. + CHECK(cross_navigation_pending_ || pending_nav_params_.get()); // 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 @@ -406,7 +441,8 @@ 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. - pending_render_view_host_->SetHasPendingCrossSiteRequest(false); + if (pending_render_view_host_) + pending_render_view_host_->SetHasPendingCrossSiteRequest(false); } void RenderViewHostManager::Observe( @@ -924,7 +960,12 @@ RenderViewHostImpl* RenderViewHostManager::UpdateRendererStateForNavigate( DCHECK(!pending_render_view_host_->are_navigations_suspended()); bool is_transfer = entry.transferred_global_request_id() != GlobalRequestID(); - if (!is_transfer) { + 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 { // 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.) @@ -933,12 +974,12 @@ 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_); diff --git a/content/browser/web_contents/render_view_host_manager.h b/content/browser/web_contents/render_view_host_manager.h index aacc90b..aef6383 100644 --- a/content/browser/web_contents/render_view_host_manager.h +++ b/content/browser/web_contents/render_view_host_manager.h @@ -14,6 +14,7 @@ #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 { @@ -217,7 +218,11 @@ class CONTENT_EXPORT RenderViewHostManager const base::TimeTicks& proceed_time) OVERRIDE; virtual void OnCrossSiteResponse( RenderViewHost* pending_render_view_host, - const GlobalRequestID& global_request_id) OVERRIDE; + const GlobalRequestID& global_request_id, + bool is_transfer, + const GURL& transfer_url, + const Referrer& referrer, + int64 frame_id) OVERRIDE; // NotificationObserver implementation. virtual void Observe(int type, @@ -235,7 +240,8 @@ 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. + // cross-process navigation is going to commit. We may initiate a transfer + // to a new process after this completes or times out. void SwapOutOldPage(); private: @@ -243,13 +249,34 @@ class CONTENT_EXPORT RenderViewHostManager friend class TestWebContents; // Tracks information about a navigation while a cross-process transition is - // in progress. - // TODO(creis): Add transfer navigation params for http://crbug.com/238331. + // in progress, in case we need to transfer it to a new RenderViewHost. struct PendingNavigationParams { PendingNavigationParams(); - explicit PendingNavigationParams(const GlobalRequestID& global_request_id); - + 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; + + // 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; }; // 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 2ebe303..e0c955f 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -113,34 +113,43 @@ // a new RenderViewHost dedicated to the new SiteInstance. This works as // follows: // -// - Navigate determines whether the destination is cross-site, and if so, +// - RVHM::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 onbeforeunload JavaScript handler has a chance to +// its renderer until the beforeunload 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. ResourceDispatcherHost will -// check for this when the response arrives. -// - The current RVH runs its onbeforeunload handler. If it returns false, we +// 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 // 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 checks CrossSiteRequestManager -// to see that it is a cross-site request, and installs a -// CrossSiteResourceHandler. +// 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. // - 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, 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. +// 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. // - 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 aded7ae..c11cb51 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)); + pending_rvh, GlobalRequestID(0, 0), false, GURL(), Referrer(), 1); // 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 29e22da..7835950 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -690,8 +690,6 @@ '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', |