1 // Copyright (c) 2011 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 // See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading
7 #include "content/common/resource_dispatcher.h"
9 #include "base/basictypes.h"
10 #include "base/compiler_specific.h"
11 #include "base/file_path.h"
12 #include "base/message_loop.h"
13 #include "base/shared_memory.h"
14 #include "base/string_util.h"
15 #include "content/common/resource_dispatcher_delegate.h"
16 #include "content/common/resource_messages.h"
17 #include "content/common/resource_response.h"
18 #include "net/base/net_errors.h"
19 #include "net/base/net_util.h"
20 #include "net/base/upload_data.h"
21 #include "net/http/http_response_headers.h"
22 #include "webkit/glue/resource_type.h"
24 // Each resource request is assigned an ID scoped to this process.
25 static int MakeRequestID() {
26 // NOTE: The resource_dispatcher_host also needs probably unique
27 // request_ids, so they count down from -2 (-1 is a special we're
28 // screwed value), while the renderer process counts up.
29 static int next_request_id
= 0;
30 return next_request_id
++;
33 // ResourceLoaderBridge implementation ----------------------------------------
35 namespace webkit_glue
{
37 class IPCResourceLoaderBridge
: public ResourceLoaderBridge
{
39 IPCResourceLoaderBridge(ResourceDispatcher
* dispatcher
,
40 const webkit_glue::ResourceLoaderBridge::RequestInfo
& request_info
);
41 virtual ~IPCResourceLoaderBridge();
43 // ResourceLoaderBridge
44 virtual void AppendDataToUpload(const char* data
, int data_len
);
45 virtual void AppendFileRangeToUpload(
49 const base::Time
& expected_modification_time
);
50 virtual void AppendBlobToUpload(const GURL
& blob_url
);
51 virtual void SetUploadIdentifier(int64 identifier
);
52 virtual bool Start(Peer
* peer
);
53 virtual void Cancel();
54 virtual void SetDefersLoading(bool value
);
55 virtual void SyncLoad(SyncLoadResponse
* response
);
58 ResourceLoaderBridge::Peer
* peer_
;
60 // The resource dispatcher for this loader. The bridge doesn't own it, but
61 // it's guaranteed to outlive the bridge.
62 ResourceDispatcher
* dispatcher_
;
64 // The request to send, created on initialization for modification and
66 ResourceHostMsg_Request request_
;
68 // ID for the request, valid once Start()ed, -1 if not valid yet.
71 // The routing id used when sending IPC messages.
75 IPCResourceLoaderBridge::IPCResourceLoaderBridge(
76 ResourceDispatcher
* dispatcher
,
77 const webkit_glue::ResourceLoaderBridge::RequestInfo
& request_info
)
79 dispatcher_(dispatcher
),
81 routing_id_(request_info
.routing_id
) {
82 DCHECK(dispatcher_
) << "no resource dispatcher";
83 request_
.method
= request_info
.method
;
84 request_
.url
= request_info
.url
;
85 request_
.first_party_for_cookies
= request_info
.first_party_for_cookies
;
86 request_
.referrer
= request_info
.referrer
;
87 request_
.headers
= request_info
.headers
;
88 request_
.load_flags
= request_info
.load_flags
;
89 request_
.origin_pid
= request_info
.requestor_pid
;
90 request_
.resource_type
= request_info
.request_type
;
91 request_
.request_context
= request_info
.request_context
;
92 request_
.appcache_host_id
= request_info
.appcache_host_id
;
93 request_
.download_to_file
= request_info
.download_to_file
;
94 request_
.has_user_gesture
= request_info
.has_user_gesture
;
97 IPCResourceLoaderBridge::~IPCResourceLoaderBridge() {
98 // we remove our hook for the resource dispatcher only when going away, since
99 // it doesn't keep track of whether we've force terminated the request
100 if (request_id_
>= 0) {
101 // this operation may fail, as the dispatcher will have preemptively
102 // removed us when the renderer sends the ReceivedAllData message.
103 dispatcher_
->RemovePendingRequest(request_id_
);
105 if (request_
.download_to_file
) {
106 dispatcher_
->message_sender()->Send(
107 new ResourceHostMsg_ReleaseDownloadedFile(request_id_
));
112 void IPCResourceLoaderBridge::AppendDataToUpload(const char* data
,
114 DCHECK(request_id_
== -1) << "request already started";
116 // don't bother appending empty data segments
120 if (!request_
.upload_data
)
121 request_
.upload_data
= new net::UploadData();
122 request_
.upload_data
->AppendBytes(data
, data_len
);
125 void IPCResourceLoaderBridge::AppendFileRangeToUpload(
126 const FilePath
& path
, uint64 offset
, uint64 length
,
127 const base::Time
& expected_modification_time
) {
128 DCHECK(request_id_
== -1) << "request already started";
130 if (!request_
.upload_data
)
131 request_
.upload_data
= new net::UploadData();
132 request_
.upload_data
->AppendFileRange(path
, offset
, length
,
133 expected_modification_time
);
136 void IPCResourceLoaderBridge::AppendBlobToUpload(const GURL
& blob_url
) {
137 DCHECK(request_id_
== -1) << "request already started";
139 if (!request_
.upload_data
)
140 request_
.upload_data
= new net::UploadData();
141 request_
.upload_data
->AppendBlob(blob_url
);
144 void IPCResourceLoaderBridge::SetUploadIdentifier(int64 identifier
) {
145 DCHECK(request_id_
== -1) << "request already started";
147 if (!request_
.upload_data
)
148 request_
.upload_data
= new net::UploadData();
149 request_
.upload_data
->set_identifier(identifier
);
152 // Writes a footer on the message and sends it
153 bool IPCResourceLoaderBridge::Start(Peer
* peer
) {
154 if (request_id_
!= -1) {
155 NOTREACHED() << "Starting a request twice";
161 // generate the request ID, and append it to the message
162 request_id_
= dispatcher_
->AddPendingRequest(
163 peer_
, request_
.resource_type
, request_
.url
);
165 return dispatcher_
->message_sender()->Send(
166 new ResourceHostMsg_RequestResource(routing_id_
, request_id_
, request_
));
169 void IPCResourceLoaderBridge::Cancel() {
170 if (request_id_
< 0) {
171 NOTREACHED() << "Trying to cancel an unstarted request";
175 dispatcher_
->CancelPendingRequest(routing_id_
, request_id_
);
177 // We can't remove the request ID from the resource dispatcher because more
178 // data might be pending. Sending the cancel message may cause more data
179 // to be flushed, and will then cause a complete message to be sent.
182 void IPCResourceLoaderBridge::SetDefersLoading(bool value
) {
183 if (request_id_
< 0) {
184 NOTREACHED() << "Trying to (un)defer an unstarted request";
188 dispatcher_
->SetDefersLoading(request_id_
, value
);
191 void IPCResourceLoaderBridge::SyncLoad(SyncLoadResponse
* response
) {
192 if (request_id_
!= -1) {
193 NOTREACHED() << "Starting a request twice";
194 response
->status
.set_status(net::URLRequestStatus::FAILED
);
198 request_id_
= MakeRequestID();
200 SyncLoadResult result
;
201 IPC::SyncMessage
* msg
= new ResourceHostMsg_SyncLoad(routing_id_
, request_id_
,
203 // NOTE: This may pump events (see RenderThread::Send).
204 if (!dispatcher_
->message_sender()->Send(msg
)) {
205 response
->status
.set_status(net::URLRequestStatus::FAILED
);
209 response
->status
= result
.status
;
210 response
->url
= result
.final_url
;
211 response
->headers
= result
.headers
;
212 response
->mime_type
= result
.mime_type
;
213 response
->charset
= result
.charset
;
214 response
->request_time
= result
.request_time
;
215 response
->response_time
= result
.response_time
;
216 response
->encoded_data_length
= result
.encoded_data_length
;
217 response
->connection_id
= result
.connection_id
;
218 response
->connection_reused
= result
.connection_reused
;
219 response
->load_timing
= result
.load_timing
;
220 response
->devtools_info
= result
.devtools_info
;
221 response
->data
.swap(result
.data
);
222 response
->download_file_path
= result
.download_file_path
;
225 } // namespace webkit_glue
227 // ResourceDispatcher ---------------------------------------------------------
229 ResourceDispatcher::ResourceDispatcher(IPC::Message::Sender
* sender
)
230 : message_sender_(sender
),
231 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
235 ResourceDispatcher::~ResourceDispatcher() {
238 // ResourceDispatcher implementation ------------------------------------------
240 bool ResourceDispatcher::OnMessageReceived(const IPC::Message
& message
) {
241 if (!IsResourceDispatcherMessage(message
)) {
248 if (!message
.ReadInt(&iter
, &request_id
)) {
249 NOTREACHED() << "malformed resource message";
253 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
255 // Release resources in the message if it is a data message.
256 ReleaseResourcesInDataMessage(message
);
260 if (request_info
->is_deferred
) {
261 request_info
->deferred_message_queue
.push_back(new IPC::Message(message
));
264 // Make sure any deferred messages are dispatched before we dispatch more.
265 if (!request_info
->deferred_message_queue
.empty()) {
266 FlushDeferredMessages(request_id
);
267 // The request could have been deferred now. If yes then the current
268 // message has to be queued up. The request_info instance should remain
269 // valid here as there are pending messages for it.
270 DCHECK(pending_requests_
.find(request_id
) != pending_requests_
.end());
271 if (request_info
->is_deferred
) {
272 request_info
->deferred_message_queue
.push_back(new IPC::Message(message
));
277 DispatchMessage(message
);
281 ResourceDispatcher::PendingRequestInfo
*
282 ResourceDispatcher::GetPendingRequestInfo(int request_id
) {
283 PendingRequestList::iterator it
= pending_requests_
.find(request_id
);
284 if (it
== pending_requests_
.end()) {
285 // This might happen for kill()ed requests on the webkit end, so perhaps it
286 // shouldn't be a warning...
287 DLOG(WARNING
) << "Received message for a nonexistent or finished request";
290 return &(it
->second
);
293 void ResourceDispatcher::OnUploadProgress(
294 const IPC::Message
& message
, int request_id
, int64 position
, int64 size
) {
295 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
299 request_info
->peer
->OnUploadProgress(position
, size
);
301 // Acknowledge receipt
302 message_sender()->Send(
303 new ResourceHostMsg_UploadProgress_ACK(message
.routing_id(), request_id
));
306 void ResourceDispatcher::OnReceivedResponse(
307 int request_id
, const ResourceResponseHead
& response_head
) {
308 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
313 webkit_glue::ResourceLoaderBridge::Peer
* new_peer
=
314 delegate_
->OnReceivedResponse(
315 request_info
->peer
, response_head
.mime_type
, request_info
->url
);
317 request_info
->peer
= new_peer
;
320 request_info
->peer
->OnReceivedResponse(response_head
);
323 void ResourceDispatcher::OnReceivedCachedMetadata(
324 int request_id
, const std::vector
<char>& data
) {
325 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
330 request_info
->peer
->OnReceivedCachedMetadata(&data
.front(), data
.size());
333 void ResourceDispatcher::OnReceivedData(const IPC::Message
& message
,
335 base::SharedMemoryHandle shm_handle
,
337 int encoded_data_length
) {
338 // Acknowledge the reception of this data.
339 message_sender()->Send(
340 new ResourceHostMsg_DataReceived_ACK(message
.routing_id(), request_id
));
342 const bool shm_valid
= base::SharedMemory::IsHandleValid(shm_handle
);
343 DCHECK((shm_valid
&& data_len
> 0) || (!shm_valid
&& !data_len
));
344 base::SharedMemory
shared_mem(shm_handle
, true); // read only
346 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
350 if (data_len
> 0 && shared_mem
.Map(data_len
)) {
351 const char* data
= static_cast<char*>(shared_mem
.memory());
352 request_info
->peer
->OnReceivedData(data
, data_len
, encoded_data_length
);
356 void ResourceDispatcher::OnDownloadedData(const IPC::Message
& message
,
359 // Acknowledge the reception of this message.
360 message_sender()->Send(
361 new ResourceHostMsg_DataDownloaded_ACK(message
.routing_id(), request_id
));
363 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
367 request_info
->peer
->OnDownloadedData(data_len
);
370 void ResourceDispatcher::OnReceivedRedirect(
371 const IPC::Message
& message
,
374 const webkit_glue::ResourceResponseInfo
& info
) {
375 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
379 int32 routing_id
= message
.routing_id();
380 bool has_new_first_party_for_cookies
= false;
381 GURL new_first_party_for_cookies
;
382 if (request_info
->peer
->OnReceivedRedirect(new_url
, info
,
383 &has_new_first_party_for_cookies
,
384 &new_first_party_for_cookies
)) {
385 // Double-check if the request is still around. The call above could
386 // potentially remove it.
387 request_info
= GetPendingRequestInfo(request_id
);
390 request_info
->pending_redirect_message
.reset(
391 new ResourceHostMsg_FollowRedirect(routing_id
, request_id
,
392 has_new_first_party_for_cookies
,
393 new_first_party_for_cookies
));
394 if (!request_info
->is_deferred
) {
395 FollowPendingRedirect(request_id
, *request_info
);
398 CancelPendingRequest(routing_id
, request_id
);
402 void ResourceDispatcher::FollowPendingRedirect(
404 PendingRequestInfo
& request_info
) {
405 IPC::Message
* msg
= request_info
.pending_redirect_message
.release();
407 message_sender()->Send(msg
);
410 void ResourceDispatcher::OnRequestComplete(int request_id
,
411 const net::URLRequestStatus
& status
,
412 const std::string
& security_info
,
413 const base::Time
& completion_time
) {
414 PendingRequestInfo
* request_info
= GetPendingRequestInfo(request_id
);
418 webkit_glue::ResourceLoaderBridge::Peer
* peer
= request_info
->peer
;
421 webkit_glue::ResourceLoaderBridge::Peer
* new_peer
=
422 delegate_
->OnRequestComplete(
423 request_info
->peer
, request_info
->resource_type
, status
);
425 request_info
->peer
= new_peer
;
428 // The request ID will be removed from our pending list in the destructor.
429 // Normally, dispatching this message causes the reference-counted request to
431 peer
->OnCompletedRequest(status
, security_info
, completion_time
);
434 int ResourceDispatcher::AddPendingRequest(
435 webkit_glue::ResourceLoaderBridge::Peer
* callback
,
436 ResourceType::Type resource_type
,
437 const GURL
& request_url
) {
438 // Compute a unique request_id for this renderer process.
439 int id
= MakeRequestID();
440 pending_requests_
[id
] =
441 PendingRequestInfo(callback
, resource_type
, request_url
);
445 bool ResourceDispatcher::RemovePendingRequest(int request_id
) {
446 PendingRequestList::iterator it
= pending_requests_
.find(request_id
);
447 if (it
== pending_requests_
.end())
450 PendingRequestInfo
& request_info
= it
->second
;
451 ReleaseResourcesInMessageQueue(&request_info
.deferred_message_queue
);
452 pending_requests_
.erase(it
);
457 void ResourceDispatcher::CancelPendingRequest(int routing_id
,
459 PendingRequestList::iterator it
= pending_requests_
.find(request_id
);
460 if (it
== pending_requests_
.end()) {
461 DLOG(WARNING
) << "unknown request";
465 PendingRequestInfo
& request_info
= it
->second
;
466 ReleaseResourcesInMessageQueue(&request_info
.deferred_message_queue
);
467 pending_requests_
.erase(it
);
469 message_sender()->Send(
470 new ResourceHostMsg_CancelRequest(routing_id
, request_id
));
473 void ResourceDispatcher::SetDefersLoading(int request_id
, bool value
) {
474 PendingRequestList::iterator it
= pending_requests_
.find(request_id
);
475 if (it
== pending_requests_
.end()) {
476 DLOG(ERROR
) << "unknown request";
479 PendingRequestInfo
& request_info
= it
->second
;
481 request_info
.is_deferred
= value
;
482 } else if (request_info
.is_deferred
) {
483 request_info
.is_deferred
= false;
485 FollowPendingRedirect(request_id
, request_info
);
487 MessageLoop::current()->PostTask(FROM_HERE
,
488 method_factory_
.NewRunnableMethod(
489 &ResourceDispatcher::FlushDeferredMessages
, request_id
));
493 void ResourceDispatcher::DispatchMessage(const IPC::Message
& message
) {
494 IPC_BEGIN_MESSAGE_MAP(ResourceDispatcher
, message
)
495 IPC_MESSAGE_HANDLER(ResourceMsg_UploadProgress
, OnUploadProgress
)
496 IPC_MESSAGE_HANDLER(ResourceMsg_ReceivedResponse
, OnReceivedResponse
)
497 IPC_MESSAGE_HANDLER(ResourceMsg_ReceivedCachedMetadata
,
498 OnReceivedCachedMetadata
)
499 IPC_MESSAGE_HANDLER(ResourceMsg_ReceivedRedirect
, OnReceivedRedirect
)
500 IPC_MESSAGE_HANDLER(ResourceMsg_DataReceived
, OnReceivedData
)
501 IPC_MESSAGE_HANDLER(ResourceMsg_DataDownloaded
, OnDownloadedData
)
502 IPC_MESSAGE_HANDLER(ResourceMsg_RequestComplete
, OnRequestComplete
)
503 IPC_END_MESSAGE_MAP()
506 void ResourceDispatcher::FlushDeferredMessages(int request_id
) {
507 PendingRequestList::iterator it
= pending_requests_
.find(request_id
);
508 if (it
== pending_requests_
.end()) // The request could have become invalid.
510 PendingRequestInfo
& request_info
= it
->second
;
511 if (request_info
.is_deferred
)
513 // Because message handlers could result in request_info being destroyed,
514 // we need to work with a stack reference to the deferred queue.
516 q
.swap(request_info
.deferred_message_queue
);
518 IPC::Message
* m
= q
.front();
522 // If this request is deferred in the context of the above message, then
523 // we should honor the same and stop dispatching further messages.
524 // We need to find the request again in the list as it may have completed
525 // by now and the request_info instance above may be invalid.
526 PendingRequestList::iterator index
= pending_requests_
.find(request_id
);
527 if (index
!= pending_requests_
.end()) {
528 PendingRequestInfo
& pending_request
= index
->second
;
529 if (pending_request
.is_deferred
) {
530 pending_request
.deferred_message_queue
.swap(q
);
537 webkit_glue::ResourceLoaderBridge
* ResourceDispatcher::CreateBridge(
538 const webkit_glue::ResourceLoaderBridge::RequestInfo
& request_info
) {
539 return new webkit_glue::IPCResourceLoaderBridge(this, request_info
);
542 bool ResourceDispatcher::IsResourceDispatcherMessage(
543 const IPC::Message
& message
) {
544 switch (message
.type()) {
545 case ResourceMsg_UploadProgress::ID
:
546 case ResourceMsg_ReceivedResponse::ID
:
547 case ResourceMsg_ReceivedCachedMetadata::ID
:
548 case ResourceMsg_ReceivedRedirect::ID
:
549 case ResourceMsg_DataReceived::ID
:
550 case ResourceMsg_DataDownloaded::ID
:
551 case ResourceMsg_RequestComplete::ID
:
562 void ResourceDispatcher::ReleaseResourcesInDataMessage(
563 const IPC::Message
& message
) {
566 if (!message
.ReadInt(&iter
, &request_id
)) {
567 NOTREACHED() << "malformed resource message";
571 // If the message contains a shared memory handle, we should close the
572 // handle or there will be a memory leak.
573 if (message
.type() == ResourceMsg_DataReceived::ID
) {
574 base::SharedMemoryHandle shm_handle
;
575 if (IPC::ParamTraits
<base::SharedMemoryHandle
>::Read(&message
,
578 base::SharedMemory::CloseHandle(shm_handle
);
584 void ResourceDispatcher::ReleaseResourcesInMessageQueue(MessageQueue
* queue
) {
585 while (!queue
->empty()) {
586 IPC::Message
* message
= queue
->front();
587 ReleaseResourcesInDataMessage(*message
);