// Copyright 2012 The Chromium Authors
// 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 <stddef.h>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "ppapi/c/ppb_var.h"
#include "ppapi/c/private/ppb_proxy_private.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<PP_Instance, HostDispatcher*> InstanceToHostDispatcherMap;
InstanceToHostDispatcherMap* g_instance_to_host_dispatcher = NULL;
typedef std::map<PP_Module, HostDispatcher*> 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();
}
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,
const PpapiPermissions& permissions)
: Dispatcher(local_get_interface, permissions),
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<const PPB_Proxy_Private*>(
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,
base::ProcessId peer_pid,
const IPC::ChannelHandle& channel_handle,
bool is_client,
const ppapi::Preferences& preferences,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
if (!Dispatcher::InitWithChannel(delegate, peer_pid, channel_handle,
is_client, task_runner))
return false;
Send(new PpapiMsg_SetPreferences(preferences));
return true;
}
// static
HostDispatcher* HostDispatcher::GetForInstance(PP_Instance instance) {
if (!g_instance_to_host_dispatcher)
return NULL;
InstanceToHostDispatcherMap::iterator found =
g_instance_to_host_dispatcher->find(instance);
if (found == g_instance_to_host_dispatcher->end())
return NULL;
return found->second;
}
// static
void HostDispatcher::SetForInstance(PP_Instance instance,
HostDispatcher* dispatcher) {
if (!g_instance_to_host_dispatcher)
g_instance_to_host_dispatcher = new InstanceToHostDispatcherMap;
(*g_instance_to_host_dispatcher)[instance] = dispatcher;
}
// static
void HostDispatcher::RemoveForInstance(PP_Instance instance) {
if (!g_instance_to_host_dispatcher)
return;
InstanceToHostDispatcherMap::iterator found =
g_instance_to_host_dispatcher->find(instance);
if (found != g_instance_to_host_dispatcher->end())
g_instance_to_host_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);
for (auto& observer : sync_status_observer_list_)
observer.BeginBlockOnSyncMessage();
bool result = Dispatcher::Send(msg);
for (auto& observer : sync_status_observer_list_)
observer.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;
Send(new PpapiMsg_SupportsInterface(iface_name, &supported));
std::pair<PluginSupportedMap::iterator, bool> 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;
}
base::OnceClosure HostDispatcher::AddSyncMessageStatusObserver(
SyncMessageStatusObserver* obs) {
sync_status_observer_list_.AddObserver(obs);
return base::BindOnce(&HostDispatcher::RemoveSyncMessageStatusObserver,
weak_ptr_factory_.GetWeakPtr(), obs);
}
void HostDispatcher::RemoveSyncMessageStatusObserver(
SyncMessageStatusObserver* obs) {
sync_status_observer_list_.RemoveObserver(obs);
}
void HostDispatcher::AddFilter(IPC::Listener* listener) {
filters_.push_back(listener);
}
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 level = static_cast<PP_LogLevel>(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<HostDispatcher*>(dispatcher);
dispatcher_->ppb_proxy()->AddRefModule(dispatcher_->pp_module());
}
}
ScopedModuleReference::~ScopedModuleReference() {
if (dispatcher_)
dispatcher_->ppb_proxy()->ReleaseModule(dispatcher_->pp_module());
}
} // namespace proxy
} // namespace ppapi