| // 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 |