Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / browser / loader / cross_site_resource_handler.cc
blobcb344d4b98532082476919b04d49370642b03df9
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"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "content/browser/appcache/appcache_interceptor.h"
13 #include "content/browser/child_process_security_policy_impl.h"
14 #include "content/browser/frame_host/cross_site_transferring_request.h"
15 #include "content/browser/frame_host/render_frame_host_impl.h"
16 #include "content/browser/loader/resource_dispatcher_host_impl.h"
17 #include "content/browser/loader/resource_request_info_impl.h"
18 #include "content/browser/site_instance_impl.h"
19 #include "content/browser/web_contents/web_contents_impl.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/content_browser_client.h"
22 #include "content/public/browser/global_request_id.h"
23 #include "content/public/browser/resource_controller.h"
24 #include "content/public/browser/site_instance.h"
25 #include "content/public/common/content_switches.h"
26 #include "content/public/common/resource_response.h"
27 #include "content/public/common/url_constants.h"
28 #include "net/http/http_response_headers.h"
29 #include "net/url_request/url_request.h"
31 namespace content {
33 namespace {
35 bool leak_requests_for_testing_ = false;
37 // The parameters to OnCrossSiteResponseHelper exceed the number of arguments
38 // base::Bind supports.
39 struct CrossSiteResponseParams {
40 CrossSiteResponseParams(
41 int render_frame_id,
42 const GlobalRequestID& global_request_id,
43 const std::vector<GURL>& transfer_url_chain,
44 const Referrer& referrer,
45 ui::PageTransition page_transition,
46 bool should_replace_current_entry)
47 : render_frame_id(render_frame_id),
48 global_request_id(global_request_id),
49 transfer_url_chain(transfer_url_chain),
50 referrer(referrer),
51 page_transition(page_transition),
52 should_replace_current_entry(should_replace_current_entry) {
55 int render_frame_id;
56 GlobalRequestID global_request_id;
57 std::vector<GURL> transfer_url_chain;
58 Referrer referrer;
59 ui::PageTransition page_transition;
60 bool should_replace_current_entry;
63 void OnCrossSiteResponseHelper(const CrossSiteResponseParams& params) {
64 scoped_ptr<CrossSiteTransferringRequest> cross_site_transferring_request(
65 new CrossSiteTransferringRequest(params.global_request_id));
67 RenderFrameHostImpl* rfh =
68 RenderFrameHostImpl::FromID(params.global_request_id.child_id,
69 params.render_frame_id);
70 if (rfh) {
71 if (rfh->GetParent()) {
72 // We should only swap processes for subframes in --site-per-process mode.
73 // CrossSiteResourceHandler is not installed on subframe requests in
74 // default Chrome.
75 CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
76 switches::kSitePerProcess));
78 rfh->OnCrossSiteResponse(
79 params.global_request_id, cross_site_transferring_request.Pass(),
80 params.transfer_url_chain, params.referrer,
81 params.page_transition, params.should_replace_current_entry);
82 } else if (leak_requests_for_testing_ && cross_site_transferring_request) {
83 // Some unit tests expect requests to be leaked in this case, so they can
84 // pass them along manually.
85 cross_site_transferring_request->ReleaseRequest();
89 // Returns whether a transfer is needed by doing a check on the UI thread.
90 bool CheckNavigationPolicyOnUI(GURL url, int process_id, int render_frame_id) {
91 CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
92 switches::kSitePerProcess));
93 RenderFrameHostImpl* rfh =
94 RenderFrameHostImpl::FromID(process_id, render_frame_id);
95 if (!rfh)
96 return false;
98 // A transfer is not needed if the current SiteInstance doesn't yet have a
99 // site. This is the case for tests that use NavigateToURL.
100 if (!rfh->GetSiteInstance()->HasSite())
101 return false;
103 // For now, GuestViews never transfer on cross-site navigations.
104 WebContentsImpl* web_contents =
105 static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(rfh));
106 if (web_contents->GetBrowserPluginGuest())
107 return false;
109 // TODO(nasko): This check is very simplistic and is used temporarily only
110 // for --site-per-process. It should be updated to match the check performed
111 // by RenderFrameHostManager::UpdateStateForNavigate.
112 return !SiteInstance::IsSameWebSite(
113 rfh->GetSiteInstance()->GetBrowserContext(),
114 rfh->GetSiteInstance()->GetSiteURL(), url);
117 } // namespace
119 CrossSiteResourceHandler::CrossSiteResourceHandler(
120 scoped_ptr<ResourceHandler> next_handler,
121 net::URLRequest* request)
122 : LayeredResourceHandler(request, next_handler.Pass()),
123 has_started_response_(false),
124 in_cross_site_transition_(false),
125 completed_during_transition_(false),
126 did_defer_(false),
127 weak_ptr_factory_(this) {
130 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
131 // Cleanup back-pointer stored on the request info.
132 GetRequestInfo()->set_cross_site_handler(NULL);
135 bool CrossSiteResourceHandler::OnRequestRedirected(
136 const net::RedirectInfo& redirect_info,
137 ResourceResponse* response,
138 bool* defer) {
139 // We should not have started the transition before being redirected.
140 DCHECK(!in_cross_site_transition_);
141 return next_handler_->OnRequestRedirected(redirect_info, response, defer);
144 bool CrossSiteResourceHandler::OnResponseStarted(
145 ResourceResponse* response,
146 bool* defer) {
147 response_ = response;
148 has_started_response_ = true;
150 // Store this handler on the ExtraRequestInfo, so that RDH can call our
151 // ResumeResponse method when we are ready to resume.
152 ResourceRequestInfoImpl* info = GetRequestInfo();
153 info->set_cross_site_handler(this);
155 return OnNormalResponseStarted(response, defer);
158 bool CrossSiteResourceHandler::OnNormalResponseStarted(
159 ResourceResponse* response,
160 bool* defer) {
161 // At this point, we know that the response is safe to send back to the
162 // renderer: it is not a download, and it has passed the SSL and safe
163 // browsing checks.
164 // We should not have already started the transition before now.
165 DCHECK(!in_cross_site_transition_);
167 ResourceRequestInfoImpl* info = GetRequestInfo();
169 // We only need to pause the response if a transfer to a different process is
170 // required. Other cross-process navigations can proceed immediately, since
171 // we run the unload handler at commit time.
172 // Note that a process swap may no longer be necessary if we transferred back
173 // into the original process due to a redirect.
174 bool should_transfer =
175 GetContentClient()->browser()->ShouldSwapProcessesForRedirect(
176 info->GetContext(), request()->original_url(), request()->url());
178 // If this is a download, just pass the response through without doing a
179 // cross-site check. The renderer will see it is a download and abort the
180 // request.
182 // Similarly, HTTP 204 (No Content) responses leave us showing the previous
183 // page. We should allow the navigation to finish without running the unload
184 // handler or swapping in the pending RenderFrameHost.
186 // In both cases, any pending RenderFrameHost (if one was created for this
187 // navigation) will stick around until the next cross-site navigation, since
188 // we are unable to tell when to destroy it.
189 // See RenderFrameHostManager::RendererAbortedProvisionalLoad.
191 // TODO(davidben): Unify IsDownload() and is_stream(). Several places need to
192 // check for both and remembering about streams is error-prone.
193 if (info->IsDownload() || info->is_stream() ||
194 (response->head.headers.get() &&
195 response->head.headers->response_code() == 204)) {
196 return next_handler_->OnResponseStarted(response, defer);
199 // When the --site-per-process flag is passed, we transfer processes for
200 // cross-site navigations. This is skipped if a transfer is already required
201 // or for WebUI processes for now, since pages like the NTP host multiple
202 // cross-site WebUI iframes.
203 if (!should_transfer &&
204 base::CommandLine::ForCurrentProcess()->HasSwitch(
205 switches::kSitePerProcess) &&
206 !ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
207 info->GetChildID())) {
208 return DeferForNavigationPolicyCheck(info, response, defer);
211 if (!should_transfer)
212 return next_handler_->OnResponseStarted(response, defer);
214 // Now that we know a transfer is needed and we have something to commit, we
215 // pause to let the UI thread set up the transfer.
216 StartCrossSiteTransition(response);
218 // Defer loading until after the new renderer process has issued a
219 // corresponding request.
220 *defer = true;
221 OnDidDefer();
222 return true;
225 void CrossSiteResourceHandler::ResumeOrTransfer(bool is_transfer) {
226 if (is_transfer) {
227 StartCrossSiteTransition(response_.get());
228 } else {
229 ResumeResponse();
233 bool CrossSiteResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
234 CHECK(!in_cross_site_transition_);
235 return next_handler_->OnReadCompleted(bytes_read, defer);
238 void CrossSiteResourceHandler::OnResponseCompleted(
239 const net::URLRequestStatus& status,
240 const std::string& security_info,
241 bool* defer) {
242 if (!in_cross_site_transition_) {
243 // If we're not transferring, then we should pass this through.
244 next_handler_->OnResponseCompleted(status, security_info, defer);
245 return;
248 // We have to buffer the call until after the transition completes.
249 completed_during_transition_ = true;
250 completed_status_ = status;
251 completed_security_info_ = security_info;
253 // Defer to tell RDH not to notify the world or clean up the pending request.
254 // We will do so in ResumeResponse.
255 *defer = true;
256 OnDidDefer();
259 // We can now send the response to the new renderer, which will cause
260 // WebContentsImpl to swap in the new renderer and destroy the old one.
261 void CrossSiteResourceHandler::ResumeResponse() {
262 TRACE_EVENT_ASYNC_END0(
263 "navigation", "CrossSiteResourceHandler transition", this);
264 DCHECK(request());
265 in_cross_site_transition_ = false;
266 ResourceRequestInfoImpl* info = GetRequestInfo();
268 if (has_started_response_) {
269 // Send OnResponseStarted to the new renderer.
270 DCHECK(response_.get());
271 bool defer = false;
272 if (!next_handler_->OnResponseStarted(response_.get(), &defer)) {
273 controller()->Cancel();
274 } else if (!defer) {
275 // Unpause the request to resume reading. Any further reads will be
276 // directed toward the new renderer.
277 ResumeIfDeferred();
281 // Remove ourselves from the ExtraRequestInfo.
282 info->set_cross_site_handler(NULL);
284 // If the response completed during the transition, notify the next
285 // event handler.
286 if (completed_during_transition_) {
287 bool defer = false;
288 next_handler_->OnResponseCompleted(completed_status_,
289 completed_security_info_,
290 &defer);
291 if (!defer)
292 ResumeIfDeferred();
296 // static
297 void CrossSiteResourceHandler::SetLeakRequestsForTesting(
298 bool leak_requests_for_testing) {
299 leak_requests_for_testing_ = leak_requests_for_testing;
302 // Prepare to transfer the response to a new RenderFrameHost.
303 void CrossSiteResourceHandler::StartCrossSiteTransition(
304 ResourceResponse* response) {
305 TRACE_EVENT_ASYNC_BEGIN0(
306 "navigation", "CrossSiteResourceHandler transition", this);
307 in_cross_site_transition_ = true;
308 response_ = response;
310 // Store this handler on the ExtraRequestInfo, so that RDH can call our
311 // ResumeResponse method when we are ready to resume.
312 ResourceRequestInfoImpl* info = GetRequestInfo();
313 info->set_cross_site_handler(this);
315 GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
317 // Tell the contents responsible for this request that a cross-site response
318 // is starting, so that it can tell its old renderer to run its onunload
319 // handler now. We will wait until the unload is finished and (if a transfer
320 // is needed) for the new renderer's request to arrive.
321 // The |transfer_url_chain| contains any redirect URLs that have already
322 // occurred, plus the destination URL at the end.
323 std::vector<GURL> transfer_url_chain;
324 Referrer referrer;
325 int render_frame_id = info->GetRenderFrameID();
326 transfer_url_chain = request()->url_chain();
327 referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
328 ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(global_id);
330 BrowserThread::PostTask(
331 BrowserThread::UI,
332 FROM_HERE,
333 base::Bind(
334 &OnCrossSiteResponseHelper,
335 CrossSiteResponseParams(render_frame_id,
336 global_id,
337 transfer_url_chain,
338 referrer,
339 info->GetPageTransition(),
340 info->should_replace_current_entry())));
343 bool CrossSiteResourceHandler::DeferForNavigationPolicyCheck(
344 ResourceRequestInfoImpl* info,
345 ResourceResponse* response,
346 bool* defer) {
347 // Store the response_ object internally, since the navigation is deferred
348 // regardless of whether it will be a transfer or not.
349 response_ = response;
351 // Always defer the navigation to the UI thread to make a policy decision.
352 // It will send the result back to the IO thread to either resume or
353 // transfer it to a new renderer.
354 // TODO(nasko): If the UI thread result is that transfer is required, the
355 // IO thread will defer to the UI thread again through
356 // StartCrossSiteTransition. This is unnecessary and the policy check on the
357 // UI thread should be refactored to avoid the extra hop.
358 BrowserThread::PostTaskAndReplyWithResult(
359 BrowserThread::UI,
360 FROM_HERE,
361 base::Bind(&CheckNavigationPolicyOnUI,
362 request()->url(),
363 info->GetChildID(),
364 info->GetRenderFrameID()),
365 base::Bind(&CrossSiteResourceHandler::ResumeOrTransfer,
366 weak_ptr_factory_.GetWeakPtr()));
368 // Defer loading until it is known whether the navigation will transfer
369 // to a new process or continue in the existing one.
370 *defer = true;
371 OnDidDefer();
372 return true;
375 void CrossSiteResourceHandler::ResumeIfDeferred() {
376 if (did_defer_) {
377 request()->LogUnblocked();
378 did_defer_ = false;
379 controller()->Resume();
383 void CrossSiteResourceHandler::OnDidDefer() {
384 did_defer_ = true;
385 request()->LogBlockedBy("CrossSiteResourceHandler");
388 } // namespace content