- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / loader / cross_site_resource_handler.cc
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.
4
5 #include "content/browser/loader/cross_site_resource_handler.h"
6
7 #include <string>
8
9 #include "base/bind.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"
23
24 namespace content {
25
26 namespace {
27
28 void OnCrossSiteResponseHelper(int render_view_id,
29                                const GlobalRequestID& global_request_id,
30                                bool is_transfer,
31                                const std::vector<GURL>& transfer_url_chain,
32                                const Referrer& referrer,
33                                PageTransition page_transition,
34                                int64 frame_id) {
35   RenderViewHostImpl* rvh =
36       RenderViewHostImpl::FromID(global_request_id.child_id, render_view_id);
37   if (!rvh)
38     return;
39   RenderViewHostDelegate* delegate = rvh->GetDelegate();
40   if (!delegate || !delegate->GetRendererManagementDelegate())
41     return;
42
43   delegate->GetRendererManagementDelegate()->OnCrossSiteResponse(
44       rvh, global_request_id, is_transfer, transfer_url_chain, referrer,
45       page_transition, frame_id);
46 }
47
48 }  // namespace
49
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),
57       did_defer_(false),
58       completed_status_(),
59       response_(NULL) {
60 }
61
62 CrossSiteResourceHandler::~CrossSiteResourceHandler() {
63   // Cleanup back-pointer stored on the request info.
64   GetRequestInfo()->set_cross_site_handler(NULL);
65 }
66
67 bool CrossSiteResourceHandler::OnRequestRedirected(
68     int request_id,
69     const GURL& new_url,
70     ResourceResponse* response,
71     bool* defer) {
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);
76 }
77
78 bool CrossSiteResourceHandler::OnResponseStarted(
79     int request_id,
80     ResourceResponse* response,
81     bool* defer) {
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
84   // browsing checks.
85   // We should not have already started the transition before now.
86   DCHECK(!in_cross_site_transition_);
87   has_started_response_ = true;
88
89   ResourceRequestInfoImpl* info = GetRequestInfo();
90
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());
101
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
104   // request.
105   //
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.
109   //
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);
118   }
119
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);
124
125   // Defer loading until after the onunload event handler has run.
126   did_defer_ = *defer = true;
127   return true;
128 }
129
130 bool CrossSiteResourceHandler::OnReadCompleted(int request_id,
131                                                int bytes_read,
132                                                bool* defer) {
133   CHECK(!in_cross_site_transition_);
134   return next_handler_->OnReadCompleted(request_id, bytes_read, defer);
135 }
136
137 bool CrossSiteResourceHandler::OnResponseCompleted(
138     int request_id,
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,
151                                                 security_info);
152     }
153
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);
159   }
160
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;
165
166   // Return false to tell RDH not to notify the world or clean up the
167   // pending request.  We will do so in ResumeResponse.
168   did_defer_ = true;
169   return false;
170 }
171
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() {
175   DCHECK(request());
176   DCHECK(in_cross_site_transition_);
177   in_cross_site_transition_ = false;
178   ResourceRequestInfoImpl* info = GetRequestInfo();
179
180   if (has_started_response_) {
181     // Send OnResponseStarted to the new renderer.
182     DCHECK(response_);
183     bool defer = false;
184     if (!next_handler_->OnResponseStarted(info->GetRequestID(), response_,
185                                           &defer)) {
186       controller()->Cancel();
187     } else if (!defer) {
188       // Unpause the request to resume reading.  Any further reads will be
189       // directed toward the new renderer.
190       ResumeIfDeferred();
191     }
192   }
193
194   // Remove ourselves from the ExtraRequestInfo.
195   info->set_cross_site_handler(NULL);
196
197   // If the response completed during the transition, notify the next
198   // event handler.
199   if (completed_during_transition_) {
200     if (next_handler_->OnResponseCompleted(info->GetRequestID(),
201                                            completed_status_,
202                                            completed_security_info_)) {
203       ResumeIfDeferred();
204     }
205   }
206 }
207
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(
211     int request_id,
212     ResourceResponse* response,
213     bool should_transfer) {
214   in_cross_site_transition_ = true;
215   response_ = response;
216
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);
221
222   DCHECK_EQ(request_id, info->GetRequestID());
223   GlobalRequestID global_id(info->GetChildID(), info->GetRequestID());
224
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;
232   Referrer referrer;
233   int frame_id = -1;
234   if (should_transfer) {
235     transfer_url_chain = request()->url_chain();
236     referrer = Referrer(GURL(request()->referrer()), info->GetReferrerPolicy());
237     frame_id = info->GetFrameID();
238
239     ResourceDispatcherHostImpl::Get()->MarkAsTransferredNavigation(
240         global_id, transfer_url_chain.front());
241   }
242   BrowserThread::PostTask(
243       BrowserThread::UI,
244       FROM_HERE,
245       base::Bind(
246           &OnCrossSiteResponseHelper,
247           info->GetRouteID(),
248           global_id,
249           should_transfer,
250           transfer_url_chain,
251           referrer,
252           info->GetPageTransition(),
253           frame_id));
254 }
255
256 void CrossSiteResourceHandler::ResumeIfDeferred() {
257   if (did_defer_) {
258     did_defer_ = false;
259     controller()->Resume();
260   }
261 }
262
263 }  // namespace content