// 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/message_loop/message_loop.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "ppapi/c/pp_errors.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() : resource_(NULL), retval_(PP_OK) { PpapiGlobals::Get()->MarkPluginIsActive(); } EnterBase::EnterBase(PP_Resource resource) : resource_(GetResource(resource)), retval_(PP_OK) { PpapiGlobals::Get()->MarkPluginIsActive(); } EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id) : resource_(GetSingletonResource(instance, resource_id)), retval_(PP_OK) { PpapiGlobals::Get()->MarkPluginIsActive(); } EnterBase::EnterBase(PP_Resource resource, const PP_CompletionCallback& callback) : resource_(GetResource(resource)), retval_(PP_OK) { callback_ = new TrackedCallback(resource_, callback); PpapiGlobals::Get()->MarkPluginIsActive(); } EnterBase::EnterBase(PP_Instance instance, SingletonResourceID resource_id, const PP_CompletionCallback& callback) : resource_(GetSingletonResource(instance, resource_id)), retval_(PP_OK) { if (!resource_) retval_ = PP_ERROR_BADARGUMENT; callback_ = new TrackedCallback(resource_, callback); PpapiGlobals::Get()->MarkPluginIsActive(); } 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_.get()) << "|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_.get()) { // 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_ = NULL; 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 NULL; 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_.get()) { if (callback_->is_blocking() && IsMainThread()) { // Blocking callbacks are never allowed on the main thread. callback_->MarkAsCompleted(); callback_ = NULL; 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_ = NULL; 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_ = NULL; 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_ = NULL; } 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_.get() && callback_->is_required()) { callback_->PostRun(static_cast(PP_ERROR_BADRESOURCE)); callback_ = NULL; retval_ = PP_OK_COMPLETIONPENDING; } else { if (callback_.get()) callback_->MarkAsCompleted(); callback_ = NULL; 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_.get() && callback_->is_required()) { callback_->PostRun(static_cast(PP_ERROR_BADARGUMENT)); callback_ = NULL; retval_ = PP_OK_COMPLETIONPENDING; } else { if (callback_.get()) callback_->MarkAsCompleted(); callback_ = NULL; 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