text: // Copyright (c) 2012 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/thunk/enter.h"
  
  #include "base/bind.h"
  #include "base/logging.h"
  #include "base/strings/stringprintf.h"
  #include "base/synchronization/lock.h"
  #include "base/task/single_thread_task_runner.h"
  #include "ppapi/shared_impl/ppapi_globals.h"
  #include "ppapi/shared_impl/resource_tracker.h"
  #include "ppapi/shared_impl/tracked_callback.h"
  #include "ppapi/thunk/ppb_instance_api.h"
  #include "ppapi/thunk/resource_creation_api.h"
  
  namespace ppapi {
  namespace {
  
  bool IsMainThread() {
    return
        PpapiGlobals::Get()->GetMainThreadMessageLoop()->BelongsToCurrentThread();
  }
  
  bool CurrentThreadHandlingBlockingMessage() {
    ppapi::MessageLoopShared* current =
        PpapiGlobals::Get()->GetCurrentMessageLoop();
    return current && current->CurrentlyHandlingBlockingMessage();
  }
  
  }  // namespace
  
  namespace thunk {
  
  namespace subtle {
  
  EnterBase::EnterBase() {}
  
  EnterBase::EnterBase(PP_Resource resource) : resource_(GetResource(resource)) {}
  
  EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id)
      : resource_(GetSingletonResource(instance, resource_id)) {
    if (!resource_)
      retval_ = PP_ERROR_BADARGUMENT;
  }
  
  EnterBase::EnterBase(PP_Resource resource,
                       const PP_CompletionCallback& callback)
      : EnterBase(resource) {
    callback_ = new TrackedCallback(resource_, callback);
  }
  
  EnterBase::EnterBase(PP_Instance instance,
                       SingletonResourceID resource_id,
                       const PP_CompletionCallback& callback)
      : EnterBase(instance, resource_id) {
    callback_ = new TrackedCallback(resource_, callback);
  }
  
  EnterBase::~EnterBase() {
    // callback_ is cleared any time it is run, scheduled to be run, or once we
    // know it will be completed asynchronously. So by this point it should be
    // null.
    DCHECK(!callback_) << "|callback_| is not null. Did you forget to call "
                          "|EnterBase::SetResult| in the interface's thunk?";
  }
  
  int32_t EnterBase::SetResult(int32_t result) {
    if (!callback_) {
      // It doesn't make sense to call SetResult if there is no callback.
      NOTREACHED();
      retval_ = result;
      return result;
    }
    if (result == PP_OK_COMPLETIONPENDING) {
      retval_ = result;
      if (callback_->is_blocking()) {
        DCHECK(!IsMainThread());  // We should have returned an error before this.
        retval_ = callback_->BlockUntilComplete();
      } else {
        // The callback is not blocking and the operation will complete
        // asynchronously, so there's nothing to do.
        retval_ = result;
      }
    } else {
      // The function completed synchronously.
      if (callback_->is_required()) {
        // This is a required callback, so we must issue it asynchronously.
        callback_->PostRun(result);
        retval_ = PP_OK_COMPLETIONPENDING;
      } else {
        // The callback is blocking or optional, so all we need to do is mark
        // the callback as completed so that it won't be issued later.
        callback_->MarkAsCompleted();
        retval_ = result;
      }
    }
    callback_ = nullptr;
    return retval_;
  }
  
  // static
  Resource* EnterBase::GetResource(PP_Resource resource) {
    return PpapiGlobals::Get()->GetResourceTracker()->GetResource(resource);
  }
  
  // static
  Resource* EnterBase::GetSingletonResource(PP_Instance instance,
                                            SingletonResourceID resource_id) {
    PPB_Instance_API* ppb_instance =
        PpapiGlobals::Get()->GetInstanceAPI(instance);
    if (!ppb_instance)
      return nullptr;
  
    return ppb_instance->GetSingletonResource(instance, resource_id);
  }
  
  void EnterBase::SetStateForCallbackError(bool report_error) {
    if (PpapiGlobals::Get()->IsHostGlobals()) {
      // In-process plugins can't make PPAPI calls off the main thread.
      CHECK(IsMainThread());
    }
    if (callback_) {
      if (callback_->is_blocking() && IsMainThread()) {
        // Blocking callbacks are never allowed on the main thread.
        callback_->MarkAsCompleted();
        callback_ = nullptr;
        retval_ = PP_ERROR_BLOCKS_MAIN_THREAD;
        if (report_error) {
          std::string message(
              "Blocking callbacks are not allowed on the main thread.");
          PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
                                                      std::string(), message);
        }
      } else if (callback_->is_blocking() &&
                 CurrentThreadHandlingBlockingMessage()) {
        // Blocking callbacks are not allowed while handling a blocking message.
        callback_->MarkAsCompleted();
        callback_ = nullptr;
        retval_ = PP_ERROR_WOULD_BLOCK_THREAD;
        if (report_error) {
          std::string message("Blocking callbacks are not allowed while handling "
                              "a blocking message from JavaScript.");
          PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
                                                      std::string(), message);
        }
      } else if (!IsMainThread() &&
                 callback_->has_null_target_loop() &&
                 !callback_->is_blocking()) {
        // On a non-main thread, there must be a valid target loop for non-
        // blocking callbacks, or we will have no place to run them.
  
        // If the callback is required, there's no nice way to tell the plugin.
        // We can't run their callback asynchronously without a message loop, and
        // the plugin won't expect any return code other than
        // PP_OK_COMPLETIONPENDING. So we crash to make the problem more obvious.
        if (callback_->is_required()) {
          std::string message("Attempted to use a required callback, but there "
                              "is no attached message loop on which to run the "
                              "callback.");
          PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
                                                      std::string(), message);
          LOG(FATAL) << message;
        }
  
        callback_->MarkAsCompleted();
        callback_ = nullptr;
        retval_ = PP_ERROR_NO_MESSAGE_LOOP;
        if (report_error) {
          std::string message(
              "The calling thread must have a message loop attached.");
          PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
                                                      std::string(), message);
        }
      }
    }
  }
  
  void EnterBase::ClearCallback() {
    callback_ = nullptr;
  }
  
  void EnterBase::SetStateForResourceError(PP_Resource pp_resource,
                                           Resource* resource_base,
                                           void* object,
                                           bool report_error) {
    // Check for callback errors. If we get any, SetStateForCallbackError will
    // emit a log message. But we also want to check for resource errors. If there
    // are both kinds of errors, we'll emit two log messages and return
    // PP_ERROR_BADRESOURCE.
    SetStateForCallbackError(report_error);
  
    if (object)
      return;  // Everything worked.
  
    if (callback_ && callback_->is_required()) {
      callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADRESOURCE));
      callback_ = nullptr;
      retval_ = PP_OK_COMPLETIONPENDING;
    } else {
      if (callback_)
        callback_->MarkAsCompleted();
      callback_ = nullptr;
      retval_ = PP_ERROR_BADRESOURCE;
    }
  
    // We choose to silently ignore the error when the pp_resource is null
    // because this is a pretty common case and we don't want to have lots
    // of errors in the log. This should be an obvious case to debug.
    if (report_error && pp_resource) {
      std::string message;
      if (resource_base) {
        message = base::StringPrintf(
            "0x%X is not the correct type for this function.",
            pp_resource);
      } else {
        message = base::StringPrintf(
            "0x%X is not a valid resource ID.",
            pp_resource);
      }
      PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
                                                  std::string(), message);
    }
  }
  
  void EnterBase::SetStateForFunctionError(PP_Instance pp_instance,
                                           void* object,
                                           bool report_error) {
    // Check for callback errors. If we get any, SetStateForCallbackError will
    // emit a log message. But we also want to check for instance errors. If there
    // are both kinds of errors, we'll emit two log messages and return
    // PP_ERROR_BADARGUMENT.
    SetStateForCallbackError(report_error);
  
    if (object)
      return;  // Everything worked.
  
    if (callback_ && callback_->is_required()) {
      callback_->PostRun(static_cast<int32_t>(PP_ERROR_BADARGUMENT));
      callback_ = nullptr;
      retval_ = PP_OK_COMPLETIONPENDING;
    } else {
      if (callback_)
        callback_->MarkAsCompleted();
      callback_ = nullptr;
      retval_ = PP_ERROR_BADARGUMENT;
    }
  
    // We choose to silently ignore the error when the pp_instance is null as
    // for PP_Resources above.
    if (report_error && pp_instance) {
      std::string message;
      message = base::StringPrintf(
          "0x%X is not a valid instance ID.",
          pp_instance);
      PpapiGlobals::Get()->BroadcastLogWithSource(0, PP_LOGLEVEL_ERROR,
                                                  std::string(), message);
    }
  }
  
  }  // namespace subtle
  
  EnterInstance::EnterInstance(PP_Instance instance)
      : EnterBase(),
        functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
    SetStateForFunctionError(instance, functions_, true);
  }
  
  EnterInstance::EnterInstance(PP_Instance instance,
                               const PP_CompletionCallback& callback)
      : EnterBase(0 /* resource */, callback),
        // TODO(dmichael): This means that the callback_ we get is not associated
        //                 even with the instance, but we should handle that for
        //                 MouseLock (maybe others?).
        functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
    SetStateForFunctionError(instance, functions_, true);
  }
  
  EnterInstance::~EnterInstance() {
  }
  
  EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
      : EnterBase(),
        functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
    SetStateForFunctionError(instance, functions_, true);
  }
  
  EnterInstanceNoLock::EnterInstanceNoLock(
      PP_Instance instance,
      const PP_CompletionCallback& callback)
      : EnterBase(0 /* resource */, callback),
        // TODO(dmichael): This means that the callback_ we get is not associated
        //                 even with the instance, but we should handle that for
        //                 MouseLock (maybe others?).
        functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
    SetStateForFunctionError(instance, functions_, true);
  }
  
  EnterInstanceNoLock::~EnterInstanceNoLock() {
  }
  
  EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
      : EnterBase(),
        functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
    SetStateForFunctionError(instance, functions_, true);
  }
  
  EnterResourceCreation::~EnterResourceCreation() {
  }
  
  EnterResourceCreationNoLock::EnterResourceCreationNoLock(PP_Instance instance)
      : EnterBase(),
        functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
    SetStateForFunctionError(instance, functions_, true);
  }
  
  EnterResourceCreationNoLock::~EnterResourceCreationNoLock() {
  }
  
  }  // namespace thunk
  }  // namespace ppapi
  