// Copyright 2012 The Chromium Authors
// 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/functional/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();
}
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