| // 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/single_thread_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/lock.h" |
| #include "ppapi/shared_impl/ppapi_globals.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 |