diff options
author | dmichael@chromium.org <dmichael@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-18 16:44:00 +0000 |
---|---|---|
committer | dmichael@chromium.org <dmichael@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-18 16:44:00 +0000 |
commit | e87640bdf9afb28737f94c6784b7723d27b9dfdd (patch) | |
tree | dba77d110fa9b84c2ba1547a35208a11629b2e76 | |
parent | 2073de75d29457e2e865bdbb87148e42b3ccf454 (diff) | |
download | chromium_src-e87640bdf9afb28737f94c6784b7723d27b9dfdd.zip chromium_src-e87640bdf9afb28737f94c6784b7723d27b9dfdd.tar.gz chromium_src-e87640bdf9afb28737f94c6784b7723d27b9dfdd.tar.bz2 |
PPAPI: Implement synchronous postMessage
BUG=367896
Review URL: https://codereview.chromium.org/264303002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278102 0039d316-1c4b-4281-b951-d872f2087c98
27 files changed, 895 insertions, 30 deletions
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc index d23323b..d153b06 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 7a04d37..2fd4410 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> 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::Value> 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 077a68d..8c744a0 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 6e7ff9f..a26b13b 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<PepperPluginInstanceImpl> ref(this); diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.h b/content/renderer/pepper/pepper_plugin_instance_impl.h index 5fb61d8..ea21418 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 b64fe8e..b09fccd 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<v8::Value> val, + v8::Handle<v8::Context> 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<v8::Value> val, v8::Handle<v8::Context> context, diff --git a/content/renderer/pepper/v8_var_converter.h b/content/renderer/pepper/v8_var_converter.h index 97adc33..42ef7a6 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<v8::Value> val, v8::Handle<v8::Context> context, const base::Callback<void(const ppapi::ScopedPPVar&, bool)>& callback); + bool FromV8ValueSync(v8::Handle<v8::Value> val, + v8::Handle<v8::Context> context, + ppapi::ScopedPPVar* result_var); private: // Returns true on success, false on failure. bool FromV8ValueInternal(v8::Handle<v8::Value> val, diff --git a/content/test/ppapi/ppapi_browsertest.cc b/content/test/ppapi/ppapi_browsertest.cc index 95947be..704e704 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 dc50788..5100f5f 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); /** - * <strong>Note:</strong> 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 { * <code>message_loop</code> is attached, when <code>message_loop</code> 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 <code>message_loop</code> 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); /** - * <strong>Note:</strong> This function is not yet implemented. Please use - * PPB_Messaging_1_0. - * * Unregisters the current message handler for <code>instance</code> 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 d08afcf..675b1a3 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 21c8467..c361d14 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); /** - * <strong>Note:</strong> 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 */ * <code>message_loop</code> is attached, when <code>message_loop</code> 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 <code>message_loop</code> 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); /** - * <strong>Note:</strong> This function is not yet implemented. Please use - * PPB_Messaging_1_0. - * * Unregisters the current message handler for <code>instance</code> 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 26cd5e0..8032378 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 117ede6..c775386 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 50e84c3..8c0c531 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 0000000..6e44d0e --- /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<IPC::Message> 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> MessageHandler::Create( + PP_Instance instance, + const PPP_MessageHandler_0_1* handler_if, + void* user_data, + PP_Resource message_loop, + int32_t* error) { + scoped_ptr<MessageHandler> 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<thunk::PPB_MessageLoop_API> + enter_loop(message_loop, true); + if (enter_loop.failed()) { + *error = PP_ERROR_BADRESOURCE; + return result.Pass(); + } + scoped_refptr<MessageLoopResource> message_loop_resource( + static_cast<MessageLoopResource*>(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<IPC::Message> 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<MessageLoopResource> 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 0000000..61ee639 --- /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<MessageHandler> 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<IPC::Message> reply_msg); + + private: + MessageHandler(PP_Instance instance, + const PPP_MessageHandler_0_1* handler_if, + void* user_data, + scoped_refptr<MessageLoopResource> message_loop); + + PP_Instance instance_; + const PPP_MessageHandler_0_1* handler_if_; + void* user_data_; + scoped_refptr<MessageLoopResource> 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 d071e61..f8e22f6 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<MessageHandler> message_handler; }; class PPAPI_PROXY_EXPORT PluginDispatcher diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index 255cc9c..24e993a 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 2b1a988..4f64e91 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<PluginDispatcher*>(dispatcher())->GetInstanceData(instance); + if (!data) + return PP_ERROR_BADARGUMENT; + + int32_t result = PP_ERROR_FAILED; + scoped_ptr<MessageHandler> 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<PluginDispatcher*>(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 d8bfc4c..f6cc252 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<base::MessageLoopProxy>& 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 ba83ca7..75614ae 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<PluginDispatcher*>(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<PluginDispatcher*>(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<IPC::Message>(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 210574a..d9dde3c 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 987c102..f0992ec 100644 --- a/ppapi/tests/test_case.html +++ b/ppapi/tests/test_case.html @@ -3,6 +3,42 @@ <meta http-equiv="Expires" content="-1" /> <link rel="stylesheet" href="test_page.css"> <script> +// Do a deep comparison of two values. Return true if their values are +// identical, false otherwise. +function deepCompare(left, right) { + if (typeof(left) !== typeof(right)) + return false; + // If their identity is the same or they're basic types with the same value, + // they are equal. + if (left === right) + return true; + // If it's a basic type and we got here, we know they're not equal. + if (["undefined", "boolean", "number", "string", "function"].indexOf( + typeof(left)) > -1) { + return false; + } + // Use right_keys as a set containing all keys from |right| which we haven't + // yet compared. + var right_keys = {}; + for (var key in right) + right_keys[key] = true; + for (var key in left) { + if (key in right_keys) { + if (!deepCompare(left[key], right[key])) + return false; + } else { + // |left| had a key that |right| didn't. + return false; + } + delete right_keys[key]; + } + // If there are keys left in |right_keys|, it means they didn't exist in + // |left|, so the objects aren't equal. + if (Object.keys(right_keys).length > 0) + return false; + return true; +} + function AdjustHeight(frameWin) { var div = frameWin.document.getElementsByTagName("div")[0]; var height = frameWin.getComputedStyle(div).height; diff --git a/ppapi/tests/test_message_handler.cc b/ppapi/tests/test_message_handler.cc new file mode 100644 index 0000000..54474f7 --- /dev/null +++ b/ppapi/tests/test_message_handler.cc @@ -0,0 +1,297 @@ +// 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/tests/test_message_handler.h" + +#include <string.h> +#include <algorithm> +#include <map> +#include <sstream> + +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb_file_io.h" +#include "ppapi/c/ppp_message_handler.h" +#include "ppapi/cpp/file_io.h" +#include "ppapi/cpp/file_ref.h" +#include "ppapi/cpp/file_system.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module_impl.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/var_array.h" +#include "ppapi/cpp/var_array_buffer.h" +#include "ppapi/cpp/var_dictionary.h" +#include "ppapi/tests/pp_thread.h" +#include "ppapi/tests/test_utils.h" +#include "ppapi/tests/testing_instance.h" + +// Windows defines 'PostMessage', so we have to undef it. +#ifdef PostMessage +#undef PostMessage +#endif + +REGISTER_TEST_CASE(MessageHandler); + +namespace { + +// Created and destroyed on the main thread. All public methods should be called +// on the main thread. Most data members are only accessed on the main thread. +// (Though it handles messages on the background thread). +class EchoingMessageHandler { + public: + explicit EchoingMessageHandler(PP_Instance instance, + const pp::MessageLoop& loop) + : pp_instance_(instance), + message_handler_loop_(loop), + ppb_messaging_if_(static_cast<const PPB_Messaging_1_1*>( + pp::Module::Get()->GetBrowserInterface( + PPB_MESSAGING_INTERFACE_1_1))), + ppp_message_handler_if_(), + is_registered_(false), + test_finished_event_(instance), + destroy_event_(instance) { + AssertOnMainThread(); + ppp_message_handler_if_.HandleMessage = &HandleMessage; + ppp_message_handler_if_.HandleBlockingMessage = &HandleBlockingMessage; + ppp_message_handler_if_.Destroy = &Destroy; + } + void Register() { + AssertOnMainThread(); + assert(!is_registered_); + int32_t result = ppb_messaging_if_->RegisterMessageHandler( + pp_instance_, + this, + &ppp_message_handler_if_, + message_handler_loop_.pp_resource()); + if (result == PP_OK) { + is_registered_ = true; + } else { + std::ostringstream stream; + stream << "Failed to register message handler; got error " << result; + AddError(stream.str()); + test_finished_event_.Signal(); + } + // Note, at this point, we can't safely read or write errors_ until we wait + // on destroy_event_. + } + void Unregister() { + AssertOnMainThread(); + assert(is_registered_); + ppb_messaging_if_->UnregisterMessageHandler(pp_instance_); + is_registered_ = false; + } + void WaitForTestFinishedMessage() { + test_finished_event_.Wait(); + test_finished_event_.Reset(); + } + // Wait for Destroy() to be called on the MessageHandler thread. When it's + // done, return any errors that occurred during the time the MessageHandler + // was getting messages. + std::string WaitForDestroy() { + AssertOnMainThread(); + // If we haven't called Unregister, we'll be waiting forever. + assert(!is_registered_); + destroy_event_.Wait(); + destroy_event_.Reset(); + // Now that we know Destroy() has been called, we know errors_ isn't being + // written on the MessageHandler thread anymore. So we can safely read it + // here on the main thread (since destroy_event_ gave us a memory barrier). + std::string temp_errors; + errors_.swap(temp_errors); + return temp_errors; + } + private: + static void AssertOnMainThread() { + assert(pp::MessageLoop::GetForMainThread() == + pp::MessageLoop::GetCurrent()); + } + void AddError(const std::string& error) { + if (!error.empty()) { + if (!errors_.empty()) + errors_ += "<p>"; + errors_ += error; + } + } + static void HandleMessage(PP_Instance instance, + void* user_data, + struct PP_Var message_data) { + EchoingMessageHandler* thiz = + static_cast<EchoingMessageHandler*>(user_data); + if (pp::MessageLoop::GetCurrent() != thiz->message_handler_loop_) + thiz->AddError("HandleMessage was called on the wrong thread!"); + if (instance != thiz->pp_instance_) + thiz->AddError("HandleMessage was passed the wrong instance!"); + pp::Var var(message_data); + if (var.is_string() && var.AsString() == "FINISHED_TEST") + thiz->test_finished_event_.Signal(); + else + thiz->ppb_messaging_if_->PostMessage(instance, message_data); + } + + static PP_Var HandleBlockingMessage(PP_Instance instance, + void* user_data, + struct PP_Var message_data) { + EchoingMessageHandler* thiz = + static_cast<EchoingMessageHandler*>(user_data); + if (pp::MessageLoop::GetCurrent() != thiz->message_handler_loop_) + thiz->AddError("HandleBlockingMessage was called on the wrong thread!"); + if (instance != thiz->pp_instance_) + thiz->AddError("HandleBlockingMessage was passed the wrong instance!"); + + // The PP_Var we are passed is an in-parameter, so the browser is not + // giving us a ref-count. The ref-count it has will be decremented after we + // return. But we need to add a ref when returning a PP_Var, to pass to the + // caller. + pp::Var take_ref(message_data); + take_ref.Detach(); + return message_data; + } + + static void Destroy(PP_Instance instance, void* user_data) { + EchoingMessageHandler* thiz = + static_cast<EchoingMessageHandler*>(user_data); + if (pp::MessageLoop::GetCurrent() != thiz->message_handler_loop_) + thiz->AddError("Destroy was called on the wrong thread!"); + if (instance != thiz->pp_instance_) + thiz->AddError("Destroy was passed the wrong instance!"); + thiz->destroy_event_.Signal(); + } + + // These data members are initialized on the main thread, but don't change for + // the life of the object, so are safe to access on the background thread, + // because there will be a memory barrier before the the MessageHandler calls + // are invoked. + const PP_Instance pp_instance_; + const pp::MessageLoop message_handler_loop_; + const pp::MessageLoop main_loop_; + const PPB_Messaging_1_1* const ppb_messaging_if_; + // Spiritually, this member is const, but we can't initialize it in C++03, + // so it has to be non-const to be set in the constructor body. + PPP_MessageHandler_0_1 ppp_message_handler_if_; + + // is_registered_ is only read/written on the main thread. + bool is_registered_; + + // errors_ is written on the MessageHandler thread. When Destroy() is + // called, we stop writing to errors_ and signal destroy_event_. This causes + // a memory barrier, so it's safe to read errors_ after that. + std::string errors_; + NestedEvent test_finished_event_; + NestedEvent destroy_event_; + + // Undefined & private to disallow copy and assign. + EchoingMessageHandler(const EchoingMessageHandler&); + EchoingMessageHandler& operator=(const EchoingMessageHandler&); +}; + +void FakeHandleMessage(PP_Instance instance, + void* user_data, + struct PP_Var message_data) {} +PP_Var FakeHandleBlockingMessage(PP_Instance instance, + void* user_data, + struct PP_Var message_data) { + return PP_MakeUndefined(); +} +void FakeDestroy(PP_Instance instance, void* user_data) {} + +} // namespace + +TestMessageHandler::TestMessageHandler(TestingInstance* instance) + : TestCase(instance), + ppb_messaging_if_(NULL), + handler_thread_(instance) { +} + +TestMessageHandler::~TestMessageHandler() { + handler_thread_.Join(); +} + +bool TestMessageHandler::Init() { + ppb_messaging_if_ = static_cast<const PPB_Messaging_1_1*>( + pp::Module::Get()->GetBrowserInterface(PPB_MESSAGING_INTERFACE_1_1)); + return ppb_messaging_if_ && + CheckTestingInterface() && + handler_thread_.Start(); +} + +void TestMessageHandler::RunTests(const std::string& filter) { + RUN_TEST(RegisterErrorConditions, filter); + RUN_TEST(PostMessageAndAwaitResponse, filter); +} + +void TestMessageHandler::HandleMessage(const pp::Var& message_data) { + // All messages should go to the background thread message handler. + assert(false); +} + +std::string TestMessageHandler::TestRegisterErrorConditions() { + { + // Test registering with the main thread as the message loop. + PPP_MessageHandler_0_1 fake_ppp_message_handler = { + &FakeHandleMessage, &FakeHandleBlockingMessage, &FakeDestroy + }; + pp::MessageLoop main_loop = pp::MessageLoop::GetForMainThread(); + int32_t result = ppb_messaging_if_->RegisterMessageHandler( + instance()->pp_instance(), + reinterpret_cast<void*>(0xdeadbeef), + &fake_ppp_message_handler, + main_loop.pp_resource()); + ASSERT_EQ(PP_ERROR_WRONG_THREAD, result); + } + { + // Test registering with incomplete PPP_Messaging interface. + PPP_MessageHandler_0_1 bad_ppp_ifs[] = { + { NULL, &FakeHandleBlockingMessage, &FakeDestroy }, + { &FakeHandleMessage, NULL, &FakeDestroy }, + { &FakeHandleMessage, &FakeHandleBlockingMessage, NULL }}; + for (size_t i = 0; i < sizeof(bad_ppp_ifs)/sizeof(bad_ppp_ifs[0]); ++i) { + int32_t result = ppb_messaging_if_->RegisterMessageHandler( + instance()->pp_instance(), + reinterpret_cast<void*>(0xdeadbeef), + &bad_ppp_ifs[i], + handler_thread_.message_loop().pp_resource()); + ASSERT_EQ(PP_ERROR_BADARGUMENT, result); + } + } + PASS(); +} + +std::string TestMessageHandler::TestPostMessageAndAwaitResponse() { + EchoingMessageHandler handler(instance()->pp_instance(), + handler_thread_.message_loop()); + handler.Register(); + std::string js_code("var plugin = document.getElementById('plugin');\n"); + js_code += "var result = undefined;\n"; + const char* const values_to_test[] = { + "5", + "undefined", + "1.5", + "'hello'", + "{'key': 'value', 'array_key': [1, 2, 3, 4, 5]}", + NULL + }; + for (size_t i = 0; values_to_test[i]; ++i) { + js_code += "result = plugin.postMessageAndAwaitResponse("; + js_code += values_to_test[i]; + js_code += ");\n"; + js_code += "if (!deepCompare(result, "; + js_code += values_to_test[i]; + js_code += "))\n"; + js_code += " InternalError(\" Failed postMessageAndAwaitResponse for: "; + js_code += values_to_test[i]; + js_code += " result: \" + result);\n"; + } + // TODO(dmichael): Setting a property uses GetInstanceObject, which sends sync + // message, which can get interrupted with message to eval script, etc. + // FINISHED_WAITING message can therefore jump ahead. This test is + // currently carefully crafted to avoid races by doing all the JS in one call. + // That should be fixed before this API goes to stable. See crbug.com/384528 + js_code += "plugin.postMessage('FINISHED_TEST');\n"; + instance_->EvalScript(js_code); + handler.WaitForTestFinishedMessage(); + handler.Unregister(); + ASSERT_SUBTEST_SUCCESS(handler.WaitForDestroy()); + + PASS(); +} + diff --git a/ppapi/tests/test_message_handler.h b/ppapi/tests/test_message_handler.h new file mode 100644 index 0000000..560a1ff --- /dev/null +++ b/ppapi/tests/test_message_handler.h @@ -0,0 +1,34 @@ +// 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_TESTS_TEST_MESSAGE_HANDLER_H_ +#define PPAPI_TESTS_TEST_MESSAGE_HANDLER_H_ + +#include <string> +#include <vector> + +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/tests/test_case.h" +#include "ppapi/utility/threading/simple_thread.h" + +class TestMessageHandler : public TestCase { + public: + explicit TestMessageHandler(TestingInstance* instance); + virtual ~TestMessageHandler(); + + private: + // TestCase implementation. + virtual bool Init(); + virtual void RunTests(const std::string& filter); + virtual void HandleMessage(const pp::Var& message_data); + + std::string TestRegisterErrorConditions(); + std::string TestPostMessageAndAwaitResponse(); + + const PPB_Messaging_1_1* ppb_messaging_if_; + pp::SimpleThread handler_thread_; +}; + +#endif // PPAPI_TESTS_TEST_MESSAGE_HANDLER_H_ + diff --git a/ppapi/thunk/interfaces_ppb_public_dev_channel.h b/ppapi/thunk/interfaces_ppb_public_dev_channel.h index f3ee665..f08470e 100644 --- a/ppapi/thunk/interfaces_ppb_public_dev_channel.h +++ b/ppapi/thunk/interfaces_ppb_public_dev_channel.h @@ -11,6 +11,7 @@ PROXIED_IFACE(PPB_COMPOSITOR_INTERFACE_0_1, PPB_Compositor_0_1) PROXIED_IFACE(PPB_COMPOSITORLAYER_INTERFACE_0_1, PPB_CompositorLayer_0_1) PROXIED_IFACE(PPB_FILEMAPPING_INTERFACE_0_1, PPB_FileMapping_0_1) +PROXIED_IFACE(PPB_MESSAGING_INTERFACE_1_1, PPB_Messaging_1_1) PROXIED_IFACE(PPB_VIDEODECODER_INTERFACE_0_1, PPB_VideoDecoder_0_1) #include "ppapi/thunk/interfaces_postamble.h" diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 2a77de3..bba0069 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -41970,6 +41970,7 @@ Therefore, the affected-histogram name has to have at least one dot in it. <int value="1981643755" label="PPB_FileMapping;0.1"/> <int value="1994108724" label="PPB_Flash_File_FileRef;2"/> <int value="1998274350" label="PPB_Font(Dev);0.6"/> + <int value="2001322203" label="PPB_Messaging;1.1"/> <int value="2003778556" label="PPB_MouseInputEvent;1.1"/> <int value="2005291722" label="PPB_NetAddress_Private;1.1"/> <int value="2012645499" label="PPB_Find(Dev);0.3"/> |