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