url_loader_resource.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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.
4
5#include "ppapi/proxy/url_loader_resource.h"
6
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/ppapi_messages.h"
13#include "ppapi/proxy/ppb_file_ref_proxy.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"
20
21using ppapi::thunk::EnterResourceNoLock;
22using ppapi::thunk::PPB_URLLoader_API;
23using ppapi::thunk::PPB_URLRequestInfo_API;
24
25#ifdef _MSC_VER
26// Do not warn about use of std::copy with raw pointers.
27#pragma warning(disable : 4996)
28#endif
29
30namespace ppapi {
31namespace proxy {
32
33URLLoaderResource::URLLoaderResource(Connection connection,
34                                     PP_Instance instance)
35    : PluginResource(connection, instance),
36      mode_(MODE_WAITING_TO_OPEN),
37      status_callback_(NULL),
38      bytes_sent_(0),
39      total_bytes_to_be_sent_(-1),
40      bytes_received_(0),
41      total_bytes_to_be_received_(-1),
42      user_buffer_(NULL),
43      user_buffer_size_(0),
44      done_status_(PP_OK_COMPLETIONPENDING),
45      is_streaming_to_file_(false),
46      is_asynchronous_load_suspended_(false) {
47  SendCreate(RENDERER, PpapiHostMsg_URLLoader_Create());
48}
49
50URLLoaderResource::URLLoaderResource(Connection connection,
51                                     PP_Instance instance,
52                                     int pending_main_document_loader_id,
53                                     const ppapi::URLResponseInfoData& data)
54    : PluginResource(connection, instance),
55      mode_(MODE_OPENING),
56      status_callback_(NULL),
57      bytes_sent_(0),
58      total_bytes_to_be_sent_(-1),
59      bytes_received_(0),
60      total_bytes_to_be_received_(-1),
61      user_buffer_(NULL),
62      user_buffer_size_(0),
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);
68}
69
70URLLoaderResource::~URLLoaderResource() {
71}
72
73PPB_URLLoader_API* URLLoaderResource::AsPPB_URLLoader_API() {
74  return this;
75}
76
77int32_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;
86  }
87  return Open(enter_request.object()->GetData(), 0, callback);
88}
89
90int32_t URLLoaderResource::Open(
91    const ::ppapi::URLRequestInfoData& request_data,
92    int requestor_pid,
93    scoped_refptr<TrackedCallback> callback) {
94  int32_t rv = ValidateCallback(callback);
95  if (rv != PP_OK)
96    return rv;
97  if (mode_ != MODE_WAITING_TO_OPEN)
98    return PP_ERROR_INPROGRESS;
99
100  request_data_ = request_data;
101
102  mode_ = MODE_OPENING;
103  is_asynchronous_load_suspended_ = false;
104
105  RegisterCallback(callback);
106  Post(RENDERER, PpapiHostMsg_URLLoader_Open(request_data));
107  return PP_OK_COMPLETIONPENDING;
108}
109
110int32_t URLLoaderResource::FollowRedirect(
111    scoped_refptr<TrackedCallback> callback) {
112  int32_t rv = ValidateCallback(callback);
113  if (rv != PP_OK)
114    return rv;
115  if (mode_ != MODE_OPENING)
116    return PP_ERROR_INPROGRESS;
117
118  SetDefersLoading(false);  // Allow the redirect to continue.
119  RegisterCallback(callback);
120  return PP_OK_COMPLETIONPENDING;
121}
122
123PP_Bool URLLoaderResource::GetUploadProgress(int64_t* bytes_sent,
124                                              int64_t* total_bytes_to_be_sent) {
125  if (!request_data_.record_upload_progress) {
126    *bytes_sent = 0;
127    *total_bytes_to_be_sent = 0;
128    return PP_FALSE;
129  }
130  *bytes_sent = bytes_sent_;
131  *total_bytes_to_be_sent = total_bytes_to_be_sent_;
132  return PP_TRUE;
133}
134
135PP_Bool URLLoaderResource::GetDownloadProgress(
136    int64_t* bytes_received,
137    int64_t* total_bytes_to_be_received) {
138  if (!request_data_.record_download_progress) {
139    *bytes_received = 0;
140    *total_bytes_to_be_received = 0;
141    return PP_FALSE;
142  }
143  *bytes_received = bytes_received_;
144  *total_bytes_to_be_received = total_bytes_to_be_received_;
145  return PP_TRUE;
146}
147
148PP_Resource URLLoaderResource::GetResponseInfo() {
149  if (response_info_.get())
150    return response_info_->GetReference();
151  return 0;
152}
153
154int32_t URLLoaderResource::ReadResponseBody(
155    void* buffer,
156    int32_t bytes_to_read,
157    scoped_refptr<TrackedCallback> callback) {
158  int32_t rv = ValidateCallback(callback);
159  if (rv != PP_OK)
160    return rv;
161  if (!response_info_.get() ||
162      !response_info_->data().body_as_file_ref.resource.is_null())
163    return PP_ERROR_FAILED;
164  if (bytes_to_read <= 0 || !buffer)
165    return PP_ERROR_BADARGUMENT;
166
167  user_buffer_ = static_cast<char*>(buffer);
168  user_buffer_size_ = bytes_to_read;
169
170  if (!buffer_.empty())
171    return FillUserBuffer();
172
173  // We may have already reached EOF.
174  if (done_status_ != PP_OK_COMPLETIONPENDING) {
175    user_buffer_ = NULL;
176    user_buffer_size_ = 0;
177    return done_status_;
178  }
179
180  RegisterCallback(callback);
181  return PP_OK_COMPLETIONPENDING;
182}
183
184int32_t URLLoaderResource::FinishStreamingToFile(
185    scoped_refptr<TrackedCallback> callback) {
186  int32_t rv = ValidateCallback(callback);
187  if (rv != PP_OK)
188    return rv;
189  if (!response_info_.get() ||
190      response_info_->data().body_as_file_ref.resource.is_null())
191    return PP_ERROR_FAILED;
192
193  // We may have already reached EOF.
194  if (done_status_ != PP_OK_COMPLETIONPENDING)
195    return done_status_;
196
197  is_streaming_to_file_ = true;
198  if (is_asynchronous_load_suspended_)
199    SetDefersLoading(false);
200
201  // Wait for didFinishLoading / didFail.
202  RegisterCallback(callback);
203  return PP_OK_COMPLETIONPENDING;
204}
205
206void URLLoaderResource::Close() {
207  mode_ = MODE_LOAD_COMPLETE;
208  done_status_ = PP_ERROR_ABORTED;
209
210  Post(RENDERER, PpapiHostMsg_URLLoader_Close());
211
212  // Abort the callbacks, the plugin doesn't want to be called back after this.
213  // TODO(brettw) this should fix bug 69457, mark it fixed. ============
214  if (TrackedCallback::IsPending(pending_callback_))
215    pending_callback_->PostAbort();
216}
217
218void URLLoaderResource::GrantUniversalAccess() {
219  Post(RENDERER, PpapiHostMsg_URLLoader_GrantUniversalAccess());
220}
221
222void URLLoaderResource::RegisterStatusCallback(
223    PP_URLLoaderTrusted_StatusCallback callback) {
224  status_callback_ = callback;
225}
226
227void URLLoaderResource::OnReplyReceived(
228    const ResourceMessageReplyParams& params,
229    const IPC::Message& msg) {
230  IPC_BEGIN_MESSAGE_MAP(URLLoaderResource, msg)
231    case PpapiPluginMsg_URLLoader_SendData::ID:
232      // Special message, manually dispatch since we don't want the automatic
233      // unpickling.
234      OnPluginMsgSendData(params, msg);
235      break;
236
237    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
238        PpapiPluginMsg_URLLoader_ReceivedResponse,
239        OnPluginMsgReceivedResponse)
240    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
241        PpapiPluginMsg_URLLoader_FinishedLoading,
242        OnPluginMsgFinishedLoading)
243    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
244        PpapiPluginMsg_URLLoader_UpdateProgress,
245        OnPluginMsgUpdateProgress)
246  IPC_END_MESSAGE_MAP()
247}
248
249void URLLoaderResource::OnPluginMsgReceivedResponse(
250    const ResourceMessageReplyParams& params,
251    const URLResponseInfoData& data) {
252  SaveResponseInfo(data);
253  RunCallback(PP_OK);
254}
255
256void URLLoaderResource::OnPluginMsgSendData(
257    const ResourceMessageReplyParams& params,
258    const IPC::Message& message) {
259  PickleIterator iter(message);
260  const char* data;
261  int data_length;
262  if (!iter.ReadData(&data, &data_length)) {
263    NOTREACHED() << "Expecting data";
264    return;
265  }
266
267  mode_ = MODE_STREAMING_DATA;
268  buffer_.insert(buffer_.end(), data, data + data_length);
269
270  // To avoid letting the network stack download an entire stream all at once,
271  // defer loading when we have enough buffer.
272  // Check for this before we run the callback, even though that could move
273  // data out of the buffer. Doing anything after the callback is unsafe.
274  DCHECK(request_data_.prefetch_buffer_lower_threshold <
275         request_data_.prefetch_buffer_upper_threshold);
276  if (!is_streaming_to_file_ &&
277      !is_asynchronous_load_suspended_ &&
278      (buffer_.size() >= static_cast<size_t>(
279          request_data_.prefetch_buffer_upper_threshold))) {
280    DVLOG(1) << "Suspending async load - buffer size: " << buffer_.size();
281    SetDefersLoading(true);
282  }
283
284  if (user_buffer_)
285    RunCallback(FillUserBuffer());
286  else
287    DCHECK(!TrackedCallback::IsPending(pending_callback_));
288}
289
290void URLLoaderResource::OnPluginMsgFinishedLoading(
291    const ResourceMessageReplyParams& params,
292    int32_t result) {
293  mode_ = MODE_LOAD_COMPLETE;
294  done_status_ = result;
295  user_buffer_ = NULL;
296  user_buffer_size_ = 0;
297
298  // If the client hasn't called any function that takes a callback since
299  // the initial call to Open, or called ReadResponseBody and got a
300  // synchronous return, then the callback will be NULL.
301  if (TrackedCallback::IsPending(pending_callback_))
302    RunCallback(done_status_);
303}
304
305void URLLoaderResource::OnPluginMsgUpdateProgress(
306    const ResourceMessageReplyParams& params,
307    int64_t bytes_sent,
308    int64_t total_bytes_to_be_sent,
309    int64_t bytes_received,
310    int64_t total_bytes_to_be_received) {
311  bytes_sent_ = bytes_sent;
312  total_bytes_to_be_sent_ = total_bytes_to_be_sent;
313  bytes_received_ = bytes_received;
314  total_bytes_to_be_received_ = total_bytes_to_be_received;
315
316  if (status_callback_)
317    status_callback_(pp_instance(), pp_resource(),
318                     bytes_sent_, total_bytes_to_be_sent_,
319                     bytes_received_, total_bytes_to_be_received_);
320}
321
322void URLLoaderResource::SetDefersLoading(bool defers_loading) {
323  Post(RENDERER, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading));
324}
325
326int32_t URLLoaderResource::ValidateCallback(
327    scoped_refptr<TrackedCallback> callback) {
328  DCHECK(callback);
329  if (TrackedCallback::IsPending(pending_callback_))
330    return PP_ERROR_INPROGRESS;
331  return PP_OK;
332}
333
334void URLLoaderResource::RegisterCallback(
335    scoped_refptr<TrackedCallback> callback) {
336  DCHECK(!TrackedCallback::IsPending(pending_callback_));
337  pending_callback_ = callback;
338}
339
340void URLLoaderResource::RunCallback(int32_t result) {
341  // This may be null when this is a main document loader.
342  if (!pending_callback_.get())
343    return;
344
345  // If |user_buffer_| was set as part of registering a callback, the paths
346  // which trigger that callack must have cleared it since the callback is now
347  // free to delete it.
348  DCHECK(!user_buffer_);
349
350  // As a second line of defense, clear the |user_buffer_| in case the
351  // callbacks get called in an unexpected order.
352  user_buffer_ = NULL;
353  user_buffer_size_ = 0;
354  pending_callback_->Run(result);
355}
356
357void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData& data) {
358  // Create a proxy resource for the the file ref host resource if needed.
359  PP_Resource body_as_file_ref = 0;
360  if (!data.body_as_file_ref.resource.is_null()) {
361    thunk::EnterResourceCreationNoLock enter(pp_instance());
362    body_as_file_ref =
363        enter.functions()->CreateFileRef(data.body_as_file_ref);
364  }
365  response_info_ = new URLResponseInfoResource(
366      connection(), pp_instance(), data, body_as_file_ref);
367}
368
369size_t URLLoaderResource::FillUserBuffer() {
370  DCHECK(user_buffer_);
371  DCHECK(user_buffer_size_);
372
373  size_t bytes_to_copy = std::min(buffer_.size(), user_buffer_size_);
374  std::copy(buffer_.begin(), buffer_.begin() + bytes_to_copy, user_buffer_);
375  buffer_.erase(buffer_.begin(), buffer_.begin() + bytes_to_copy);
376
377  // If the buffer is getting too empty, resume asynchronous loading.
378  if (is_asynchronous_load_suspended_ &&
379      buffer_.size() <= static_cast<size_t>(
380          request_data_.prefetch_buffer_lower_threshold)) {
381    DVLOG(1) << "Resuming async load - buffer size: " << buffer_.size();
382    SetDefersLoading(false);
383  }
384
385  // Reset for next time.
386  user_buffer_ = NULL;
387  user_buffer_size_ = 0;
388  return bytes_to_copy;
389}
390
391}  // namespace proxy
392}  // namespace ppapi