1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/loader/cross_site_resource_handler.h"
10 #include "base/logging.h"
11 #include "content/browser/cross_site_request_manager.h"
12 #include "content/browser/loader/resource_dispatcher_host_impl.h"
13 #include "content/browser/loader/resource_request_info_impl.h"
14 #include "content/browser/renderer_host/render_view_host_delegate.h"
15 #include "content/browser/renderer_host/render_view_host_impl.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/content_browser_client.h"
18 #include "content/public/browser/global_request_id.h"
19 #include "content/public/browser/resource_controller.h"
20 #include "content/public/common/resource_response.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/url_request/url_request.h"
28 void OnCrossSiteResponseHelper(int render_view_id,
29 const GlobalRequestID& global_request_id,
31 const std::vector<GURL>& transfer_url_chain,
32 const Referrer& referrer,
33 PageTransition page_transition,
35 RenderViewHostImpl* rvh =
36 RenderViewHostImpl::FromID(global_request_id.child_id, render_view_id);
39 RenderViewHostDelegate* delegate = rvh->GetDelegate();
40 if (!delegate || !delegate->GetRendererManagementDelegate())
43 delegate->GetRendererManagementDelegate()->OnCrossSiteResponse(
44 rvh, global_request_id, is_transfer, transfer_url_chain, referrer,
45 page_transition, frame_id);
50 CrossSiteResourceHandler::CrossSiteResourceHandler(
51 scoped_ptr<ResourceHandler> next_handler,
52 net::URLRequest* request)
53 : LayeredResourceHandler(request, next_handler.Pass()),
54 has_started_response_(false),
55 in_cross_site_transition_(false),
56 completed_during_transition_(false),
62 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
63 // Cleanup back-pointer stored on the request info.
64 GetRequestInfo()->set_cross_site_handler(NULL);
67 bool CrossSiteResourceHandler::OnRequestRedirected(
70 ResourceResponse* response,
72 // We should not have started the transition before being redirected.
73 DCHECK(!in_cross_site_transition_);
74 return next_handler_->OnRequestRedirected(
75 request_id, new_url, response, defer);
78 bool CrossSiteResourceHandler::OnResponseStarted(
80 ResourceResponse* response,
82 // At this point, we know that the response is safe to send back to the
83 // renderer: it is not a download, and it has passed the SSL and safe
85 // We should not have already started the transition before now.
86 DCHECK(!in_cross_site_transition_);
87 has_started_response_ = true;
89 ResourceRequestInfoImpl* info = GetRequestInfo();
91 // We will need to swap processes if either (1) a redirect that requires a
92 // transfer occurred before we got here, or (2) a pending cross-site request
93 // was already in progress. Note that a swap may no longer be needed if we
94 // transferred back into the original process due to a redirect.
95 bool should_transfer =
96 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
97 info->GetContext(), request()->original_url(), request()->url());
98 bool swap_needed = should_transfer ||
99 CrossSiteRequestManager::GetInstance()->
100 HasPendingCrossSiteRequest(info->GetChildID(), info->GetRouteID());
102 // If this is a download, just pass the response through without doing a
103 // cross-site check. The renderer will see it is a download and abort the
106 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
107 // page. We should allow the navigation to finish without running the unload
108 // handler or swapping in the pending RenderViewHost.
110 // In both cases, any pending RenderViewHost (if one was created for this
111 // navigation) will stick around until the next cross-site navigation, since
112 // we are unable to tell when to destroy it.
113 // See RenderViewHostManager::RendererAbortedProvisionalLoad.
114 if (!swap_needed || info->is_download() ||
115 (response->head.headers.get() &&
116 response->head.headers->response_code() == 204)) {
117 return next_handler_->OnResponseStarted(request_id, response, defer);
120 // Now that we know a swap is needed and we have something to commit, we
121 // pause to let the UI thread run the unload handler of the previous page
122 // and set up a transfer if needed.
123 StartCrossSiteTransition(request_id, response, should_transfer);
125 // Defer loading until after the onunload event handler has run.
126 did_defer_ = *defer = true;
130 bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
133 CHECK(!in_cross_site_transition_);
134 return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
137 bool CrossSiteResourceHandler::OnResponseCompleted(
139 const net::URLRequestStatus& status,
140 const std::string& security_info) {
141 if (!in_cross_site_transition_) {
142 ResourceRequestInfoImpl* info = GetRequestInfo();
143 // If we've already completed the transition, or we're canceling the
144 // request, or an error occurred with no cross-process navigation in
145 // progress, then we should just pass this through.
146 if (has_started_response_ ||
147 status.status() != net::URLRequestStatus::FAILED ||
148 !CrossSiteRequestManager::GetInstance()->HasPendingCrossSiteRequest(
149 info->GetChildID(), info->GetRouteID())) {
150 return next_handler_->OnResponseCompleted(request_id, status,
154 // An error occurred. We should wait now for the cross-process transition,
155 // so that the error message (e.g., 404) can be displayed to the user.
156 // Also continue with the logic below to remember that we completed
157 // during the cross-site transition.
158 StartCrossSiteTransition(request_id, NULL, false);
161 // We have to buffer the call until after the transition completes.
162 completed_during_transition_ = true;
163 completed_status_ = status;
164 completed_security_info_ = security_info;
166 // Return false to tell RDH not to notify the world or clean up the
167 // pending request. We will do so in ResumeResponse.
172 // We can now send the response to the new renderer, which will cause
173 // WebContentsImpl to swap in the new renderer and destroy the old one.
174 void CrossSiteResourceHandler::ResumeResponse() {
176 DCHECK(in_cross_site_transition_);
177 in_cross_site_transition_ = false;
178 ResourceRequestInfoImpl* info = GetRequestInfo();
180 if (has_started_response_) {
181 // Send OnResponseStarted to the new renderer.
184 if (!next_handler_->OnResponseStarted(info->GetRequestID(), response_,
186 controller()->Cancel();
188 // Unpause the request to resume reading. Any further reads will be
189 // directed toward the new renderer.
194 // Remove ourselves from the ExtraRequestInfo.
195 info->set_cross_site_handler(NULL);
197 // If the response completed during the transition, notify the next
199 if (completed_during_transition_) {
200 if (next_handler_->OnResponseCompleted(info->GetRequestID(),
202 completed_security_info_)) {
208 // Prepare to render the cross-site response in a new RenderViewHost, by
209 // telling the old RenderViewHost to run its onunload handler.
210 void CrossSiteResourceHandler::StartCrossSiteTransition(
212 ResourceResponse* response,
213 bool should_transfer) {
214 in_cross_site_transition_ = true;
215 response_ = response;
217 // Store this handler on the ExtraRequestInfo, so that RDH can call our
218 // ResumeResponse method when we are ready to resume.
219 ResourceRequestInfoImpl* info = GetRequestInfo();
220 info->set_cross_site_handler(this);
222 DCHECK_EQ(request_id, info->GetRequestID());
223 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
225 // Tell the contents responsible for this request that a cross-site response
226 // is starting, so that it can tell its old renderer to run its onunload
227 // handler now. We will wait until the unload is finished and (if a transfer
228 // is needed) for the new renderer's request to arrive.
229 // The |transfer_url_chain| contains any redirect URLs that have already
230 // occurred, plus the destination URL at the end.
231 std::vector<GURL> transfer_url_chain;
234 if (should_transfer) {
235 transfer_url_chain = request()->url_chain();
236 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
237 frame_id = info->GetFrameID();
239 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(
240 global_id, transfer_url_chain.front());
242 BrowserThread::PostTask(
246 &OnCrossSiteResponseHelper,
252 info->GetPageTransition(),
256 void CrossSiteResourceHandler::ResumeIfDeferred() {
259 controller()->Resume();
263 } // namespace content