Home | History | Annotate | Download | only in thunk
      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 
     18 namespace ppapi {
     19 namespace {
     20 
     21 bool IsMainThread() {
     22   return
     23       PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread();
     24 }
     25 
     26 }  // namespace
     27 
     28 namespace thunk {
     29 
     30 namespace subtle {
     31 
     32 void 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 
     40 EnterBase::EnterBase()
     41     : resource_(NULL),
     42       retval_(PP_OK) {
     43 }
     44 
     45 EnterBase::EnterBase(PP_Resource resource)
     46     : resource_(GetResource(resource)),
     47       retval_(PP_OK) {
     48 }
     49 
     50 EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id)
     51     : resource_(GetSingletonResource(instance, resource_id)),
     52       retval_(PP_OK) {
     53 }
     54 
     55 EnterBase::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 
     62 EnterBase::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 
     72 EnterBase::~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 
     81 int32_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
    116 Resource* EnterBase::GetResource(PP_Resource resource) {
    117   return PpapiGlobals::Get()->GetResourceTracker()->GetResource(resource);
    118 }
    119 
    120 // static
    121 Resource* 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 
    131 void 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 
    180 void EnterBase::ClearCallback() {
    181   callback_ = NULL;
    182 }
    183 
    184 void 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 
    227 void 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 
    264 EnterInstance::EnterInstance(PP_Instance instance)
    265     : EnterBase(),
    266       functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
    267   SetStateForFunctionError(instance, functions_, true);
    268 }
    269 
    270 EnterInstance::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 
    280 EnterInstance::~EnterInstance() {
    281 }
    282 
    283 EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
    284     : EnterBase(),
    285       functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
    286   SetStateForFunctionError(instance, functions_, true);
    287 }
    288 
    289 EnterInstanceNoLock::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 
    300 EnterInstanceNoLock::~EnterInstanceNoLock() {
    301 }
    302 
    303 EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
    304     : EnterBase(),
    305       functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
    306   SetStateForFunctionError(instance, functions_, true);
    307 }
    308 
    309 EnterResourceCreation::~EnterResourceCreation() {
    310 }
    311 
    312 EnterResourceCreationNoLock::EnterResourceCreationNoLock(PP_Instance instance)
    313     : EnterBase(),
    314       functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
    315   SetStateForFunctionError(instance, functions_, true);
    316 }
    317 
    318 EnterResourceCreationNoLock::~EnterResourceCreationNoLock() {
    319 }
    320 
    321 }  // namespace thunk
    322 }  // namespace ppapi
    323