1 // Copyright (c) 2013 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 "ppapi/proxy/url_loader_resource.h"
7 #include "base/logging.h"
8 #include "ppapi/c/pp_completion_callback.h"
9 #include "ppapi/c/pp_errors.h"
10 #include "ppapi/c/ppb_url_loader.h"
11 #include "ppapi/proxy/dispatch_reply_message.h"
12 #include "ppapi/proxy/file_ref_resource.h"
13 #include "ppapi/proxy/ppapi_messages.h"
14 #include "ppapi/proxy/url_request_info_resource.h"
15 #include "ppapi/proxy/url_response_info_resource.h"
16 #include "ppapi/shared_impl/ppapi_globals.h"
17 #include "ppapi/shared_impl/url_response_info_data.h"
18 #include "ppapi/thunk/enter.h"
19 #include "ppapi/thunk/resource_creation_api.h"
21 using ppapi::thunk::EnterResourceNoLock
;
22 using ppapi::thunk::PPB_URLLoader_API
;
23 using ppapi::thunk::PPB_URLRequestInfo_API
;
26 // Do not warn about use of std::copy with raw pointers.
27 #pragma warning(disable : 4996)
33 URLLoaderResource::URLLoaderResource(Connection connection
,
35 : PluginResource(connection
, instance
),
36 mode_(MODE_WAITING_TO_OPEN
),
37 status_callback_(NULL
),
39 total_bytes_to_be_sent_(-1),
41 total_bytes_to_be_received_(-1),
44 done_status_(PP_OK_COMPLETIONPENDING
),
45 is_streaming_to_file_(false),
46 is_asynchronous_load_suspended_(false) {
47 SendCreate(RENDERER
, PpapiHostMsg_URLLoader_Create());
50 URLLoaderResource::URLLoaderResource(Connection connection
,
52 int pending_main_document_loader_id
,
53 const ppapi::URLResponseInfoData
& data
)
54 : PluginResource(connection
, instance
),
56 status_callback_(NULL
),
58 total_bytes_to_be_sent_(-1),
60 total_bytes_to_be_received_(-1),
63 done_status_(PP_OK_COMPLETIONPENDING
),
64 is_streaming_to_file_(false),
65 is_asynchronous_load_suspended_(false) {
66 AttachToPendingHost(RENDERER
, pending_main_document_loader_id
);
67 SaveResponseInfo(data
);
70 URLLoaderResource::~URLLoaderResource() {
73 PPB_URLLoader_API
* URLLoaderResource::AsPPB_URLLoader_API() {
77 int32_t URLLoaderResource::Open(PP_Resource request_id
,
78 scoped_refptr
<TrackedCallback
> callback
) {
79 EnterResourceNoLock
<PPB_URLRequestInfo_API
> enter_request(request_id
, true);
80 if (enter_request
.failed()) {
81 Log(PP_LOGLEVEL_ERROR
,
82 "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper"
83 " users: use the ResourceRequest constructor that takes an instance or"
84 " else the request will be null.)");
85 return PP_ERROR_BADARGUMENT
;
87 return Open(enter_request
.object()->GetData(), 0, callback
);
90 int32_t URLLoaderResource::Open(
91 const ::ppapi::URLRequestInfoData
& request_data
,
93 scoped_refptr
<TrackedCallback
> callback
) {
94 int32_t rv
= ValidateCallback(callback
);
97 if (mode_
!= MODE_WAITING_TO_OPEN
)
98 return PP_ERROR_INPROGRESS
;
100 request_data_
= request_data
;
102 mode_
= MODE_OPENING
;
103 is_asynchronous_load_suspended_
= false;
105 RegisterCallback(callback
);
106 Post(RENDERER
, PpapiHostMsg_URLLoader_Open(request_data
));
107 return PP_OK_COMPLETIONPENDING
;
110 int32_t URLLoaderResource::FollowRedirect(
111 scoped_refptr
<TrackedCallback
> callback
) {
112 int32_t rv
= ValidateCallback(callback
);
115 if (mode_
!= MODE_OPENING
)
116 return PP_ERROR_INPROGRESS
;
118 SetDefersLoading(false); // Allow the redirect to continue.
119 RegisterCallback(callback
);
120 return PP_OK_COMPLETIONPENDING
;
123 PP_Bool
URLLoaderResource::GetUploadProgress(int64_t* bytes_sent
,
124 int64_t* total_bytes_to_be_sent
) {
125 if (!request_data_
.record_upload_progress
) {
127 *total_bytes_to_be_sent
= 0;
130 *bytes_sent
= bytes_sent_
;
131 *total_bytes_to_be_sent
= total_bytes_to_be_sent_
;
135 PP_Bool
URLLoaderResource::GetDownloadProgress(
136 int64_t* bytes_received
,
137 int64_t* total_bytes_to_be_received
) {
138 if (!request_data_
.record_download_progress
) {
140 *total_bytes_to_be_received
= 0;
143 *bytes_received
= bytes_received_
;
144 *total_bytes_to_be_received
= total_bytes_to_be_received_
;
148 PP_Resource
URLLoaderResource::GetResponseInfo() {
149 if (response_info_
.get())
150 return response_info_
->GetReference();
154 int32_t URLLoaderResource::ReadResponseBody(
156 int32_t bytes_to_read
,
157 scoped_refptr
<TrackedCallback
> callback
) {
158 int32_t rv
= ValidateCallback(callback
);
161 if (!response_info_
.get())
162 return PP_ERROR_FAILED
;
164 // Fail if we have a valid file ref.
165 // ReadResponseBody() is for reading to a user-provided buffer.
166 if (response_info_
->data().body_as_file_ref
.IsValid())
167 return PP_ERROR_FAILED
;
169 if (bytes_to_read
<= 0 || !buffer
)
170 return PP_ERROR_BADARGUMENT
;
172 user_buffer_
= static_cast<char*>(buffer
);
173 user_buffer_size_
= bytes_to_read
;
175 if (!buffer_
.empty())
176 return FillUserBuffer();
178 // We may have already reached EOF.
179 if (done_status_
!= PP_OK_COMPLETIONPENDING
) {
181 user_buffer_size_
= 0;
185 RegisterCallback(callback
);
186 return PP_OK_COMPLETIONPENDING
;
189 int32_t URLLoaderResource::FinishStreamingToFile(
190 scoped_refptr
<TrackedCallback
> callback
) {
191 int32_t rv
= ValidateCallback(callback
);
194 if (!response_info_
.get())
195 return PP_ERROR_FAILED
;
197 // Fail if we do not have a valid file ref.
198 if (!response_info_
->data().body_as_file_ref
.IsValid())
199 return PP_ERROR_FAILED
;
201 // We may have already reached EOF.
202 if (done_status_
!= PP_OK_COMPLETIONPENDING
)
205 is_streaming_to_file_
= true;
206 if (is_asynchronous_load_suspended_
)
207 SetDefersLoading(false);
209 // Wait for didFinishLoading / didFail.
210 RegisterCallback(callback
);
211 return PP_OK_COMPLETIONPENDING
;
214 void URLLoaderResource::Close() {
215 mode_
= MODE_LOAD_COMPLETE
;
216 done_status_
= PP_ERROR_ABORTED
;
218 Post(RENDERER
, PpapiHostMsg_URLLoader_Close());
220 // Abort the callbacks, the plugin doesn't want to be called back after this.
221 // TODO(brettw) this should fix bug 69457, mark it fixed. ============
222 if (TrackedCallback::IsPending(pending_callback_
))
223 pending_callback_
->PostAbort();
226 void URLLoaderResource::GrantUniversalAccess() {
227 Post(RENDERER
, PpapiHostMsg_URLLoader_GrantUniversalAccess());
230 void URLLoaderResource::RegisterStatusCallback(
231 PP_URLLoaderTrusted_StatusCallback callback
) {
232 status_callback_
= callback
;
235 void URLLoaderResource::OnReplyReceived(
236 const ResourceMessageReplyParams
& params
,
237 const IPC::Message
& msg
) {
238 PPAPI_BEGIN_MESSAGE_MAP(URLLoaderResource
, msg
)
239 case PpapiPluginMsg_URLLoader_SendData::ID
:
240 // Special message, manually dispatch since we don't want the automatic
242 OnPluginMsgSendData(params
, msg
);
245 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
246 PpapiPluginMsg_URLLoader_ReceivedResponse
,
247 OnPluginMsgReceivedResponse
)
248 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
249 PpapiPluginMsg_URLLoader_FinishedLoading
,
250 OnPluginMsgFinishedLoading
)
251 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
252 PpapiPluginMsg_URLLoader_UpdateProgress
,
253 OnPluginMsgUpdateProgress
)
254 PPAPI_END_MESSAGE_MAP()
257 void URLLoaderResource::OnPluginMsgReceivedResponse(
258 const ResourceMessageReplyParams
& params
,
259 const URLResponseInfoData
& data
) {
260 SaveResponseInfo(data
);
264 void URLLoaderResource::OnPluginMsgSendData(
265 const ResourceMessageReplyParams
& params
,
266 const IPC::Message
& message
) {
267 PickleIterator
iter(message
);
270 if (!iter
.ReadData(&data
, &data_length
)) {
271 NOTREACHED() << "Expecting data";
275 mode_
= MODE_STREAMING_DATA
;
276 buffer_
.insert(buffer_
.end(), data
, data
+ data_length
);
278 // To avoid letting the network stack download an entire stream all at once,
279 // defer loading when we have enough buffer.
280 // Check for this before we run the callback, even though that could move
281 // data out of the buffer. Doing anything after the callback is unsafe.
282 DCHECK(request_data_
.prefetch_buffer_lower_threshold
<
283 request_data_
.prefetch_buffer_upper_threshold
);
284 if (!is_streaming_to_file_
&&
285 !is_asynchronous_load_suspended_
&&
286 (buffer_
.size() >= static_cast<size_t>(
287 request_data_
.prefetch_buffer_upper_threshold
))) {
288 DVLOG(1) << "Suspending async load - buffer size: " << buffer_
.size();
289 SetDefersLoading(true);
293 RunCallback(FillUserBuffer());
295 DCHECK(!TrackedCallback::IsPending(pending_callback_
));
298 void URLLoaderResource::OnPluginMsgFinishedLoading(
299 const ResourceMessageReplyParams
& params
,
301 mode_
= MODE_LOAD_COMPLETE
;
302 done_status_
= result
;
304 user_buffer_size_
= 0;
306 // If the client hasn't called any function that takes a callback since
307 // the initial call to Open, or called ReadResponseBody and got a
308 // synchronous return, then the callback will be NULL.
309 if (TrackedCallback::IsPending(pending_callback_
))
310 RunCallback(done_status_
);
313 void URLLoaderResource::OnPluginMsgUpdateProgress(
314 const ResourceMessageReplyParams
& params
,
316 int64_t total_bytes_to_be_sent
,
317 int64_t bytes_received
,
318 int64_t total_bytes_to_be_received
) {
319 bytes_sent_
= bytes_sent
;
320 total_bytes_to_be_sent_
= total_bytes_to_be_sent
;
321 bytes_received_
= bytes_received
;
322 total_bytes_to_be_received_
= total_bytes_to_be_received
;
324 if (status_callback_
)
325 status_callback_(pp_instance(), pp_resource(),
326 bytes_sent_
, total_bytes_to_be_sent_
,
327 bytes_received_
, total_bytes_to_be_received_
);
330 void URLLoaderResource::SetDefersLoading(bool defers_loading
) {
331 Post(RENDERER
, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading
));
334 int32_t URLLoaderResource::ValidateCallback(
335 scoped_refptr
<TrackedCallback
> callback
) {
336 DCHECK(callback
.get());
337 if (TrackedCallback::IsPending(pending_callback_
))
338 return PP_ERROR_INPROGRESS
;
342 void URLLoaderResource::RegisterCallback(
343 scoped_refptr
<TrackedCallback
> callback
) {
344 DCHECK(!TrackedCallback::IsPending(pending_callback_
));
345 pending_callback_
= callback
;
348 void URLLoaderResource::RunCallback(int32_t result
) {
349 // This may be null when this is a main document loader.
350 if (!pending_callback_
.get())
353 // If |user_buffer_| was set as part of registering a callback, the paths
354 // which trigger that callack must have cleared it since the callback is now
355 // free to delete it.
356 DCHECK(!user_buffer_
);
358 // As a second line of defense, clear the |user_buffer_| in case the
359 // callbacks get called in an unexpected order.
361 user_buffer_size_
= 0;
362 pending_callback_
->Run(result
);
365 void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData
& data
) {
366 // Create a proxy resource for the the file ref host resource if needed.
367 PP_Resource body_as_file_ref
= 0;
368 if (data
.body_as_file_ref
.IsValid()) {
369 body_as_file_ref
= FileRefResource::CreateFileRef(connection(),
371 data
.body_as_file_ref
);
373 response_info_
= new URLResponseInfoResource(
374 connection(), pp_instance(), data
, body_as_file_ref
);
377 size_t URLLoaderResource::FillUserBuffer() {
378 DCHECK(user_buffer_
);
379 DCHECK(user_buffer_size_
);
381 size_t bytes_to_copy
= std::min(buffer_
.size(), user_buffer_size_
);
382 std::copy(buffer_
.begin(), buffer_
.begin() + bytes_to_copy
, user_buffer_
);
383 buffer_
.erase(buffer_
.begin(), buffer_
.begin() + bytes_to_copy
);
385 // If the buffer is getting too empty, resume asynchronous loading.
386 if (is_asynchronous_load_suspended_
&&
387 buffer_
.size() <= static_cast<size_t>(
388 request_data_
.prefetch_buffer_lower_threshold
)) {
389 DVLOG(1) << "Resuming async load - buffer size: " << buffer_
.size();
390 SetDefersLoading(false);
393 // Reset for next time.
395 user_buffer_size_
= 0;
396 return bytes_to_copy
;