| // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "ppapi/proxy/url_loader_resource.h" | |
| #include <algorithm> | |
| #include "base/logging.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "ppapi/c/pp_completion_callback.h" | |
| #include "ppapi/c/pp_errors.h" | |
| #include "ppapi/c/ppb_url_loader.h" | |
| #include "ppapi/proxy/dispatch_reply_message.h" | |
| #include "ppapi/proxy/file_ref_resource.h" | |
| #include "ppapi/proxy/ppapi_messages.h" | |
| #include "ppapi/proxy/url_request_info_resource.h" | |
| #include "ppapi/proxy/url_response_info_resource.h" | |
| #include "ppapi/shared_impl/ppapi_globals.h" | |
| #include "ppapi/shared_impl/url_response_info_data.h" | |
| #include "ppapi/thunk/enter.h" | |
| #include "ppapi/thunk/resource_creation_api.h" | |
| using ppapi::thunk::EnterResourceNoLock; | |
| using ppapi::thunk::PPB_URLLoader_API; | |
| using ppapi::thunk::PPB_URLRequestInfo_API; | |
| namespace ppapi { | |
| namespace proxy { | |
| URLLoaderResource::URLLoaderResource(Connection connection, | |
| PP_Instance instance) | |
| : PluginResource(connection, instance), | |
| mode_(MODE_WAITING_TO_OPEN), | |
| status_callback_(NULL), | |
| bytes_sent_(0), | |
| total_bytes_to_be_sent_(-1), | |
| bytes_received_(0), | |
| total_bytes_to_be_received_(-1), | |
| user_buffer_(NULL), | |
| user_buffer_size_(0), | |
| done_status_(PP_OK_COMPLETIONPENDING), | |
| is_streaming_to_file_(false), | |
| is_asynchronous_load_suspended_(false) { | |
| SendCreate(RENDERER, PpapiHostMsg_URLLoader_Create()); | |
| } | |
| URLLoaderResource::URLLoaderResource(Connection connection, | |
| PP_Instance instance, | |
| int pending_main_document_loader_id, | |
| const ppapi::URLResponseInfoData& data) | |
| : PluginResource(connection, instance), | |
| mode_(MODE_OPENING), | |
| status_callback_(NULL), | |
| bytes_sent_(0), | |
| total_bytes_to_be_sent_(-1), | |
| bytes_received_(0), | |
| total_bytes_to_be_received_(-1), | |
| user_buffer_(NULL), | |
| user_buffer_size_(0), | |
| done_status_(PP_OK_COMPLETIONPENDING), | |
| is_streaming_to_file_(false), | |
| is_asynchronous_load_suspended_(false) { | |
| AttachToPendingHost(RENDERER, pending_main_document_loader_id); | |
| SaveResponseInfo(data); | |
| } | |
| URLLoaderResource::~URLLoaderResource() { | |
| } | |
| PPB_URLLoader_API* URLLoaderResource::AsPPB_URLLoader_API() { | |
| return this; | |
| } | |
| int32_t URLLoaderResource::Open(PP_Resource request_id, | |
| scoped_refptr<TrackedCallback> callback) { | |
| EnterResourceNoLock<PPB_URLRequestInfo_API> enter_request(request_id, true); | |
| if (enter_request.failed()) { | |
| Log(PP_LOGLEVEL_ERROR, | |
| "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper" | |
| " users: use the ResourceRequest constructor that takes an instance or" | |
| " else the request will be null.)"); | |
| return PP_ERROR_BADARGUMENT; | |
| } | |
| return Open(enter_request.object()->GetData(), 0, callback); | |
| } | |
| int32_t URLLoaderResource::Open( | |
| const ::ppapi::URLRequestInfoData& request_data, | |
| int requestor_pid, | |
| scoped_refptr<TrackedCallback> callback) { | |
| int32_t rv = ValidateCallback(callback); | |
| if (rv != PP_OK) | |
| return rv; | |
| if (mode_ != MODE_WAITING_TO_OPEN) | |
| return PP_ERROR_INPROGRESS; | |
| request_data_ = request_data; | |
| mode_ = MODE_OPENING; | |
| is_asynchronous_load_suspended_ = false; | |
| RegisterCallback(callback); | |
| Post(RENDERER, PpapiHostMsg_URLLoader_Open(request_data)); | |
| return PP_OK_COMPLETIONPENDING; | |
| } | |
| int32_t URLLoaderResource::FollowRedirect( | |
| scoped_refptr<TrackedCallback> callback) { | |
| int32_t rv = ValidateCallback(callback); | |
| if (rv != PP_OK) | |
| return rv; | |
| if (mode_ != MODE_OPENING) | |
| return PP_ERROR_INPROGRESS; | |
| SetDefersLoading(false); // Allow the redirect to continue. | |
| RegisterCallback(callback); | |
| return PP_OK_COMPLETIONPENDING; | |
| } | |
| PP_Bool URLLoaderResource::GetUploadProgress(int64_t* bytes_sent, | |
| int64_t* total_bytes_to_be_sent) { | |
| if (!request_data_.record_upload_progress) { | |
| *bytes_sent = 0; | |
| *total_bytes_to_be_sent = 0; | |
| return PP_FALSE; | |
| } | |
| *bytes_sent = bytes_sent_; | |
| *total_bytes_to_be_sent = total_bytes_to_be_sent_; | |
| return PP_TRUE; | |
| } | |
| PP_Bool URLLoaderResource::GetDownloadProgress( | |
| int64_t* bytes_received, | |
| int64_t* total_bytes_to_be_received) { | |
| if (!request_data_.record_download_progress) { | |
| *bytes_received = 0; | |
| *total_bytes_to_be_received = 0; | |
| return PP_FALSE; | |
| } | |
| *bytes_received = bytes_received_; | |
| *total_bytes_to_be_received = total_bytes_to_be_received_; | |
| return PP_TRUE; | |
| } | |
| PP_Resource URLLoaderResource::GetResponseInfo() { | |
| if (response_info_.get()) | |
| return response_info_->GetReference(); | |
| return 0; | |
| } | |
| int32_t URLLoaderResource::ReadResponseBody( | |
| void* buffer, | |
| int32_t bytes_to_read, | |
| scoped_refptr<TrackedCallback> callback) { | |
| int32_t rv = ValidateCallback(callback); | |
| if (rv != PP_OK) | |
| return rv; | |
| if (!response_info_.get()) | |
| return PP_ERROR_FAILED; | |
| if (bytes_to_read <= 0 || !buffer) | |
| return PP_ERROR_BADARGUMENT; | |
| user_buffer_ = static_cast<char*>(buffer); | |
| user_buffer_size_ = bytes_to_read; | |
| if (!buffer_.empty()) | |
| return FillUserBuffer(); | |
| // We may have already reached EOF. | |
| if (done_status_ != PP_OK_COMPLETIONPENDING) { | |
| user_buffer_ = NULL; | |
| user_buffer_size_ = 0; | |
| return done_status_; | |
| } | |
| RegisterCallback(callback); | |
| return PP_OK_COMPLETIONPENDING; | |
| } | |
| int32_t URLLoaderResource::FinishStreamingToFile( | |
| scoped_refptr<TrackedCallback> callback) { | |
| return PP_ERROR_NOTSUPPORTED; | |
| } | |
| void URLLoaderResource::Close() { | |
| mode_ = MODE_LOAD_COMPLETE; | |
| done_status_ = PP_ERROR_ABORTED; | |
| Post(RENDERER, PpapiHostMsg_URLLoader_Close()); | |
| // Abort the callbacks, the plugin doesn't want to be called back after this. | |
| if (TrackedCallback::IsPending(pending_callback_)) | |
| pending_callback_->PostAbort(); | |
| } | |
| void URLLoaderResource::GrantUniversalAccess() { | |
| Post(RENDERER, PpapiHostMsg_URLLoader_GrantUniversalAccess()); | |
| } | |
| void URLLoaderResource::RegisterStatusCallback( | |
| PP_URLLoaderTrusted_StatusCallback callback) { | |
| status_callback_ = callback; | |
| } | |
| void URLLoaderResource::OnReplyReceived( | |
| const ResourceMessageReplyParams& params, | |
| const IPC::Message& msg) { | |
| PPAPI_BEGIN_MESSAGE_MAP(URLLoaderResource, msg) | |
| case PpapiPluginMsg_URLLoader_SendData::ID: | |
| // Special message, manually dispatch since we don't want the automatic | |
| // unpickling. | |
| OnPluginMsgSendData(params, msg); | |
| break; | |
| PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( | |
| PpapiPluginMsg_URLLoader_ReceivedResponse, | |
| OnPluginMsgReceivedResponse) | |
| PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( | |
| PpapiPluginMsg_URLLoader_FinishedLoading, | |
| OnPluginMsgFinishedLoading) | |
| PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( | |
| PpapiPluginMsg_URLLoader_UpdateProgress, | |
| OnPluginMsgUpdateProgress) | |
| PPAPI_END_MESSAGE_MAP() | |
| } | |
| void URLLoaderResource::OnPluginMsgReceivedResponse( | |
| const ResourceMessageReplyParams& params, | |
| const URLResponseInfoData& data) { | |
| SaveResponseInfo(data); | |
| RunCallback(PP_OK); | |
| } | |
| void URLLoaderResource::OnPluginMsgSendData( | |
| const ResourceMessageReplyParams& params, | |
| const IPC::Message& message) { | |
| base::PickleIterator iter(message); | |
| const char* data; | |
| int data_length; | |
| if (!iter.ReadData(&data, &data_length)) { | |
| NOTREACHED() << "Expecting data"; | |
| return; | |
| } | |
| mode_ = MODE_STREAMING_DATA; | |
| buffer_.insert(buffer_.end(), data, data + data_length); | |
| // To avoid letting the network stack download an entire stream all at once, | |
| // defer loading when we have enough buffer. | |
| // Check for this before we run the callback, even though that could move | |
| // data out of the buffer. Doing anything after the callback is unsafe. | |
| DCHECK(request_data_.prefetch_buffer_lower_threshold < | |
| request_data_.prefetch_buffer_upper_threshold); | |
| if (!is_streaming_to_file_ && | |
| !is_asynchronous_load_suspended_ && | |
| (buffer_.size() >= static_cast<size_t>( | |
| request_data_.prefetch_buffer_upper_threshold))) { | |
| DVLOG(1) << "Suspending async load - buffer size: " << buffer_.size(); | |
| SetDefersLoading(true); | |
| } | |
| if (user_buffer_) | |
| RunCallback(FillUserBuffer()); | |
| else | |
| DCHECK(!TrackedCallback::IsPending(pending_callback_)); | |
| } | |
| void URLLoaderResource::OnPluginMsgFinishedLoading( | |
| const ResourceMessageReplyParams& params, | |
| int32_t result) { | |
| mode_ = MODE_LOAD_COMPLETE; | |
| done_status_ = result; | |
| user_buffer_ = NULL; | |
| user_buffer_size_ = 0; | |
| // If the client hasn't called any function that takes a callback since | |
| // the initial call to Open, or called ReadResponseBody and got a | |
| // synchronous return, then the callback will be NULL. | |
| if (TrackedCallback::IsPending(pending_callback_)) | |
| RunCallback(done_status_); | |
| } | |
| void URLLoaderResource::OnPluginMsgUpdateProgress( | |
| const ResourceMessageReplyParams& params, | |
| int64_t bytes_sent, | |
| int64_t total_bytes_to_be_sent, | |
| int64_t bytes_received, | |
| int64_t total_bytes_to_be_received) { | |
| bytes_sent_ = bytes_sent; | |
| total_bytes_to_be_sent_ = total_bytes_to_be_sent; | |
| bytes_received_ = bytes_received; | |
| total_bytes_to_be_received_ = total_bytes_to_be_received; | |
| if (status_callback_) { | |
| status_callback_(pp_instance(), pp_resource(), | |
| bytes_sent_, total_bytes_to_be_sent_, | |
| bytes_received_, total_bytes_to_be_received_); | |
| } | |
| } | |
| void URLLoaderResource::SetDefersLoading(bool defers_loading) { | |
| is_asynchronous_load_suspended_ = defers_loading; | |
| Post(RENDERER, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading)); | |
| } | |
| int32_t URLLoaderResource::ValidateCallback( | |
| scoped_refptr<TrackedCallback> callback) { | |
| DCHECK(callback.get()); | |
| if (TrackedCallback::IsPending(pending_callback_)) | |
| return PP_ERROR_INPROGRESS; | |
| return PP_OK; | |
| } | |
| void URLLoaderResource::RegisterCallback( | |
| scoped_refptr<TrackedCallback> callback) { | |
| DCHECK(!TrackedCallback::IsPending(pending_callback_)); | |
| pending_callback_ = callback; | |
| } | |
| void URLLoaderResource::RunCallback(int32_t result) { | |
| // This may be null when this is a main document loader. | |
| if (!pending_callback_.get()) | |
| return; | |
| // If |user_buffer_| was set as part of registering a callback, the paths | |
| // which trigger that callack must have cleared it since the callback is now | |
| // free to delete it. | |
| DCHECK(!user_buffer_); | |
| // As a second line of defense, clear the |user_buffer_| in case the | |
| // callbacks get called in an unexpected order. | |
| user_buffer_ = NULL; | |
| user_buffer_size_ = 0; | |
| pending_callback_->Run(result); | |
| } | |
| void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData& data) { | |
| response_info_ = | |
| new URLResponseInfoResource(connection(), pp_instance(), data); | |
| } | |
| int32_t URLLoaderResource::FillUserBuffer() { | |
| DCHECK(user_buffer_); | |
| DCHECK(user_buffer_size_); | |
| size_t bytes_to_copy = std::min(buffer_.size(), user_buffer_size_); | |
| std::copy(buffer_.begin(), buffer_.begin() + bytes_to_copy, user_buffer_); | |
| buffer_.erase(buffer_.begin(), buffer_.begin() + bytes_to_copy); | |
| // If the buffer is getting too empty, resume asynchronous loading. | |
| if (is_asynchronous_load_suspended_ && | |
| buffer_.size() <= static_cast<size_t>( | |
| request_data_.prefetch_buffer_lower_threshold)) { | |
| DVLOG(1) << "Resuming async load - buffer size: " << buffer_.size(); | |
| SetDefersLoading(false); | |
| } | |
| // Reset for next time. | |
| user_buffer_ = NULL; | |
| user_buffer_size_ = 0; | |
| return base::checked_cast<int32_t>(bytes_to_copy); | |
| } | |
| } // namespace proxy | |
| } // namespace ppapi |