// 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/proxy/host_dispatcher.h" #include "base/debug/trace_event.h" #include "base/logging.h" #include "ppapi/c/private/ppb_proxy_private.h" #include "ppapi/c/ppb_var.h" #include "ppapi/proxy/host_var_serialization_rules.h" #include "ppapi/proxy/interface_list.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/resource_creation_proxy.h" #include "ppapi/shared_impl/ppapi_globals.h" namespace ppapi { namespace proxy { namespace { typedef std::map InstanceToDispatcherMap; InstanceToDispatcherMap* g_instance_to_dispatcher = NULL; typedef std::map ModuleToDispatcherMap; ModuleToDispatcherMap* g_module_to_dispatcher = NULL; PP_Bool ReserveInstanceID(PP_Module module, PP_Instance instance) { // Default to returning true (usable) failure. Otherwise, if there's some // kind of communication error or the plugin just crashed, we'll get into an // infinite loop generating new instnace IDs since we think they're all in // use. ModuleToDispatcherMap::const_iterator found = g_module_to_dispatcher->find(module); if (found == g_module_to_dispatcher->end()) { NOTREACHED(); return PP_TRUE; } bool usable = true; if (!found->second->Send(new PpapiMsg_ReserveInstanceId(instance, &usable))) return PP_TRUE; return PP_FromBool(usable); } // Saves the state of the given bool and puts it back when it goes out of // scope. class BoolRestorer { public: BoolRestorer(bool* var) : var_(var), old_value_(*var) { } ~BoolRestorer() { *var_ = old_value_; } private: bool* var_; bool old_value_; }; } // namespace HostDispatcher::HostDispatcher(PP_Module module, PP_GetInterface_Func local_get_interface, SyncMessageStatusReceiver* sync_status, const PpapiPermissions& permissions) : Dispatcher(local_get_interface, permissions), sync_status_(sync_status), pp_module_(module), ppb_proxy_(NULL), allow_plugin_reentrancy_(false) { if (!g_module_to_dispatcher) g_module_to_dispatcher = new ModuleToDispatcherMap; (*g_module_to_dispatcher)[pp_module_] = this; SetSerializationRules(new HostVarSerializationRules); ppb_proxy_ = reinterpret_cast( local_get_interface(PPB_PROXY_PRIVATE_INTERFACE)); DCHECK(ppb_proxy_) << "The proxy interface should always be supported."; ppb_proxy_->SetReserveInstanceIDCallback(pp_module_, &ReserveInstanceID); } HostDispatcher::~HostDispatcher() { g_module_to_dispatcher->erase(pp_module_); } bool HostDispatcher::InitHostWithChannel( Delegate* delegate, const IPC::ChannelHandle& channel_handle, bool is_client, const ppapi::Preferences& preferences) { if (!Dispatcher::InitWithChannel(delegate, channel_handle, is_client)) return false; AddIOThreadMessageFilter(sync_status_.get()); Send(new PpapiMsg_SetPreferences(preferences)); return true; } // static HostDispatcher* HostDispatcher::GetForInstance(PP_Instance instance) { if (!g_instance_to_dispatcher) return NULL; InstanceToDispatcherMap::iterator found = g_instance_to_dispatcher->find( instance); if (found == g_instance_to_dispatcher->end()) return NULL; return found->second; } // static void HostDispatcher::SetForInstance(PP_Instance instance, HostDispatcher* dispatcher) { if (!g_instance_to_dispatcher) g_instance_to_dispatcher = new InstanceToDispatcherMap; (*g_instance_to_dispatcher)[instance] = dispatcher; } // static void HostDispatcher::RemoveForInstance(PP_Instance instance) { if (!g_instance_to_dispatcher) return; InstanceToDispatcherMap::iterator found = g_instance_to_dispatcher->find( instance); if (found != g_instance_to_dispatcher->end()) g_instance_to_dispatcher->erase(found); } bool HostDispatcher::IsPlugin() const { return false; } bool HostDispatcher::Send(IPC::Message* msg) { TRACE_EVENT2("ppapi proxy", "HostDispatcher::Send", "Class", IPC_MESSAGE_ID_CLASS(msg->type()), "Line", IPC_MESSAGE_ID_LINE(msg->type())); // Normal sync messages are set to unblock, which would normally cause the // plugin to be reentered to process them. We only want to do this when we // know the plugin is in a state to accept reentrancy. Since the plugin side // never clears this flag on messages it sends, we can't get deadlock, but we // may still get reentrancy in the host as a result. if (!allow_plugin_reentrancy_) msg->set_unblock(false); if (msg->is_sync()) { // Don't allow sending sync messages during module shutdown. Seee the "else" // block below for why. CHECK(!PP_ToBool(ppb_proxy()->IsInModuleDestructor(pp_module()))); // Prevent the dispatcher from going away during sync calls. Scenarios // where this could happen include a Send for a sync message which while // waiting for the reply, dispatches an incoming ExecuteScript call which // destroys the plugin module and in turn the dispatcher. ScopedModuleReference scoped_ref(this); sync_status_->BeginBlockOnSyncMessage(); bool result = Dispatcher::Send(msg); sync_status_->EndBlockOnSyncMessage(); return result; } else { // We don't want to have a scoped ref for async message cases since since // async messages are sent during module desruction. In this case, the // module will have a 0 refcount and addrefing and releasing it will // reenter the destructor and it will crash. return Dispatcher::Send(msg); } } bool HostDispatcher::OnMessageReceived(const IPC::Message& msg) { // Prevent the dispatcher from going away during a message handler. This must // be at the outermost scope so it's released last. ScopedModuleReference death_grip(this); TRACE_EVENT2("ppapi proxy", "HostDispatcher::OnMessageReceived", "Class", IPC_MESSAGE_ID_CLASS(msg.type()), "Line", IPC_MESSAGE_ID_LINE(msg.type())); // We only want to allow reentrancy when the most recent message from the // plugin was a scripting message. We save the old state of the flag on the // stack in case we're (we are the host) being reentered ourselves. The flag // is set to false here for all messages, and then the scripting API will // explicitly set it to true during processing of those messages that can be // reentered. BoolRestorer restorer(&allow_plugin_reentrancy_); allow_plugin_reentrancy_ = false; for (size_t i = 0; i < filters_.size(); i++) { if (filters_[i]->OnMessageReceived(msg)) return true; } bool handled = true; IPC_BEGIN_MESSAGE_MAP(HostDispatcher, msg) IPC_MESSAGE_HANDLER(PpapiHostMsg_LogWithSource, OnHostMsgLogWithSource) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() if (handled) return true; return Dispatcher::OnMessageReceived(msg); // Note: |this| may be deleted once the death_grip goes out of scope! } void HostDispatcher::OnChannelError() { Dispatcher::OnChannelError(); // Stop using the channel. // Tell the host about the crash so it can clean up and display notification. ppb_proxy_->PluginCrashed(pp_module()); } const void* HostDispatcher::GetProxiedInterface(const std::string& iface_name) { const void* proxied_interface = InterfaceList::GetInstance()->GetInterfaceForPPP(iface_name); if (!proxied_interface) return NULL; // Don't have a proxy for this interface, don't query further. PluginSupportedMap::iterator iter(plugin_supported_.find(iface_name)); if (iter == plugin_supported_.end()) { // Need to query. Cache the result so we only do this once. bool supported = false; bool previous_reentrancy_value = allow_plugin_reentrancy_; allow_plugin_reentrancy_ = true; Send(new PpapiMsg_SupportsInterface(iface_name, &supported)); allow_plugin_reentrancy_ = previous_reentrancy_value; std::pair iter_success_pair; iter_success_pair = plugin_supported_.insert( PluginSupportedMap::value_type(iface_name, supported)); iter = iter_success_pair.first; } if (iter->second) return proxied_interface; return NULL; } void HostDispatcher::OnInvalidMessageReceived() { // TODO(brettw) bug 95345 kill the plugin when an invalid message is // received. } void HostDispatcher::OnHostMsgLogWithSource(PP_Instance instance, int int_log_level, const std::string& source, const std::string& value) { PP_LogLevel_Dev level = static_cast(int_log_level); if (instance) { PpapiGlobals::Get()->LogWithSource(instance, level, source, value); } else { PpapiGlobals::Get()->BroadcastLogWithSource(pp_module_, level, source, value); } } // ScopedModuleReference ------------------------------------------------------- ScopedModuleReference::ScopedModuleReference(Dispatcher* dispatcher) : dispatcher_(NULL) { if (!dispatcher->IsPlugin()) { dispatcher_ = static_cast(dispatcher); dispatcher_->ppb_proxy()->AddRefModule(dispatcher_->pp_module()); } } ScopedModuleReference::~ScopedModuleReference() { if (dispatcher_) dispatcher_->ppb_proxy()->ReleaseModule(dispatcher_->pp_module()); } } // namespace proxy } // namespace ppapi