diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-16 17:23:58 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-16 17:23:58 +0000 |
commit | 3c5c6d8d4dd6b6b6fd3115dbbe2b155b9eb207f9 (patch) | |
tree | 6832386fa85e7dc6db0e094041b5bd3587dd7748 /content/plugin | |
parent | 0590a140d2291f2aebfb54179f1282d798faa6a8 (diff) | |
download | chromium_src-3c5c6d8d4dd6b6b6fd3115dbbe2b155b9eb207f9.zip chromium_src-3c5c6d8d4dd6b6b6fd3115dbbe2b155b9eb207f9.tar.gz chromium_src-3c5c6d8d4dd6b6b6fd3115dbbe2b155b9eb207f9.tar.bz2 |
Move plugin code to content.
TBR=avi
Review URL: http://codereview.chromium.org/6672048
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78386 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/plugin')
25 files changed, 4806 insertions, 0 deletions
diff --git a/content/plugin/DEPS b/content/plugin/DEPS new file mode 100644 index 0000000..5868246 --- /dev/null +++ b/content/plugin/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+sandbox/src", + "+skia/ext", + "+third_party/npapi", + "+webkit/glue", + "+webkit/plugins", +] + diff --git a/content/plugin/npobject_base.h b/content/plugin/npobject_base.h new file mode 100644 index 0000000..fc6117a --- /dev/null +++ b/content/plugin/npobject_base.h @@ -0,0 +1,27 @@ +// Copyright (c) 2006-2010 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. +// +// Base interface implemented by NPObjectProxy and NPObjectStub + +#ifndef CONTENT_PLUGIN_NPOBJECT_BASE_H_ +#define CONTENT_PLUGIN_NPOBJECT_BASE_H_ +#pragma once + +#include "ipc/ipc_channel.h" +#include "third_party/npapi/bindings/npruntime.h" + +struct NPObject; + +class NPObjectBase { + public: + virtual ~NPObjectBase() {} + + // Returns the underlying NPObject handled by this NPObjectBase instance. + virtual NPObject* GetUnderlyingNPObject() = 0; + + // Returns the channel listener for this NPObjectBase instance. + virtual IPC::Channel::Listener* GetChannelListener() = 0; +}; + +#endif // CONTENT_PLUGIN_NPOBJECT_BASE_H_ diff --git a/content/plugin/npobject_proxy.cc b/content/plugin/npobject_proxy.cc new file mode 100644 index 0000000..5c08724 --- /dev/null +++ b/content/plugin/npobject_proxy.cc @@ -0,0 +1,500 @@ +// Copyright (c) 2006-2008 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 "content/plugin/npobject_proxy.h" + +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_util.h" +#include "content/plugin/plugin_channel.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/plugins/npapi/plugin_instance.h" + +using WebKit::WebBindings; + +struct NPObjectWrapper { + NPObject object; + NPObjectProxy* proxy; +}; + +NPClass NPObjectProxy::npclass_proxy_ = { + NP_CLASS_STRUCT_VERSION, + NPObjectProxy::NPAllocate, + NPObjectProxy::NPDeallocate, + NPObjectProxy::NPPInvalidate, + NPObjectProxy::NPHasMethod, + NPObjectProxy::NPInvoke, + NPObjectProxy::NPInvokeDefault, + NPObjectProxy::NPHasProperty, + NPObjectProxy::NPGetProperty, + NPObjectProxy::NPSetProperty, + NPObjectProxy::NPRemoveProperty, + NPObjectProxy::NPNEnumerate, + NPObjectProxy::NPNConstruct +}; + +NPObjectProxy* NPObjectProxy::GetProxy(NPObject* object) { + NPObjectProxy* proxy = NULL; + + // Wrapper exists only for NPObjects that we had created. + if (&npclass_proxy_ == object->_class) { + NPObjectWrapper* wrapper = reinterpret_cast<NPObjectWrapper*>(object); + proxy = wrapper->proxy; + } + + return proxy; +} + +NPObject* NPObjectProxy::GetUnderlyingNPObject() { + return NULL; +} + +IPC::Channel::Listener* NPObjectProxy::GetChannelListener() { + return static_cast<IPC::Channel::Listener*>(this); +} + +NPObjectProxy::NPObjectProxy( + PluginChannelBase* channel, + int route_id, + gfx::NativeViewId containing_window, + const GURL& page_url) + : channel_(channel), + route_id_(route_id), + containing_window_(containing_window), + page_url_(page_url) { + channel_->AddRoute(route_id, this, this); +} + +NPObjectProxy::~NPObjectProxy() { + if (channel_.get()) { + Send(new NPObjectMsg_Release(route_id_)); + if (channel_.get()) + channel_->RemoveRoute(route_id_); + } +} + +NPObject* NPObjectProxy::Create(PluginChannelBase* channel, + int route_id, + gfx::NativeViewId containing_window, + const GURL& page_url) { + NPObjectWrapper* obj = reinterpret_cast<NPObjectWrapper*>( + WebBindings::createObject(0, &npclass_proxy_)); + obj->proxy = new NPObjectProxy( + channel, route_id, containing_window, page_url); + + return reinterpret_cast<NPObject*>(obj); +} + +bool NPObjectProxy::Send(IPC::Message* msg) { + if (channel_.get()) + return channel_->Send(msg); + + delete msg; + return false; +} + +NPObject* NPObjectProxy::NPAllocate(NPP, NPClass*) { + return reinterpret_cast<NPObject*>(new NPObjectWrapper); +} + +void NPObjectProxy::NPDeallocate(NPObject* npObj) { + NPObjectWrapper* obj = reinterpret_cast<NPObjectWrapper*>(npObj); + delete obj->proxy; + delete obj; +} + +bool NPObjectProxy::OnMessageReceived(const IPC::Message& msg) { + NOTREACHED(); + return false; +} + +void NPObjectProxy::OnChannelError() { + // Release our ref count of the plugin channel object, as it addrefs the + // process. + channel_ = NULL; +} + +bool NPObjectProxy::NPHasMethod(NPObject *obj, + NPIdentifier name) { + if (obj == NULL) + return false; + + bool result = false; + NPObjectProxy* proxy = GetProxy(obj); + + if (!proxy) { + return obj->_class->hasMethod(obj, name); + } + + NPIdentifier_Param name_param; + CreateNPIdentifierParam(name, &name_param); + + proxy->Send(new NPObjectMsg_HasMethod(proxy->route_id(), name_param, + &result)); + return result; +} + +bool NPObjectProxy::NPInvoke(NPObject *obj, + NPIdentifier name, + const NPVariant *args, + uint32_t arg_count, + NPVariant *result) { + return NPInvokePrivate(0, obj, false, name, args, arg_count, result); +} + +bool NPObjectProxy::NPInvokeDefault(NPObject *npobj, + const NPVariant *args, + uint32_t arg_count, + NPVariant *result) { + return NPInvokePrivate(0, npobj, true, 0, args, arg_count, result); +} + +bool NPObjectProxy::NPInvokePrivate(NPP npp, + NPObject *obj, + bool is_default, + NPIdentifier name, + const NPVariant *args, + uint32_t arg_count, + NPVariant *np_result) { + if (obj == NULL) + return false; + + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + if (is_default) { + return obj->_class->invokeDefault(obj, args, arg_count, np_result); + } else { + return obj->_class->invoke(obj, name, args, arg_count, np_result); + } + } + + bool result = false; + gfx::NativeViewId containing_window = proxy->containing_window_; + NPIdentifier_Param name_param; + if (is_default) { + // The data won't actually get used, but set it so we don't send random + // data. + name_param.identifier = NULL; + } else { + CreateNPIdentifierParam(name, &name_param); + } + + // Note: This instance can get destroyed in the context of + // Send so addref the channel in this scope. + scoped_refptr<PluginChannelBase> channel_copy = proxy->channel_; + std::vector<NPVariant_Param> args_param; + for (unsigned int i = 0; i < arg_count; ++i) { + NPVariant_Param param; + CreateNPVariantParam( + args[i], channel_copy, ¶m, false, containing_window, + proxy->page_url_); + args_param.push_back(param); + } + + NPVariant_Param param_result; + NPObjectMsg_Invoke* msg = new NPObjectMsg_Invoke( + proxy->route_id_, is_default, name_param, args_param, ¶m_result, + &result); + + // If we're in the plugin process and this invoke leads to a dialog box, the + // plugin will hang the window hierarchy unless we pump the window message + // queue while waiting for a reply. We need to do this to simulate what + // happens when everything runs in-process (while calling MessageBox window + // messages are pumped). + if (IsPluginProcess()) { + PluginChannel* channel = static_cast<PluginChannel*>(proxy->channel_.get()); + if (channel) { + msg->set_pump_messages_event( + channel->GetModalDialogEvent(containing_window)); + } + } + + GURL page_url = proxy->page_url_; + proxy->Send(msg); + + // Send may delete proxy. + proxy = NULL; + + if (!result) + return false; + + CreateNPVariant( + param_result, channel_copy, np_result, containing_window, page_url); + return true; +} + +bool NPObjectProxy::NPHasProperty(NPObject *obj, + NPIdentifier name) { + if (obj == NULL) + return false; + + bool result = false; + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + return obj->_class->hasProperty(obj, name); + } + + NPIdentifier_Param name_param; + CreateNPIdentifierParam(name, &name_param); + + NPVariant_Param param; + proxy->Send(new NPObjectMsg_HasProperty( + proxy->route_id(), name_param, &result)); + + // Send may delete proxy. + proxy = NULL; + + return result; +} + +bool NPObjectProxy::NPGetProperty(NPObject *obj, + NPIdentifier name, + NPVariant *np_result) { + // Please refer to http://code.google.com/p/chromium/issues/detail?id=2556, + // which was a crash in the XStandard plugin during plugin shutdown. The + // crash occured because the plugin requests the plugin script object, + // which fails. The plugin does not check the result of the operation and + // invokes NPN_GetProperty on a NULL object which lead to the crash. If + // we observe similar crashes in other methods in the future, these null + // checks may have to be replicated in the other methods in this class. + if (obj == NULL) + return false; + + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + return obj->_class->getProperty(obj, name, np_result); + } + + bool result = false; + gfx::NativeViewId containing_window = proxy->containing_window_; + NPIdentifier_Param name_param; + CreateNPIdentifierParam(name, &name_param); + + NPVariant_Param param; + scoped_refptr<PluginChannelBase> channel(proxy->channel_); + + GURL page_url = proxy->page_url_; + proxy->Send(new NPObjectMsg_GetProperty( + proxy->route_id(), name_param, ¶m, &result)); + // Send may delete proxy. + proxy = NULL; + if (!result) + return false; + + CreateNPVariant( + param, channel.get(), np_result, containing_window, page_url); + + return true; +} + +bool NPObjectProxy::NPSetProperty(NPObject *obj, + NPIdentifier name, + const NPVariant *value) { + if (obj == NULL) + return false; + + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + return obj->_class->setProperty(obj, name, value); + } + + bool result = false; + gfx::NativeViewId containing_window = proxy->containing_window_; + NPIdentifier_Param name_param; + CreateNPIdentifierParam(name, &name_param); + + NPVariant_Param value_param; + CreateNPVariantParam( + *value, proxy->channel(), &value_param, false, + containing_window, proxy->page_url_); + + proxy->Send(new NPObjectMsg_SetProperty( + proxy->route_id(), name_param, value_param, &result)); + // Send may delete proxy. + proxy = NULL; + + return result; +} + +bool NPObjectProxy::NPRemoveProperty(NPObject *obj, + NPIdentifier name) { + if (obj == NULL) + return false; + + bool result = false; + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + return obj->_class->removeProperty(obj, name); + } + + NPIdentifier_Param name_param; + CreateNPIdentifierParam(name, &name_param); + + NPVariant_Param param; + proxy->Send(new NPObjectMsg_RemoveProperty( + proxy->route_id(), name_param, &result)); + // Send may delete proxy. + proxy = NULL; + + return result; +} + +void NPObjectProxy::NPPInvalidate(NPObject *obj) { + if (obj == NULL) + return; + + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + obj->_class->invalidate(obj); + return; + } + + proxy->Send(new NPObjectMsg_Invalidate(proxy->route_id())); + // Send may delete proxy. + proxy = NULL; +} + +bool NPObjectProxy::NPNEnumerate(NPObject *obj, + NPIdentifier **value, + uint32_t *count) { + if (obj == NULL) + return false; + + bool result = false; + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + if (obj->_class->structVersion >= NP_CLASS_STRUCT_VERSION_ENUM) { + return obj->_class->enumerate(obj, value, count); + } else { + return false; + } + } + + std::vector<NPIdentifier_Param> value_param; + proxy->Send(new NPObjectMsg_Enumeration( + proxy->route_id(), &value_param, &result)); + // Send may delete proxy. + proxy = NULL; + + if (!result) + return false; + + *count = static_cast<unsigned int>(value_param.size()); + *value = static_cast<NPIdentifier *>( + NPN_MemAlloc(sizeof(NPIdentifier) * *count)); + for (unsigned int i = 0; i < *count; ++i) + (*value)[i] = CreateNPIdentifier(value_param[i]); + + return true; +} + +bool NPObjectProxy::NPNConstruct(NPObject *obj, + const NPVariant *args, + uint32_t arg_count, + NPVariant *np_result) { + if (obj == NULL) + return false; + + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + if (obj->_class->structVersion >= NP_CLASS_STRUCT_VERSION_CTOR) { + return obj->_class->construct(obj, args, arg_count, np_result); + } else { + return false; + } + } + + bool result = false; + gfx::NativeViewId containing_window = proxy->containing_window_; + + // Note: This instance can get destroyed in the context of + // Send so addref the channel in this scope. + scoped_refptr<PluginChannelBase> channel_copy = proxy->channel_; + std::vector<NPVariant_Param> args_param; + for (unsigned int i = 0; i < arg_count; ++i) { + NPVariant_Param param; + CreateNPVariantParam( + args[i], channel_copy, ¶m, false, containing_window, + proxy->page_url_); + args_param.push_back(param); + } + + NPVariant_Param param_result; + NPObjectMsg_Construct* msg = new NPObjectMsg_Construct( + proxy->route_id_, args_param, ¶m_result, &result); + + // See comment in NPObjectProxy::NPInvokePrivate. + if (IsPluginProcess()) { + PluginChannel* channel = static_cast<PluginChannel*>(proxy->channel_.get()); + if (channel) { + msg->set_pump_messages_event( + channel->GetModalDialogEvent(proxy->containing_window_)); + } + } + + GURL page_url = proxy->page_url_; + proxy->Send(msg); + + // Send may delete proxy. + proxy = NULL; + + if (!result) + return false; + + CreateNPVariant( + param_result, channel_copy, np_result, containing_window, page_url); + return true; +} + +bool NPObjectProxy::NPNEvaluate(NPP npp, + NPObject *obj, + NPString *script, + NPVariant *result_var) { + NPObjectProxy* proxy = GetProxy(obj); + if (!proxy) { + return false; + } + + bool result = false; + gfx::NativeViewId containing_window = proxy->containing_window_; + bool popups_allowed = false; + + if (npp) { + webkit::npapi::PluginInstance* plugin_instance = + reinterpret_cast<webkit::npapi::PluginInstance*>(npp->ndata); + if (plugin_instance) + popups_allowed = plugin_instance->popups_allowed(); + } + + NPVariant_Param result_param; + std::string script_str = std::string( + script->UTF8Characters, script->UTF8Length); + + NPObjectMsg_Evaluate* msg = new NPObjectMsg_Evaluate(proxy->route_id(), + script_str, + popups_allowed, + &result_param, + &result); + + // See comment in NPObjectProxy::NPInvokePrivate. + if (IsPluginProcess()) { + PluginChannel* channel = static_cast<PluginChannel*>(proxy->channel_.get()); + if (channel) { + msg->set_pump_messages_event( + channel->GetModalDialogEvent(proxy->containing_window_)); + } + } + scoped_refptr<PluginChannelBase> channel(proxy->channel_); + + GURL page_url = proxy->page_url_; + proxy->Send(msg); + // Send may delete proxy. + proxy = NULL; + if (!result) + return false; + + CreateNPVariant( + result_param, channel.get(), result_var, containing_window, page_url); + return true; +} diff --git a/content/plugin/npobject_proxy.h b/content/plugin/npobject_proxy.h new file mode 100644 index 0000000..39996de --- /dev/null +++ b/content/plugin/npobject_proxy.h @@ -0,0 +1,123 @@ +// Copyright (c) 2006-2008 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. +// +// A proxy for NPObject that sends all calls to the object to an NPObjectStub +// running in a different process. + +#ifndef CONTENT_PLUGIN_NPOBJECT_PROXY_H_ +#define CONTENT_PLUGIN_NPOBJECT_PROXY_H_ +#pragma once + +#include "base/ref_counted.h" +#include "content/plugin/npobject_base.h" +#include "googleurl/src/gurl.h" +#include "ipc/ipc_channel.h" +#include "third_party/npapi/bindings/npruntime.h" +#include "ui/gfx/native_widget_types.h" + +class PluginChannelBase; +struct NPObject; + +// When running a plugin in a different process from the renderer, we need to +// proxy calls to NPObjects across process boundaries. This happens both ways, +// as a plugin can get an NPObject for the window, and a page can get an +// NPObject for the plugin. In the process that interacts with the NPobject we +// give it an NPObjectProxy instead. All calls to it are sent across an IPC +// channel (specifically, a PluginChannelBase). The NPObjectStub on the other +// side translates the IPC messages into calls to the actual NPObject, and +// returns the marshalled result. +class NPObjectProxy : public IPC::Channel::Listener, + public IPC::Message::Sender, + public NPObjectBase { + public: + ~NPObjectProxy(); + + static NPObject* Create(PluginChannelBase* channel, + int route_id, + gfx::NativeViewId containing_window, + const GURL& page_url); + + // IPC::Message::Sender implementation: + virtual bool Send(IPC::Message* msg); + int route_id() { return route_id_; } + PluginChannelBase* channel() { return channel_; } + + // The next 9 functions are called on NPObjects from the plugin and browser. + static bool NPHasMethod(NPObject *obj, + NPIdentifier name); + static bool NPInvoke(NPObject *obj, + NPIdentifier name, + const NPVariant *args, + uint32_t arg_count, + NPVariant *result); + static bool NPInvokeDefault(NPObject *npobj, + const NPVariant *args, + uint32_t arg_count, + NPVariant *result); + static bool NPHasProperty(NPObject *obj, + NPIdentifier name); + static bool NPGetProperty(NPObject *obj, + NPIdentifier name, + NPVariant *result); + static bool NPSetProperty(NPObject *obj, + NPIdentifier name, + const NPVariant *value); + static bool NPRemoveProperty(NPObject *obj, + NPIdentifier name); + static bool NPNEnumerate(NPObject *obj, + NPIdentifier **value, + uint32_t *count); + static bool NPNConstruct(NPObject *npobj, + const NPVariant *args, + uint32_t arg_count, + NPVariant *result); + + // The next two functions are only called on NPObjects from the browser. + static bool NPNEvaluate(NPP npp, + NPObject *obj, + NPString *script, + NPVariant *result); + + static bool NPInvokePrivate(NPP npp, + NPObject *obj, + bool is_default, + NPIdentifier name, + const NPVariant *args, + uint32_t arg_count, + NPVariant *result); + + static NPObjectProxy* GetProxy(NPObject* object); + static const NPClass* npclass() { return &npclass_proxy_; } + + // NPObjectBase implementation. + virtual NPObject* GetUnderlyingNPObject(); + + virtual IPC::Channel::Listener* GetChannelListener(); + + private: + NPObjectProxy(PluginChannelBase* channel, + int route_id, + gfx::NativeViewId containing_window, + const GURL& page_url); + + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelError(); + + static NPObject* NPAllocate(NPP, NPClass*); + static void NPDeallocate(NPObject* npObj); + + // This function is only caled on NPObjects from the plugin. + static void NPPInvalidate(NPObject *obj); + static NPClass npclass_proxy_; + + scoped_refptr<PluginChannelBase> channel_; + int route_id_; + gfx::NativeViewId containing_window_; + + // The url of the main frame hosting the plugin. + GURL page_url_; +}; + +#endif // CONTENT_PLUGIN_NPOBJECT_PROXY_H_ diff --git a/content/plugin/npobject_stub.cc b/content/plugin/npobject_stub.cc new file mode 100644 index 0000000..e7402a8 --- /dev/null +++ b/content/plugin/npobject_stub.cc @@ -0,0 +1,402 @@ +// Copyright (c) 2011 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 "content/plugin/npobject_stub.h" + +#include "content/common/content_client.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_util.h" +#include "content/plugin/plugin_channel_base.h" +#include "content/plugin/plugin_thread.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/npruntime.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" +#include "webkit/plugins/npapi/plugin_constants_win.h" + +using WebKit::WebBindings; + +NPObjectStub::NPObjectStub( + NPObject* npobject, + PluginChannelBase* channel, + int route_id, + gfx::NativeViewId containing_window, + const GURL& page_url) + : npobject_(npobject), + channel_(channel), + route_id_(route_id), + containing_window_(containing_window), + page_url_(page_url) { + channel_->AddRoute(route_id, this, this); + + // We retain the object just as PluginHost does if everything was in-process. + WebBindings::retainObject(npobject_); +} + +NPObjectStub::~NPObjectStub() { + channel_->RemoveRoute(route_id_); + if (npobject_) + WebBindings::releaseObject(npobject_); +} + +bool NPObjectStub::Send(IPC::Message* msg) { + return channel_->Send(msg); +} + +void NPObjectStub::OnPluginDestroyed() { + // We null out the underlying NPObject pointer since it's not valid anymore ( + // ScriptController manually deleted the object). As a result, + // OnMessageReceived won't dispatch any more messages. Since this includes + // OnRelease, this object won't get deleted until OnChannelError which might + // not happen for a long time if this renderer process has a long lived + // plugin instance to the same process. So we delete this object manually. + npobject_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +NPObject* NPObjectStub::GetUnderlyingNPObject() { + return npobject_; +} + +IPC::Channel::Listener* NPObjectStub::GetChannelListener() { + return static_cast<IPC::Channel::Listener*>(this); +} + +bool NPObjectStub::OnMessageReceived(const IPC::Message& msg) { + content::GetContentClient()->SetActiveURL(page_url_); + + if (!npobject_) { + if (msg.is_sync()) { + // The object could be garbage because the frame has gone away, so + // just send an error reply to the caller. + IPC::Message* reply = IPC::SyncMessage::GenerateReply(&msg); + reply->set_reply_error(); + Send(reply); + } + + return true; + } + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(NPObjectStub, msg) + IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Release, OnRelease); + IPC_MESSAGE_HANDLER(NPObjectMsg_HasMethod, OnHasMethod); + IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Invoke, OnInvoke); + IPC_MESSAGE_HANDLER(NPObjectMsg_HasProperty, OnHasProperty); + IPC_MESSAGE_HANDLER(NPObjectMsg_GetProperty, OnGetProperty); + IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_SetProperty, OnSetProperty); + IPC_MESSAGE_HANDLER(NPObjectMsg_RemoveProperty, OnRemoveProperty); + IPC_MESSAGE_HANDLER(NPObjectMsg_Invalidate, OnInvalidate); + IPC_MESSAGE_HANDLER(NPObjectMsg_Enumeration, OnEnumeration); + IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Construct, OnConstruct); + IPC_MESSAGE_HANDLER_DELAY_REPLY(NPObjectMsg_Evaluate, OnEvaluate); + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +void NPObjectStub::OnChannelError() { + // If npobject_ is NULLed out, that means a DeleteSoon is happening. + if (npobject_) + delete this; +} + +void NPObjectStub::OnRelease(IPC::Message* reply_msg) { + Send(reply_msg); + delete this; +} + +void NPObjectStub::OnHasMethod(const NPIdentifier_Param& name, + bool* result) { + NPIdentifier id = CreateNPIdentifier(name); + // If we're in the plugin process, then the stub is holding onto an NPObject + // from the plugin, so all function calls on it need to go through the + // functions in NPClass. If we're in the renderer process, then we just call + // the NPN_ functions. + if (IsPluginProcess()) { + if (npobject_->_class->hasMethod) { + *result = npobject_->_class->hasMethod(npobject_, id); + } else { + *result = false; + } + } else { + *result = WebBindings::hasMethod(0, npobject_, id); + } +} + +void NPObjectStub::OnInvoke(bool is_default, + const NPIdentifier_Param& method, + const std::vector<NPVariant_Param>& args, + IPC::Message* reply_msg) { + scoped_refptr<PluginChannelBase> local_channel = channel_; + bool return_value = false; + NPVariant_Param result_param; + NPVariant result_var; + + VOID_TO_NPVARIANT(result_var); + result_param.type = NPVARIANT_PARAM_VOID; + + int arg_count = static_cast<int>(args.size()); + NPVariant* args_var = new NPVariant[arg_count]; + for (int i = 0; i < arg_count; ++i) { + if (!CreateNPVariant( + args[i], local_channel, &(args_var[i]), containing_window_, + page_url_)) { + NPObjectMsg_Invoke::WriteReplyParams(reply_msg, result_param, + return_value); + local_channel->Send(reply_msg); + return; + } + } + + if (is_default) { + if (IsPluginProcess()) { + if (npobject_->_class->invokeDefault) { + return_value = npobject_->_class->invokeDefault( + npobject_, args_var, arg_count, &result_var); + } else { + return_value = false; + } + } else { + return_value = WebBindings::invokeDefault( + 0, npobject_, args_var, arg_count, &result_var); + } + } else { + NPIdentifier id = CreateNPIdentifier(method); + if (IsPluginProcess()) { + if (npobject_->_class->invoke) { + return_value = npobject_->_class->invoke( + npobject_, id, args_var, arg_count, &result_var); + } else { + return_value = false; + } + } else { + return_value = WebBindings::invoke( + 0, npobject_, id, args_var, arg_count, &result_var); + } + } + + for (int i = 0; i < arg_count; ++i) + WebBindings::releaseVariantValue(&(args_var[i])); + + delete[] args_var; + + CreateNPVariantParam( + result_var, local_channel, &result_param, true, containing_window_, + page_url_); + NPObjectMsg_Invoke::WriteReplyParams(reply_msg, result_param, return_value); + local_channel->Send(reply_msg); +} + +void NPObjectStub::OnHasProperty(const NPIdentifier_Param& name, + bool* result) { + NPIdentifier id = CreateNPIdentifier(name); + if (IsPluginProcess()) { + if (npobject_->_class->hasProperty) { + *result = npobject_->_class->hasProperty(npobject_, id); + } else { + *result = false; + } + } else { + *result = WebBindings::hasProperty(0, npobject_, id); + } +} + +void NPObjectStub::OnGetProperty(const NPIdentifier_Param& name, + NPVariant_Param* property, + bool* result) { + NPVariant result_var; + VOID_TO_NPVARIANT(result_var); + NPIdentifier id = CreateNPIdentifier(name); + + if (IsPluginProcess()) { + if (npobject_->_class->getProperty) { + *result = npobject_->_class->getProperty(npobject_, id, &result_var); + } else { + *result = false; + } + } else { + *result = WebBindings::getProperty(0, npobject_, id, &result_var); + } + + CreateNPVariantParam( + result_var, channel_, property, true, containing_window_, page_url_); +} + +void NPObjectStub::OnSetProperty(const NPIdentifier_Param& name, + const NPVariant_Param& property, + IPC::Message* reply_msg) { + bool result = false; + NPVariant result_var; + VOID_TO_NPVARIANT(result_var); + NPIdentifier id = CreateNPIdentifier(name); + NPVariant property_var; + if (!CreateNPVariant( + property, channel_, &property_var, containing_window_, page_url_)) { + NPObjectMsg_SetProperty::WriteReplyParams(reply_msg, result); + channel_->Send(reply_msg); + return; + } + + if (IsPluginProcess()) { + if (npobject_->_class->setProperty) { +#if defined(OS_WIN) + static std::wstring filename = StringToLowerASCII( + PluginThread::current()->plugin_path().BaseName().value()); + static NPIdentifier fullscreen = + WebBindings::getStringIdentifier("fullScreen"); + if (filename == webkit::npapi::kNewWMPPlugin && id == fullscreen) { + // Workaround for bug 15985, which is if Flash causes WMP to go + // full screen a deadlock can occur when WMP calls SetFocus. + NPObjectMsg_SetProperty::WriteReplyParams(reply_msg, true); + Send(reply_msg); + reply_msg = NULL; + } +#endif + result = npobject_->_class->setProperty(npobject_, id, &property_var); + } else { + result = false; + } + } else { + result = WebBindings::setProperty(0, npobject_, id, &property_var); + } + + WebBindings::releaseVariantValue(&property_var); + + if (reply_msg) { + NPObjectMsg_SetProperty::WriteReplyParams(reply_msg, result); + Send(reply_msg); + } +} + +void NPObjectStub::OnRemoveProperty(const NPIdentifier_Param& name, + bool* result) { + NPIdentifier id = CreateNPIdentifier(name); + if (IsPluginProcess()) { + if (npobject_->_class->removeProperty) { + *result = npobject_->_class->removeProperty(npobject_, id); + } else { + *result = false; + } + } else { + *result = WebBindings::removeProperty(0, npobject_, id); + } +} + +void NPObjectStub::OnInvalidate() { + if (!IsPluginProcess()) { + NOTREACHED() << "Should only be called on NPObjects in the plugin"; + return; + } + + if (!npobject_->_class->invalidate) + return; + + npobject_->_class->invalidate(npobject_); +} + +void NPObjectStub::OnEnumeration(std::vector<NPIdentifier_Param>* value, + bool* result) { + NPIdentifier* value_np = NULL; + unsigned int count = 0; + if (!IsPluginProcess()) { + *result = WebBindings::enumerate(0, npobject_, &value_np, &count); + } else { + if (npobject_->_class->structVersion < NP_CLASS_STRUCT_VERSION_ENUM || + !npobject_->_class->enumerate) { + *result = false; + return; + } + + *result = npobject_->_class->enumerate(npobject_, &value_np, &count); + } + + if (!*result) + return; + + for (unsigned int i = 0; i < count; ++i) { + NPIdentifier_Param param; + CreateNPIdentifierParam(value_np[i], ¶m); + value->push_back(param); + } + + NPN_MemFree(value_np); +} + +void NPObjectStub::OnConstruct(const std::vector<NPVariant_Param>& args, + IPC::Message* reply_msg) { + scoped_refptr<PluginChannelBase> local_channel = channel_; + bool return_value = false; + NPVariant_Param result_param; + NPVariant result_var; + + VOID_TO_NPVARIANT(result_var); + + int arg_count = static_cast<int>(args.size()); + NPVariant* args_var = new NPVariant[arg_count]; + for (int i = 0; i < arg_count; ++i) { + if (!CreateNPVariant( + args[i], local_channel, &(args_var[i]), containing_window_, + page_url_)) { + NPObjectMsg_Invoke::WriteReplyParams(reply_msg, result_param, + return_value); + local_channel->Send(reply_msg); + return; + } + } + + if (IsPluginProcess()) { + if (npobject_->_class->structVersion >= NP_CLASS_STRUCT_VERSION_CTOR && + npobject_->_class->construct) { + return_value = npobject_->_class->construct( + npobject_, args_var, arg_count, &result_var); + } else { + return_value = false; + } + } else { + return_value = WebBindings::construct( + 0, npobject_, args_var, arg_count, &result_var); + } + + for (int i = 0; i < arg_count; ++i) + WebBindings::releaseVariantValue(&(args_var[i])); + + delete[] args_var; + + CreateNPVariantParam( + result_var, local_channel, &result_param, true, containing_window_, + page_url_); + NPObjectMsg_Invoke::WriteReplyParams(reply_msg, result_param, return_value); + local_channel->Send(reply_msg); +} + +void NPObjectStub::OnEvaluate(const std::string& script, + bool popups_allowed, + IPC::Message* reply_msg) { + if (IsPluginProcess()) { + NOTREACHED() << "Should only be called on NPObjects in the renderer"; + return; + } + + // Grab a reference to the underlying channel, as the NPObjectStub + // instance can be destroyed in the context of NPN_Evaluate. This + // can happen if the containing plugin instance is destroyed in + // NPN_Evaluate. + scoped_refptr<PluginChannelBase> local_channel = channel_; + + NPVariant result_var; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + + bool return_value = WebBindings::evaluateHelper(0, popups_allowed, npobject_, + &script_string, &result_var); + + NPVariant_Param result_param; + CreateNPVariantParam( + result_var, local_channel, &result_param, true, containing_window_, + page_url_); + NPObjectMsg_Evaluate::WriteReplyParams(reply_msg, result_param, return_value); + local_channel->Send(reply_msg); +} diff --git a/content/plugin/npobject_stub.h b/content/plugin/npobject_stub.h new file mode 100644 index 0000000..a7637d66 --- /dev/null +++ b/content/plugin/npobject_stub.h @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2008 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. +// +// A class that receives IPC messages from an NPObjectProxy and calls the real +// NPObject. + +#ifndef CONTENT_PLUGIN_NPOBJECT_STUB_H_ +#define CONTENT_PLUGIN_NPOBJECT_STUB_H_ +#pragma once + +#include <vector> + +#include "base/ref_counted.h" +#include "base/weak_ptr.h" +#include "content/plugin/npobject_base.h" +#include "googleurl/src/gurl.h" +#include "ipc/ipc_channel.h" +#include "ui/gfx/native_widget_types.h" + +class PluginChannelBase; +struct NPIdentifier_Param; +struct NPObject; +struct NPVariant_Param; + +// This wraps an NPObject and converts IPC messages from NPObjectProxy to calls +// to the object. The results are marshalled back. See npobject_proxy.h for +// more information. +class NPObjectStub : public IPC::Channel::Listener, + public IPC::Message::Sender, + public base::SupportsWeakPtr<NPObjectStub>, + public NPObjectBase { + public: + NPObjectStub(NPObject* npobject, + PluginChannelBase* channel, + int route_id, + gfx::NativeViewId containing_window, + const GURL& page_url); + ~NPObjectStub(); + + // IPC::Message::Sender implementation: + virtual bool Send(IPC::Message* msg); + + // Called when the plugin widget that this NPObject came from is destroyed. + // This is needed because the renderer calls NPN_DeallocateObject on the + // window script object on destruction to avoid leaks. + void OnPluginDestroyed(); + + // NPObjectBase implementation. + virtual NPObject* GetUnderlyingNPObject(); + + virtual IPC::Channel::Listener* GetChannelListener(); + + private: + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& message); + virtual void OnChannelError(); + + // message handlers + void OnRelease(IPC::Message* reply_msg); + void OnHasMethod(const NPIdentifier_Param& name, + bool* result); + void OnInvoke(bool is_default, + const NPIdentifier_Param& method, + const std::vector<NPVariant_Param>& args, + IPC::Message* reply_msg); + void OnHasProperty(const NPIdentifier_Param& name, + bool* result); + void OnGetProperty(const NPIdentifier_Param& name, + NPVariant_Param* property, + bool* result); + void OnSetProperty(const NPIdentifier_Param& name, + const NPVariant_Param& property, + IPC::Message* reply_msg); + void OnRemoveProperty(const NPIdentifier_Param& name, + bool* result); + void OnInvalidate(); + void OnEnumeration(std::vector<NPIdentifier_Param>* value, + bool* result); + void OnConstruct(const std::vector<NPVariant_Param>& args, + IPC::Message* reply_msg); + void OnEvaluate(const std::string& script, bool popups_allowed, + IPC::Message* reply_msg); + + private: + NPObject* npobject_; + scoped_refptr<PluginChannelBase> channel_; + int route_id_; + gfx::NativeViewId containing_window_; + + // The url of the main frame hosting the plugin. + GURL page_url_; +}; + +#endif // CONTENT_PLUGIN_NPOBJECT_STUB_H_ diff --git a/content/plugin/npobject_util.cc b/content/plugin/npobject_util.cc new file mode 100644 index 0000000..c9a2a68 --- /dev/null +++ b/content/plugin/npobject_util.cc @@ -0,0 +1,273 @@ +// Copyright (c) 2010 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 "content/plugin/npobject_util.h" + +#include "base/string_util.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_proxy.h" +#include "content/plugin/plugin_channel_base.h" +#include "third_party/npapi/bindings/nphostapi.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" +#include "webkit/plugins/npapi/plugin_host.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebBindings; + +// true if the current process is a plugin process, false if it's a renderer +// process. +static bool g_plugin_process; + +namespace { +// The next 7 functions are called by the plugin code when it's using the +// NPObject. Plugins always ignore the functions in NPClass (except allocate +// and deallocate), and instead just use the function pointers that were +// passed in NPInitialize. +// When the renderer interacts with an NPObject from the plugin, it of course +// uses the function pointers in its NPClass structure. +static bool NPN_HasMethodPatch(NPP npp, + NPObject *npobj, + NPIdentifier methodName) { + return NPObjectProxy::NPHasMethod(npobj, methodName); +} + +static bool NPN_InvokePatch(NPP npp, NPObject *npobj, + NPIdentifier methodName, + const NPVariant *args, + uint32_t argCount, + NPVariant *result) { + return NPObjectProxy::NPInvokePrivate(npp, npobj, false, methodName, args, + argCount, result); +} + +static bool NPN_InvokeDefaultPatch(NPP npp, + NPObject *npobj, + const NPVariant *args, + uint32_t argCount, + NPVariant *result) { + return NPObjectProxy::NPInvokePrivate(npp, npobj, true, 0, args, argCount, + result); +} + +static bool NPN_HasPropertyPatch(NPP npp, + NPObject *npobj, + NPIdentifier propertyName) { + return NPObjectProxy::NPHasProperty(npobj, propertyName); +} + +static bool NPN_GetPropertyPatch(NPP npp, + NPObject *npobj, + NPIdentifier propertyName, + NPVariant *result) { + return NPObjectProxy::NPGetProperty(npobj, propertyName, result); +} + +static bool NPN_SetPropertyPatch(NPP npp, + NPObject *npobj, + NPIdentifier propertyName, + const NPVariant *value) { + return NPObjectProxy::NPSetProperty(npobj, propertyName, value); +} + +static bool NPN_RemovePropertyPatch(NPP npp, + NPObject *npobj, + NPIdentifier propertyName) { + return NPObjectProxy::NPRemoveProperty(npobj, propertyName); +} + +static bool NPN_EvaluatePatch(NPP npp, + NPObject *npobj, + NPString *script, + NPVariant *result) { + return NPObjectProxy::NPNEvaluate(npp, npobj, script, result); +} + + +static void NPN_SetExceptionPatch(NPObject *obj, const NPUTF8 *message) { + std::string message_str(message); + if (IsPluginProcess()) { + PluginChannelBase* renderer_channel = + PluginChannelBase::GetCurrentChannel(); + if (renderer_channel) + renderer_channel->Send(new PluginHostMsg_SetException(message_str)); + } else { + WebBindings::setException(obj, message_str.c_str()); + } +} + +static bool NPN_EnumeratePatch(NPP npp, NPObject *obj, + NPIdentifier **identifier, uint32_t *count) { + return NPObjectProxy::NPNEnumerate(obj, identifier, count); +} + +// The overrided table of functions provided to the plugin. +NPNetscapeFuncs *GetHostFunctions() { + static bool init = false; + static NPNetscapeFuncs host_funcs; + if (init) + return &host_funcs; + + memset(&host_funcs, 0, sizeof(host_funcs)); + host_funcs.invoke = NPN_InvokePatch; + host_funcs.invokeDefault = NPN_InvokeDefaultPatch; + host_funcs.evaluate = NPN_EvaluatePatch; + host_funcs.getproperty = NPN_GetPropertyPatch; + host_funcs.setproperty = NPN_SetPropertyPatch; + host_funcs.removeproperty = NPN_RemovePropertyPatch; + host_funcs.hasproperty = NPN_HasPropertyPatch; + host_funcs.hasmethod = NPN_HasMethodPatch; + host_funcs.setexception = NPN_SetExceptionPatch; + host_funcs.enumerate = NPN_EnumeratePatch; + + init = true; + return &host_funcs; +} + +} + +void PatchNPNFunctions() { + g_plugin_process = true; + NPNetscapeFuncs* funcs = GetHostFunctions(); + webkit::npapi::PluginHost::Singleton()->PatchNPNetscapeFuncs(funcs); +} + +bool IsPluginProcess() { + return g_plugin_process; +} + +void CreateNPIdentifierParam(NPIdentifier id, NPIdentifier_Param* param) { + param->identifier = id; +} + +NPIdentifier CreateNPIdentifier(const NPIdentifier_Param& param) { + return param.identifier; +} + +void CreateNPVariantParam(const NPVariant& variant, + PluginChannelBase* channel, + NPVariant_Param* param, + bool release, + gfx::NativeViewId containing_window, + const GURL& page_url) { + switch (variant.type) { + case NPVariantType_Void: + param->type = NPVARIANT_PARAM_VOID; + break; + case NPVariantType_Null: + param->type = NPVARIANT_PARAM_NULL; + break; + case NPVariantType_Bool: + param->type = NPVARIANT_PARAM_BOOL; + param->bool_value = variant.value.boolValue; + break; + case NPVariantType_Int32: + param->type = NPVARIANT_PARAM_INT; + param->int_value = variant.value.intValue; + break; + case NPVariantType_Double: + param->type = NPVARIANT_PARAM_DOUBLE; + param->double_value = variant.value.doubleValue; + break; + case NPVariantType_String: + param->type = NPVARIANT_PARAM_STRING; + if (variant.value.stringValue.UTF8Length) { + param->string_value.assign(variant.value.stringValue.UTF8Characters, + variant.value.stringValue.UTF8Length); + } + break; + case NPVariantType_Object: { + if (variant.value.objectValue->_class == NPObjectProxy::npclass()) { + param->type = NPVARIANT_PARAM_RECEIVER_OBJECT_ROUTING_ID; + NPObjectProxy* proxy = + NPObjectProxy::GetProxy(variant.value.objectValue); + param->npobject_routing_id = proxy->route_id(); + // Don't release, because our original variant is the same as our proxy. + release = false; + } else { + // The channel could be NULL if there was a channel error. The caller's + // Send call will fail anyways. + if (channel) { + // NPObjectStub adds its own reference to the NPObject it owns, so if + // we were supposed to release the corresponding variant + // (release==true), we should still do that. + param->type = NPVARIANT_PARAM_SENDER_OBJECT_ROUTING_ID; + int route_id = channel->GenerateRouteID(); + new NPObjectStub( + variant.value.objectValue, channel, route_id, containing_window, + page_url); + param->npobject_routing_id = route_id; + } else { + param->type = NPVARIANT_PARAM_VOID; + } + } + break; + } + default: + NOTREACHED(); + } + + if (release) + WebBindings::releaseVariantValue(const_cast<NPVariant*>(&variant)); +} + +bool CreateNPVariant(const NPVariant_Param& param, + PluginChannelBase* channel, + NPVariant* result, + gfx::NativeViewId containing_window, + const GURL& page_url) { + switch (param.type) { + case NPVARIANT_PARAM_VOID: + result->type = NPVariantType_Void; + break; + case NPVARIANT_PARAM_NULL: + result->type = NPVariantType_Null; + break; + case NPVARIANT_PARAM_BOOL: + result->type = NPVariantType_Bool; + result->value.boolValue = param.bool_value; + break; + case NPVARIANT_PARAM_INT: + result->type = NPVariantType_Int32; + result->value.intValue = param.int_value; + break; + case NPVARIANT_PARAM_DOUBLE: + result->type = NPVariantType_Double; + result->value.doubleValue = param.double_value; + break; + case NPVARIANT_PARAM_STRING: + result->type = NPVariantType_String; + result->value.stringValue.UTF8Characters = + static_cast<NPUTF8 *>(base::strdup(param.string_value.c_str())); + result->value.stringValue.UTF8Length = + static_cast<int>(param.string_value.size()); + break; + case NPVARIANT_PARAM_SENDER_OBJECT_ROUTING_ID: + result->type = NPVariantType_Object; + result->value.objectValue = + NPObjectProxy::Create(channel, + param.npobject_routing_id, + containing_window, + page_url); + break; + case NPVARIANT_PARAM_RECEIVER_OBJECT_ROUTING_ID: { + NPObjectBase* npobject_base = + channel->GetNPObjectListenerForRoute(param.npobject_routing_id); + if (!npobject_base) { + DLOG(WARNING) << "Invalid routing id passed in" + << param.npobject_routing_id; + return false; + } + + DCHECK(npobject_base->GetUnderlyingNPObject() != NULL); + + result->type = NPVariantType_Object; + result->value.objectValue = npobject_base->GetUnderlyingNPObject(); + WebBindings::retainObject(result->value.objectValue); + break; + } + default: + NOTREACHED(); + } + return true; +} diff --git a/content/plugin/npobject_util.h b/content/plugin/npobject_util.h new file mode 100644 index 0000000..cca210d --- /dev/null +++ b/content/plugin/npobject_util.h @@ -0,0 +1,73 @@ +// Copyright (c) 2006-2008 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. +// +// Helper functions that are used by the NPObject proxy and stub. + +#ifndef CONTENT_PLUGIN_NPOBJECT_UTIL_H_ +#define CONTENT_PLUGIN_NPOBJECT_UTIL_H_ +#pragma once + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include "content/plugin/npobject_stub.h" + +class GURL; +class NPObjectProxy; +class PluginChannelBase; + +struct _NPVariant; +struct NPIdentifier_Param; +struct NPVariant_Param; + +typedef _NPVariant NPVariant; +typedef void *NPIdentifier; + +// Needs to be called early in the plugin process lifetime, before any +// plugin instances are initialized. +void PatchNPNFunctions(); + +// Returns true if the current process is a plugin process, or false if it's a +// renderer process. +bool IsPluginProcess(); + +// Creates an object similar to NPIdentifier that can be marshalled. +void CreateNPIdentifierParam(NPIdentifier id, NPIdentifier_Param* param); + +// Creates an NPIdentifier from the marshalled object. +NPIdentifier CreateNPIdentifier(const NPIdentifier_Param& param); + +// Creates an object similar to NPVariant that can be marshalled. +// If the containing NPObject happens to be an NPObject, then a stub +// is created around it and param holds the routing id for it. +// If release is true, the NPVariant object is released (except if +// it contains an NPObject, since the stub will manage its lifetime). +void CreateNPVariantParam(const NPVariant& variant, + PluginChannelBase* channel, + NPVariant_Param* param, + bool release, + gfx::NativeViewId containing_window, + const GURL& page_url); + +// Creates an NPVariant from the marshalled object. +// Returns true on success. +bool CreateNPVariant(const NPVariant_Param& param, + PluginChannelBase* channel, + NPVariant* result, + gfx::NativeViewId containing_window, + const GURL& page_url); + +#if defined(OS_WIN) +// Given a plugin's HWND, returns an event associated with the TabContents +// that's set when inside a messagebox. This tells the plugin process that +// the message queue should be pumped (as what would happen if everything was +// in-process). This avoids deadlocks when a plugin invokes javascript that +// causes a message box to come up. +HANDLE GetMessageBoxEvent(HWND hwnd); +#endif // defined(OS_WIN) + +#endif // CONTENT_PLUGIN_NPOBJECT_UTIL_H_ diff --git a/content/plugin/plugin_channel.cc b/content/plugin/plugin_channel.cc new file mode 100644 index 0000000..8d90601 --- /dev/null +++ b/content/plugin/plugin_channel.cc @@ -0,0 +1,328 @@ +// Copyright (c) 2010 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 "content/plugin/plugin_channel.h" + +#include "base/command_line.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "build/build_config.h" +#include "content/common/child_process.h" +#include "content/common/content_switches.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/plugin_thread.h" +#include "content/plugin/webplugin_delegate_stub.h" +#include "content/plugin/webplugin_proxy.h" +#include "webkit/plugins/npapi/plugin_instance.h" + +#if defined(OS_POSIX) +#include "base/eintr_wrapper.h" +#include "ipc/ipc_channel_posix.h" +#endif + +namespace { + +class PluginReleaseTask : public Task { + public: + void Run() { + ChildProcess::current()->ReleaseProcess(); + } +}; + +// How long we wait before releasing the plugin process. +const int kPluginReleaseTimeMs = 5 * 60 * 1000; // 5 minutes + +} // namespace + +// If a sync call to the renderer results in a modal dialog, we need to have a +// way to know so that we can run a nested message loop to simulate what would +// happen in a single process browser and avoid deadlock. +class PluginChannel::MessageFilter : public IPC::ChannelProxy::MessageFilter { + public: + MessageFilter() : channel_(NULL) { } + ~MessageFilter() { + // Clean up in case of renderer crash. + for (ModalDialogEventMap::iterator i = modal_dialog_event_map_.begin(); + i != modal_dialog_event_map_.end(); ++i) { + delete i->second.event; + } + } + + base::WaitableEvent* GetModalDialogEvent( + gfx::NativeViewId containing_window) { + base::AutoLock auto_lock(modal_dialog_event_map_lock_); + if (!modal_dialog_event_map_.count(containing_window)) { + NOTREACHED(); + return NULL; + } + + return modal_dialog_event_map_[containing_window].event; + } + + // Decrement the ref count associated with the modal dialog event for the + // given tab. + void ReleaseModalDialogEvent(gfx::NativeViewId containing_window) { + base::AutoLock auto_lock(modal_dialog_event_map_lock_); + if (!modal_dialog_event_map_.count(containing_window)) { + NOTREACHED(); + return; + } + + if (--(modal_dialog_event_map_[containing_window].refcount)) + return; + + // Delete the event when the stack unwinds as it could be in use now. + MessageLoop::current()->DeleteSoon( + FROM_HERE, modal_dialog_event_map_[containing_window].event); + modal_dialog_event_map_.erase(containing_window); + } + + bool Send(IPC::Message* message) { + // Need this function for the IPC_MESSAGE_HANDLER_DELAY_REPLY macro. + return channel_->Send(message); + } + + private: + void OnFilterAdded(IPC::Channel* channel) { channel_ = channel; } + + bool OnMessageReceived(const IPC::Message& message) { + IPC_BEGIN_MESSAGE_MAP(PluginChannel::MessageFilter, message) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_Init, OnInit) + IPC_MESSAGE_HANDLER(PluginMsg_SignalModalDialogEvent, + OnSignalModalDialogEvent) + IPC_MESSAGE_HANDLER(PluginMsg_ResetModalDialogEvent, + OnResetModalDialogEvent) + IPC_END_MESSAGE_MAP() + return message.type() == PluginMsg_SignalModalDialogEvent::ID || + message.type() == PluginMsg_ResetModalDialogEvent::ID; + } + + void OnInit(const PluginMsg_Init_Params& params, IPC::Message* reply_msg) { + base::AutoLock auto_lock(modal_dialog_event_map_lock_); + if (modal_dialog_event_map_.count(params.containing_window)) { + modal_dialog_event_map_[params.containing_window].refcount++; + return; + } + + WaitableEventWrapper wrapper; + wrapper.event = new base::WaitableEvent(true, false); + wrapper.refcount = 1; + modal_dialog_event_map_[params.containing_window] = wrapper; + } + + void OnSignalModalDialogEvent(gfx::NativeViewId containing_window) { + base::AutoLock auto_lock(modal_dialog_event_map_lock_); + if (modal_dialog_event_map_.count(containing_window)) + modal_dialog_event_map_[containing_window].event->Signal(); + } + + void OnResetModalDialogEvent(gfx::NativeViewId containing_window) { + base::AutoLock auto_lock(modal_dialog_event_map_lock_); + if (modal_dialog_event_map_.count(containing_window)) + modal_dialog_event_map_[containing_window].event->Reset(); + } + + struct WaitableEventWrapper { + base::WaitableEvent* event; + int refcount; // There could be multiple plugin instances per tab. + }; + typedef std::map<gfx::NativeViewId, WaitableEventWrapper> ModalDialogEventMap; + ModalDialogEventMap modal_dialog_event_map_; + base::Lock modal_dialog_event_map_lock_; + + IPC::Channel* channel_; +}; + +PluginChannel* PluginChannel::GetPluginChannel(int renderer_id, + MessageLoop* ipc_message_loop) { + // Map renderer ID to a (single) channel to that process. + std::string channel_key = StringPrintf( + "%d.r%d", base::GetCurrentProcId(), renderer_id); + + PluginChannel* channel = + static_cast<PluginChannel*>(PluginChannelBase::GetChannel( + channel_key, + IPC::Channel::MODE_SERVER, + ClassFactory, + ipc_message_loop, + false)); + + if (channel) + channel->renderer_id_ = renderer_id; + + return channel; +} + +// static +void PluginChannel::NotifyRenderersOfPendingShutdown() { + Broadcast(new PluginHostMsg_PluginShuttingDown()); +} + +PluginChannel::PluginChannel() + : renderer_handle_(0), + renderer_id_(-1), + in_send_(0), + incognito_(false), + filter_(new MessageFilter()) { + set_send_unblocking_only_during_unblock_dispatch(); + ChildProcess::current()->AddRefProcess(); + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + log_messages_ = command_line->HasSwitch(switches::kLogPluginMessages); +} + +PluginChannel::~PluginChannel() { + if (renderer_handle_) + base::CloseProcessHandle(renderer_handle_); + + MessageLoop::current()->PostDelayedTask(FROM_HERE, new PluginReleaseTask(), + kPluginReleaseTimeMs); +} + +bool PluginChannel::Send(IPC::Message* msg) { + in_send_++; + if (log_messages_) { + VLOG(1) << "sending message @" << msg << " on channel @" << this + << " with type " << msg->type(); + } + bool result = PluginChannelBase::Send(msg); + in_send_--; + return result; +} + +bool PluginChannel::OnMessageReceived(const IPC::Message& msg) { + if (log_messages_) { + VLOG(1) << "received message @" << &msg << " on channel @" << this + << " with type " << msg.type(); + } + return PluginChannelBase::OnMessageReceived(msg); +} + +bool PluginChannel::OnControlMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PluginChannel, msg) + IPC_MESSAGE_HANDLER(PluginMsg_CreateInstance, OnCreateInstance) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginMsg_DestroyInstance, + OnDestroyInstance) + IPC_MESSAGE_HANDLER(PluginMsg_GenerateRouteID, OnGenerateRouteID) + IPC_MESSAGE_HANDLER(PluginMsg_ClearSiteData, OnClearSiteData) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +void PluginChannel::OnCreateInstance(const std::string& mime_type, + int* instance_id) { + *instance_id = GenerateRouteID(); + scoped_refptr<WebPluginDelegateStub> stub(new WebPluginDelegateStub( + mime_type, *instance_id, this)); + AddRoute(*instance_id, stub, NULL); + plugin_stubs_.push_back(stub); +} + +void PluginChannel::OnDestroyInstance(int instance_id, + IPC::Message* reply_msg) { + for (size_t i = 0; i < plugin_stubs_.size(); ++i) { + if (plugin_stubs_[i]->instance_id() == instance_id) { + scoped_refptr<MessageFilter> filter(filter_); + gfx::NativeViewId window = + plugin_stubs_[i]->webplugin()->containing_window(); + plugin_stubs_.erase(plugin_stubs_.begin() + i); + Send(reply_msg); + RemoveRoute(instance_id); + // NOTE: *this* might be deleted as a result of calling RemoveRoute. + // Don't release the modal dialog event right away, but do it after the + // stack unwinds since the plugin can be destroyed later if it's in use + // right now. + MessageLoop::current()->PostNonNestableTask(FROM_HERE, NewRunnableMethod( + filter.get(), &MessageFilter::ReleaseModalDialogEvent, window)); + return; + } + } + + NOTREACHED() << "Couldn't find WebPluginDelegateStub to destroy"; +} + +void PluginChannel::OnGenerateRouteID(int* route_id) { + *route_id = GenerateRouteID(); +} + +int PluginChannel::GenerateRouteID() { + static int last_id = 0; + return ++last_id; +} + +void PluginChannel::OnClearSiteData(const std::string& site, + uint64 flags, + base::Time begin_time) { + bool success = false; + CommandLine* command_line = CommandLine::ForCurrentProcess(); + FilePath path = command_line->GetSwitchValuePath(switches::kPluginPath); + scoped_refptr<webkit::npapi::PluginLib> plugin_lib( + webkit::npapi::PluginLib::CreatePluginLib(path)); + if (plugin_lib.get()) { + NPError err = plugin_lib->NP_Initialize(); + if (err == NPERR_NO_ERROR) { + const char* site_str = site.empty() ? NULL : site.c_str(); + uint64 max_age; + if (begin_time > base::Time()) { + base::TimeDelta delta = base::Time::Now() - begin_time; + max_age = delta.InSeconds(); + } else { + max_age = kuint64max; + } + err = plugin_lib->NP_ClearSiteData(site_str, flags, max_age); + success = (err == NPERR_NO_ERROR); + } + } + Send(new PluginHostMsg_ClearSiteDataResult(success)); +} + +base::WaitableEvent* PluginChannel::GetModalDialogEvent( + gfx::NativeViewId containing_window) { + return filter_->GetModalDialogEvent(containing_window); +} + +void PluginChannel::OnChannelConnected(int32 peer_pid) { + base::ProcessHandle handle; + if (!base::OpenProcessHandle(peer_pid, &handle)) { + NOTREACHED(); + } + renderer_handle_ = handle; + PluginChannelBase::OnChannelConnected(peer_pid); +} + +void PluginChannel::OnChannelError() { + base::CloseProcessHandle(renderer_handle_); + renderer_handle_ = 0; + PluginChannelBase::OnChannelError(); + CleanUp(); +} + +void PluginChannel::CleanUp() { + // We need to clean up the stubs so that they call NPPDestroy. This will + // also lead to them releasing their reference on this object so that it can + // be deleted. + for (size_t i = 0; i < plugin_stubs_.size(); ++i) + RemoveRoute(plugin_stubs_[i]->instance_id()); + + // Need to addref this object temporarily because otherwise removing the last + // stub will cause the destructor of this object to be called, however at + // that point plugin_stubs_ will have one element and its destructor will be + // called twice. + scoped_refptr<PluginChannel> me(this); + + plugin_stubs_.clear(); +} + +bool PluginChannel::Init(MessageLoop* ipc_message_loop, bool create_pipe_now) { + if (!PluginChannelBase::Init(ipc_message_loop, create_pipe_now)) + return false; + + channel_->AddFilter(filter_.get()); + return true; +} + diff --git a/content/plugin/plugin_channel.h b/content/plugin/plugin_channel.h new file mode 100644 index 0000000..6b1d278 --- /dev/null +++ b/content/plugin/plugin_channel.h @@ -0,0 +1,99 @@ +// Copyright (c) 2010 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 CONTENT_PLUGIN_PLUGIN_CHANNEL_H_ +#define CONTENT_PLUGIN_PLUGIN_CHANNEL_H_ +#pragma once + +#include <vector> +#include "base/ref_counted.h" +#include "base/scoped_handle.h" +#include "build/build_config.h" +#include "content/plugin/plugin_channel_base.h" +#include "content/plugin/webplugin_delegate_stub.h" + +namespace base { +class WaitableEvent; +} + +// Encapsulates an IPC channel between the plugin process and one renderer +// process. On the renderer side there's a corresponding PluginChannelHost. +class PluginChannel : public PluginChannelBase { + public: + // Get a new PluginChannel object for the current process to talk to the + // given renderer process. The renderer ID is an opaque unique ID generated + // by the browser. + static PluginChannel* GetPluginChannel(int renderer_id, + MessageLoop* ipc_message_loop); + + // Send a message to all renderers that the process is going to shutdown. + static void NotifyRenderersOfPendingShutdown(); + + virtual ~PluginChannel(); + + virtual bool Send(IPC::Message* msg); + virtual bool OnMessageReceived(const IPC::Message& message); + + base::ProcessHandle renderer_handle() const { return renderer_handle_; } + int renderer_id() { return renderer_id_; } + + virtual int GenerateRouteID(); + + // Returns the event that's set when a call to the renderer causes a modal + // dialog to come up. + base::WaitableEvent* GetModalDialogEvent(gfx::NativeViewId containing_window); + + bool in_send() { return in_send_ != 0; } + + bool incognito() { return incognito_; } + void set_incognito(bool value) { incognito_ = value; } + +#if defined(OS_POSIX) + int renderer_fd() const { return channel_->GetClientFileDescriptor(); } +#endif + + protected: + // IPC::Channel::Listener implementation: + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelError(); + + virtual void CleanUp(); + + // Overrides PluginChannelBase::Init. + virtual bool Init(MessageLoop* ipc_message_loop, bool create_pipe_now); + + private: + class MessageFilter; + + // Called on the plugin thread + PluginChannel(); + + virtual bool OnControlMessageReceived(const IPC::Message& msg); + + static PluginChannelBase* ClassFactory() { return new PluginChannel(); } + + void OnCreateInstance(const std::string& mime_type, int* instance_id); + void OnDestroyInstance(int instance_id, IPC::Message* reply_msg); + void OnGenerateRouteID(int* route_id); + void OnClearSiteData(const std::string& site, + uint64 flags, + base::Time begin_time); + + std::vector<scoped_refptr<WebPluginDelegateStub> > plugin_stubs_; + + // Handle to the renderer process who is on the other side of the channel. + base::ProcessHandle renderer_handle_; + + // The id of the renderer who is on the other side of the channel. + int renderer_id_; + + int in_send_; // Tracks if we're in a Send call. + bool log_messages_; // True if we should log sent and received messages. + bool incognito_; // True if the renderer is in incognito mode. + scoped_refptr<MessageFilter> filter_; // Handles the modal dialog events. + + DISALLOW_COPY_AND_ASSIGN(PluginChannel); +}; + +#endif // CONTENT_PLUGIN_PLUGIN_CHANNEL_H_ diff --git a/content/plugin/plugin_channel_base.cc b/content/plugin/plugin_channel_base.cc new file mode 100644 index 0000000..d78d48c --- /dev/null +++ b/content/plugin/plugin_channel_base.cc @@ -0,0 +1,241 @@ +// Copyright (c) 2009 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 "content/plugin/plugin_channel_base.h" + +#include <stack> + +#include "base/auto_reset.h" +#include "base/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/string_number_conversions.h" +#include "content/common/child_process.h" +#include "ipc/ipc_sync_message.h" + +#if defined(OS_POSIX) +#include "ipc/ipc_channel_posix.h" +#endif + +typedef base::hash_map<std::string, scoped_refptr<PluginChannelBase> > + PluginChannelMap; + +static PluginChannelMap g_plugin_channels_; + +static base::LazyInstance<std::stack<scoped_refptr<PluginChannelBase> > > + lazy_plugin_channel_stack_(base::LINKER_INITIALIZED); + +static int next_pipe_id = 0; + +PluginChannelBase* PluginChannelBase::GetChannel( + const IPC::ChannelHandle& channel_handle, IPC::Channel::Mode mode, + PluginChannelFactory factory, MessageLoop* ipc_message_loop, + bool create_pipe_now) { + scoped_refptr<PluginChannelBase> channel; + std::string channel_key = channel_handle.name; + PluginChannelMap::const_iterator iter = g_plugin_channels_.find(channel_key); + if (iter == g_plugin_channels_.end()) { + channel = factory(); + } else { + channel = iter->second; + } + + DCHECK(channel != NULL); + + if (!channel->channel_valid()) { + channel->channel_handle_ = channel_handle; + if (mode & IPC::Channel::MODE_SERVER_FLAG) { + channel->channel_handle_.name.append("."); + channel->channel_handle_.name.append(base::IntToString(next_pipe_id++)); + } + channel->mode_ = mode; + if (channel->Init(ipc_message_loop, create_pipe_now)) { + g_plugin_channels_[channel_key] = channel; + } else { + channel = NULL; + } + } + + return channel; +} + +void PluginChannelBase::Broadcast(IPC::Message* message) { + for (PluginChannelMap::iterator iter = g_plugin_channels_.begin(); + iter != g_plugin_channels_.end(); + ++iter) { + iter->second->Send(new IPC::Message(*message)); + } + delete message; +} + +PluginChannelBase::PluginChannelBase() + : mode_(IPC::Channel::MODE_NONE), + plugin_count_(0), + peer_pid_(0), + in_remove_route_(false), + channel_valid_(false), + in_unblock_dispatch_(0), + send_unblocking_only_during_unblock_dispatch_(false) { +} + +PluginChannelBase::~PluginChannelBase() { +} + +PluginChannelBase* PluginChannelBase::GetCurrentChannel() { + return lazy_plugin_channel_stack_.Pointer()->top(); +} + +void PluginChannelBase::CleanupChannels() { + // Make a copy of the references as we can't iterate the map since items will + // be removed from it as we clean them up. + std::vector<scoped_refptr<PluginChannelBase> > channels; + for (PluginChannelMap::const_iterator iter = g_plugin_channels_.begin(); + iter != g_plugin_channels_.end(); + ++iter) { + channels.push_back(iter->second); + } + + for (size_t i = 0; i < channels.size(); ++i) + channels[i]->CleanUp(); + + // This will clean up channels added to the map for which subsequent + // AddRoute wasn't called + g_plugin_channels_.clear(); +} + +NPObjectBase* PluginChannelBase::GetNPObjectListenerForRoute(int route_id) { + ListenerMap::iterator iter = npobject_listeners_.find(route_id); + if (iter == npobject_listeners_.end()) { + DLOG(WARNING) << "Invalid route id passed in:" << route_id; + return NULL; + } + return iter->second; +} + +bool PluginChannelBase::Init(MessageLoop* ipc_message_loop, + bool create_pipe_now) { + channel_.reset(new IPC::SyncChannel( + channel_handle_, mode_, this, ipc_message_loop, create_pipe_now, + ChildProcess::current()->GetShutDownEvent())); + channel_valid_ = true; + return true; +} + +bool PluginChannelBase::Send(IPC::Message* message) { + if (!channel_.get()) { + delete message; + return false; + } + + if (send_unblocking_only_during_unblock_dispatch_ && + in_unblock_dispatch_ == 0 && + message->is_sync()) { + message->set_unblock(false); + } + + return channel_->Send(message); +} + +int PluginChannelBase::Count() { + return static_cast<int>(g_plugin_channels_.size()); +} + +bool PluginChannelBase::OnMessageReceived(const IPC::Message& message) { + // This call might cause us to be deleted, so keep an extra reference to + // ourself so that we can send the reply and decrement back in_dispatch_. + lazy_plugin_channel_stack_.Pointer()->push( + scoped_refptr<PluginChannelBase>(this)); + + bool handled; + if (message.should_unblock()) + in_unblock_dispatch_++; + if (message.routing_id() == MSG_ROUTING_CONTROL) { + handled = OnControlMessageReceived(message); + } else { + handled = router_.RouteMessage(message); + if (!handled && message.is_sync()) { + // The listener has gone away, so we must respond or else the caller will + // hang waiting for a reply. + IPC::Message* reply = IPC::SyncMessage::GenerateReply(&message); + reply->set_reply_error(); + Send(reply); + } + } + if (message.should_unblock()) + in_unblock_dispatch_--; + + lazy_plugin_channel_stack_.Pointer()->pop(); + return handled; +} + +void PluginChannelBase::OnChannelConnected(int32 peer_pid) { + peer_pid_ = peer_pid; +} + +void PluginChannelBase::AddRoute(int route_id, + IPC::Channel::Listener* listener, + NPObjectBase* npobject) { + if (npobject) { + npobject_listeners_[route_id] = npobject; + } else { + plugin_count_++; + } + + router_.AddRoute(route_id, listener); +} + +void PluginChannelBase::RemoveRoute(int route_id) { + router_.RemoveRoute(route_id); + + ListenerMap::iterator iter = npobject_listeners_.find(route_id); + if (iter != npobject_listeners_.end()) { + // This was an NPObject proxy or stub, it's not involved in the refcounting. + + // If this RemoveRoute call from the NPObject is a result of us calling + // OnChannelError below, don't call erase() here because that'll corrupt + // the iterator below. + if (in_remove_route_) { + iter->second = NULL; + } else { + npobject_listeners_.erase(iter); + } + + return; + } + + plugin_count_--; + DCHECK(plugin_count_ >= 0); + + if (!plugin_count_) { + AutoReset<bool> auto_reset_in_remove_route(&in_remove_route_, true); + for (ListenerMap::iterator npobj_iter = npobject_listeners_.begin(); + npobj_iter != npobject_listeners_.end(); ++npobj_iter) { + if (npobj_iter->second) { + IPC::Channel::Listener* channel_listener = + npobj_iter->second->GetChannelListener(); + DCHECK(channel_listener != NULL); + channel_listener->OnChannelError(); + } + } + + for (PluginChannelMap::iterator iter = g_plugin_channels_.begin(); + iter != g_plugin_channels_.end(); ++iter) { + if (iter->second == this) { + g_plugin_channels_.erase(iter); + return; + } + } + + NOTREACHED(); + } +} + +bool PluginChannelBase::OnControlMessageReceived(const IPC::Message& msg) { + NOTREACHED() << + "should override in subclass if you care about control messages"; + return false; +} + +void PluginChannelBase::OnChannelError() { + channel_valid_ = false; +} diff --git a/content/plugin/plugin_channel_base.h b/content/plugin/plugin_channel_base.h new file mode 100644 index 0000000..8d3cbc5 --- /dev/null +++ b/content/plugin/plugin_channel_base.h @@ -0,0 +1,149 @@ +// Copyright (c) 2006-2008 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 CONTENT_PLUGIN_PLUGIN_CHANNEL_BASE_H_ +#define CONTENT_PLUGIN_PLUGIN_CHANNEL_BASE_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "content/common/message_router.h" +#include "content/plugin/npobject_base.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_sync_channel.h" +#include "ui/gfx/native_widget_types.h" + +// Encapsulates an IPC channel between a renderer and a plugin process. +class PluginChannelBase : public IPC::Channel::Listener, + public IPC::Message::Sender, + public base::RefCountedThreadSafe<PluginChannelBase> { + public: + + // WebPlugin[Delegate] call these on construction and destruction to setup + // the routing and manage lifetime of this object. This is also called by + // NPObjectProxy and NPObjectStub. However the latter don't control the + // lifetime of this object (by passing true for npobject) because we don't + // want a leak of an NPObject in a plugin to keep the channel around longer + // than necessary. + void AddRoute(int route_id, IPC::Channel::Listener* listener, + NPObjectBase* npobject); + void RemoveRoute(int route_id); + + // IPC::Message::Sender implementation: + virtual bool Send(IPC::Message* msg); + + int peer_pid() { return peer_pid_; } + IPC::ChannelHandle channel_handle() const { return channel_handle_; } + + // Returns the number of open plugin channels in this process. + static int Count(); + + // Returns a new route id. + virtual int GenerateRouteID() = 0; + + // Returns whether the channel is valid or not. A channel is invalid + // if it is disconnected due to a channel error. + bool channel_valid() { + return channel_valid_; + } + + // Returns the most recent PluginChannelBase to have received a message + // in this process. + static PluginChannelBase* GetCurrentChannel(); + + static void CleanupChannels(); + + // Returns the NPObjectBase object for the route id passed in. + // Returns NULL on failure. + NPObjectBase* GetNPObjectListenerForRoute(int route_id); + + protected: + typedef PluginChannelBase* (*PluginChannelFactory)(); + + friend class base::RefCountedThreadSafe<PluginChannelBase>; + + virtual ~PluginChannelBase(); + + // Returns a PluginChannelBase derived object for the given channel name. + // If an existing channel exists returns that object, otherwise creates a + // new one. Even though on creation the object is refcounted, each caller + // must still ref count the returned value. When there are no more routes + // on the channel and its ref count is 0, the object deletes itself. + static PluginChannelBase* GetChannel( + const IPC::ChannelHandle& channel_handle, IPC::Channel::Mode mode, + PluginChannelFactory factory, MessageLoop* ipc_message_loop, + bool create_pipe_now); + + // Sends a message to all instances. + static void Broadcast(IPC::Message* message); + + // Called on the worker thread + PluginChannelBase(); + + virtual void CleanUp() { } + + // Implemented by derived classes to handle control messages + virtual bool OnControlMessageReceived(const IPC::Message& msg); + + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelError(); + + void set_send_unblocking_only_during_unblock_dispatch() { + send_unblocking_only_during_unblock_dispatch_ = true; + } + + virtual bool Init(MessageLoop* ipc_message_loop, bool create_pipe_now); + + scoped_ptr<IPC::SyncChannel> channel_; + + private: + IPC::Channel::Mode mode_; + IPC::ChannelHandle channel_handle_; + int plugin_count_; + int peer_pid_; + + // true when in the middle of a RemoveRoute call + bool in_remove_route_; + + // Keep track of all the registered NPObjects proxies/stubs so that when the + // channel is closed we can inform them. + typedef base::hash_map<int, NPObjectBase*> ListenerMap; + ListenerMap npobject_listeners_; + + // Used to implement message routing functionality to WebPlugin[Delegate] + // objects + MessageRouter router_; + + // A channel is invalid if it is disconnected as a result of a channel + // error. This flag is used to indicate the same. + bool channel_valid_; + + // Track whether we're dispatching a message with the unblock flag; works like + // a refcount, 0 when we're not. + int in_unblock_dispatch_; + + // If true, sync messages will only be marked as unblocking if the channel is + // in the middle of dispatching an unblocking message. + // The plugin process wants to avoid setting the unblock flag on its sync + // messages unless necessary, since it can potentially introduce reentrancy + // into WebKit in ways that it doesn't expect (i.e. causing layout during + // paint). However to avoid deadlock, we must ensure that any message that's + // sent as a result of a sync call from the renderer must unblock the + // renderer. We additionally have to do this for async messages from the + // renderer that have the unblock flag set, since they could be followed by a + // sync message that won't get dispatched until the call to the renderer is + // complete. + bool send_unblocking_only_during_unblock_dispatch_; + + DISALLOW_COPY_AND_ASSIGN(PluginChannelBase); +}; + +#endif // CONTENT_PLUGIN_PLUGIN_CHANNEL_BASE_H_ diff --git a/content/plugin/plugin_interpose_util_mac.h b/content/plugin/plugin_interpose_util_mac.h new file mode 100644 index 0000000..2a715ba --- /dev/null +++ b/content/plugin/plugin_interpose_util_mac.h @@ -0,0 +1,57 @@ +// Copyright (c) 2009 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 CONTENT_PLUGIN_PLUGIN_INTERPOSE_UTIL_MAC_H_ +#define CONTENT_PLUGIN_PLUGIN_INTERPOSE_UTIL_MAC_H_ +#pragma once + +#include <Carbon/Carbon.h> + +#import "base/basictypes.h" + +// This is really a WebPluginDelegateImpl, but that class is private to the +// framework, and these functions are called from a dylib. +typedef void* OpaquePluginRef; + +namespace mac_plugin_interposing { + +// Swizzles methods we need to watch in order to manage process and window +// focus correctly. +void SetUpCocoaInterposing(); + +// Brings the plugin process to the front so that the user can see its windows. +void SwitchToPluginProcess(); + +// Returns the delegate currently processing events. +OpaquePluginRef GetActiveDelegate(); + +// Sends a message to the browser process to inform it that the given window +// has been brought forward. +void NotifyBrowserOfPluginSelectWindow(uint32 window_id, CGRect bounds, + bool modal); + +// Sends a message to the browser process to inform it that the given window +// has been shown. +void NotifyBrowserOfPluginShowWindow(uint32 window_id, CGRect bounds, + bool modal); + +// Sends a message to the browser process to inform it that the given window +// has been hidden, and switches focus back to the browser process if there are +// no remaining plugin windows. +void NotifyBrowserOfPluginHideWindow(uint32 window_id, CGRect bounds); + +// Sends a message to the plugin that a theme cursor was set. +void NotifyPluginOfSetThemeCursor(OpaquePluginRef delegate, + ThemeCursor cursor); + +// Sends a message to the plugin that a cursor was set. +void NotifyPluginOfSetCursor(OpaquePluginRef delegate, + const Cursor* cursor); + +// Returns true if the window containing the given plugin delegate is focused. +bool GetPluginWindowHasFocus(const OpaquePluginRef delegate); + +} // namespace MacPluginInterpose + +#endif // CONTENT_PLUGIN_PLUGIN_INTERPOSE_UTIL_MAC_H_ diff --git a/content/plugin/plugin_interpose_util_mac.mm b/content/plugin/plugin_interpose_util_mac.mm new file mode 100644 index 0000000..e1cd5bd --- /dev/null +++ b/content/plugin/plugin_interpose_util_mac.mm @@ -0,0 +1,306 @@ +// Copyright (c) 2011 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 "content/plugin/plugin_interpose_util_mac.h" + +#import <AppKit/AppKit.h> +#import <objc/runtime.h> + +#include "content/common/plugin_messages.h" +#include "content/plugin/plugin_thread.h" +#include "webkit/plugins/npapi/webplugin_delegate_impl.h" + +namespace mac_plugin_interposing { + +// TODO(stuartmorgan): Make this an IPC to order the plugin process above the +// browser process only if the browser is current frontmost. +__attribute__((visibility("default"))) +void SwitchToPluginProcess() { + ProcessSerialNumber this_process, front_process; + if ((GetCurrentProcess(&this_process) != noErr) || + (GetFrontProcess(&front_process) != noErr)) { + return; + } + + Boolean matched = false; + if ((SameProcess(&this_process, &front_process, &matched) == noErr) && + !matched) { + SetFrontProcess(&this_process); + } +} + +__attribute__((visibility("default"))) +OpaquePluginRef GetActiveDelegate() { + return webkit::npapi::WebPluginDelegateImpl::GetActiveDelegate(); +} + +__attribute__((visibility("default"))) +void NotifyBrowserOfPluginSelectWindow(uint32 window_id, CGRect bounds, + bool modal) { + PluginThread* plugin_thread = PluginThread::current(); + if (plugin_thread) { + gfx::Rect window_bounds(bounds); + plugin_thread->Send( + new PluginProcessHostMsg_PluginSelectWindow(window_id, window_bounds, + modal)); + } +} + +__attribute__((visibility("default"))) +void NotifyBrowserOfPluginShowWindow(uint32 window_id, CGRect bounds, + bool modal) { + PluginThread* plugin_thread = PluginThread::current(); + if (plugin_thread) { + gfx::Rect window_bounds(bounds); + plugin_thread->Send( + new PluginProcessHostMsg_PluginShowWindow(window_id, window_bounds, + modal)); + } +} + +__attribute__((visibility("default"))) +void NotifyBrowserOfPluginHideWindow(uint32 window_id, CGRect bounds) { + PluginThread* plugin_thread = PluginThread::current(); + if (plugin_thread) { + gfx::Rect window_bounds(bounds); + plugin_thread->Send( + new PluginProcessHostMsg_PluginHideWindow(window_id, window_bounds)); + } +} + +__attribute__((visibility("default"))) +void NotifyPluginOfSetThemeCursor(OpaquePluginRef delegate, + ThemeCursor cursor) { + static_cast<webkit::npapi::WebPluginDelegateImpl*>(delegate)->SetThemeCursor( + cursor); +} + +__attribute__((visibility("default"))) +void NotifyPluginOfSetCursor(OpaquePluginRef delegate, + const Cursor* cursor) { + static_cast<webkit::npapi::WebPluginDelegateImpl*>(delegate)->SetCarbonCursor( + cursor); +} + +void NotifyPluginOfSetCursorVisibility(bool visibility) { + PluginThread* plugin_thread = PluginThread::current(); + if (plugin_thread) { + plugin_thread->Send( + new PluginProcessHostMsg_PluginSetCursorVisibility(visibility)); + } +} + +__attribute__((visibility("default"))) +bool GetPluginWindowHasFocus(const OpaquePluginRef delegate) { + return static_cast<webkit::npapi::WebPluginDelegateImpl*>( + delegate)->GetWindowHasFocus(); +} + +} // namespace mac_plugin_interposing + +#pragma mark - + +struct WindowInfo { + uint32 window_id; + CGRect bounds; + WindowInfo(NSWindow* window) { + NSInteger window_num = [window windowNumber]; + window_id = window_num > 0 ? window_num : 0; + bounds = NSRectToCGRect([window frame]); + } +}; + +static void OnPluginWindowClosed(const WindowInfo& window_info) { + if (window_info.window_id == 0) + return; + mac_plugin_interposing::NotifyBrowserOfPluginHideWindow(window_info.window_id, + window_info.bounds); +} + +static BOOL g_waiting_for_window_number = NO; + +static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { + // The window id is 0 if it has never been shown (including while it is the + // process of being shown for the first time); when that happens, we'll catch + // it in _setWindowNumber instead. + static BOOL s_pending_display_is_modal = NO; + if (window_info.window_id == 0) { + g_waiting_for_window_number = YES; + if (is_modal) + s_pending_display_is_modal = YES; + return; + } + g_waiting_for_window_number = NO; + if (s_pending_display_is_modal) { + is_modal = YES; + s_pending_display_is_modal = NO; + } + mac_plugin_interposing::NotifyBrowserOfPluginShowWindow( + window_info.window_id, window_info.bounds, is_modal); +} + +@interface NSWindow (ChromePluginUtilities) +// Returns YES if the window is visible and actually on the screen. +- (BOOL)chromePlugin_isWindowOnScreen; +// Returns YES if the window is the dummy window we use for popup menus; +// see PluginInstance::PopUpContextMenu. +// This can be removed once 10.5 is no longer supported. +- (BOOL)chromePlugin_isPopupMenuWindow; +@end + +@implementation NSWindow (ChromePluginUtilities) + +- (BOOL)chromePlugin_isWindowOnScreen { + if (![self isVisible]) + return NO; + NSRect window_frame = [self frame]; + for (NSScreen* screen in [NSScreen screens]) { + if (NSIntersectsRect(window_frame, [screen frame])) + return YES; + } + return NO; +} + +- (BOOL)chromePlugin_isPopupMenuWindow { + return [[self title] isEqualToString:@"PopupMenuDummy"]; +} + +@end + +@interface NSWindow (ChromePluginInterposing) +- (void)chromePlugin_orderOut:(id)sender; +- (void)chromePlugin_orderFront:(id)sender; +- (void)chromePlugin_makeKeyAndOrderFront:(id)sender; +- (void)chromePlugin_setWindowNumber:(NSInteger)num; +@end + +@implementation NSWindow (ChromePluginInterposing) + +- (void)chromePlugin_orderOut:(id)sender { + WindowInfo window_info(self); + [self chromePlugin_orderOut:sender]; + OnPluginWindowClosed(window_info); +} + +- (void)chromePlugin_orderFront:(id)sender { + [self chromePlugin_orderFront:sender]; + if ([self chromePlugin_isWindowOnScreen] && + ![self chromePlugin_isPopupMenuWindow]) + mac_plugin_interposing::SwitchToPluginProcess(); + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)chromePlugin_makeKeyAndOrderFront:(id)sender { + [self chromePlugin_makeKeyAndOrderFront:sender]; + if ([self chromePlugin_isWindowOnScreen] && + ![self chromePlugin_isPopupMenuWindow]) + mac_plugin_interposing::SwitchToPluginProcess(); + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)chromePlugin_setWindowNumber:(NSInteger)num { + if (!g_waiting_for_window_number || num <= 0) { + [self chromePlugin_setWindowNumber:num]; + return; + } + if (![self chromePlugin_isPopupMenuWindow]) + mac_plugin_interposing::SwitchToPluginProcess(); + [self chromePlugin_setWindowNumber:num]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +@end + +@interface NSApplication (ChromePluginInterposing) +- (NSInteger)chromePlugin_runModalForWindow:(NSWindow*)window; +@end + +@implementation NSApplication (ChromePluginInterposing) + +- (NSInteger)chromePlugin_runModalForWindow:(NSWindow*)window { + mac_plugin_interposing::SwitchToPluginProcess(); + // This is out-of-order relative to the other calls, but runModalForWindow: + // won't return until the window closes, and the order only matters for + // full-screen windows. + OnPluginWindowShown(WindowInfo(window), YES); + return [self chromePlugin_runModalForWindow:window]; +} + +@end + +@interface NSCursor (ChromePluginInterposing) +- (void)chromePlugin_set; ++ (void)chromePlugin_hide; ++ (void)chromePlugin_unhide; +@end + +@implementation NSCursor (ChromePluginInterposing) + +- (void)chromePlugin_set { + OpaquePluginRef delegate = mac_plugin_interposing::GetActiveDelegate(); + if (delegate) { + static_cast<webkit::npapi::WebPluginDelegateImpl*>(delegate)->SetNSCursor( + self); + return; + } + [self chromePlugin_set]; +} + ++ (void)chromePlugin_hide { + mac_plugin_interposing::NotifyPluginOfSetCursorVisibility(false); +} + ++ (void)chromePlugin_unhide { + mac_plugin_interposing::NotifyPluginOfSetCursorVisibility(true); +} + +@end + +#pragma mark - + +static void ExchangeMethods(Class target_class, + BOOL class_method, + SEL original, + SEL replacement) { + Method m1; + Method m2; + if (class_method) { + m1 = class_getClassMethod(target_class, original); + m2 = class_getClassMethod(target_class, replacement); + } else { + m1 = class_getInstanceMethod(target_class, original); + m2 = class_getInstanceMethod(target_class, replacement); + } + if (m1 && m2) + method_exchangeImplementations(m1, m2); + else + NOTREACHED() << "Cocoa swizzling failed"; +} + +namespace mac_plugin_interposing { + +void SetUpCocoaInterposing() { + Class nswindow_class = [NSWindow class]; + ExchangeMethods(nswindow_class, NO, @selector(orderOut:), + @selector(chromePlugin_orderOut:)); + ExchangeMethods(nswindow_class, NO, @selector(orderFront:), + @selector(chromePlugin_orderFront:)); + ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:), + @selector(chromePlugin_makeKeyAndOrderFront:)); + ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:), + @selector(chromePlugin_setWindowNumber:)); + + ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:), + @selector(chromePlugin_runModalForWindow:)); + + Class nscursor_class = [NSCursor class]; + ExchangeMethods(nscursor_class, NO, @selector(set), + @selector(chromePlugin_set)); + ExchangeMethods(nscursor_class, YES, @selector(hide), + @selector(chromePlugin_hide)); + ExchangeMethods(nscursor_class, YES, @selector(unhide), + @selector(chromePlugin_unhide)); +} + +} // namespace mac_plugin_interposing diff --git a/content/plugin/plugin_main.cc b/content/plugin/plugin_main.cc new file mode 100644 index 0000000..fef3e95 --- /dev/null +++ b/content/plugin/plugin_main.cc @@ -0,0 +1,171 @@ +// Copyright (c) 2010 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 "build/build_config.h" + +#if defined(OS_WIN) +#include <objbase.h> +#include <windows.h> +#endif + +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/threading/platform_thread.h" +#include "content/common/child_process.h" +#include "content/common/content_switches.h" +#include "content/common/hi_res_timer_manager.h" +#include "content/common/main_function_params.h" +#include "content/plugin/plugin_thread.h" +#include "ui/base/system_monitor/system_monitor.h" + +#if defined(OS_WIN) +#include "content/common/injection_test_dll.h" +#include "sandbox/src/sandbox.h" +#elif defined(OS_POSIX) && !defined(OS_MACOSX) +#include "base/global_descriptors_posix.h" +#include "ipc/ipc_descriptors.h" +#endif + +#if defined(OS_MACOSX) +// Removes our Carbon library interposing from the environment so that it +// doesn't carry into any processes that plugins might start. +void TrimInterposeEnvironment(); + +// Initializes the global Cocoa application object. +void InitializeChromeApplication(); +#elif defined(OS_LINUX) +// Work around an unimplemented instruction in 64-bit Flash. +void WorkaroundFlashLAHF(); +#endif + +#if defined(OS_WIN) +// This function is provided so that the built-in flash can lock down the +// sandbox by calling DelayedLowerToken(0). +extern "C" DWORD __declspec(dllexport) __stdcall DelayedLowerToken(void* ts) { + // s_ts is only set the first time the function is called, which happens + // in PluginMain. + static sandbox::TargetServices* s_ts = + reinterpret_cast<sandbox::TargetServices*>(ts); + if (ts) + return 0; + s_ts->LowerToken(); + return 1; +}; + +// Returns true if the plugin to be loaded is the internal flash. +bool IsPluginBuiltInFlash(const CommandLine& cmd_line) { + FilePath path = cmd_line.GetSwitchValuePath(switches::kPluginPath); + return (path.BaseName() == FilePath(L"gcswf32.dll")); +} + +// Before we lock down the flash sandbox, we need to activate +// the IME machinery. After lock down it seems it is unable +// to start. Note that we leak the IME context on purpose. +int PreloadIMEForFlash() { + HIMC imc = ::ImmCreateContext(); + if (!imc) + return 0; + if (::ImmGetOpenStatus(imc)) + return 1; + return 2; +} + +#endif + +// main() routine for running as the plugin process. +int PluginMain(const MainFunctionParams& parameters) { + // The main thread of the plugin services UI. +#if defined(OS_MACOSX) +#if !defined(__LP64__) + TrimInterposeEnvironment(); +#endif + InitializeChromeApplication(); +#endif + MessageLoop main_message_loop(MessageLoop::TYPE_UI); + base::PlatformThread::SetName("CrPluginMain"); + + ui::SystemMonitor system_monitor; + HighResolutionTimerManager high_resolution_timer_manager; + + const CommandLine& parsed_command_line = parameters.command_line_; + +#if defined(OS_LINUX) + +#if defined(ARCH_CPU_64_BITS) + WorkaroundFlashLAHF(); +#endif + +#elif defined(OS_WIN) + sandbox::TargetServices* target_services = + parameters.sandbox_info_.TargetServices(); + + CoInitialize(NULL); + DVLOG(1) << "Started plugin with " + << parsed_command_line.command_line_string(); + + HMODULE sandbox_test_module = NULL; + bool no_sandbox = parsed_command_line.HasSwitch(switches::kNoSandbox); + + if (target_services && !no_sandbox) { + // The command line might specify a test plugin to load. + if (parsed_command_line.HasSwitch(switches::kTestSandbox)) { + std::wstring test_plugin_name = + parsed_command_line.GetSwitchValueNative(switches::kTestSandbox); + sandbox_test_module = LoadLibrary(test_plugin_name.c_str()); + DCHECK(sandbox_test_module); + } + } +#endif + if (parsed_command_line.HasSwitch(switches::kPluginStartupDialog)) { + ChildProcess::WaitForDebugger("Plugin"); + } + + { + ChildProcess plugin_process; + plugin_process.set_main_thread(new PluginThread()); +#if defined(OS_WIN) + if (!no_sandbox && target_services) { + // We are sandboxing the plugin. If it is a generic plug-in, we lock down + // the sandbox right away, but if it is the built-in flash we let flash + // start elevated and it will call DelayedLowerToken(0) when it's ready. + if (IsPluginBuiltInFlash(parsed_command_line)) { + DVLOG(1) << "Sandboxing flash"; + if (!PreloadIMEForFlash()) + DVLOG(1) << "IME preload failed"; + DelayedLowerToken(target_services); + } else { + target_services->LowerToken(); + } + } + if (sandbox_test_module) { + RunPluginTests run_security_tests = + reinterpret_cast<RunPluginTests>(GetProcAddress(sandbox_test_module, + kPluginTestCall)); + DCHECK(run_security_tests); + if (run_security_tests) { + int test_count = 0; + DVLOG(1) << "Running plugin security tests"; + BOOL result = run_security_tests(&test_count); + DCHECK(result) << "Test number " << test_count << " has failed."; + // If we are in release mode, crash or debug the process. + if (!result) { + __debugbreak(); + _exit(1); + } + } + + FreeLibrary(sandbox_test_module); + } +#endif + + MessageLoop::current()->Run(); + } + +#if defined(OS_WIN) + CoUninitialize(); +#endif + + return 0; +} diff --git a/content/plugin/plugin_main_linux.cc b/content/plugin/plugin_main_linux.cc new file mode 100644 index 0000000..6bb9da7 --- /dev/null +++ b/content/plugin/plugin_main_linux.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2009 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 <signal.h> +#include <string.h> +#include <sys/types.h> +#include <syscall.h> +#include <unistd.h> + +#include "build/build_config.h" + +// This whole file is only useful on 64-bit architectures. +#if defined(ARCH_CPU_64_BITS) + +namespace { + +// Signal handler for SIGILL; see WorkaroundFlashLAHF(). +void SignalHandler(int signum, siginfo_t* info, void* void_context) { + const char kLAHFInstruction = 0x9f; + ucontext_t* context = static_cast<ucontext_t*>(void_context); + greg_t* regs = context->uc_mcontext.gregs; + char instruction = *reinterpret_cast<char*>(regs[REG_RIP]); + + // Check whether this is the kind of SIGILL we care about. + // (info->si_addr can be NULL when we get a SIGILL via other means, + // like with kill.) + if (signum != SIGILL || instruction != kLAHFInstruction) { + // Not the problem we're interested in. Reraise the signal. We + // need to be careful to handle threads etc. properly. + + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction(signum, &sa, NULL); + + // block the current signal + sigset_t block_set; + sigemptyset(&block_set); + sigaddset(&block_set, signum); + sigprocmask(SIG_BLOCK, &block_set, NULL); + + // Re-raise signal. It won't be delivered until we return. + syscall(SYS_tkill, syscall(SYS_gettid), signum); + return; + } + + // LAHF moves the low byte of the EFLAGS register to AH. Emulate that. + reinterpret_cast<char*>(®s[REG_RAX])[1] = + reinterpret_cast<char*>(®s[REG_EFL])[0]; + // And advance the instruction pointer past the (one-byte) instruction. + ++regs[REG_RIP]; +} + +} // namespace + +// 64-bit Flash sometimes uses the LAHF instruction which isn't +// available on some CPUs. We can work around it by catching SIGILL +// (illegal instruction), checking if the signal was caused by this +// particular circumstance, emulating the instruction, and resuming. +// This function registers the signal handler. +void WorkaroundFlashLAHF() { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = &SignalHandler; + + sigaction(SIGILL, &action, NULL); +} + +#endif // defined(ARCH_CPU_64_BITS) diff --git a/content/plugin/plugin_main_mac.mm b/content/plugin/plugin_main_mac.mm new file mode 100644 index 0000000..15f230c --- /dev/null +++ b/content/plugin/plugin_main_mac.mm @@ -0,0 +1,50 @@ +// Copyright (c) 2010 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 "base/environment.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "content/common/chrome_application_mac.h" +#include "content/common/plugin_carbon_interpose_constants_mac.h" +#include "content/plugin/plugin_interpose_util_mac.h" + +#if !defined(__LP64__) +void TrimInterposeEnvironment() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + + std::string interpose_list; + if (!env->GetVar(plugin_interpose_strings::kDYLDInsertLibrariesKey, + &interpose_list)) { + NOTREACHED() << "No interposing libraries set"; + return; + } + + // The list is a :-separated list of paths. Because we append our interpose + // library just before forking in plugin_process_host.cc, the only cases we + // need to handle are: + // 1) The whole string is "<kInterposeLibraryPath>", so just clear it, or + // 2) ":<kInterposeLibraryPath>" is the end of the string, so trim and re-set. + std::string interpose_library_path( + plugin_interpose_strings::kInterposeLibraryPath); + DCHECK_GE(interpose_list.size(), interpose_library_path.size()); + size_t suffix_offset = interpose_list.size() - interpose_library_path.size(); + if (suffix_offset == 0 && + interpose_list == interpose_library_path) { + env->UnSetVar(plugin_interpose_strings::kDYLDInsertLibrariesKey); + } else if (suffix_offset > 0 && interpose_list[suffix_offset - 1] == ':' && + interpose_list.substr(suffix_offset) == interpose_library_path) { + std::string trimmed_list = interpose_list.substr(0, suffix_offset - 1); + env->SetVar(plugin_interpose_strings::kDYLDInsertLibrariesKey, + trimmed_list.c_str()); + } else { + NOTREACHED() << "Missing Carbon interposing library"; + } +} +#endif + +void InitializeChromeApplication() { + [CrApplication sharedApplication]; + + mac_plugin_interposing::SetUpCocoaInterposing(); +} diff --git a/content/plugin/plugin_thread.cc b/content/plugin/plugin_thread.cc new file mode 100644 index 0000000..c2ca935 --- /dev/null +++ b/content/plugin/plugin_thread.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2010 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 "content/plugin/plugin_thread.h" + +#include "build/build_config.h" + +#if defined(USE_X11) +#include <gtk/gtk.h> +#elif defined(OS_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#endif + +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/process_util.h" +#include "base/threading/thread_local.h" +#include "content/common/child_process.h" +#include "content/common/content_client.h" +#include "content/common/content_switches.h" +#include "content/common/child_process_messages.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_util.h" +#include "ipc/ipc_channel_handle.h" +#include "net/base/net_errors.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/plugins/npapi/plugin_lib.h" +#include "webkit/plugins/npapi/webplugin_delegate_impl.h" + +#if defined(TOOLKIT_USES_GTK) +#include "ui/gfx/gtk_util.h" +#endif + +#if defined(USE_X11) +#include "ui/base/x/x11_util.h" +#endif + +static base::LazyInstance<base::ThreadLocalPointer<PluginThread> > lazy_tls( + base::LINKER_INITIALIZED); + +PluginThread::PluginThread() + : preloaded_plugin_module_(NULL) { + plugin_path_ = + CommandLine::ForCurrentProcess()->GetSwitchValuePath( + switches::kPluginPath); + + lazy_tls.Pointer()->Set(this); +#if defined(OS_LINUX) + { + // XEmbed plugins assume they are hosted in a Gtk application, so we need + // to initialize Gtk in the plugin process. + g_thread_init(NULL); + + // Flash has problems receiving clicks with newer GTKs due to the + // client-side windows change. To be safe, we just always set the + // backwards-compat environment variable. + setenv("GDK_NATIVE_WINDOWS", "1", 1); + + gfx::GtkInitFromCommandLine(*CommandLine::ForCurrentProcess()); + + // GTK after 2.18 resets the environment variable. But if we're using + // nspluginwrapper, that means it'll spawn its subprocess without the + // environment variable! So set it again. + setenv("GDK_NATIVE_WINDOWS", "1", 1); + } + + ui::SetDefaultX11ErrorHandlers(); +#endif + + PatchNPNFunctions(); + + // Preload the library to avoid loading, unloading then reloading + preloaded_plugin_module_ = base::LoadNativeLibrary(plugin_path_); + + scoped_refptr<webkit::npapi::PluginLib> plugin( + webkit::npapi::PluginLib::CreatePluginLib(plugin_path_)); + if (plugin.get()) + plugin->NP_Initialize(); + + content::GetContentClient()->PluginProcessStarted( + plugin.get() ? plugin->plugin_info().name : string16()); + + // Certain plugins, such as flash, steal the unhandled exception filter + // thus we never get crash reports when they fault. This call fixes it. + message_loop()->set_exception_restoration(true); +} + +PluginThread::~PluginThread() { + if (preloaded_plugin_module_) { + base::UnloadNativeLibrary(preloaded_plugin_module_); + preloaded_plugin_module_ = NULL; + } + PluginChannelBase::CleanupChannels(); + webkit::npapi::PluginLib::UnloadAllPlugins(); + + if (webkit_glue::ShouldForcefullyTerminatePluginProcess()) + base::KillProcess(base::GetCurrentProcessHandle(), 0, /* wait= */ false); + + lazy_tls.Pointer()->Set(NULL); +} + +PluginThread* PluginThread::current() { + return lazy_tls.Pointer()->Get(); +} + +bool PluginThread::OnControlMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PluginThread, msg) + IPC_MESSAGE_HANDLER(PluginProcessMsg_CreateChannel, OnCreateChannel) + IPC_MESSAGE_HANDLER(PluginProcessMsg_NotifyRenderersOfPendingShutdown, + OnNotifyRenderersOfPendingShutdown) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PluginThread::OnCreateChannel(int renderer_id, + bool incognito) { + scoped_refptr<PluginChannel> channel(PluginChannel::GetPluginChannel( + renderer_id, ChildProcess::current()->io_message_loop())); + IPC::ChannelHandle channel_handle; + if (channel.get()) { + channel_handle.name = channel->channel_handle().name; +#if defined(OS_POSIX) + // On POSIX, pass the renderer-side FD. + channel_handle.socket = base::FileDescriptor(channel->renderer_fd(), false); +#endif + channel->set_incognito(incognito); + } + + Send(new PluginProcessHostMsg_ChannelCreated(channel_handle)); +} + +void PluginThread::OnNotifyRenderersOfPendingShutdown() { + PluginChannel::NotifyRenderersOfPendingShutdown(); +} + +namespace webkit_glue { + +#if defined(OS_WIN) +bool DownloadUrl(const std::string& url, HWND caller_window) { + PluginThread* plugin_thread = PluginThread::current(); + if (!plugin_thread) { + return false; + } + + IPC::Message* message = + new PluginProcessHostMsg_DownloadUrl(MSG_ROUTING_NONE, url, + ::GetCurrentProcessId(), + caller_window); + return plugin_thread->Send(message); +} +#endif + +bool GetPluginFinderURL(std::string* plugin_finder_url) { + if (!plugin_finder_url) { + NOTREACHED(); + return false; + } + + PluginThread* plugin_thread = PluginThread::current(); + if (!plugin_thread) + return false; + + plugin_thread->Send( + new PluginProcessHostMsg_GetPluginFinderUrl(plugin_finder_url)); + // If we get an empty string back this means the plugin finder has been + // disabled. + return true; +} + +bool IsDefaultPluginEnabled() { + return true; +} + +bool FindProxyForUrl(const GURL& url, std::string* proxy_list) { + int net_error; + std::string proxy_result; + + bool result = ChildThread::current()->Send( + new ChildProcessHostMsg_ResolveProxy(url, &net_error, &proxy_result)); + if (!result || net_error != net::OK) + return false; + + *proxy_list = proxy_result; + return true; +} + +} // namespace webkit_glue diff --git a/content/plugin/plugin_thread.h b/content/plugin/plugin_thread.h new file mode 100644 index 0000000..efd96b6 --- /dev/null +++ b/content/plugin/plugin_thread.h @@ -0,0 +1,54 @@ +// Copyright (c) 2010 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 CONTENT_PLUGIN_PLUGIN_THREAD_H_ +#define CONTENT_PLUGIN_PLUGIN_THREAD_H_ +#pragma once + +#include "base/file_path.h" +#include "base/native_library.h" +#include "build/build_config.h" +#include "content/common/child_thread.h" +#include "content/plugin/plugin_channel.h" +#include "webkit/plugins/npapi/plugin_lib.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +// The PluginThread class represents a background thread where plugin instances +// live. Communication occurs between WebPluginDelegateProxy in the renderer +// process and WebPluginDelegateStub in this thread through IPC messages. +class PluginThread : public ChildThread { + public: + PluginThread(); + ~PluginThread(); + + // Returns the one plugin thread. + static PluginThread* current(); + + FilePath plugin_path() { return plugin_path_; } + + private: + virtual bool OnControlMessageReceived(const IPC::Message& msg); + + // Callback for when a channel has been created. + void OnCreateChannel(int renderer_id, bool incognito); + void OnPluginMessage(const std::vector<uint8> &data); + void OnNotifyRenderersOfPendingShutdown(); +#if defined(OS_MACOSX) + void OnAppActivated(); + void OnPluginFocusNotify(uint32 instance_id); +#endif + + // The plugin module which is preloaded in Init + base::NativeLibrary preloaded_plugin_module_; + + // Points to the plugin file that this process hosts. + FilePath plugin_path_; + + DISALLOW_COPY_AND_ASSIGN(PluginThread); +}; + +#endif // CONTENT_PLUGIN_PLUGIN_THREAD_H_ diff --git a/content/plugin/webplugin_accelerated_surface_proxy_mac.cc b/content/plugin/webplugin_accelerated_surface_proxy_mac.cc new file mode 100644 index 0000000..7f5d486 --- /dev/null +++ b/content/plugin/webplugin_accelerated_surface_proxy_mac.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2010 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. + +#import <OpenGL/OpenGL.h> + +#include "content/plugin/webplugin_accelerated_surface_proxy_mac.h" + +#include "app/surface/accelerated_surface_mac.h" +#include "app/surface/transport_dib.h" +#include "content/plugin/webplugin_proxy.h" + +WebPluginAcceleratedSurfaceProxy::WebPluginAcceleratedSurfaceProxy( + WebPluginProxy* plugin_proxy) + : plugin_proxy_(plugin_proxy), + window_handle_(NULL) { + surface_ = new AcceleratedSurface; + // It's possible for OpenGL to fail to initialze (e.g., if an incompatible + // mode is forced via flags), so handle that gracefully. + if (!surface_->Initialize(NULL, true)) { + delete surface_; + surface_ = NULL; + return; + } + + // Only used for 10.5 support, but harmless on 10.6+. + surface_->SetTransportDIBAllocAndFree( + NewCallback(plugin_proxy_, &WebPluginProxy::AllocSurfaceDIB), + NewCallback(plugin_proxy_, &WebPluginProxy::FreeSurfaceDIB)); +} + +WebPluginAcceleratedSurfaceProxy::~WebPluginAcceleratedSurfaceProxy() { + if (surface_) { + surface_->Destroy(); + delete surface_; + surface_ = NULL; + } +} + +void WebPluginAcceleratedSurfaceProxy::SetWindowHandle( + gfx::PluginWindowHandle window) { + window_handle_ = window; +} + +void WebPluginAcceleratedSurfaceProxy::SetSize(const gfx::Size& size) { + if (!surface_) + return; + + uint64 io_surface_id = surface_->SetSurfaceSize(size); + if (io_surface_id) { + plugin_proxy_->SetAcceleratedSurface(window_handle_, size, io_surface_id); + } else { + TransportDIB::Handle transport_dib = surface_->SetTransportDIBSize(size); + if (TransportDIB::is_valid(transport_dib)) { + plugin_proxy_->SetAcceleratedDIB(window_handle_, size, transport_dib); + } + } +} + +CGLContextObj WebPluginAcceleratedSurfaceProxy::context() { + return surface_ ? surface_->context() : NULL; +} + +void WebPluginAcceleratedSurfaceProxy::StartDrawing() { + if (!surface_) + return; + + surface_->MakeCurrent(); + surface_->Clear(gfx::Rect(surface_->GetSize())); +} + +void WebPluginAcceleratedSurfaceProxy::EndDrawing() { + if (!surface_) + return; + + surface_->SwapBuffers(); + plugin_proxy_->AcceleratedFrameBuffersDidSwap( + window_handle_, surface_->GetSurfaceId()); +} diff --git a/content/plugin/webplugin_accelerated_surface_proxy_mac.h b/content/plugin/webplugin_accelerated_surface_proxy_mac.h new file mode 100644 index 0000000..b41239f --- /dev/null +++ b/content/plugin/webplugin_accelerated_surface_proxy_mac.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 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 CONTENT_PLUGIN_WEBPLUGIN_ACCELERATED_SURFACE_PROXY_H_ +#define CONTENT_PLUGIN_WEBPLUGIN_ACCELERATED_SURFACE_PROXY_H_ +#pragma once + +#include "webkit/plugins/npapi/webplugin_accelerated_surface_mac.h" + +class WebPluginProxy; +class AcceleratedSurface; + +// Out-of-process implementation of WebPluginAcceleratedSurface that proxies +// calls through a WebPluginProxy. +class WebPluginAcceleratedSurfaceProxy + : public webkit::npapi::WebPluginAcceleratedSurface { + public: + // Creates a new WebPluginAcceleratedSurfaceProxy that uses plugin_proxy + // to proxy calls. plugin_proxy must outlive this object. + WebPluginAcceleratedSurfaceProxy(WebPluginProxy* plugin_proxy); + virtual ~WebPluginAcceleratedSurfaceProxy(); + + // WebPluginAcceleratedSurface implementation. + virtual void SetWindowHandle(gfx::PluginWindowHandle window); + virtual void SetSize(const gfx::Size& size); + virtual CGLContextObj context(); + virtual void StartDrawing(); + virtual void EndDrawing(); + + private: + WebPluginProxy* plugin_proxy_; // Weak ref. + gfx::PluginWindowHandle window_handle_; + AcceleratedSurface* surface_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginAcceleratedSurfaceProxy); +}; + +#endif // CONTENT_PLUGIN_WEBPLUGIN_ACCELERATED_SURFACE_PROXY_H_ diff --git a/content/plugin/webplugin_delegate_stub.cc b/content/plugin/webplugin_delegate_stub.cc new file mode 100644 index 0000000..cf26fc0 --- /dev/null +++ b/content/plugin/webplugin_delegate_stub.cc @@ -0,0 +1,451 @@ +// Copyright (c) 2011 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 "content/plugin/webplugin_delegate_stub.h" + +#include "build/build_config.h" + +#include "base/command_line.h" +#include "content/common/content_client.h" +#include "content/common/content_switches.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_stub.h" +#include "content/plugin/plugin_channel.h" +#include "content/plugin/plugin_thread.h" +#include "content/plugin/webplugin_proxy.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/npruntime.h" +#include "skia/ext/platform_device.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h" +#include "webkit/plugins/npapi/webplugin_delegate_impl.h" +#include "webkit/glue/webcursor.h" + +#if defined(OS_WIN) +#include "base/scoped_ptr.h" +#include "printing/native_metafile_factory.h" +#include "printing/native_metafile.h" +#endif // defined(OS_WIN) + +using WebKit::WebBindings; +using WebKit::WebCursorInfo; +using webkit::npapi::WebPlugin; +using webkit::npapi::WebPluginResourceClient; + +class FinishDestructionTask : public Task { + public: + FinishDestructionTask(webkit::npapi::WebPluginDelegateImpl* delegate, + WebPlugin* webplugin) + : delegate_(delegate), webplugin_(webplugin) { + } + + void Run() { + // WebPlugin must outlive WebPluginDelegate. + if (delegate_) + delegate_->PluginDestroyed(); + + delete webplugin_; + } + + private: + webkit::npapi::WebPluginDelegateImpl* delegate_; + webkit::npapi::WebPlugin* webplugin_; +}; + +WebPluginDelegateStub::WebPluginDelegateStub( + const std::string& mime_type, int instance_id, PluginChannel* channel) : + mime_type_(mime_type), + instance_id_(instance_id), + channel_(channel), + delegate_(NULL), + webplugin_(NULL), + in_destructor_(false) { + DCHECK(channel); +} + +WebPluginDelegateStub::~WebPluginDelegateStub() { + in_destructor_ = true; + content::GetContentClient()->SetActiveURL(page_url_); + + if (channel_->in_send()) { + // The delegate or an npobject is in the callstack, so don't delete it + // right away. + MessageLoop::current()->PostNonNestableTask(FROM_HERE, + new FinishDestructionTask(delegate_, webplugin_)); + } else { + // Safe to delete right away. + if (delegate_) + delegate_->PluginDestroyed(); + + delete webplugin_; + } +} + +bool WebPluginDelegateStub::OnMessageReceived(const IPC::Message& msg) { + content::GetContentClient()->SetActiveURL(page_url_); + + // A plugin can execute a script to delete itself in any of its NPP methods. + // Hold an extra reference to ourself so that if this does occur and we're + // handling a sync message, we don't crash when attempting to send a reply. + // The exception to this is when we're already in the destructor. + if (!in_destructor_) + AddRef(); + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(WebPluginDelegateStub, msg) + IPC_MESSAGE_HANDLER(PluginMsg_Init, OnInit) + IPC_MESSAGE_HANDLER(PluginMsg_WillSendRequest, OnWillSendRequest) + IPC_MESSAGE_HANDLER(PluginMsg_DidReceiveResponse, OnDidReceiveResponse) + IPC_MESSAGE_HANDLER(PluginMsg_DidReceiveData, OnDidReceiveData) + IPC_MESSAGE_HANDLER(PluginMsg_DidFinishLoading, OnDidFinishLoading) + IPC_MESSAGE_HANDLER(PluginMsg_DidFail, OnDidFail) + IPC_MESSAGE_HANDLER(PluginMsg_DidFinishLoadWithReason, + OnDidFinishLoadWithReason) + IPC_MESSAGE_HANDLER(PluginMsg_SetFocus, OnSetFocus) + IPC_MESSAGE_HANDLER(PluginMsg_HandleInputEvent, OnHandleInputEvent) + IPC_MESSAGE_HANDLER(PluginMsg_Paint, OnPaint) + IPC_MESSAGE_HANDLER(PluginMsg_DidPaint, OnDidPaint) + IPC_MESSAGE_HANDLER(PluginMsg_Print, OnPrint) + IPC_MESSAGE_HANDLER(PluginMsg_GetPluginScriptableObject, + OnGetPluginScriptableObject) + IPC_MESSAGE_HANDLER(PluginMsg_UpdateGeometry, OnUpdateGeometry) + IPC_MESSAGE_HANDLER(PluginMsg_UpdateGeometrySync, OnUpdateGeometry) + IPC_MESSAGE_HANDLER(PluginMsg_SendJavaScriptStream, + OnSendJavaScriptStream) + IPC_MESSAGE_HANDLER(PluginMsg_SetContentAreaFocus, OnSetContentAreaFocus) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(PluginMsg_SetWindowFocus, OnSetWindowFocus) + IPC_MESSAGE_HANDLER(PluginMsg_ContainerHidden, OnContainerHidden) + IPC_MESSAGE_HANDLER(PluginMsg_ContainerShown, OnContainerShown) + IPC_MESSAGE_HANDLER(PluginMsg_WindowFrameChanged, OnWindowFrameChanged) + IPC_MESSAGE_HANDLER(PluginMsg_ImeCompositionCompleted, + OnImeCompositionCompleted) +#endif + IPC_MESSAGE_HANDLER(PluginMsg_DidReceiveManualResponse, + OnDidReceiveManualResponse) + IPC_MESSAGE_HANDLER(PluginMsg_DidReceiveManualData, OnDidReceiveManualData) + IPC_MESSAGE_HANDLER(PluginMsg_DidFinishManualLoading, + OnDidFinishManualLoading) + IPC_MESSAGE_HANDLER(PluginMsg_DidManualLoadFail, OnDidManualLoadFail) + IPC_MESSAGE_HANDLER(PluginMsg_InstallMissingPlugin, OnInstallMissingPlugin) + IPC_MESSAGE_HANDLER(PluginMsg_HandleURLRequestReply, + OnHandleURLRequestReply) + IPC_MESSAGE_HANDLER(PluginMsg_HTTPRangeRequestReply, + OnHTTPRangeRequestReply) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(PluginMsg_SetFakeAcceleratedSurfaceWindowHandle, + OnSetFakeAcceleratedSurfaceWindowHandle) +#endif + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + if (!in_destructor_) + Release(); + + DCHECK(handled); + return handled; +} + +bool WebPluginDelegateStub::Send(IPC::Message* msg) { + return channel_->Send(msg); +} + +void WebPluginDelegateStub::OnInit(const PluginMsg_Init_Params& params, + bool* result) { + page_url_ = params.page_url; + content::GetContentClient()->SetActiveURL(page_url_); + + *result = false; + if (params.arg_names.size() != params.arg_values.size()) { + NOTREACHED(); + return; + } + + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + FilePath path = + command_line.GetSwitchValuePath(switches::kPluginPath); + + gfx::PluginWindowHandle parent = gfx::kNullPluginWindow; +#if defined(OS_WIN) + parent = gfx::NativeViewFromId(params.containing_window); +#elif defined(OS_LINUX) + // This code is disabled, See issue 17110. + // The problem is that the XID can change at arbitrary times (e.g. when the + // tab is detached then reattached), so we need to be able to track these + // changes, and let the PluginInstance know. + // PluginThread::current()->Send(new PluginProcessHostMsg_MapNativeViewId( + // params.containing_window, &parent)); +#endif + + webplugin_ = new WebPluginProxy( + channel_, instance_id_, page_url_, params.containing_window, + params.host_render_view_routing_id); + delegate_ = webkit::npapi::WebPluginDelegateImpl::Create( + path, mime_type_, parent); + if (delegate_) { + webplugin_->set_delegate(delegate_); + *result = delegate_->Initialize(params.url, + params.arg_names, + params.arg_values, + webplugin_, + params.load_manually); + } +} + +void WebPluginDelegateStub::OnWillSendRequest(int id, const GURL& url, + int http_status_code) { + WebPluginResourceClient* client = webplugin_->GetResourceClient(id); + if (!client) + return; + + client->WillSendRequest(url, http_status_code); +} + +void WebPluginDelegateStub::OnDidReceiveResponse( + const PluginMsg_DidReceiveResponseParams& params) { + WebPluginResourceClient* client = webplugin_->GetResourceClient(params.id); + if (!client) + return; + + client->DidReceiveResponse(params.mime_type, + params.headers, + params.expected_length, + params.last_modified, + params.request_is_seekable); +} + +void WebPluginDelegateStub::OnDidReceiveData(int id, + const std::vector<char>& buffer, + int data_offset) { + WebPluginResourceClient* client = webplugin_->GetResourceClient(id); + if (!client) + return; + + client->DidReceiveData(&buffer.front(), static_cast<int>(buffer.size()), + data_offset); +} + +void WebPluginDelegateStub::OnDidFinishLoading(int id) { + WebPluginResourceClient* client = webplugin_->GetResourceClient(id); + if (!client) + return; + + client->DidFinishLoading(); +} + +void WebPluginDelegateStub::OnDidFail(int id) { + WebPluginResourceClient* client = webplugin_->GetResourceClient(id); + if (!client) + return; + + client->DidFail(); +} + +void WebPluginDelegateStub::OnDidFinishLoadWithReason( + const GURL& url, int reason, int notify_id) { + delegate_->DidFinishLoadWithReason(url, reason, notify_id); +} + +void WebPluginDelegateStub::OnSetFocus(bool focused) { + delegate_->SetFocus(focused); +} + +void WebPluginDelegateStub::OnHandleInputEvent( + const WebKit::WebInputEvent *event, + bool* handled, + WebCursor* cursor) { + WebCursorInfo cursor_info; + *handled = delegate_->HandleInputEvent(*event, &cursor_info); + cursor->InitFromCursorInfo(cursor_info); +} + +void WebPluginDelegateStub::OnPaint(const gfx::Rect& damaged_rect) { + webplugin_->Paint(damaged_rect); +} + +void WebPluginDelegateStub::OnDidPaint() { + webplugin_->DidPaint(); +} + +void WebPluginDelegateStub::OnPrint(base::SharedMemoryHandle* shared_memory, + uint32* size) { +#if defined(OS_WIN) + scoped_ptr<printing::NativeMetafile> metafile( + printing::NativeMetafileFactory::CreateMetafile()); + if (!metafile->CreateDc(NULL, NULL)) { + NOTREACHED(); + return; + } + HDC hdc = metafile->context(); + skia::PlatformDevice::InitializeDC(hdc); + delegate_->Print(hdc); + if (!metafile->Close()) { + NOTREACHED(); + return; + } + + *size = metafile->GetDataSize(); + DCHECK(*size); + base::SharedMemory shared_buf; + CreateSharedBuffer(*size, &shared_buf, shared_memory); + + // Retrieve a copy of the data. + bool success = metafile->GetData(shared_buf.memory(), *size); + DCHECK(success); +#else + // TODO(port): plugin printing. + NOTIMPLEMENTED(); +#endif +} + +void WebPluginDelegateStub::OnUpdateGeometry( + const PluginMsg_UpdateGeometry_Param& param) { + webplugin_->UpdateGeometry( + param.window_rect, param.clip_rect, + param.windowless_buffer, param.background_buffer, + param.transparent +#if defined(OS_MACOSX) + , + param.ack_key +#endif + ); +} + +void WebPluginDelegateStub::OnGetPluginScriptableObject(int* route_id) { + NPObject* object = delegate_->GetPluginScriptableObject(); + if (!object) { + *route_id = MSG_ROUTING_NONE; + return; + } + + *route_id = channel_->GenerateRouteID(); + // The stub will delete itself when the proxy tells it that it's released, or + // otherwise when the channel is closed. + new NPObjectStub( + object, channel_.get(), *route_id, webplugin_->containing_window(), + page_url_); + + // Release ref added by GetPluginScriptableObject (our stub holds its own). + WebBindings::releaseObject(object); +} + +void WebPluginDelegateStub::OnSendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) { + delegate_->SendJavaScriptStream(url, result, success, notify_id); +} + +void WebPluginDelegateStub::OnSetContentAreaFocus(bool has_focus) { + if (delegate_) + delegate_->SetContentAreaHasFocus(has_focus); +} + +#if defined(OS_MACOSX) +void WebPluginDelegateStub::OnSetWindowFocus(bool has_focus) { + if (delegate_) + delegate_->SetWindowHasFocus(has_focus); +} + +void WebPluginDelegateStub::OnContainerHidden() { + if (delegate_) + delegate_->SetContainerVisibility(false); +} + +void WebPluginDelegateStub::OnContainerShown(gfx::Rect window_frame, + gfx::Rect view_frame, + bool has_focus) { + if (delegate_) { + delegate_->WindowFrameChanged(window_frame, view_frame); + delegate_->SetContainerVisibility(true); + delegate_->SetWindowHasFocus(has_focus); + } +} + +void WebPluginDelegateStub::OnWindowFrameChanged(const gfx::Rect& window_frame, + const gfx::Rect& view_frame) { + if (delegate_) + delegate_->WindowFrameChanged(window_frame, view_frame); +} + +void WebPluginDelegateStub::OnImeCompositionCompleted(const string16& text) { + if (delegate_) + delegate_->ImeCompositionCompleted(text); +} +#endif // OS_MACOSX + +void WebPluginDelegateStub::OnDidReceiveManualResponse( + const GURL& url, + const PluginMsg_DidReceiveResponseParams& params) { + delegate_->DidReceiveManualResponse(url, params.mime_type, params.headers, + params.expected_length, + params.last_modified); +} + +void WebPluginDelegateStub::OnDidReceiveManualData( + const std::vector<char>& buffer) { + delegate_->DidReceiveManualData(&buffer.front(), + static_cast<int>(buffer.size())); +} + +void WebPluginDelegateStub::OnDidFinishManualLoading() { + delegate_->DidFinishManualLoading(); +} + +void WebPluginDelegateStub::OnDidManualLoadFail() { + delegate_->DidManualLoadFail(); +} + +void WebPluginDelegateStub::OnInstallMissingPlugin() { + delegate_->InstallMissingPlugin(); +} + +void WebPluginDelegateStub::CreateSharedBuffer( + uint32 size, + base::SharedMemory* shared_buf, + base::SharedMemoryHandle* remote_handle) { + if (!shared_buf->CreateAndMapAnonymous(size)) { + NOTREACHED(); + shared_buf->Close(); + return; + } + +#if defined(OS_WIN) + BOOL result = DuplicateHandle(GetCurrentProcess(), + shared_buf->handle(), + channel_->renderer_handle(), + remote_handle, 0, FALSE, + DUPLICATE_SAME_ACCESS); + DCHECK_NE(result, 0); + + // If the calling function's shared_buf is on the stack, its destructor will + // close the shared memory buffer handle. This is fine since we already + // duplicated the handle to the renderer process so it will stay "alive". +#else + // TODO(port): this should use TransportDIB. + NOTIMPLEMENTED(); +#endif +} + +void WebPluginDelegateStub::OnHandleURLRequestReply( + unsigned long resource_id, const GURL& url, int notify_id) { + WebPluginResourceClient* resource_client = + delegate_->CreateResourceClient(resource_id, url, notify_id); + webplugin_->OnResourceCreated(resource_id, resource_client); +} + +void WebPluginDelegateStub::OnHTTPRangeRequestReply( + unsigned long resource_id, int range_request_id) { + WebPluginResourceClient* resource_client = + delegate_->CreateSeekableResourceClient(resource_id, range_request_id); + webplugin_->OnResourceCreated(resource_id, resource_client); +} + +#if defined(OS_MACOSX) +void WebPluginDelegateStub::OnSetFakeAcceleratedSurfaceWindowHandle( + gfx::PluginWindowHandle window) { + delegate_->set_windowed_handle(window); +} +#endif + diff --git a/content/plugin/webplugin_delegate_stub.h b/content/plugin/webplugin_delegate_stub.h new file mode 100644 index 0000000..74fd5bd --- /dev/null +++ b/content/plugin/webplugin_delegate_stub.h @@ -0,0 +1,135 @@ +// Copyright (c) 2010 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 CONTENT_PLUGIN_WEBPLUGIN_DELEGATE_STUB_H_ +#define CONTENT_PLUGIN_WEBPLUGIN_DELEGATE_STUB_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "base/shared_memory.h" +#include "base/task.h" +#include "googleurl/src/gurl.h" +#include "ipc/ipc_channel.h" +#include "third_party/npapi/bindings/npapi.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" + +class PluginChannel; +class WebPluginProxy; +struct PluginMsg_Init_Params; +struct PluginMsg_DidReceiveResponseParams; +struct PluginMsg_UpdateGeometry_Param; +class WebCursor; + +namespace WebKit { +class WebInputEvent; +} + +namespace webkit { +namespace npapi { +class WebPluginDelegateImpl; +} +} + +// Converts the IPC messages from WebPluginDelegateProxy into calls to the +// actual WebPluginDelegateImpl object. +class WebPluginDelegateStub : public IPC::Channel::Listener, + public IPC::Message::Sender, + public base::RefCounted<WebPluginDelegateStub> { + public: + WebPluginDelegateStub(const std::string& mime_type, int instance_id, + PluginChannel* channel); + + // IPC::Channel::Listener implementation: + virtual bool OnMessageReceived(const IPC::Message& msg); + + // IPC::Message::Sender implementation: + virtual bool Send(IPC::Message* msg); + + int instance_id() { return instance_id_; } + WebPluginProxy* webplugin() { return webplugin_; } + + private: + friend class base::RefCounted<WebPluginDelegateStub>; + + ~WebPluginDelegateStub(); + + // Message handlers for the WebPluginDelegate calls that are proxied from the + // renderer over the IPC channel. + void OnInit(const PluginMsg_Init_Params& params, bool* result); + void OnWillSendRequest(int id, const GURL& url, int http_status_code); + void OnDidReceiveResponse(const PluginMsg_DidReceiveResponseParams& params); + void OnDidReceiveData(int id, const std::vector<char>& buffer, + int data_offset); + void OnDidFinishLoading(int id); + void OnDidFail(int id); + void OnDidFinishLoadWithReason(const GURL& url, int reason, int notify_id); + void OnSetFocus(bool focused); + void OnHandleInputEvent(const WebKit::WebInputEvent* event, + bool* handled, WebCursor* cursor); + void OnPaint(const gfx::Rect& damaged_rect); + void OnDidPaint(); + void OnPrint(base::SharedMemoryHandle* shared_memory, uint32* size); + void OnUpdateGeometry(const PluginMsg_UpdateGeometry_Param& param); + void OnGetPluginScriptableObject(int* route_id); + void OnSendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id); + + void OnSetContentAreaFocus(bool has_focus); +#if defined(OS_MACOSX) + void OnSetWindowFocus(bool has_focus); + void OnContainerHidden(); + void OnContainerShown(gfx::Rect window_frame, gfx::Rect view_frame, + bool has_focus); + void OnWindowFrameChanged(const gfx::Rect& window_frame, + const gfx::Rect& view_frame); + void OnImeCompositionCompleted(const string16& text); +#endif + + void OnDidReceiveManualResponse( + const GURL& url, + const PluginMsg_DidReceiveResponseParams& params); + void OnDidReceiveManualData(const std::vector<char>& buffer); + void OnDidFinishManualLoading(); + void OnDidManualLoadFail(); + void OnInstallMissingPlugin(); + void OnHandleURLRequestReply(unsigned long resource_id, + const GURL& url, + int notify_id); + void OnHTTPRangeRequestReply(unsigned long resource_id, int range_request_id); + + void CreateSharedBuffer(uint32 size, + base::SharedMemory* shared_buf, + base::SharedMemoryHandle* remote_handle); + + std::string mime_type_; + int instance_id_; + + scoped_refptr<PluginChannel> channel_; + + webkit::npapi::WebPluginDelegateImpl* delegate_; + WebPluginProxy* webplugin_; + bool in_destructor_; + + // The url of the main frame hosting the plugin. + GURL page_url_; + +#if defined(ENABLE_GPU) +#if defined(OS_MACOSX) + // If this is a GPU-accelerated plug-in, we need to be able to receive a fake + // window handle which is used for subsequent communication back to the + // browser. + void OnSetFakeAcceleratedSurfaceWindowHandle(gfx::PluginWindowHandle window); +#endif +#endif + + DISALLOW_IMPLICIT_CONSTRUCTORS(WebPluginDelegateStub); +}; + +#endif // CONTENT_PLUGIN_WEBPLUGIN_DELEGATE_STUB_H_ diff --git a/content/plugin/webplugin_proxy.cc b/content/plugin/webplugin_proxy.cc new file mode 100644 index 0000000..57e61b2 --- /dev/null +++ b/content/plugin/webplugin_proxy.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2011 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 "content/plugin/webplugin_proxy.h" + +#include "build/build_config.h" + +#include "base/lazy_instance.h" +#include "base/scoped_handle.h" +#include "base/shared_memory.h" +#include "build/build_config.h" +#include "content/common/content_client.h" +#include "content/common/plugin_messages.h" +#include "content/plugin/npobject_proxy.h" +#include "content/plugin/npobject_util.h" +#include "content/plugin/plugin_channel.h" +#include "content/plugin/plugin_thread.h" +#include "skia/ext/platform_device.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h" +#include "ui/gfx/blit.h" +#include "ui/gfx/canvas.h" +#include "webkit/plugins/npapi/webplugin_delegate_impl.h" + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "content/plugin/webplugin_accelerated_surface_proxy_mac.h" +#endif + +#if defined(OS_WIN) +#include "content/common/section_util_win.h" +#include "ui/gfx/gdi_util.h" +#endif + +#if defined(USE_X11) +#include "ui/base/x/x11_util_internal.h" +#endif + +using WebKit::WebBindings; + +using webkit::npapi::WebPluginResourceClient; +#if defined(OS_MACOSX) +using webkit::npapi::WebPluginAcceleratedSurface; +#endif + +WebPluginProxy::WebPluginProxy( + PluginChannel* channel, + int route_id, + const GURL& page_url, + gfx::NativeViewId containing_window, + int host_render_view_routing_id) + : channel_(channel), + route_id_(route_id), + window_npobject_(NULL), + plugin_element_(NULL), + delegate_(NULL), + waiting_for_paint_(false), + containing_window_(containing_window), + page_url_(page_url), + transparent_(false), + host_render_view_routing_id_(host_render_view_routing_id), + ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)) { +#if defined(USE_X11) + windowless_shm_pixmap_ = None; + use_shm_pixmap_ = false; + + // If the X server supports SHM pixmaps + // and the color depth and masks match, + // then consider using SHM pixmaps for windowless plugin painting. + Display* display = ui::GetXDisplay(); + if (ui::QuerySharedMemorySupport(display) == ui::SHARED_MEMORY_PIXMAP && + ui::BitsPerPixelForPixmapDepth( + display, DefaultDepth(display, 0)) == 32) { + Visual* vis = DefaultVisual(display, 0); + + if (vis->red_mask == 0xff0000 && + vis->green_mask == 0xff00 && + vis->blue_mask == 0xff) + use_shm_pixmap_ = true; + } +#endif +} + +WebPluginProxy::~WebPluginProxy() { +#if defined(USE_X11) + if (windowless_shm_pixmap_ != None) + XFreePixmap(ui::GetXDisplay(), windowless_shm_pixmap_); +#endif + +#if defined(OS_MACOSX) + // Destroy the surface early, since it may send messages during cleanup. + if (accelerated_surface_.get()) + accelerated_surface_.reset(); +#endif +} + +bool WebPluginProxy::Send(IPC::Message* msg) { + return channel_->Send(msg); +} + +void WebPluginProxy::SetWindow(gfx::PluginWindowHandle window) { + Send(new PluginHostMsg_SetWindow(route_id_, window)); +} + +void WebPluginProxy::SetAcceptsInputEvents(bool accepts) { + NOTREACHED(); +} + +void WebPluginProxy::WillDestroyWindow(gfx::PluginWindowHandle window) { +#if defined(OS_WIN) + PluginThread::current()->Send( + new PluginProcessHostMsg_PluginWindowDestroyed( + window, ::GetParent(window))); +#elif defined(USE_X11) + // Nothing to do. +#else + NOTIMPLEMENTED(); +#endif +} + +#if defined(OS_WIN) +void WebPluginProxy::SetWindowlessPumpEvent(HANDLE pump_messages_event) { + HANDLE pump_messages_event_for_renderer = NULL; + DuplicateHandle(GetCurrentProcess(), pump_messages_event, + channel_->renderer_handle(), + &pump_messages_event_for_renderer, + 0, FALSE, DUPLICATE_SAME_ACCESS); + DCHECK(pump_messages_event_for_renderer != NULL); + Send(new PluginHostMsg_SetWindowlessPumpEvent( + route_id_, pump_messages_event_for_renderer)); +} +#endif + +void WebPluginProxy::CancelResource(unsigned long id) { + Send(new PluginHostMsg_CancelResource(route_id_, id)); + resource_clients_.erase(id); +} + +void WebPluginProxy::Invalidate() { + gfx::Rect rect(0, 0, + delegate_->GetRect().width(), + delegate_->GetRect().height()); + InvalidateRect(rect); +} + +void WebPluginProxy::InvalidateRect(const gfx::Rect& rect) { +#if defined(OS_MACOSX) + // If this is a Core Animation plugin, all we need to do is inform the + // delegate. + if (!windowless_context_.get()) { + delegate_->PluginDidInvalidate(); + return; + } + + // Some plugins will send invalidates larger than their own rect when + // offscreen, so constrain invalidates to the plugin rect. + gfx::Rect plugin_rect = delegate_->GetRect(); + plugin_rect.set_origin(gfx::Point(0, 0)); + const gfx::Rect invalidate_rect(rect.Intersect(plugin_rect)); +#else + const gfx::Rect invalidate_rect(rect); +#endif + damaged_rect_ = damaged_rect_.Union(invalidate_rect); + // Ignore NPN_InvalidateRect calls with empty rects. Also don't send an + // invalidate if it's outside the clipping region, since if we did it won't + // lead to a paint and we'll be stuck waiting forever for a DidPaint response. + // + // TODO(piman): There is a race condition here, because this test assumes + // that when the paint actually occurs, the clip rect will not have changed. + // This is not true because scrolling (or window resize) could occur and be + // handled by the renderer before it receives the InvalidateRect message, + // changing the clip rect and then not painting. + if (damaged_rect_.IsEmpty() || + !delegate_->GetClipRect().Intersects(damaged_rect_)) + return; + + // Only send a single InvalidateRect message at a time. From DidPaint we + // will dispatch an additional InvalidateRect message if necessary. + if (!waiting_for_paint_) { + waiting_for_paint_ = true; + // Invalidates caused by calls to NPN_InvalidateRect/NPN_InvalidateRgn + // need to be painted asynchronously as per the NPAPI spec. + MessageLoop::current()->PostTask(FROM_HERE, + runnable_method_factory_.NewRunnableMethod( + &WebPluginProxy::OnPaint, damaged_rect_)); + damaged_rect_ = gfx::Rect(); + } +} + +NPObject* WebPluginProxy::GetWindowScriptNPObject() { + if (window_npobject_) + return WebBindings::retainObject(window_npobject_); + + int npobject_route_id = channel_->GenerateRouteID(); + bool success = false; + Send(new PluginHostMsg_GetWindowScriptNPObject( + route_id_, npobject_route_id, &success)); + if (!success) + return NULL; + + window_npobject_ = NPObjectProxy::Create( + channel_, npobject_route_id, containing_window_, page_url_); + + return window_npobject_; +} + +NPObject* WebPluginProxy::GetPluginElement() { + if (plugin_element_) + return WebBindings::retainObject(plugin_element_); + + int npobject_route_id = channel_->GenerateRouteID(); + bool success = false; + Send(new PluginHostMsg_GetPluginElement(route_id_, npobject_route_id, + &success)); + if (!success) + return NULL; + + plugin_element_ = NPObjectProxy::Create( + channel_, npobject_route_id, containing_window_, page_url_); + + return plugin_element_; +} + +void WebPluginProxy::SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) { + Send(new PluginHostMsg_SetCookie(route_id_, url, + first_party_for_cookies, cookie)); +} + +std::string WebPluginProxy::GetCookies(const GURL& url, + const GURL& first_party_for_cookies) { + std::string cookies; + Send(new PluginHostMsg_GetCookies(route_id_, url, + first_party_for_cookies, &cookies)); + + return cookies; +} + +void WebPluginProxy::OnMissingPluginStatus(int status) { + Send(new PluginHostMsg_MissingPluginStatus(route_id_, status)); +} + +WebPluginResourceClient* WebPluginProxy::GetResourceClient(int id) { + ResourceClientMap::iterator iterator = resource_clients_.find(id); + // The IPC messages which deal with streams are now asynchronous. It is + // now possible to receive stream messages from the renderer for streams + // which may have been cancelled by the plugin. + if (iterator == resource_clients_.end()) { + return NULL; + } + + return iterator->second; +} + +int WebPluginProxy::GetRendererId() { + if (channel_.get()) + return channel_->renderer_id(); + return -1; +} + +void WebPluginProxy::DidPaint() { + // If we have an accumulated damaged rect, then check to see if we need to + // send out another InvalidateRect message. + waiting_for_paint_ = false; + if (!damaged_rect_.IsEmpty()) + InvalidateRect(damaged_rect_); +} + +void WebPluginProxy::OnResourceCreated(int resource_id, + WebPluginResourceClient* client) { + DCHECK(resource_clients_.find(resource_id) == resource_clients_.end()); + resource_clients_[resource_id] = client; +} + +void WebPluginProxy::HandleURLRequest(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + bool notify_redirects) { + if (!target && (0 == base::strcasecmp(method, "GET"))) { + // Please refer to https://bugzilla.mozilla.org/show_bug.cgi?id=366082 + // for more details on this. + if (delegate_->GetQuirks() & + webkit::npapi::WebPluginDelegateImpl:: + PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS) { + GURL request_url(url); + if (!request_url.SchemeIs("http") && + !request_url.SchemeIs("https") && + !request_url.SchemeIs("ftp")) { + return; + } + } + } + + PluginHostMsg_URLRequest_Params params; + params.url = url; + params.method = method; + if (target) + params.target = std::string(target); + + if (len) { + params.buffer.resize(len); + memcpy(¶ms.buffer.front(), buf, len); + } + + params.notify_id = notify_id; + params.popups_allowed = popups_allowed; + params.notify_redirects = notify_redirects; + + Send(new PluginHostMsg_URLRequest(route_id_, params)); +} + +void WebPluginProxy::Paint(const gfx::Rect& rect) { +#if defined(OS_MACOSX) + if (!windowless_context_.get()) + return; +#else + if (!windowless_canvas_.get()) + return; +#endif + + // Clear the damaged area so that if the plugin doesn't paint there we won't + // end up with the old values. + gfx::Rect offset_rect = rect; + offset_rect.Offset(delegate_->GetRect().origin()); +#if defined(OS_MACOSX) + CGContextSaveGState(windowless_context_); + // It is possible for windowless_context_ to change during plugin painting + // (since the plugin can make a synchronous call during paint event handling), + // in which case we don't want to try to restore it later. Not an owning ref + // since owning the ref without owning the shared backing memory doesn't make + // sense, so this should only be used for pointer comparisons. + CGContextRef saved_context_weak = windowless_context_.get(); + + if (background_context_.get()) { + base::mac::ScopedCFTypeRef<CGImageRef> image( + CGBitmapContextCreateImage(background_context_)); + CGRect source_rect = rect.ToCGRect(); + // Flip the rect we use to pull from the canvas, since it's upside-down. + source_rect.origin.y = CGImageGetHeight(image) - rect.y() - rect.height(); + base::mac::ScopedCFTypeRef<CGImageRef> sub_image( + CGImageCreateWithImageInRect(image, source_rect)); + CGContextDrawImage(windowless_context_, rect.ToCGRect(), sub_image); + } else if (transparent_) { + CGContextClearRect(windowless_context_, rect.ToCGRect()); + } + CGContextClipToRect(windowless_context_, rect.ToCGRect()); + delegate_->Paint(windowless_context_, rect); + if (windowless_context_.get() == saved_context_weak) + CGContextRestoreGState(windowless_context_); +#else + windowless_canvas_->save(); + + // The given clip rect is relative to the plugin coordinate system. + SkRect sk_rect = { SkIntToScalar(rect.x()), + SkIntToScalar(rect.y()), + SkIntToScalar(rect.right()), + SkIntToScalar(rect.bottom()) }; + windowless_canvas_->clipRect(sk_rect); + + // Setup the background. + if (background_canvas_.get()) { + // When a background canvas is given, we're in transparent mode. This means + // the plugin wants to have the image of the page in the canvas it's drawing + // into (which is windowless_canvas_) so it can do blending. So we copy the + // background bitmap into the windowless_canvas_. + const SkBitmap& background_bitmap = + background_canvas_->getTopPlatformDevice().accessBitmap(false); + windowless_canvas_->drawBitmap(background_bitmap, 0, 0); + } else { + // In non-transparent mode, the plugin doesn't care what's underneath, so we + // can just give it black. + SkPaint black_fill_paint; + black_fill_paint.setARGB(0xFF, 0x00, 0x00, 0x00); + windowless_canvas_->drawPaint(black_fill_paint); + } + + // Bring the windowless_canvas_ into the window coordinate system, which is + // how the plugin expects to draw (since the windowless API was originally + // designed just for scribbling over the web page). + windowless_canvas_->translate(SkIntToScalar(-delegate_->GetRect().x()), + SkIntToScalar(-delegate_->GetRect().y())); + + // Before we send the invalidate, paint so that renderer uses the updated + // bitmap. + delegate_->Paint(windowless_canvas_.get(), offset_rect); + + windowless_canvas_->restore(); +#endif +} + +void WebPluginProxy::UpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect, + const TransportDIB::Handle& windowless_buffer, + const TransportDIB::Handle& background_buffer, + bool transparent +#if defined(OS_MACOSX) + , + int ack_key +#endif + ) { + gfx::Rect old = delegate_->GetRect(); + gfx::Rect old_clip_rect = delegate_->GetClipRect(); + transparent_ = transparent; + + // Update the buffers before doing anything that could call into plugin code, + // so that we don't process buffer changes out of order if plugins make + // synchronous calls that lead to nested UpdateGeometry calls. + if (TransportDIB::is_valid(windowless_buffer)) { + // The plugin's rect changed, so now we have a new buffer to draw into. + SetWindowlessBuffer(windowless_buffer, background_buffer, window_rect); + } + +#if defined(OS_MACOSX) + delegate_->UpdateGeometryAndContext(window_rect, clip_rect, + windowless_context_); +#else + delegate_->UpdateGeometry(window_rect, clip_rect); +#endif + + // Send over any pending invalidates which occured when the plugin was + // off screen. + if (delegate_->IsWindowless() && !clip_rect.IsEmpty() && + !damaged_rect_.IsEmpty()) { + InvalidateRect(damaged_rect_); + } + +#if defined(OS_MACOSX) + // The renderer is expecting an ACK message if ack_key is not -1. + if (ack_key != -1) { + Send(new PluginHostMsg_UpdateGeometry_ACK(route_id_, ack_key)); + } +#endif +} + +#if defined(OS_WIN) +void WebPluginProxy::SetWindowlessBuffer( + const TransportDIB::Handle& windowless_buffer, + const TransportDIB::Handle& background_buffer, + const gfx::Rect& window_rect) { + // Create a canvas that will reference the shared bits. We have to handle + // errors here since we're mapping a large amount of memory that may not fit + // in our address space, or go wrong in some other way. + windowless_canvas_.reset(new skia::PlatformCanvas); + if (!windowless_canvas_->initialize( + window_rect.width(), + window_rect.height(), + true, + chrome::GetSectionFromProcess(windowless_buffer, + channel_->renderer_handle(), false))) { + windowless_canvas_.reset(); + background_canvas_.reset(); + return; + } + + if (background_buffer) { + background_canvas_.reset(new skia::PlatformCanvas); + if (!background_canvas_->initialize( + window_rect.width(), + window_rect.height(), + true, + chrome::GetSectionFromProcess(background_buffer, + channel_->renderer_handle(), false))) { + windowless_canvas_.reset(); + background_canvas_.reset(); + return; + } + } +} + +#elif defined(OS_MACOSX) + +void WebPluginProxy::SetWindowlessBuffer( + const TransportDIB::Handle& windowless_buffer, + const TransportDIB::Handle& background_buffer, + const gfx::Rect& window_rect) { + // Convert the shared memory handle to a handle that works in our process, + // and then use that to create a CGContextRef. + windowless_dib_.reset(TransportDIB::Map(windowless_buffer)); + background_dib_.reset(TransportDIB::Map(background_buffer)); + windowless_context_.reset(CGBitmapContextCreate( + windowless_dib_->memory(), + window_rect.width(), + window_rect.height(), + 8, 4 * window_rect.width(), + base::mac::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host)); + CGContextTranslateCTM(windowless_context_, 0, window_rect.height()); + CGContextScaleCTM(windowless_context_, 1, -1); + if (background_dib_.get()) { + background_context_.reset(CGBitmapContextCreate( + background_dib_->memory(), + window_rect.width(), + window_rect.height(), + 8, 4 * window_rect.width(), + base::mac::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host)); + CGContextTranslateCTM(background_context_, 0, window_rect.height()); + CGContextScaleCTM(background_context_, 1, -1); + } +} + +#elif defined(USE_X11) + +void WebPluginProxy::SetWindowlessBuffer( + const TransportDIB::Handle& windowless_buffer, + const TransportDIB::Handle& background_buffer, + const gfx::Rect& window_rect) { + int width = window_rect.width(); + int height = window_rect.height(); + windowless_dib_.reset(TransportDIB::Map(windowless_buffer)); + if (windowless_dib_.get()) { + windowless_canvas_.reset(windowless_dib_->GetPlatformCanvas(width, height)); + } else { + // This can happen if the renderer has already destroyed the TransportDIB + // by the time we receive the handle, e.g. in case of multiple resizes. + windowless_canvas_.reset(); + } + background_dib_.reset(TransportDIB::Map(background_buffer)); + if (background_dib_.get()) { + background_canvas_.reset(background_dib_->GetPlatformCanvas(width, height)); + } else { + background_canvas_.reset(); + } + + // If SHM pixmaps support is available, create a SHM pixmap and + // pass it to the delegate for windowless plugin painting. + if (delegate_->IsWindowless() && use_shm_pixmap_ && windowless_dib_.get()) { + Display* display = ui::GetXDisplay(); + XID root_window = ui::GetX11RootWindow(); + XShmSegmentInfo shminfo = {0}; + + if (windowless_shm_pixmap_ != None) + XFreePixmap(display, windowless_shm_pixmap_); + + shminfo.shmseg = windowless_dib_->MapToX(display); + // Create a shared memory pixmap based on the image buffer. + windowless_shm_pixmap_ = XShmCreatePixmap(display, root_window, + NULL, &shminfo, + width, height, + DefaultDepth(display, 0)); + + delegate_->SetWindowlessShmPixmap(windowless_shm_pixmap_); + } +} + +#endif + +void WebPluginProxy::CancelDocumentLoad() { + Send(new PluginHostMsg_CancelDocumentLoad(route_id_)); +} + +void WebPluginProxy::InitiateHTTPRangeRequest( + const char* url, const char* range_info, int range_request_id) { + Send(new PluginHostMsg_InitiateHTTPRangeRequest( + route_id_, url, range_info, range_request_id)); +} + +void WebPluginProxy::SetDeferResourceLoading(unsigned long resource_id, + bool defer) { + Send(new PluginHostMsg_DeferResourceLoading(route_id_, resource_id, defer)); +} + +#if defined(OS_MACOSX) +void WebPluginProxy::FocusChanged(bool focused) { + IPC::Message* msg = new PluginHostMsg_FocusChanged(route_id_, focused); + Send(msg); +} + +void WebPluginProxy::StartIme() { + IPC::Message* msg = new PluginHostMsg_StartIme(route_id_); + // This message can be sent during event-handling, and needs to be delivered + // within that context. + msg->set_unblock(true); + Send(msg); +} + +void WebPluginProxy::BindFakePluginWindowHandle(bool opaque) { + Send(new PluginHostMsg_BindFakePluginWindowHandle(route_id_, opaque)); +} + +WebPluginAcceleratedSurface* WebPluginProxy::GetAcceleratedSurface() { + if (!accelerated_surface_.get()) + accelerated_surface_.reset(new WebPluginAcceleratedSurfaceProxy(this)); + return accelerated_surface_.get(); +} + +void WebPluginProxy::AcceleratedFrameBuffersDidSwap( + gfx::PluginWindowHandle window, uint64 surface_id) { + Send(new PluginHostMsg_AcceleratedSurfaceBuffersSwapped( + route_id_, window, surface_id)); +} + +void WebPluginProxy::SetAcceleratedSurface( + gfx::PluginWindowHandle window, + const gfx::Size& size, + uint64 accelerated_surface_identifier) { + Send(new PluginHostMsg_AcceleratedSurfaceSetIOSurface( + route_id_, window, size.width(), size.height(), + accelerated_surface_identifier)); +} + +void WebPluginProxy::SetAcceleratedDIB( + gfx::PluginWindowHandle window, + const gfx::Size& size, + const TransportDIB::Handle& dib_handle) { + Send(new PluginHostMsg_AcceleratedSurfaceSetTransportDIB( + route_id_, window, size.width(), size.height(), dib_handle)); +} + +void WebPluginProxy::AllocSurfaceDIB(const size_t size, + TransportDIB::Handle* dib_handle) { + Send(new PluginHostMsg_AllocTransportDIB(route_id_, size, dib_handle)); +} + +void WebPluginProxy::FreeSurfaceDIB(TransportDIB::Id dib_id) { + Send(new PluginHostMsg_FreeTransportDIB(route_id_, dib_id)); +} +#endif + +void WebPluginProxy::OnPaint(const gfx::Rect& damaged_rect) { + content::GetContentClient()->SetActiveURL(page_url_); + + Paint(damaged_rect); + Send(new PluginHostMsg_InvalidateRect(route_id_, damaged_rect)); +} + +bool WebPluginProxy::IsOffTheRecord() { + return channel_->incognito(); +} + +void WebPluginProxy::ResourceClientDeleted( + WebPluginResourceClient* resource_client) { + ResourceClientMap::iterator index = resource_clients_.begin(); + while (index != resource_clients_.end()) { + WebPluginResourceClient* client = (*index).second; + + if (client == resource_client) { + resource_clients_.erase(index++); + } else { + index++; + } + } +} + +void WebPluginProxy::URLRedirectResponse(bool allow, int resource_id) { + Send(new PluginHostMsg_URLRedirectResponse(route_id_, allow, resource_id)); +} diff --git a/content/plugin/webplugin_proxy.h b/content/plugin/webplugin_proxy.h new file mode 100644 index 0000000..8dbfa9e --- /dev/null +++ b/content/plugin/webplugin_proxy.h @@ -0,0 +1,225 @@ +// Copyright (c) 2010 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 CONTENT_PLUGIN_WEBPLUGIN_PROXY_H_ +#define CONTENT_PLUGIN_WEBPLUGIN_PROXY_H_ +#pragma once + +#include <string> + +#if defined(USE_X11) +#include "ui/base/x/x11_util.h" +#endif +#include "app/surface/transport_dib.h" +#include "base/hash_tables.h" +#include "base/ref_counted.h" +#if defined(OS_MACOSX) +#include "base/mac/scoped_cftyperef.h" +#endif +#include "base/scoped_handle.h" +#include "base/scoped_ptr.h" +#include "base/shared_memory.h" +#include "base/timer.h" +#include "googleurl/src/gurl.h" +#include "ipc/ipc_message.h" +#include "webkit/plugins/npapi/webplugin.h" + +class PluginChannel; + +namespace webkit { +namespace npapi { +class WebPluginDelegateImpl; +} +} + +#if defined(OS_MACOSX) +class WebPluginAcceleratedSurfaceProxy; +#endif + +// This is an implementation of WebPlugin that proxies all calls to the +// renderer. +class WebPluginProxy : public webkit::npapi::WebPlugin { + public: + // Creates a new proxy for WebPlugin, using the given sender to send the + // marshalled WebPlugin calls. + WebPluginProxy(PluginChannel* channel, + int route_id, + const GURL& page_url, + gfx::NativeViewId containing_window, + int host_render_view_routing_id); + ~WebPluginProxy(); + + void set_delegate(webkit::npapi::WebPluginDelegateImpl* d) { delegate_ = d; } + + // WebPlugin overrides + virtual void SetWindow(gfx::PluginWindowHandle window); + + // Whether input events should be sent to the delegate. + virtual void SetAcceptsInputEvents(bool accepts); + + virtual void WillDestroyWindow(gfx::PluginWindowHandle window); +#if defined(OS_WIN) + void SetWindowlessPumpEvent(HANDLE pump_messages_event); +#endif + + virtual void CancelResource(unsigned long id); + virtual void Invalidate(); + virtual void InvalidateRect(const gfx::Rect& rect); + virtual NPObject* GetWindowScriptNPObject(); + virtual NPObject* GetPluginElement(); + virtual void SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie); + virtual std::string GetCookies(const GURL& url, + const GURL& first_party_for_cookies); + + virtual void OnMissingPluginStatus(int status); + // class-specific methods + + // Returns a WebPluginResourceClient object given its id, or NULL if no + // object with that id exists. + webkit::npapi::WebPluginResourceClient* GetResourceClient(int id); + + // Returns the id of the renderer that contains this plugin. + int GetRendererId(); + + // Returns the id of the associated render view. + int host_render_view_routing_id() const { + return host_render_view_routing_id_; + } + + // For windowless plugins, paints the given rectangle into the local buffer. + void Paint(const gfx::Rect& rect); + + // Callback from the renderer to let us know that a paint occurred. + void DidPaint(); + + // Notification received on a plugin issued resource request creation. + void OnResourceCreated(int resource_id, + webkit::npapi::WebPluginResourceClient* client); + + virtual void HandleURLRequest(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + bool notify_redirects); + void UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect, + const TransportDIB::Handle& windowless_buffer, + const TransportDIB::Handle& background_buffer, + bool transparent +#if defined(OS_MACOSX) + , + int ack_key +#endif + ); + virtual void CancelDocumentLoad(); + virtual void InitiateHTTPRangeRequest( + const char* url, const char* range_info, int range_request_id); + virtual void SetDeferResourceLoading(unsigned long resource_id, bool defer); + virtual bool IsOffTheRecord(); + virtual void ResourceClientDeleted( + webkit::npapi::WebPluginResourceClient* resource_client); + gfx::NativeViewId containing_window() { return containing_window_; } + +#if defined(OS_MACOSX) + virtual void FocusChanged(bool focused); + + virtual void StartIme(); + + virtual void BindFakePluginWindowHandle(bool opaque); + + virtual webkit::npapi::WebPluginAcceleratedSurface* GetAcceleratedSurface(); + + // Tell the browser (via the renderer) to invalidate because the + // accelerated buffers have changed. + virtual void AcceleratedFrameBuffersDidSwap( + gfx::PluginWindowHandle window, uint64 surface_id); + + // Tell the renderer and browser to associate the given plugin handle with + // |accelerated_surface_identifier|. The geometry is used to resize any + // native "window" (which on the Mac is a just a view). + // This method is used when IOSurface support is available. + virtual void SetAcceleratedSurface(gfx::PluginWindowHandle window, + const gfx::Size& size, + uint64 accelerated_surface_identifier); + + // Tell the renderer and browser to associate the given plugin handle with + // |dib_handle|. The geometry is used to resize any native "window" (which + // on the Mac is just a view). + // This method is used when IOSurface support is not available. + virtual void SetAcceleratedDIB( + gfx::PluginWindowHandle window, + const gfx::Size& size, + const TransportDIB::Handle& dib_handle); + + // Create/destroy TranportDIBs via messages to the browser process. + // These are only used when IOSurface support is not available. + virtual void AllocSurfaceDIB(const size_t size, + TransportDIB::Handle* dib_handle); + virtual void FreeSurfaceDIB(TransportDIB::Id dib_id); +#endif + + virtual void URLRedirectResponse(bool allow, int resource_id); + + private: + bool Send(IPC::Message* msg); + + // Handler for sending over the paint event to the plugin. + void OnPaint(const gfx::Rect& damaged_rect); + + // Updates the shared memory section where windowless plugins paint. + void SetWindowlessBuffer(const TransportDIB::Handle& windowless_buffer, + const TransportDIB::Handle& background_buffer, + const gfx::Rect& window_rect); + + typedef base::hash_map<int, webkit::npapi::WebPluginResourceClient*> + ResourceClientMap; + ResourceClientMap resource_clients_; + + scoped_refptr<PluginChannel> channel_; + int route_id_; + NPObject* window_npobject_; + NPObject* plugin_element_; + webkit::npapi::WebPluginDelegateImpl* delegate_; + gfx::Rect damaged_rect_; + bool waiting_for_paint_; + gfx::NativeViewId containing_window_; + // The url of the main frame hosting the plugin. + GURL page_url_; + + // Variables used for desynchronized windowless plugin painting. See note in + // webplugin_delegate_proxy.h for how this works. + bool transparent_; +#if defined(OS_MACOSX) + scoped_ptr<TransportDIB> windowless_dib_; + scoped_ptr<TransportDIB> background_dib_; + base::mac::ScopedCFTypeRef<CGContextRef> windowless_context_; + base::mac::ScopedCFTypeRef<CGContextRef> background_context_; + scoped_ptr<WebPluginAcceleratedSurfaceProxy> accelerated_surface_; +#else + scoped_ptr<skia::PlatformCanvas> windowless_canvas_; + scoped_ptr<skia::PlatformCanvas> background_canvas_; + +#if defined(USE_X11) + scoped_ptr<TransportDIB> windowless_dib_; + scoped_ptr<TransportDIB> background_dib_; + // If we can use SHM pixmaps for windowless plugin painting or not. + bool use_shm_pixmap_; + // The SHM pixmap for windowless plugin painting. + XID windowless_shm_pixmap_; +#endif + +#endif + + // Contains the routing id of the host render view. + int host_render_view_routing_id_; + + ScopedRunnableMethodFactory<WebPluginProxy> runnable_method_factory_; +}; + +#endif // CONTENT_PLUGIN_WEBPLUGIN_PROXY_H_ |