diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc index d23323bd6e6fd4..d153b06ae904a0 100644 --- a/chrome/test/ppapi/ppapi_browsertest.cc +++ b/chrome/test/ppapi/ppapi_browsertest.cc @@ -62,11 +62,25 @@ using content::RenderViewHost; #if defined(DISABLE_NACL) #define TEST_PPAPI_NACL(test_name) +#define TEST_PPAPI_NACL_NO_PNACL(test_name) #define TEST_PPAPI_NACL_DISALLOWED_SOCKETS(test_name) #define TEST_PPAPI_NACL_WITH_SSL_SERVER(test_name) #else +// TODO(dmichael): Remove this macro, crbug.com/384539 +#define TEST_PPAPI_NACL_NO_PNACL(test_name) \ + IN_PROC_BROWSER_TEST_F(PPAPINaClNewlibTest, test_name) { \ + RunTestViaHTTP(STRIP_PREFIXES(test_name)); \ + } \ + IN_PROC_BROWSER_TEST_F(PPAPINaClGLibcTest, MAYBE_GLIBC(test_name)) { \ + RunTestViaHTTP(STRIP_PREFIXES(test_name)); \ + } \ + IN_PROC_BROWSER_TEST_F(PPAPINaClPNaClNonSfiTest, \ + MAYBE_PNACL_NONSFI(test_name)) { \ + RunTestViaHTTP(STRIP_PREFIXES(test_name)); \ + } + // NaCl based PPAPI tests #define TEST_PPAPI_NACL(test_name) \ IN_PROC_BROWSER_TEST_F(PPAPINaClNewlibTest, test_name) { \ @@ -1219,6 +1233,10 @@ TEST_PPAPI_NACL(VideoSource) // Printing doesn't work in content_browsertests. TEST_PPAPI_OUT_OF_PROCESS(Printing) +// TODO(dmichael): Make this work on PNaCl and remove the macro. +// crbug.com/384539 +TEST_PPAPI_NACL_NO_PNACL(MessageHandler) + TEST_PPAPI_NACL(MessageLoop_Basics) TEST_PPAPI_NACL(MessageLoop_Post) diff --git a/content/renderer/pepper/message_channel.cc b/content/renderer/pepper/message_channel.cc index 7a04d37964ad5f..2fd4410bda3b73 100644 --- a/content/renderer/pepper/message_channel.cc +++ b/content/renderer/pepper/message_channel.cc @@ -45,6 +45,7 @@ namespace content { namespace { const char kPostMessage[] = "postMessage"; +const char kPostMessageAndAwaitResponse[] = "postMessageAndAwaitResponse"; const char kV8ToVarConversionError[] = "Failed to convert a PostMessage " "argument from a JavaScript value to a PP_Var. It may have cycles or be of " @@ -71,6 +72,14 @@ bool IdentifierIs(NPIdentifier identifier, const char string[]) { return WebBindings::getStringIdentifier(string) == identifier; } +bool HasDevChannelPermission(NPObject* channel_object) { + MessageChannel* channel = ToMessageChannel(channel_object); + if (!channel) + return false; + return channel->instance()->module()->permissions().HasPermission( + ppapi::PERMISSION_DEV_CHANNEL); +} + //------------------------------------------------------------------------------ // Implementations of NPClass functions. These are here to: // - Implement postMessage behavior. @@ -93,7 +102,10 @@ bool MessageChannelHasMethod(NPObject* np_obj, NPIdentifier name) { if (IdentifierIs(name, kPostMessage)) return true; - + if (IdentifierIs(name, kPostMessageAndAwaitResponse) && + HasDevChannelPermission(np_obj)) { + return true; + } // Other method names we will pass to the passthrough object, if we have one. NPObject* passthrough = ToPassThroughObject(np_obj); if (passthrough) @@ -113,12 +125,17 @@ bool MessageChannelInvoke(NPObject* np_obj, if (!message_channel) return false; - // Check to see if we should handle this function ourselves. We only handle - // kPostMessage. + // Check to see if we should handle this function ourselves. if (IdentifierIs(name, kPostMessage) && (arg_count == 1)) { message_channel->PostMessageToNative(&args[0]); return true; + } else if (IdentifierIs(name, kPostMessageAndAwaitResponse) && + (arg_count == 1) && + HasDevChannelPermission(np_obj)) { + message_channel->PostBlockingMessageToNative(&args[0], result); + return true; } + // Other method calls we will pass to the passthrough object, if we have one. NPObject* passthrough = ToPassThroughObject(np_obj); if (passthrough) { @@ -167,10 +184,13 @@ bool MessageChannelGetProperty(NPObject* np_obj, if (!np_obj) return false; - // Don't allow getting the postMessage function. + // Don't allow getting the postMessage functions. if (IdentifierIs(name, kPostMessage)) return false; - + if (IdentifierIs(name, kPostMessageAndAwaitResponse) && + HasDevChannelPermission(np_obj)) { + return false; + } MessageChannel* message_channel = ToMessageChannel(np_obj); if (message_channel) { if (message_channel->GetReadOnlyProperty(name, result)) @@ -190,10 +210,13 @@ bool MessageChannelSetProperty(NPObject* np_obj, if (!np_obj) return false; - // Don't allow setting the postMessage function. + // Don't allow setting the postMessage functions. if (IdentifierIs(name, kPostMessage)) return false; - + if (IdentifierIs(name, kPostMessageAndAwaitResponse) && + HasDevChannelPermission(np_obj)) { + return false; + } // Invoke on the passthrough object, if we have one. NPObject* passthrough = ToPassThroughObject(np_obj); if (passthrough) @@ -447,6 +470,80 @@ void MessageChannel::PostMessageToNative(const NPVariant* message_data) { DrainCompletedPluginMessages(); } +void MessageChannel::PostBlockingMessageToNative(const NPVariant* message_data, + NPVariant* np_result) { + if (early_message_queue_state_ == QUEUE_MESSAGES) { + WebBindings::setException( + np_object_, + "Attempted to call a synchronous method on a plugin that was not " + "yet loaded."); + return; + } + + // If the queue of messages to the plugin is non-empty, we're still waiting on + // pending Var conversions. This means at some point in the past, JavaScript + // called postMessage (the async one) and passed us something with a browser- + // side host (e.g., FileSystem) and we haven't gotten a response from the + // browser yet. We can't currently support sending a sync message if the + // plugin does this, because it will break the ordering of the messages + // arriving at the plugin. + // TODO(dmichael): Fix this. + // See https://code.google.com/p/chromium/issues/detail?id=367896#c4 + if (!plugin_message_queue_.empty()) { + WebBindings::setException( + np_object_, + "Failed to convert parameter synchronously, because a prior " + "call to postMessage contained a type which required asynchronous " + "transfer which has not completed. Not all types are supported yet by " + "postMessageAndAwaitResponse. See crbug.com/367896."); + return; + } + ScopedPPVar param; + if (message_data->type == NPVariantType_Object) { + // Convert NPVariantType_Object in to an appropriate PP_Var like Dictionary, + // Array, etc. Note NPVariantToVar would convert to an "Object" PP_Var, + // which we don't support for Messaging. + v8::Handle v8_value = WebBindings::toV8Value(message_data); + V8VarConverter v8_var_converter(instance_->pp_instance()); + bool success = v8_var_converter.FromV8ValueSync( + v8_value, + v8::Isolate::GetCurrent()->GetCurrentContext(), + ¶m); + if (!success) { + WebBindings::setException( + np_object_, + "Failed to convert the given parameter to a PP_Var to send to " + "the plugin."); + return; + } + } else { + param = ScopedPPVar(ScopedPPVar::PassRef(), + NPVariantToPPVar(instance(), message_data)); + } + ScopedPPVar pp_result; + bool was_handled = instance_->HandleBlockingMessage(param, &pp_result); + if (!was_handled) { + WebBindings::setException( + np_object_, + "The plugin has not registered a handler for synchronous messages. " + "See the documentation for PPB_Messaging::RegisterMessageHandler " + "and PPP_MessageHandler."); + return; + } + v8::Handle v8_val; + if (!V8VarConverter(instance_->pp_instance()).ToV8Value( + pp_result.get(), + v8::Isolate::GetCurrent()->GetCurrentContext(), + &v8_val)) { + WebBindings::setException( + np_object_, + "Failed to convert the plugin's result to a JavaScript type."); + return; + } + // Success! Convert the result to an NPVariant. + WebBindings::toNPVariant(v8_val, NULL, np_result); +} + MessageChannel::~MessageChannel() { WebBindings::releaseObject(np_object_); if (passthrough_object_) diff --git a/content/renderer/pepper/message_channel.h b/content/renderer/pepper/message_channel.h index 077a68dfa7e968..8c744a044191c5 100644 --- a/content/renderer/pepper/message_channel.h +++ b/content/renderer/pepper/message_channel.h @@ -60,6 +60,10 @@ class MessageChannel { // Post a message to the plugin's HandleMessage function for this channel's // instance. void PostMessageToNative(const NPVariant* message_data); + // Post a message to the plugin's HandleBlocking Message function for this + // channel's instance synchronously, and return a result. + void PostBlockingMessageToNative(const NPVariant* message_data, + NPVariant* np_result); // Return the NPObject* to which we should forward any calls which aren't // related to postMessage. Note that this can be NULL; it only gets set if diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.cc b/content/renderer/pepper/pepper_plugin_instance_impl.cc index 6e7ff9ffc624b6..a26b13b0970bd5 100644 --- a/content/renderer/pepper/pepper_plugin_instance_impl.cc +++ b/content/renderer/pepper/pepper_plugin_instance_impl.cc @@ -1168,8 +1168,8 @@ void PepperPluginInstanceImpl::HandleMessage(ScopedPPVar message) { ppapi::proxy::HostDispatcher* dispatcher = ppapi::proxy::HostDispatcher::GetForInstance(pp_instance()); if (!dispatcher || (message.get().type == PP_VARTYPE_OBJECT)) { - // The dispatcher should always be valid, and the browser should never send - // an 'object' var over PPP_Messaging. + // The dispatcher should always be valid, and MessageChannel should never + // send an 'object' var over PPP_Messaging. NOTREACHED(); return; } @@ -1180,6 +1180,32 @@ void PepperPluginInstanceImpl::HandleMessage(ScopedPPVar message) { pp_instance()))); } +bool PepperPluginInstanceImpl::HandleBlockingMessage(ScopedPPVar message, + ScopedPPVar* result) { + TRACE_EVENT0("ppapi", "PepperPluginInstanceImpl::HandleBlockingMessage"); + ppapi::proxy::HostDispatcher* dispatcher = + ppapi::proxy::HostDispatcher::GetForInstance(pp_instance()); + if (!dispatcher || (message.get().type == PP_VARTYPE_OBJECT)) { + // The dispatcher should always be valid, and MessageChannel should never + // send an 'object' var over PPP_Messaging. + NOTREACHED(); + return false; + } + ppapi::proxy::ReceiveSerializedVarReturnValue msg_reply; + bool was_handled = false; + dispatcher->Send(new PpapiMsg_PPPMessageHandler_HandleBlockingMessage( + ppapi::API_ID_PPP_MESSAGING, + pp_instance(), + ppapi::proxy::SerializedVarSendInputShmem(dispatcher, message.get(), + pp_instance()), + &msg_reply, + &was_handled)); + *result = ScopedPPVar(ScopedPPVar::PassRef(), msg_reply.Return(dispatcher)); + TRACE_EVENT0("ppapi", + "PepperPluginInstanceImpl::HandleBlockingMessage return."); + return was_handled; +} + PP_Var PepperPluginInstanceImpl::GetInstanceObject() { // Keep a reference on the stack. See NOTE above. scoped_refptr ref(this); diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.h b/content/renderer/pepper/pepper_plugin_instance_impl.h index 5fb61d8716c993..ea2141878633f7 100644 --- a/content/renderer/pepper/pepper_plugin_instance_impl.h +++ b/content/renderer/pepper/pepper_plugin_instance_impl.h @@ -303,6 +303,12 @@ class CONTENT_EXPORT PepperPluginInstanceImpl // Send the message on to the plugin. void HandleMessage(ppapi::ScopedPPVar message); + // Send the message synchronously to the plugin, and get a result. Returns + // true if the plugin handled the message, false if it didn't. The plugin + // won't handle the message if it has not registered a PPP_MessageHandler. + bool HandleBlockingMessage(ppapi::ScopedPPVar message, + ppapi::ScopedPPVar* result); + // Returns true if the plugin is processing a user gesture. bool IsProcessingUserGesture(); diff --git a/content/renderer/pepper/v8_var_converter.cc b/content/renderer/pepper/v8_var_converter.cc index b64fe8eb63ebe2..b09fccd63e4ddd 100644 --- a/content/renderer/pepper/v8_var_converter.cc +++ b/content/renderer/pepper/v8_var_converter.cc @@ -411,6 +411,18 @@ V8VarConverter::VarResult V8VarConverter::FromV8Value( return result; } +bool V8VarConverter::FromV8ValueSync( + v8::Handle val, + v8::Handle context, + ppapi::ScopedPPVar* result_var) { + bool success = FromV8ValueInternal(val, context, result_var); + if (!success || resource_converter_->NeedsFlush()) { + resource_converter_->Reset(); + return false; + } + return true; +} + bool V8VarConverter::FromV8ValueInternal( v8::Handle val, v8::Handle context, diff --git a/content/renderer/pepper/v8_var_converter.h b/content/renderer/pepper/v8_var_converter.h index 97adc33616425d..42ef7a647f5dc8 100644 --- a/content/renderer/pepper/v8_var_converter.h +++ b/content/renderer/pepper/v8_var_converter.h @@ -62,6 +62,9 @@ class CONTENT_EXPORT V8VarConverter { v8::Handle val, v8::Handle context, const base::Callback& callback); + bool FromV8ValueSync(v8::Handle val, + v8::Handle context, + ppapi::ScopedPPVar* result_var); private: // Returns true on success, false on failure. bool FromV8ValueInternal(v8::Handle val, diff --git a/content/test/ppapi/ppapi_browsertest.cc b/content/test/ppapi/ppapi_browsertest.cc index 95947be6907131..704e7045d85acc 100644 --- a/content/test/ppapi/ppapi_browsertest.cc +++ b/content/test/ppapi/ppapi_browsertest.cc @@ -108,6 +108,8 @@ TEST_PPAPI_OUT_OF_PROCESS(MediaStreamVideoTrack) TEST_PPAPI_IN_PROCESS(Memory) TEST_PPAPI_OUT_OF_PROCESS(Memory) +TEST_PPAPI_OUT_OF_PROCESS(MessageHandler) + TEST_PPAPI_OUT_OF_PROCESS(MessageLoop_Basics) TEST_PPAPI_OUT_OF_PROCESS(MessageLoop_Post) diff --git a/ppapi/api/ppb_messaging.idl b/ppapi/api/ppb_messaging.idl index dc50788e26db2c..5100f5f5ed879e 100644 --- a/ppapi/api/ppb_messaging.idl +++ b/ppapi/api/ppb_messaging.idl @@ -86,9 +86,6 @@ interface PPB_Messaging { void PostMessage([in] PP_Instance instance, [in] PP_Var message); /** - * Note: This function is not yet implemented. Please use - * PPB_Messaging_1_0. - * * Registers a handler for receiving messages from JavaScript. If a handler * is registered this way, it will replace PPP_Messaging, and all messages * sent from JavaScript via postMessage and postMessageAndAwaitResponse will @@ -99,6 +96,12 @@ interface PPB_Messaging { * message_loop is attached, when message_loop is * run. It is illegal to pass the main thread message loop; * RegisterMessageHandler will return PP_ERROR_WRONG_THREAD in that case. + * If you quit message_loop before calling Unregister(), + * the browser will not be able to call functions in the plugin's message + * handler any more. That could mean missing some messages or could cause a + * leak if you depend on Destroy() to free hander data. So you should, + * whenever possible, Unregister() the handler prior to quitting its event + * loop. * * Attempting to register a message handler when one is already registered * will cause the current MessageHandler to be unregistered and replaced. In @@ -123,9 +126,6 @@ interface PPB_Messaging { [in] PPP_MessageHandler handler, [in] PP_Resource message_loop); /** - * Note: This function is not yet implemented. Please use - * PPB_Messaging_1_0. - * * Unregisters the current message handler for instance if one * is registered. After this call, the message handler (if one was * registered) will have "Destroy" called on it and will receive no further diff --git a/ppapi/api/ppp_message_handler.idl b/ppapi/api/ppp_message_handler.idl index d08afcf9f067cf..675b1a3cf7b023 100644 --- a/ppapi/api/ppp_message_handler.idl +++ b/ppapi/api/ppp_message_handler.idl @@ -37,7 +37,7 @@ interface PPP_MessageHandler { * postMessage(). */ void HandleMessage([in] PP_Instance instance, - [in] mem_t user_data, + [inout] mem_t user_data, [in] PP_Var message); /** * Invoked as a result of JavaScript invoking postMessageAndAwaitResponse() diff --git a/ppapi/c/ppb_messaging.h b/ppapi/c/ppb_messaging.h index 21c8467d5f5bb6..c361d1488bcdd9 100644 --- a/ppapi/c/ppb_messaging.h +++ b/ppapi/c/ppb_messaging.h @@ -3,7 +3,7 @@ * found in the LICENSE file. */ -/* From ppb_messaging.idl modified Mon Jun 2 11:00:28 2014. */ +/* From ppb_messaging.idl modified Fri Jun 13 15:28:26 2014. */ #ifndef PPAPI_C_PPB_MESSAGING_H_ #define PPAPI_C_PPB_MESSAGING_H_ @@ -100,9 +100,6 @@ struct PPB_Messaging_1_1 { /* dev */ */ void (*PostMessage)(PP_Instance instance, struct PP_Var message); /** - * Note: This function is not yet implemented. Please use - * PPB_Messaging_1_0. - * * Registers a handler for receiving messages from JavaScript. If a handler * is registered this way, it will replace PPP_Messaging, and all messages * sent from JavaScript via postMessage and postMessageAndAwaitResponse will @@ -113,6 +110,12 @@ struct PPB_Messaging_1_1 { /* dev */ * message_loop is attached, when message_loop is * run. It is illegal to pass the main thread message loop; * RegisterMessageHandler will return PP_ERROR_WRONG_THREAD in that case. + * If you quit message_loop before calling Unregister(), + * the browser will not be able to call functions in the plugin's message + * handler any more. That could mean missing some messages or could cause a + * leak if you depend on Destroy() to free hander data. So you should, + * whenever possible, Unregister() the handler prior to quitting its event + * loop. * * Attempting to register a message handler when one is already registered * will cause the current MessageHandler to be unregistered and replaced. In @@ -137,9 +140,6 @@ struct PPB_Messaging_1_1 { /* dev */ const struct PPP_MessageHandler_0_1* handler, PP_Resource message_loop); /** - * Note: This function is not yet implemented. Please use - * PPB_Messaging_1_0. - * * Unregisters the current message handler for instance if one * is registered. After this call, the message handler (if one was * registered) will have "Destroy" called on it and will receive no further diff --git a/ppapi/c/ppp_message_handler.h b/ppapi/c/ppp_message_handler.h index 26cd5e008978eb..8032378a92b4f6 100644 --- a/ppapi/c/ppp_message_handler.h +++ b/ppapi/c/ppp_message_handler.h @@ -3,7 +3,7 @@ * found in the LICENSE file. */ -/* From ppp_message_handler.idl modified Fri May 30 15:49:17 2014. */ +/* From ppp_message_handler.idl modified Tue Jun 3 16:50:26 2014. */ #ifndef PPAPI_C_PPP_MESSAGE_HANDLER_H_ #define PPAPI_C_PPP_MESSAGE_HANDLER_H_ @@ -49,7 +49,7 @@ struct PPP_MessageHandler_0_1 { * postMessage(). */ void (*HandleMessage)(PP_Instance instance, - const void* user_data, + void* user_data, struct PP_Var message); /** * Invoked as a result of JavaScript invoking postMessageAndAwaitResponse() diff --git a/ppapi/ppapi_proxy.gypi b/ppapi/ppapi_proxy.gypi index 117ede6f6a6a80..c775386e7cbf37 100644 --- a/ppapi/ppapi_proxy.gypi +++ b/ppapi/ppapi_proxy.gypi @@ -94,6 +94,8 @@ 'proxy/media_stream_track_resource_base.h', 'proxy/media_stream_video_track_resource.cc', 'proxy/media_stream_video_track_resource.h', + 'proxy/message_handler.cc', + 'proxy/message_handler.h', 'proxy/net_address_resource.cc', 'proxy/net_address_resource.h', 'proxy/network_list_resource.cc', diff --git a/ppapi/ppapi_sources.gypi b/ppapi/ppapi_sources.gypi index 50e84c3f24a627..8c0c531b1ff5e7 100644 --- a/ppapi/ppapi_sources.gypi +++ b/ppapi/ppapi_sources.gypi @@ -446,6 +446,8 @@ 'tests/test_media_stream_video_track.h', 'tests/test_memory.cc', 'tests/test_memory.h', + 'tests/test_message_handler.cc', + 'tests/test_message_handler.h', 'tests/test_message_loop.cc', 'tests/test_message_loop.h', 'tests/test_mouse_cursor.cc', diff --git a/ppapi/proxy/message_handler.cc b/ppapi/proxy/message_handler.cc new file mode 100644 index 00000000000000..6e44d0e018ef06 --- /dev/null +++ b/ppapi/proxy/message_handler.cc @@ -0,0 +1,134 @@ +// Copyright 2014 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/proxy/message_handler.h" + +#include "ipc/ipc_message.h" +#include "ppapi/proxy/plugin_dispatcher.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/ppb_message_loop_proxy.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/shared_impl/scoped_pp_var.h" +#include "ppapi/thunk/enter.h" + +namespace ppapi { +namespace proxy { +namespace { + +typedef void (*HandleMessageFunc)(PP_Instance, void*, PP_Var); +typedef PP_Var (*HandleBlockingMessageFunc)(PP_Instance, void*, PP_Var); + +void HandleMessageWrapper(HandleMessageFunc function, + PP_Instance instance, + void* user_data, + ScopedPPVar message_data) { + CallWhileUnlocked(function, instance, user_data, message_data.get()); +} + +void HandleBlockingMessageWrapper(HandleBlockingMessageFunc function, + PP_Instance instance, + void* user_data, + ScopedPPVar message_data, + scoped_ptr reply_msg) { + PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance); + if (!dispatcher) + return; + PP_Var return_value = CallWhileUnlocked(function, + instance, + user_data, + message_data.get()); + PpapiMsg_PPPMessageHandler_HandleBlockingMessage::WriteReplyParams( + reply_msg.get(), + SerializedVarReturnValue::Convert(dispatcher, return_value), + true /* was_handled */); + dispatcher->Send(reply_msg.release()); +} + +} // namespace + +// static +scoped_ptr MessageHandler::Create( + PP_Instance instance, + const PPP_MessageHandler_0_1* handler_if, + void* user_data, + PP_Resource message_loop, + int32_t* error) { + scoped_ptr result; + // The interface and all function pointers must be valid. + if (!handler_if || + !handler_if->HandleMessage || + !handler_if->HandleBlockingMessage || + !handler_if->Destroy) { + *error = PP_ERROR_BADARGUMENT; + return result.Pass(); + } + thunk::EnterResourceNoLock + enter_loop(message_loop, true); + if (enter_loop.failed()) { + *error = PP_ERROR_BADRESOURCE; + return result.Pass(); + } + scoped_refptr message_loop_resource( + static_cast(enter_loop.object())); + if (message_loop_resource->is_main_thread_loop()) { + *error = PP_ERROR_WRONG_THREAD; + return result.Pass(); + } + + result.reset(new MessageHandler( + instance, handler_if, user_data, message_loop_resource)); + *error = PP_OK; + return result.Pass(); +} + +MessageHandler::~MessageHandler() { + // It's possible the message_loop_proxy is NULL if that loop has been quit. + // In that case, we unfortunately just can't call Destroy. + if (message_loop_->message_loop_proxy()) { + // The posted task won't have the proxy lock, but that's OK, it doesn't + // touch any internal state; it's a direct call on the plugin's function. + message_loop_->message_loop_proxy()->PostTask(FROM_HERE, + base::Bind(handler_if_->Destroy, + instance_, + user_data_)); + } +} + +bool MessageHandler::LoopIsValid() const { + return !!message_loop_->message_loop_proxy(); +} + +void MessageHandler::HandleMessage(ScopedPPVar var) { + message_loop_->message_loop_proxy()->PostTask(FROM_HERE, + RunWhileLocked(base::Bind(&HandleMessageWrapper, + handler_if_->HandleMessage, + instance_, + user_data_, + var))); +} + +void MessageHandler::HandleBlockingMessage(ScopedPPVar var, + scoped_ptr reply_msg) { + message_loop_->message_loop_proxy()->PostTask(FROM_HERE, + RunWhileLocked(base::Bind(&HandleBlockingMessageWrapper, + handler_if_->HandleBlockingMessage, + instance_, + user_data_, + var, + base::Passed(reply_msg.Pass())))); +} + +MessageHandler::MessageHandler( + PP_Instance instance, + const PPP_MessageHandler_0_1* handler_if, + void* user_data, + scoped_refptr message_loop) + : instance_(instance), + handler_if_(handler_if), + user_data_(user_data), + message_loop_(message_loop) { +} + +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/proxy/message_handler.h b/ppapi/proxy/message_handler.h new file mode 100644 index 00000000000000..61ee639f0b1c26 --- /dev/null +++ b/ppapi/proxy/message_handler.h @@ -0,0 +1,75 @@ +// Copyright 2014 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. + +#ifndef PPAPI_PROXY_MESSAGE_HANDLER_H_ +#define PPAPI_PROXY_MESSAGE_HANDLER_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "ppapi/c/pp_resource.h" +#include "ppapi/c/ppp_message_handler.h" +#include "ppapi/proxy/ppapi_proxy_export.h" + +namespace IPC { +class Message; +} + +namespace ppapi { + +class ScopedPPVar; + +namespace proxy { + +class MessageLoopResource; + +// MessageHandler wraps a PPP_MessageHandler to encapsulate calling methods +// on the right thread and calling the Destroy function when this +// MessageHandler is destroyed. +class PPAPI_PROXY_EXPORT MessageHandler { + public: + // Create a MessageHandler. If any parameters are invalid, it will return a + // null scoped_ptr and set |*error| appropriately. + // |handler_if| is the struct of function pointers we will invoke. All of + // the function pointers within must be valid, or we fail + // with PP_ERROR_BADARGUMENT. + // |user_data| is a pointer provided by the plugin that we pass back when we + // call functions in |handler_if|. + // |message_loop| is the message loop where we will invoke functions in + // |handler_if|. Must not be the main thread message loop, + // to try to force the plugin to not over-subscribe the main + // thread. If it's the main thread loop, |error| will be set + // to PP_ERROR_WRONGTHREAD. + // |error| is an out-param that will be set on failure. + static scoped_ptr Create( + PP_Instance instance, + const PPP_MessageHandler_0_1* handler_if, + void* user_data, + PP_Resource message_loop, + int32_t* error); + ~MessageHandler(); + + bool LoopIsValid() const; + + void HandleMessage(ScopedPPVar var); + void HandleBlockingMessage(ScopedPPVar var, + scoped_ptr reply_msg); + + private: + MessageHandler(PP_Instance instance, + const PPP_MessageHandler_0_1* handler_if, + void* user_data, + scoped_refptr message_loop); + + PP_Instance instance_; + const PPP_MessageHandler_0_1* handler_if_; + void* user_data_; + scoped_refptr message_loop_; + + DISALLOW_COPY_AND_ASSIGN(MessageHandler); +}; + +} // namespace proxy +} // namespace ppapi + +#endif // PPAPI_PROXY_MESSAGE_HANDLER_H_ diff --git a/ppapi/proxy/plugin_dispatcher.h b/ppapi/proxy/plugin_dispatcher.h index d071e61d1723f0..f8e22f6ebdedb6 100644 --- a/ppapi/proxy/plugin_dispatcher.h +++ b/ppapi/proxy/plugin_dispatcher.h @@ -20,6 +20,7 @@ #include "ppapi/c/pp_rect.h" #include "ppapi/c/ppb_console.h" #include "ppapi/proxy/dispatcher.h" +#include "ppapi/proxy/message_handler.h" #include "ppapi/shared_impl/ppapi_preferences.h" #include "ppapi/shared_impl/ppb_view_shared.h" #include "ppapi/shared_impl/singleton_resource_id.h" @@ -62,6 +63,10 @@ struct InstanceData { // calling when we shouldn't). bool is_request_surrounding_text_pending; bool should_do_request_surrounding_text; + + // The message handler which should handle JavaScript->Plugin messages, if + // one has been registered, otherwise NULL. + scoped_ptr message_handler; }; class PPAPI_PROXY_EXPORT PluginDispatcher diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index 255cc9cca8d507..24e993a7f96402 100644 --- a/ppapi/proxy/ppapi_messages.h +++ b/ppapi/proxy/ppapi_messages.h @@ -676,10 +676,15 @@ IPC_MESSAGE_ROUTED3(PpapiMsg_PPPInstance_HandleDocumentLoad, int /* pending_loader_host_id */, ppapi::URLResponseInfoData /* response */) -// PPP_Messaging. +// PPP_Messaging and PPP_MessageHandler. IPC_MESSAGE_ROUTED2(PpapiMsg_PPPMessaging_HandleMessage, PP_Instance /* instance */, ppapi::proxy::SerializedVar /* message */) +IPC_SYNC_MESSAGE_ROUTED2_2(PpapiMsg_PPPMessageHandler_HandleBlockingMessage, + PP_Instance /* instance */, + ppapi::proxy::SerializedVar /* message */, + ppapi::proxy::SerializedVar /* result */, + bool /* was_handled */); // PPP_MouseLock. IPC_MESSAGE_ROUTED1(PpapiMsg_PPPMouseLock_MouseLockLost, diff --git a/ppapi/proxy/ppb_instance_proxy.cc b/ppapi/proxy/ppb_instance_proxy.cc index 2b1a9889026c04..4f64e917af4413 100644 --- a/ppapi/proxy/ppb_instance_proxy.cc +++ b/ppapi/proxy/ppb_instance_proxy.cc @@ -26,6 +26,7 @@ #include "ppapi/proxy/gamepad_resource.h" #include "ppapi/proxy/host_dispatcher.h" #include "ppapi/proxy/isolated_file_system_private_resource.h" +#include "ppapi/proxy/message_handler.h" #include "ppapi/proxy/network_proxy_resource.h" #include "ppapi/proxy/pdf_resource.h" #include "ppapi/proxy/plugin_dispatcher.h" @@ -773,17 +774,31 @@ void PPB_Instance_Proxy::PostMessage(PP_Instance instance, instance, SerializedVarSendInputShmem(dispatcher(), message, instance))); } + int32_t PPB_Instance_Proxy::RegisterMessageHandler( PP_Instance instance, void* user_data, const PPP_MessageHandler_0_1* handler, PP_Resource message_loop) { - // Not yet implemented. See crbug.com/367896 - return PP_ERROR_NOTSUPPORTED; + InstanceData* data = + static_cast(dispatcher())->GetInstanceData(instance); + if (!data) + return PP_ERROR_BADARGUMENT; + + int32_t result = PP_ERROR_FAILED; + scoped_ptr message_handler = MessageHandler::Create( + instance, handler, user_data, message_loop, &result); + if (message_handler) + data->message_handler = message_handler.Pass(); + return result; } void PPB_Instance_Proxy::UnregisterMessageHandler(PP_Instance instance) { - // Not yet implemented. See crbug.com/367896 + InstanceData* data = + static_cast(dispatcher())->GetInstanceData(instance); + if (!data) + return; + data->message_handler.reset(); } PP_Bool PPB_Instance_Proxy::SetCursor(PP_Instance instance, diff --git a/ppapi/proxy/ppb_message_loop_proxy.h b/ppapi/proxy/ppb_message_loop_proxy.h index d8bfc4c644d92f..f6cc25227bc3cc 100644 --- a/ppapi/proxy/ppb_message_loop_proxy.h +++ b/ppapi/proxy/ppb_message_loop_proxy.h @@ -44,6 +44,10 @@ class PPAPI_PROXY_EXPORT MessageLoopResource : public MessageLoopShared { return is_main_thread_loop_; } + const scoped_refptr& message_loop_proxy() { + return loop_proxy_; + } + private: struct TaskInfo { tracked_objects::Location from_here; diff --git a/ppapi/proxy/ppp_messaging_proxy.cc b/ppapi/proxy/ppp_messaging_proxy.cc index ba83ca7432b88f..75614ae08b2266 100644 --- a/ppapi/proxy/ppp_messaging_proxy.cc +++ b/ppapi/proxy/ppp_messaging_proxy.cc @@ -8,17 +8,53 @@ #include "ppapi/c/ppp_messaging.h" #include "ppapi/proxy/host_dispatcher.h" +#include "ppapi/proxy/message_handler.h" +#include "ppapi/proxy/plugin_dispatcher.h" #include "ppapi/proxy/plugin_resource_tracker.h" #include "ppapi/proxy/plugin_var_tracker.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/serialized_var.h" #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/shared_impl/scoped_pp_var.h" #include "ppapi/shared_impl/var_tracker.h" namespace ppapi { namespace proxy { +namespace { + +MessageHandler* GetMessageHandler(Dispatcher* dispatcher, + PP_Instance instance) { + if (!dispatcher || !dispatcher->IsPlugin()) { + NOTREACHED(); + return NULL; + } + PluginDispatcher* plugin_dispatcher = + static_cast(dispatcher); + InstanceData* instance_data = plugin_dispatcher->GetInstanceData(instance); + if (!instance_data) + return NULL; + + return instance_data->message_handler.get(); +} + +void ResetMessageHandler(Dispatcher* dispatcher, PP_Instance instance) { + if (!dispatcher || !dispatcher->IsPlugin()) { + NOTREACHED(); + return; + } + PluginDispatcher* plugin_dispatcher = + static_cast(dispatcher); + InstanceData* instance_data = plugin_dispatcher->GetInstanceData(instance); + if (!instance_data) + return; + + instance_data->message_handler.reset(); +} + +} // namespace + PPP_Messaging_Proxy::PPP_Messaging_Proxy(Dispatcher* dispatcher) : InterfaceProxy(dispatcher), ppp_messaging_impl_(NULL) { @@ -39,6 +75,9 @@ bool PPP_Messaging_Proxy::OnMessageReceived(const IPC::Message& msg) { IPC_BEGIN_MESSAGE_MAP(PPP_Messaging_Proxy, msg) IPC_MESSAGE_HANDLER(PpapiMsg_PPPMessaging_HandleMessage, OnMsgHandleMessage) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + PpapiMsg_PPPMessageHandler_HandleBlockingMessage, + OnMsgHandleBlockingMessage) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -47,13 +86,57 @@ bool PPP_Messaging_Proxy::OnMessageReceived(const IPC::Message& msg) { void PPP_Messaging_Proxy::OnMsgHandleMessage( PP_Instance instance, SerializedVarReceiveInput message_data) { PP_Var received_var(message_data.GetForInstance(dispatcher(), instance)); + MessageHandler* message_handler = GetMessageHandler(dispatcher(), instance); + if (message_handler) { + if (message_handler->LoopIsValid()) { + message_handler->HandleMessage(ScopedPPVar(received_var)); + return; + } else { + // If the MessageHandler's loop has been quit, then we should treat it as + // though it has been unregistered and start sending messages to the + // default handler. This might mean the plugin has lost messages, but + // there's not really anything sane we can do about it. They should have + // used UnregisterMessageHandler. + ResetMessageHandler(dispatcher(), instance); + } + } + // If we reach this point, then there's no message handler registered, so + // we send to the default PPP_Messaging one for the instance. + // SerializedVarReceiveInput will decrement the reference count, but we want - // to give the recipient a reference. + // to give the recipient a reference in the legacy API. PpapiGlobals::Get()->GetVarTracker()->AddRefVar(received_var); CallWhileUnlocked(ppp_messaging_impl_->HandleMessage, instance, received_var); } +void PPP_Messaging_Proxy::OnMsgHandleBlockingMessage( + PP_Instance instance, + SerializedVarReceiveInput message_data, + IPC::Message* reply_msg) { + ScopedPPVar received_var(message_data.GetForInstance(dispatcher(), instance)); + MessageHandler* message_handler = GetMessageHandler(dispatcher(), instance); + if (message_handler) { + if (message_handler->LoopIsValid()) { + message_handler->HandleBlockingMessage( + received_var, scoped_ptr(reply_msg)); + return; + } else { + // If the MessageHandler's loop has been quit, then we should treat it as + // though it has been unregistered. Also see the note for PostMessage. + ResetMessageHandler(dispatcher(), instance); + } + } + // We have no handler, but we still need to respond to unblock the renderer + // and inform the JavaScript caller. + PpapiMsg_PPPMessageHandler_HandleBlockingMessage::WriteReplyParams( + reply_msg, + SerializedVarReturnValue::Convert(dispatcher(), PP_MakeUndefined()), + false /* was_handled */); + dispatcher()->Send(reply_msg); +} + + } // namespace proxy } // namespace ppapi diff --git a/ppapi/proxy/ppp_messaging_proxy.h b/ppapi/proxy/ppp_messaging_proxy.h index 210574abddc38d..d9dde3cb2e5166 100644 --- a/ppapi/proxy/ppp_messaging_proxy.h +++ b/ppapi/proxy/ppp_messaging_proxy.h @@ -27,6 +27,9 @@ class PPP_Messaging_Proxy : public InterfaceProxy { // Message handlers. void OnMsgHandleMessage(PP_Instance instance, SerializedVarReceiveInput data); + void OnMsgHandleBlockingMessage(PP_Instance instance, + SerializedVarReceiveInput data, + IPC::Message* reply); // When this proxy is in the plugin side, this value caches the interface // pointer so we don't have to retrieve it from the dispatcher each time. diff --git a/ppapi/tests/test_case.html b/ppapi/tests/test_case.html index 987c10284c8630..f0992ec44147f1 100644 --- a/ppapi/tests/test_case.html +++ b/ppapi/tests/test_case.html @@ -3,6 +3,42 @@