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 "ppapi/thunk/enter.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/stringprintf.h"
11#include "base/synchronization/lock.h"
12#include "ppapi/c/pp_errors.h"
13#include "ppapi/shared_impl/ppapi_globals.h"
14#include "ppapi/shared_impl/tracked_callback.h"
15#include "ppapi/thunk/ppb_instance_api.h"
16#include "ppapi/thunk/resource_creation_api.h"
17
18namespace ppapi {
19namespace {
20
21bool IsMainThread() {
22  return
23      PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread();
24}
25
26}  // namespace
27
28namespace thunk {
29
30namespace subtle {
31
32EnterBase::EnterBase()
33    : resource_(NULL),
34      retval_(PP_OK) {
35}
36
37EnterBase::EnterBase(PP_Resource resource)
38    : resource_(GetResource(resource)),
39      retval_(PP_OK) {
40}
41
42EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id)
43    : resource_(GetSingletonResource(instance, resource_id)),
44      retval_(PP_OK) {
45}
46
47EnterBase::EnterBase(PP_Resource resource,
48                     const PP_CompletionCallback& callback)
49    : resource_(GetResource(resource)),
50      retval_(PP_OK) {
51  callback_ = new TrackedCallback(resource_, callback);
52}
53
54EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id,
55                     const PP_CompletionCallback& callback)
56    : resource_(GetSingletonResource(instance, resource_id)),
57      retval_(PP_OK) {
58  DCHECK(resource_ || !instance);
59  if (!resource_)
60    retval_ = PP_ERROR_BADARGUMENT;
61  callback_ = new TrackedCallback(resource_, callback);
62}
63
64EnterBase::~EnterBase() {
65  // callback_ is cleared any time it is run, scheduled to be run, or once we
66  // know it will be completed asynchronously. So by this point it should be
67  // NULL.
68  DCHECK(!callback_.get())
69      << "|callback_| is not NULL. Did you forget to call "
70         "|EnterBase::SetResult| in the interface's thunk?";
71}
72
73int32_t EnterBase::SetResult(int32_t result) {
74  if (!callback_.get()) {
75    // It doesn't make sense to call SetResult if there is no callback.
76    NOTREACHED();
77    retval_ = result;
78    return result;
79  }
80  if (result == PP_OK_COMPLETIONPENDING) {
81    retval_ = result;
82    if (callback_->is_blocking()) {
83      DCHECK(!IsMainThread());  // We should have returned an error before this.
84      retval_ = callback_->BlockUntilComplete();
85    } else {
86      // The callback is not blocking and the operation will complete
87      // asynchronously, so there's nothing to do.
88      retval_ = result;
89    }
90  } else {
91    // The function completed synchronously.
92    if (callback_->is_required()) {
93      // This is a required callback, so we must issue it asynchronously.
94      callback_->PostRun(result);
95      retval_ = PP_OK_COMPLETIONPENDING;
96    } else {
97      // The callback is blocking or optional, so all we need to do is mark
98      // the callback as completed so that it won't be issued later.
99      callback_->MarkAsCompleted();
100      retval_ = result;
101    }
102  }
103  callback_ = NULL;
104  return retval_;
105}
106
107// static
108Resource* EnterBase::GetResource(PP_Resource resource) {
109  return PpapiGlobals::Get()->GetResourceTracker()->GetResource(resource);
110}
111
112// static
113Resource* EnterBase::GetSingletonResource(PP_Instance instance,
114                                          SingletonResourceID resource_id) {
115  PPB_Instance_API* ppb_instance =
116      PpapiGlobals::Get()->GetInstanceAPI(instance);
117  if (!ppb_instance)
118    return NULL;
119
120  return ppb_instance->GetSingletonResource(instance, resource_id);
121}
122
123void EnterBase::SetStateForCallbackError(bool report_error) {
124  if (PpapiGlobals::Get()->IsHostGlobals()) {
125    // In-process plugins can't make PPAPI calls off the main thread.
126    CHECK(IsMainThread());
127  }
128  if (callback_.get()) {
129    if (callback_->is_blocking() && IsMainThread()) {
130      // Blocking callbacks are never allowed on the main thread.
131      callback_->MarkAsCompleted();
132      callback_ = NULL;
133      retval_ = PP_ERROR_BLOCKS_MAIN_THREAD;
134      if (report_error) {
135        std::string message(
136            "Blocking callbacks are not allowed on the main thread.");
137        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
138                                                    std::string(), message);
139      }
140    } else if (!IsMainThread() &&
141               callback_->has_null_target_loop() &&
142               !callback_->is_blocking()) {
143      // On a non-main thread, there must be a valid target loop for non-
144      // blocking callbacks, or we will have no place to run them.
145
146      // If the callback is required, there's no nice way to tell the plugin.
147      // We can't run their callback asynchronously without a message loop, and
148      // the plugin won't expect any return code other than
149      // PP_OK_COMPLETIONPENDING. So we crash to make the problem more obvious.
150      if (callback_->is_required()) {
151        std::string message("Attempted to use a required callback, but there "
152                            "is no attached message loop on which to run the "
153                            "callback.");
154        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
155                                                    std::string(), message);
156        LOG(FATAL) << message;
157      }
158
159      callback_->MarkAsCompleted();
160      callback_ = NULL;
161      retval_ = PP_ERROR_NO_MESSAGE_LOOP;
162      if (report_error) {
163        std::string message(
164            "The calling thread must have a message loop attached.");
165        PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
166                                                    std::string(), message);
167      }
168    }
169  }
170}
171
172void EnterBase::ClearCallback() {
173  callback_ = NULL;
174}
175
176void EnterBase::SetStateForResourceError(PP_Resource pp_resource,
177                                         Resource* resource_base,
178                                         void* object,
179                                         bool report_error) {
180  // Check for callback errors. If we get any, SetStateForCallbackError will
181  // emit a log message. But we also want to check for resource errors. If there
182  // are both kinds of errors, we'll emit two log messages and return
183  // PP_ERROR_BADRESOURCE.
184  SetStateForCallbackError(report_error);
185
186  if (object)
187    return;  // Everything worked.
188
189  if (callback_.get() && callback_->is_required()) {
190    callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADRESOURCE));
191    callback_ = NULL;
192    retval_ = PP_OK_COMPLETIONPENDING;
193  } else {
194    if (callback_.get())
195      callback_->MarkAsCompleted();
196    callback_ = NULL;
197    retval_ = PP_ERROR_BADRESOURCE;
198  }
199
200  // We choose to silently ignore the error when the pp_resource is null
201  // because this is a pretty common case and we don't want to have lots
202  // of errors in the log. This should be an obvious case to debug.
203  if (report_error && pp_resource) {
204    std::string message;
205    if (resource_base) {
206      message = base::StringPrintf(
207          "0x%X is not the correct type for this function.",
208          pp_resource);
209    } else {
210      message = base::StringPrintf(
211          "0x%X is not a valid resource ID.",
212          pp_resource);
213    }
214    PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
215                                                std::string(), message);
216  }
217}
218
219void EnterBase::SetStateForFunctionError(PP_Instance pp_instance,
220                                         void* object,
221                                         bool report_error) {
222  // Check for callback errors. If we get any, SetStateForCallbackError will
223  // emit a log message. But we also want to check for instance errors. If there
224  // are both kinds of errors, we'll emit two log messages and return
225  // PP_ERROR_BADARGUMENT.
226  SetStateForCallbackError(report_error);
227
228  if (object)
229    return;  // Everything worked.
230
231  if (callback_.get() && callback_->is_required()) {
232    callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADARGUMENT));
233    callback_ = NULL;
234    retval_ = PP_OK_COMPLETIONPENDING;
235  } else {
236    if (callback_.get())
237      callback_->MarkAsCompleted();
238    callback_ = NULL;
239    retval_ = PP_ERROR_BADARGUMENT;
240  }
241
242  // We choose to silently ignore the error when the pp_instance is null as
243  // for PP_Resources above.
244  if (report_error && pp_instance) {
245    std::string message;
246    message = base::StringPrintf(
247        "0x%X is not a valid instance ID.",
248        pp_instance);
249    PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
250                                                std::string(), message);
251  }
252}
253
254}  // namespace subtle
255
256EnterInstance::EnterInstance(PP_Instance instance)
257    : EnterBase(),
258      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
259  SetStateForFunctionError(instance, functions_, true);
260}
261
262EnterInstance::EnterInstance(PP_Instance instance,
263                             const PP_CompletionCallback& callback)
264    : EnterBase(0 /* resource */, callback),
265      // TODO(dmichael): This means that the callback_ we get is not associated
266      //                 even with the instance, but we should handle that for
267      //                 MouseLock (maybe others?).
268      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
269  SetStateForFunctionError(instance, functions_, true);
270}
271
272EnterInstance::~EnterInstance() {
273}
274
275EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
276    : EnterBase(),
277      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
278  SetStateForFunctionError(instance, functions_, true);
279}
280
281EnterInstanceNoLock::EnterInstanceNoLock(
282    PP_Instance instance,
283    const PP_CompletionCallback& callback)
284    : EnterBase(0 /* resource */, callback),
285      // TODO(dmichael): This means that the callback_ we get is not associated
286      //                 even with the instance, but we should handle that for
287      //                 MouseLock (maybe others?).
288      functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
289  SetStateForFunctionError(instance, functions_, true);
290}
291
292EnterInstanceNoLock::~EnterInstanceNoLock() {
293}
294
295EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
296    : EnterBase(),
297      functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
298  SetStateForFunctionError(instance, functions_, true);
299}
300
301EnterResourceCreation::~EnterResourceCreation() {
302}
303
304EnterResourceCreationNoLock::EnterResourceCreationNoLock(PP_Instance instance)
305    : EnterBase(),
306      functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
307  SetStateForFunctionError(instance, functions_, true);
308}
309
310EnterResourceCreationNoLock::~EnterResourceCreationNoLock() {
311}
312
313}  // namespace thunk
314}  // namespace ppapi
315