summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-02 23:56:11 +0000
committermpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-02 23:56:11 +0000
commit75e5a879a500897b2409c49d4ef205e2e954c9fd (patch)
tree166ca53e8cc29520402efd7cc6ee2804edf744eb
parent720ac73783ea732c130ff6d11dffa41919a25809 (diff)
downloadchromium_src-75e5a879a500897b2409c49d4ef205e2e954c9fd.zip
chromium_src-75e5a879a500897b2409c49d4ef205e2e954c9fd.tar.gz
chromium_src-75e5a879a500897b2409c49d4ef205e2e954c9fd.tar.bz2
Add code to support 2-way communication between extensions and renderers. The code is almost fully symmetrical, except that right now a channel can only be opened to an extension (by ID). It should be trivial to open a channel to a tab, once we have a solid tab API.
Review URL: http://codereview.chromium.org/56037 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13057 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-xchrome/browser/extensions/extension_message_service.cc154
-rwxr-xr-xchrome/browser/extensions/extension_message_service.h65
-rwxr-xr-xchrome/browser/extensions/extension_view.cc6
-rw-r--r--chrome/browser/renderer_host/resource_message_filter.cc12
-rw-r--r--chrome/browser/renderer_host/resource_message_filter.h5
-rw-r--r--chrome/common/ipc_message_utils.h5
-rw-r--r--chrome/common/render_messages_internal.h25
-rwxr-xr-xchrome/renderer/extensions/renderer_extension_bindings.cc186
-rwxr-xr-xchrome/renderer/extensions/renderer_extension_bindings.h15
-rw-r--r--chrome/renderer/mock_render_thread.cc7
-rw-r--r--chrome/renderer/mock_render_thread.h4
-rw-r--r--chrome/renderer/render_thread.cc16
-rw-r--r--chrome/renderer/render_thread.h3
-rw-r--r--chrome/renderer/render_view.cc10
-rw-r--r--chrome/renderer/render_view.h2
-rw-r--r--chrome/renderer/render_view_unittest.cc90
-rwxr-xr-xchrome/renderer/renderer_resources.grd1
-rw-r--r--chrome/renderer/resources/renderer_extension_bindings.js93
-rw-r--r--chrome/test/data/extensions/good/extension1/1/toolstrip1.html13
19 files changed, 518 insertions, 194 deletions
diff --git a/chrome/browser/extensions/extension_message_service.cc b/chrome/browser/extensions/extension_message_service.cc
index 95143ce..87469f9 100755
--- a/chrome/browser/extensions/extension_message_service.cc
+++ b/chrome/browser/extensions/extension_message_service.cc
@@ -13,121 +13,117 @@
#include "chrome/browser/renderer_host/resource_message_filter.h"
#include "chrome/common/render_messages.h"
-// This class acts as the port to an extension process. It is basically just
-// gymnastics to get access to the IPC::Channel (not the ChannelProxy) belonging
-// to an ExtensionView.
-// Created on the UI thread, but accessed fully on the IO thread.
-class ExtensionMessageService::ExtensionFilter :
- public IPC::ChannelProxy::MessageFilter {
- public:
- ExtensionFilter(const std::string& extension_id, int routing_id) :
- extension_id_(extension_id), routing_id_(routing_id), channel_(NULL) {
- }
- ~ExtensionFilter() {
- ExtensionMessageService::GetInstance()->OnExtensionUnregistered(this);
- }
-
- virtual void OnFilterAdded(IPC::Channel* channel) {
- channel_ = channel;
- ExtensionMessageService::GetInstance()->OnExtensionRegistered(this);
- }
- virtual void OnChannelClosing() {
- channel_ = NULL;
- }
-
- bool Send(IPC::Message* message) {
- if (!channel_) {
- delete message;
- return false;
- }
- return channel_->Send(message);
- }
+// Since we have 2 ports for every channel, we just index channels by half the
+// port ID.
+#define GET_CHANNEL_ID(port_id) (port_id / 2)
- IPC::Channel* channel() { return channel_; }
- const std::string& extension_id() { return extension_id_; }
- int routing_id() { return routing_id_; }
+// Port1 is always even, port2 is always odd.
+#define IS_PORT1_ID(port_id) ((port_id & 1) == 0)
- private:
- std::string extension_id_;
- int routing_id_;
- IPC::Channel* channel_;
-};
+// Change even to odd and vice versa, to get the other side of a given channel.
+#define GET_OPPOSITE_PORT_ID(source_port_id) (source_port_id ^ 1)
ExtensionMessageService* ExtensionMessageService::GetInstance() {
return Singleton<ExtensionMessageService>::get();
}
ExtensionMessageService::ExtensionMessageService()
- : next_channel_id_(0) {
+ : next_port_id_(0) {
}
-void ExtensionMessageService::RegisterExtensionView(ExtensionView* view) {
- view->render_view_host()->process()->channel()->AddFilter(
- new ExtensionFilter(view->extension()->id(),
- view->render_view_host()->routing_id()));
-}
-
-void ExtensionMessageService::OnExtensionRegistered(ExtensionFilter* filter) {
- extensions_[filter->extension_id()] = filter;
-}
-
-void ExtensionMessageService::OnExtensionUnregistered(ExtensionFilter* filter) {
- // TODO(mpcomplete): support multiple filters per extension_id
- //DCHECK(extensions_[filter->extension_id()] == filter);
- extensions_.erase(filter->extension_id());
-
- // Close any channels that share this filter.
- for (MessageChannelMap::iterator it = channels_.begin();
- it != channels_.end(); ) {
- MessageChannelMap::iterator current = it++;
- if (current->second.extension_port == filter)
- channels_.erase(current);
- }
+void ExtensionMessageService::RegisterExtension(
+ const std::string& extension_id, int render_process_id) {
+ AutoLock lock(renderers_lock_);
+ // TODO(mpcomplete): We need to ensure an extension always ends up in a single
+ // process. I think this means having an ExtensionProcessManager which holds
+ // a BrowsingContext for each extension.
+ //DCHECK(process_ids_.find(extension_id) == process_ids_.end());
+ process_ids_[extension_id] = render_process_id;
}
int ExtensionMessageService::OpenChannelToExtension(
- const std::string& extension_id, ResourceMessageFilter* renderer_port) {
+ const std::string& extension_id, ResourceMessageFilter* source) {
DCHECK(MessageLoop::current() ==
ChromeThread::GetMessageLoop(ChromeThread::IO));
- ExtensionMap::iterator extension_port = extensions_.find(extension_id);
- if (extension_port == extensions_.end())
- return -1;
+ // Lookup the targeted extension process.
+ ResourceMessageFilter* dest = NULL;
+ {
+ AutoLock lock(renderers_lock_);
+ ProcessIDMap::iterator process_id = process_ids_.find(extension_id);
+ if (process_id == process_ids_.end())
+ return -1;
+
+ RendererMap::iterator renderer = renderers_.find(process_id->second);
+ if (renderer == renderers_.end())
+ return -1;
+ dest = renderer->second;
+ }
+
+ // Create a channel ID for both sides of the channel.
+ int port1_id = next_port_id_++;
+ int port2_id = next_port_id_++;
+ DCHECK(IS_PORT1_ID(port1_id));
+ DCHECK(GET_OPPOSITE_PORT_ID(port1_id) == port2_id);
+ DCHECK(GET_OPPOSITE_PORT_ID(port2_id) == port1_id);
+ DCHECK(GET_CHANNEL_ID(port1_id) == GET_CHANNEL_ID(port2_id));
- int channel_id = next_channel_id_++;
MessageChannel channel;
- channel.renderer_port = renderer_port;
- channel.extension_port = extension_port->second;
- channels_[channel_id] = channel;
+ channel.port1 = source;
+ channel.port2 = dest;
+ channels_[GET_CHANNEL_ID(port1_id)] = channel;
- return channel_id;
+ // Send each process the id for the opposite port.
+ dest->Send(new ViewMsg_ExtensionHandleConnect(port1_id));
+ return port2_id;
}
-void ExtensionMessageService::PostMessageToExtension(
- int channel_id, const std::string& message) {
+void ExtensionMessageService::PostMessageFromRenderer(
+ int port_id, const std::string& message, ResourceMessageFilter* source) {
DCHECK(MessageLoop::current() ==
ChromeThread::GetMessageLoop(ChromeThread::IO));
- MessageChannelMap::iterator iter = channels_.find(channel_id);
+ // Look up the channel by port1's ID.
+ MessageChannelMap::iterator iter =
+ channels_.find(GET_CHANNEL_ID(port_id));
if (iter == channels_.end())
return;
-
MessageChannel& channel = iter->second;
- channel.extension_port->Send(new ViewMsg_HandleExtensionMessage(
- channel.extension_port->routing_id(), message, channel_id));
+
+ // Figure out which port the ID corresponds to.
+ ResourceMessageFilter* dest = NULL;
+ if (IS_PORT1_ID(port_id)) {
+ dest = channel.port1;
+ DCHECK(source == channel.port2);
+ } else {
+ dest = channel.port2;
+ DCHECK(source == channel.port1);
+ }
+
+ int source_port_id = GET_OPPOSITE_PORT_ID(port_id);
+ dest->Send(new ViewMsg_ExtensionHandleMessage(message, source_port_id));
+}
+
+void ExtensionMessageService::RendererReady(ResourceMessageFilter* renderer) {
+ AutoLock lock(renderers_lock_);
+ DCHECK(renderers_.find(renderer->GetProcessId()) == renderers_.end());
+ renderers_[renderer->GetProcessId()] = renderer;
}
void ExtensionMessageService::RendererShutdown(
- ResourceMessageFilter* renderer_port) {
- DCHECK(MessageLoop::current() ==
- ChromeThread::GetMessageLoop(ChromeThread::IO));
+ ResourceMessageFilter* renderer) {
+ {
+ AutoLock lock(renderers_lock_);
+ DCHECK(renderers_.find(renderer->GetProcessId()) != renderers_.end());
+ renderers_.erase(renderer->GetProcessId());
+ }
// Close any channels that share this filter.
// TODO(mpcomplete): should we notify the other side of the port?
for (MessageChannelMap::iterator it = channels_.begin();
it != channels_.end(); ) {
MessageChannelMap::iterator current = it++;
- if (current->second.renderer_port == renderer_port)
+ if (current->second.port1 == renderer || current->second.port2 == renderer)
channels_.erase(current);
}
}
diff --git a/chrome/browser/extensions/extension_message_service.h b/chrome/browser/extensions/extension_message_service.h
index 1b4040d..0020a30 100755
--- a/chrome/browser/extensions/extension_message_service.h
+++ b/chrome/browser/extensions/extension_message_service.h
@@ -8,13 +8,14 @@
#include <map>
#include <string>
+#include "base/lock.h"
+
class ExtensionView;
class ResourceMessageFilter;
-// This class manages message passing to and from extension processes. It
-// maintains a list of available extensions, as well as a set of open channels.
-// It should only be accessed on the IO thread, with the exception of
-// RegisterExtensionView().
+// This class manages message passing between renderer processes. It maintains
+// a list of available extensions and which renderers each lives in, as well as
+// a set of open channels.
//
// Terminology:
// channel: connection between two ports (one of which belongs to an extension)
@@ -27,39 +28,53 @@ class ExtensionMessageService {
ExtensionMessageService();
- // Registers an extension so that it can be referenced by its ID. This method
- // should only be used by the UI thread.
- void RegisterExtensionView(ExtensionView* view);
+ // --- UI thread only:
+
+ // Register an extension and its corresponding renderer process.
+ void RegisterExtension(const std::string& extension_id,
+ int render_process_id);
+
+ // --- IO thread only:
// Given an extension's ID, opens a channel between the given renderer "port"
// and that extension. Returns a channel ID to be used for posting messages
// between the processes, or -1 if the extension doesn't exist.
int OpenChannelToExtension(const std::string& extension_id,
- ResourceMessageFilter* renderer_port);
+ ResourceMessageFilter* source);
+
+ // Sends a message from a renderer to the given port.
+ void PostMessageFromRenderer(int port_id, const std::string& message,
+ ResourceMessageFilter* source);
- // Sends a message to the extension via the given channel.
- void PostMessageToExtension(int channel_id, const std::string& message);
+ // --- UI or IO thread:
+
+ // Called to let us know that a renderer has been started.
+ void RendererReady(ResourceMessageFilter* port);
// Called to let us know that a renderer is going away.
- void RendererShutdown(ResourceMessageFilter* renderer_port);
+ void RendererShutdown(ResourceMessageFilter* port);
+
private:
- class ExtensionFilter;
- friend class ExtensionFilter;
+ // A map of extension ID to the render_process_id that the extension lives in.
+ typedef std::map<std::string, int> ProcessIDMap;
+ ProcessIDMap process_ids_;
+
+ // A map of render_process_id to its corresponding message filter, which we
+ // use for sending messages.
+ typedef std::map<int, ResourceMessageFilter*> RendererMap;
+ RendererMap renderers_;
- // Called when our ExtensionFilter is ready/going away.
- void OnExtensionRegistered(ExtensionFilter* extension);
- void OnExtensionUnregistered(ExtensionFilter* extension);
+ // Protects the two maps above, since each can be accessed on the IO thread
+ // or UI thread. Be careful not to hold this lock when calling external
+ // code (especially sending messages) to avoid deadlock.
+ Lock renderers_lock_;
- // A map of extension ID to the extension port to communicate through.
- // TODO(mpcomplete): Handle the case where there's multiple ExtensionViews
- // in a given extension.
- typedef std::map<std::string, ExtensionFilter*> ExtensionMap;
- ExtensionMap extensions_;
+ // --- IO thread only:
- // The connection between the renderer and extension.
+ // The connection between two renderers.
struct MessageChannel {
- ExtensionFilter* extension_port;
- ResourceMessageFilter* renderer_port;
+ ResourceMessageFilter* port1;
+ ResourceMessageFilter* port2;
};
// A map of channel ID to its channel object.
@@ -67,7 +82,7 @@ class ExtensionMessageService {
MessageChannelMap channels_;
// For generating unique channel IDs.
- int next_channel_id_;
+ int next_port_id_;
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_MESSAGE_SERVICE_H_
diff --git a/chrome/browser/extensions/extension_view.cc b/chrome/browser/extensions/extension_view.cc
index 8213227..a9b3ed0 100755
--- a/chrome/browser/extensions/extension_view.cc
+++ b/chrome/browser/extensions/extension_view.cc
@@ -7,6 +7,7 @@
#include "chrome/browser/extensions/extension.h"
#include "chrome/browser/extensions/extension_message_service.h"
#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
#include "chrome/common/resource_bundle.h"
#include "grit/browser_resources.h"
@@ -26,8 +27,9 @@ void ExtensionView::CreatingRenderer() {
render_view_host()->AllowExtensionBindings();
}
-void ExtensionView::RenderViewCreated(RenderViewHost* render_view_host) {
- ExtensionMessageService::GetInstance()->RegisterExtensionView(this);
+void ExtensionView::RenderViewCreated(RenderViewHost* rvh) {
+ ExtensionMessageService::GetInstance()->RegisterExtension(
+ extension_->id(), render_view_host()->process()->pid());
}
WebPreferences ExtensionView::GetWebkitPrefs() {
diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc
index b7c59af..007c5fd 100644
--- a/chrome/browser/renderer_host/resource_message_filter.cc
+++ b/chrome/browser/renderer_host/resource_message_filter.cc
@@ -155,6 +155,8 @@ ResourceMessageFilter::~ResourceMessageFilter() {
void ResourceMessageFilter::Init(int render_process_id) {
render_process_id_ = render_process_id;
render_widget_helper_->Init(render_process_id, resource_dispatcher_host_);
+
+ ExtensionMessageService::GetInstance()->RendererReady(this);
}
// Called on the IPC thread:
@@ -809,13 +811,13 @@ void ResourceMessageFilter::OnFreeTransportDIB(
#endif
void ResourceMessageFilter::OnOpenChannelToExtension(
- const std::string& extension_id, int* channel_id) {
- *channel_id = ExtensionMessageService::GetInstance()->
+ const std::string& extension_id, int* port_id) {
+ *port_id = ExtensionMessageService::GetInstance()->
OpenChannelToExtension(extension_id, this);
}
void ResourceMessageFilter::OnExtensionPostMessage(
- int channel_id, const std::string& message) {
- ExtensionMessageService::GetInstance()->PostMessageToExtension(
- channel_id, message);
+ int port_id, const std::string& message) {
+ ExtensionMessageService::GetInstance()->
+ PostMessageFromRenderer(port_id, message, this);
}
diff --git a/chrome/browser/renderer_host/resource_message_filter.h b/chrome/browser/renderer_host/resource_message_filter.h
index 64a10ba..db7c60b 100644
--- a/chrome/browser/renderer_host/resource_message_filter.h
+++ b/chrome/browser/renderer_host/resource_message_filter.h
@@ -214,9 +214,8 @@ class ResourceMessageFilter : public IPC::ChannelProxy::MessageFilter,
TransportDIB::Handle* result);
void OnFreeTransportDIB(TransportDIB::Id dib_id);
- void OnOpenChannelToExtension(const std::string& extension_id,
- int* channel_id);
- void OnExtensionPostMessage(int channel_id, const std::string& message);
+ void OnOpenChannelToExtension(const std::string& extension_id, int* port_id);
+ void OnExtensionPostMessage(int port_id, const std::string& message);
// We have our own clipboard service because we want to access the clipboard
// on the IO thread instead of forwarding (possibly synchronous) messages to
diff --git a/chrome/common/ipc_message_utils.h b/chrome/common/ipc_message_utils.h
index ac3cbef..6e82d40 100644
--- a/chrome/common/ipc_message_utils.h
+++ b/chrome/common/ipc_message_utils.h
@@ -1247,9 +1247,12 @@ void GenerateLogData(const std::wstring& channel, const Message& message,
LogData* data);
// Used for synchronous messages.
-template <class SendParam, class ReplyParam>
+template <class SendParamType, class ReplyParamType>
class MessageWithReply : public SyncMessage {
public:
+ typedef SendParamType SendParam;
+ typedef ReplyParamType ReplyParam;
+
MessageWithReply(int32 routing_id, uint16 type,
const SendParam& send, const ReplyParam& reply)
: SyncMessage(routing_id, type, PRIORITY_NORMAL,
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index cc296a6..ece0cd1 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -528,11 +528,16 @@ IPC_BEGIN_MESSAGES(View)
int /* callback id */,
std::string /* response */)
- // Relay a message sent from a renderer to an extension process. channel_id
- // is a handle that can be used for sending a response.
- IPC_MESSAGE_ROUTED2(ViewMsg_HandleExtensionMessage,
- std::string /* message */,
- int /* channel_id */)
+ // Tell the extension process about a new channel that has been opened from a
+ // renderer. source_port_id identifies the port that the extension can
+ // respond to.
+ IPC_MESSAGE_CONTROL1(ViewMsg_ExtensionHandleConnect,
+ int /* source_port_id */)
+
+ // Send a javascript message to a renderer from the given port.
+ IPC_MESSAGE_CONTROL2(ViewMsg_ExtensionHandleMessage,
+ std::string /* message */,
+ int /* source_port_id */)
// Tell the renderer process all known extension function names.
IPC_MESSAGE_CONTROL1(ViewMsg_Extension_SetFunctionNames,
@@ -1280,16 +1285,16 @@ IPC_BEGIN_MESSAGES(ViewHost)
IPC_MESSAGE_ROUTED1(ViewHostMsg_UpdateFeedList,
ViewHostMsg_UpdateFeedList_Params)
- // Get a handle to a currently-running extension process for the extension
- // with the given ID. If no such extension is found, -1 is returned. The
- // handle can be used for sending messages to the extension.
+ // Get a port handle to a currently-running extension process for the
+ // extension with the given ID. If no such extension is found, -1 is
+ // returned. The handle can be used for sending messages to the extension.
IPC_SYNC_MESSAGE_CONTROL1_1(ViewHostMsg_OpenChannelToExtension,
std::string /* extension_id */,
- int /* channel_id */)
+ int /* port_id */)
// Send a message to an extension process. The handle is the value returned
// by ViewHostMsg_OpenChannelToExtension.
IPC_MESSAGE_CONTROL2(ViewHostMsg_ExtensionPostMessage,
- int /* channel_id */,
+ int /* port_id */,
std::string /* message */)
IPC_END_MESSAGES(ViewHost)
diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc
index 9301579..8254bc6 100755
--- a/chrome/renderer/extensions/renderer_extension_bindings.cc
+++ b/chrome/renderer/extensions/renderer_extension_bindings.cc
@@ -4,50 +4,54 @@
#include "chrome/renderer/extensions/renderer_extension_bindings.h"
+#include "base/basictypes.h"
+#include "base/singleton.h"
#include "chrome/common/render_messages.h"
+#include "chrome/common/resource_bundle.h"
#include "chrome/renderer/render_thread.h"
-#include "third_party/WebKit/WebKit/chromium/public/WebScriptSource.h"
+#include "grit/renderer_resources.h"
#include "webkit/glue/webframe.h"
-using WebKit::WebScriptSource;
-using WebKit::WebString;
+// Message passing API example (in a content script):
+// var extension =
+// new chromium.Extension('00123456789abcdef0123456789abcdef0123456');
+// var channel = extension.openChannel();
+// channel.postMessage('Can you hear me now?');
+// channel.onMessage = function(msg, port) {
+// alert('response=' + msg);
+// port.postMessage('I got your reponse');
+// }
namespace {
-const char* kExtensionName = "v8/RendererExtensionBindings";
+// Keep a list of contexts that have registered themselves with us. This lets
+// us know where to dispatch events when we receive them.
+typedef std::list< v8::Persistent<v8::Context> > ContextList;
+struct SingletonData {
+ ContextList contexts;
+ std::string js_source;
+
+ SingletonData() :
+ js_source(ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_RENDERER_EXTENSION_BINDINGS_JS).as_string()) {
+ }
+};
+ContextList& GetRegisteredContexts() {
+ return Singleton<SingletonData>::get()->contexts;
+}
+const char* GetSource() {
+ return Singleton<SingletonData>::get()->js_source.c_str();
+}
-const char* kExtensionScript =
- "var chromium = chromium || {};"
- "(function () {"
- " native function OpenChannelToExtension(id);"
- " native function PostMessage(channel_id, msg);"
- " chromium.Extension = function(id) {"
- " this.channel_id_ = OpenChannelToExtension(id);"
- " if (this.channel_id_ == -1)"
- " throw new Error('No such extension \"' + id + '\"');"
- " chromium.Extension.extensions_[this.channel_id_] = this;"
- " };"
- " chromium.Extension.extensions_ = {};"
- " chromium.Extension.dispatchOnMessage = function(msg, channel_id) {"
- // TODO(mpcomplete): port param for onMessage
- " var e = chromium.Extension.extensions_[channel_id];"
- " if (e && e.onMessage) e.onMessage(msg);"
- " if (chromium.Extension.onMessage) chromium.Extension.onMessage(msg);"
- " };"
- " chromium.Extension.prototype.postMessage = function(msg) {"
- " return PostMessage(this.channel_id_, msg);"
- " };"
- "})();";
+// We use the generic interface so that unit tests can inject a mock.
+RenderThreadBase* render_thread_ = NULL;
-// Message passing API example (in a content script):
-// var extension =
-// new chromium.Extension('00123456789abcdef0123456789abcdef0123456');
-// extension.postMessage('Can you hear me now?');
-// extension.onMessage = function(msg) { alert('response=' + msg); }
+const char* kExtensionName = "v8/RendererExtensionBindings";
+const char* kScriptAPI = "chromium.extensions.scriptAPI";
class ExtensionImpl : public v8::Extension {
public:
- ExtensionImpl() : v8::Extension(kExtensionName, kExtensionScript) {}
+ ExtensionImpl() : v8::Extension(kExtensionName, GetSource()) {}
~ExtensionImpl() {}
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
@@ -56,49 +60,131 @@ class ExtensionImpl : public v8::Extension {
return v8::FunctionTemplate::New(OpenChannelToExtension);
} else if (name->Equals(v8::String::New("PostMessage"))) {
return v8::FunctionTemplate::New(PostMessage);
+ } else if (name->Equals(v8::String::New("RegisterScriptAPI"))) {
+ return v8::FunctionTemplate::New(RegisterScriptAPI);
}
return v8::Handle<v8::FunctionTemplate>();
}
+
+ // Creates a new messaging channel to the given extension.
static v8::Handle<v8::Value> OpenChannelToExtension(
const v8::Arguments& args) {
if (args.Length() >= 1 && args[0]->IsString()) {
std::string id = *v8::String::Utf8Value(args[0]->ToString());
- int channel_id;
- RenderThread::current()->Send(
- new ViewHostMsg_OpenChannelToExtension(id, &channel_id));
- return v8::Integer::New(channel_id);
- // TODO(mpcomplete): should we associate channel_id with the frame it
- // came from, so we can run the onmessage handler in that context for
- // responses?
+ int port_id = -1;
+ render_thread_->Send(
+ new ViewHostMsg_OpenChannelToExtension(id, &port_id));
+ return v8::Integer::New(port_id);
}
return v8::Undefined();
}
+
+ // Sends a message along the given channel.
static v8::Handle<v8::Value> PostMessage(const v8::Arguments& args) {
if (args.Length() >= 2 && args[0]->IsInt32() && args[1]->IsString()) {
- int channel_id = args[1]->Int32Value();
+ int port_id = args[0]->Int32Value();
std::string message = *v8::String::Utf8Value(args[1]->ToString());
- RenderThread::current()->Send(
- new ViewHostMsg_ExtensionPostMessage(channel_id, message));
+ render_thread_->Send(
+ new ViewHostMsg_ExtensionPostMessage(port_id, message));
+ }
+ return v8::Undefined();
+ }
+
+ // This method is internal to the extension, and fulfills a dual purpose:
+ // 1. Keep track of which v8::Contexts have registered event listeners.
+ // 2. Registers a private variable on the context that we use for dispatching
+ // events as they come in, by calling designated methods.
+ static v8::Handle<v8::Value> RegisterScriptAPI(const v8::Arguments& args) {
+ if (args.Length() >= 1 && args[0]->IsObject()) {
+ v8::Persistent<v8::Context> context =
+ v8::Persistent<v8::Context>::New(v8::Context::GetCurrent());
+ GetRegisteredContexts().push_back(context);
+ context.MakeWeak(NULL, WeakContextCallback);
+ context->Global()->SetHiddenValue(v8::String::New(kScriptAPI), args[0]);
+ DCHECK(args[0]->ToObject()->Get(v8::String::New("dispatchOnConnect"))->
+ IsFunction());
+ DCHECK(args[0]->ToObject()->Get(v8::String::New("dispatchOnMessage"))->
+ IsFunction());
+ return v8::Undefined();
}
return v8::Undefined();
}
+
+ // Calls the given chromiumPrivate method in each registered context.
+ static void CallMethod(const std::string& method_name, int argc,
+ v8::Handle<v8::Value>* argv) {
+ for (ContextList::iterator it = GetRegisteredContexts().begin();
+ it != GetRegisteredContexts().end(); ++it) {
+ DCHECK(!it->IsEmpty());
+ v8::Context::Scope context_scope(*it);
+ v8::Local<v8::Object> global = (*it)->Global();
+
+ // Check if the window object is gone, which means this context's frame
+ // has been unloaded.
+ v8::Local<v8::Value> window = global->Get(v8::String::New("window"));
+ if (!window->IsObject())
+ continue;
+
+ // Retrieve our hidden variable and call the method on it.
+ v8::Local<v8::Value> script_api = global->GetHiddenValue(
+ v8::String::New(kScriptAPI));
+ if (!script_api->IsObject()) {
+ NOTREACHED();
+ continue;
+ }
+
+ v8::Handle<v8::Value> function_obj = script_api->ToObject()->Get(
+ v8::String::New(method_name.c_str()));
+ if (!function_obj->IsFunction()) {
+ NOTREACHED();
+ continue;
+ }
+
+ v8::Handle<v8::Function> function =
+ v8::Handle<v8::Function>::Cast(function_obj);
+ if (!function.IsEmpty())
+ function->Call(v8::Object::New(), argc, argv);
+ }
+ }
+
+ // Called when a registered context is garbage collected.
+ static void WeakContextCallback(v8::Persistent<v8::Value> obj, void*) {
+ ContextList::iterator it = std::find(GetRegisteredContexts().begin(),
+ GetRegisteredContexts().end(), obj);
+ if (it == GetRegisteredContexts().end()) {
+ NOTREACHED();
+ return;
+ }
+
+ it->Dispose();
+ it->Clear();
+ GetRegisteredContexts().erase(it);
+ }
};
} // namespace
namespace extensions_v8 {
-v8::Extension* RendererExtensionBindings::Get() {
+v8::Extension* RendererExtensionBindings::Get(RenderThreadBase* render_thread) {
+ render_thread_ = render_thread;
return new ExtensionImpl();
}
-void RendererExtensionBindings::HandleExtensionMessage(
- WebFrame* webframe, const std::string& message, int channel_id) {
- // TODO(mpcomplete): escape message
- std::string script = StringPrintf(
- "void(chromium.Extension.dispatchOnMessage(\"%s\", %d))",
- message.c_str(), channel_id);
- webframe->ExecuteScript(WebScriptSource(WebString::fromUTF8(script)));
+void RendererExtensionBindings::HandleConnect(int port_id) {
+ v8::HandleScope handle_scope;
+ v8::Handle<v8::Value> argv[1];
+ argv[0] = v8::Integer::New(port_id);
+ ExtensionImpl::CallMethod("dispatchOnConnect", arraysize(argv), argv);
+}
+
+void RendererExtensionBindings::HandleMessage(const std::string& message,
+ int port_id) {
+ v8::HandleScope handle_scope;
+ v8::Handle<v8::Value> argv[2];
+ argv[0] = v8::String::New(message.c_str());
+ argv[1] = v8::Integer::New(port_id);
+ ExtensionImpl::CallMethod("dispatchOnMessage", arraysize(argv), argv);
}
} // namespace extensions_v8
diff --git a/chrome/renderer/extensions/renderer_extension_bindings.h b/chrome/renderer/extensions/renderer_extension_bindings.h
index 284f9bb..7c951ec 100755
--- a/chrome/renderer/extensions/renderer_extension_bindings.h
+++ b/chrome/renderer/extensions/renderer_extension_bindings.h
@@ -9,16 +9,23 @@
#include <string>
+class RenderThreadBase;
class WebFrame;
namespace extensions_v8 {
-// This class adds extension-related javascript bindings to a renderer.
+// This class adds extension-related javascript bindings to a renderer. It is
+// used by both web renderers and extension processes.
class RendererExtensionBindings {
public:
- static v8::Extension* Get();
- static void HandleExtensionMessage(
- WebFrame* webframe, const std::string& message, int channel_id);
+ static v8::Extension* Get(RenderThreadBase* render_thread);
+
+ // Notify any listeners that a message channel has been opened to this
+ // process.
+ static void HandleConnect(int port_id);
+
+ // Dispatch the given message sent on this channel.
+ static void HandleMessage(const std::string& message, int port_id);
};
} // namespace extensions_v8
diff --git a/chrome/renderer/mock_render_thread.cc b/chrome/renderer/mock_render_thread.cc
index 9e71a53..19fdd48 100644
--- a/chrome/renderer/mock_render_thread.cc
+++ b/chrome/renderer/mock_render_thread.cc
@@ -71,6 +71,8 @@ void MockRenderThread::OnMessageReceived(const IPC::Message& msg) {
bool msg_is_ok = true;
IPC_BEGIN_MESSAGE_MAP_EX(MockRenderThread, msg, msg_is_ok)
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnMsgCreateWidget);
+ IPC_MESSAGE_HANDLER(ViewHostMsg_OpenChannelToExtension,
+ OnMsgOpenChannelToExtension);
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP_EX()
}
@@ -82,3 +84,8 @@ void MockRenderThread::OnMsgCreateWidget(int opener_id,
opener_id_ = opener_id;
*route_id = routing_id_;
}
+
+void MockRenderThread::OnMsgOpenChannelToExtension(
+ const std::string& extension_id, int* channel_id) {
+ *channel_id = 0;
+}
diff --git a/chrome/renderer/mock_render_thread.h b/chrome/renderer/mock_render_thread.h
index fb47a15..a89822a6 100644
--- a/chrome/renderer/mock_render_thread.h
+++ b/chrome/renderer/mock_render_thread.h
@@ -69,6 +69,10 @@ class MockRenderThread : public RenderThreadBase {
bool activatable,
int* route_id);
+ // The callee expects to be returned a valid channel_id.
+ void OnMsgOpenChannelToExtension(const std::string& extension_id,
+ int* channel_id);
+
IPC::TestSink sink_;
// Routing id what will be assigned to the Widget.
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index 2e4ad09..e161438 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -166,6 +166,10 @@ void RenderThread::OnControlMessageReceived(const IPC::Message& msg) {
OnGetCacheResourceStats)
IPC_MESSAGE_HANDLER(ViewMsg_UserScripts_NewScripts,
OnUpdateUserScripts)
+ IPC_MESSAGE_HANDLER(ViewMsg_ExtensionHandleConnect,
+ OnExtensionHandleConnect)
+ IPC_MESSAGE_HANDLER(ViewMsg_ExtensionHandleMessage,
+ OnExtensionHandleMessage)
IPC_MESSAGE_HANDLER(ViewMsg_Extension_SetFunctionNames,
OnSetExtensionFunctionNames)
IPC_END_MESSAGE_MAP()
@@ -265,7 +269,8 @@ void RenderThread::EnsureWebKitInitialized() {
WebKit::registerExtension(extensions_v8::GearsExtension::Get());
WebKit::registerExtension(extensions_v8::IntervalExtension::Get());
- WebKit::registerExtension(extensions_v8::RendererExtensionBindings::Get());
+ WebKit::registerExtension(
+ extensions_v8::RendererExtensionBindings::Get(this));
WebKit::registerExtension(extensions_v8::ExtensionProcessBindings::Get(),
WebKit::WebString::fromUTF8(chrome::kExtensionScheme));
@@ -280,3 +285,12 @@ void RenderThread::EnsureWebKitInitialized() {
WebKit::enableWebWorkers();
}
}
+
+void RenderThread::OnExtensionHandleConnect(int port_id) {
+ extensions_v8::RendererExtensionBindings::HandleConnect(port_id);
+}
+
+void RenderThread::OnExtensionHandleMessage(const std::string& message,
+ int port_id) {
+ extensions_v8::RendererExtensionBindings::HandleMessage(message, port_id);
+}
diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h
index 0c1290c..5340b0e 100644
--- a/chrome/renderer/render_thread.h
+++ b/chrome/renderer/render_thread.h
@@ -122,6 +122,9 @@ class RenderThread : public RenderThreadBase,
// Send all histograms to browser.
void OnGetRendererHistograms();
+ void OnExtensionHandleConnect(int channel_id);
+ void OnExtensionHandleMessage(const std::string& message, int channel_id);
+
// Gather usage statistics from the in-memory cache and inform our host.
// These functions should be call periodically so that the host can make
// decisions about how to allocation resources using current information.
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index 6ed31e2..2feecfc 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -32,7 +32,6 @@
#include "chrome/renderer/devtools_agent.h"
#include "chrome/renderer/devtools_client.h"
#include "chrome/renderer/extensions/extension_process_bindings.h"
-#include "chrome/renderer/extensions/renderer_extension_bindings.h"
#include "chrome/renderer/localized_error.h"
#include "chrome/renderer/media/audio_renderer_impl.h"
#include "chrome/renderer/render_process.h"
@@ -428,8 +427,6 @@ void RenderView::OnMessageReceived(const IPC::Message& message) {
OnAudioStreamStateChanged)
IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamVolume, OnAudioStreamVolume)
IPC_MESSAGE_HANDLER(ViewMsg_MoveOrResizeStarted, OnMoveOrResizeStarted)
- IPC_MESSAGE_HANDLER(ViewMsg_HandleExtensionMessage,
- OnHandleExtensionMessage)
IPC_MESSAGE_HANDLER(ViewMsg_ExtensionResponse, OnExtensionResponse)
IPC_MESSAGE_HANDLER(ViewMsg_RequestSelectionText, OnRequestSelectionText)
@@ -2967,13 +2964,6 @@ void RenderView::OnResize(const gfx::Size& new_size,
RenderWidget::OnResize(new_size, resizer_rect);
}
-void RenderView::OnHandleExtensionMessage(const std::string& message,
- int channel_id) {
- if (webview() && webview()->GetMainFrame())
- extensions_v8::RendererExtensionBindings::HandleExtensionMessage(
- webview()->GetMainFrame(), message, channel_id);
-}
-
void RenderView::SendExtensionRequest(const std::string& name,
const std::string& args,
int callback_id,
diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h
index 25692f2..a9c8915 100644
--- a/chrome/renderer/render_view.h
+++ b/chrome/renderer/render_view.h
@@ -594,8 +594,6 @@ class RenderView : public RenderWidget,
// Notification of volume property of an audio output stream.
void OnAudioStreamVolume(int stream_id, double left, double right);
- void OnHandleExtensionMessage(const std::string& message, int channel_id);
-
// Sends the selection text to the browser.
void OnRequestSelectionText();
diff --git a/chrome/renderer/render_view_unittest.cc b/chrome/renderer/render_view_unittest.cc
index 88c4fa7..c27bcbf 100644
--- a/chrome/renderer/render_view_unittest.cc
+++ b/chrome/renderer/render_view_unittest.cc
@@ -4,6 +4,7 @@
#include "base/scoped_ptr.h"
#include "chrome/common/render_messages.h"
+#include "chrome/renderer/extensions/renderer_extension_bindings.h"
#include "chrome/renderer/mock_render_process.h"
#include "chrome/renderer/mock_render_thread.h"
#include "chrome/renderer/render_view.h"
@@ -65,6 +66,8 @@ class RenderViewTest : public testing::Test {
// testing::Test
virtual void SetUp() {
WebKit::initialize(&webkitclient_);
+ WebKit::registerExtension(
+ extensions_v8::RendererExtensionBindings::Get(&render_thread_));
mock_process_.reset(new MockProcess());
@@ -367,3 +370,90 @@ TEST_F(RenderViewTest, OnSetTextDirection) {
EXPECT_EQ(output, kTextDirection[i].expected_result);
}
}
+
+// Tests that the bindings for opening a channel to an extension and sending
+// and receiving messages through that channel all works.
+TEST_F(RenderViewTest, ExtensionMessagesOpenChannel) {
+ render_thread_.sink().ClearMessages();
+ LoadHTML("<body></body>");
+ ExecuteJavaScript(
+ "var e = new chromium.Extension('foobar');"
+ "var port = e.openChannel();"
+ "port.onMessage = doOnMessage;"
+ "port.postMessage('content ready');"
+ "function doOnMessage(msg, port) {"
+ " alert('content got: ' + msg);"
+ "}");
+
+ // Verify that we opened a channel and sent a message through it.
+ const IPC::Message* open_channel_msg =
+ render_thread_.sink().GetUniqueMessageMatching(
+ ViewHostMsg_OpenChannelToExtension::ID);
+ EXPECT_TRUE(open_channel_msg);
+
+ const IPC::Message* post_msg =
+ render_thread_.sink().GetUniqueMessageMatching(
+ ViewHostMsg_ExtensionPostMessage::ID);
+ EXPECT_TRUE(post_msg);
+ ViewHostMsg_ExtensionPostMessage::Param post_params;
+ ViewHostMsg_ExtensionPostMessage::Read(post_msg, &post_params);
+ EXPECT_EQ("content ready", post_params.b);
+
+ // Now simulate getting a message back from the other side.
+ render_thread_.sink().ClearMessages();
+ const int kPortId = 0;
+ extensions_v8::RendererExtensionBindings::HandleMessage("42", kPortId);
+
+ // Verify that we got it.
+ const IPC::Message* alert_msg =
+ render_thread_.sink().GetUniqueMessageMatching(
+ ViewHostMsg_RunJavaScriptMessage::ID);
+ EXPECT_TRUE(alert_msg);
+ void* iter = IPC::SyncMessage::GetDataIterator(alert_msg);
+ ViewHostMsg_RunJavaScriptMessage::SendParam alert_param;
+ IPC::ReadParam(alert_msg, &iter, &alert_param);
+ EXPECT_EQ(L"content got: 42", alert_param.a);
+}
+
+// Tests that the bindings for handling a new channel connection and sending
+// and receiving messages through that channel all works.
+TEST_F(RenderViewTest, ExtensionMessagesOnConnect) {
+ LoadHTML("<body></body>");
+ ExecuteJavaScript(
+ "chromium.addConnectListener(function (port) {"
+ " port.onMessage = doOnMessage;"
+ " port.postMessage('onconnect');"
+ " });"
+ "function doOnMessage(msg, port) {"
+ " alert('got: ' + msg);"
+ "}");
+
+ render_thread_.sink().ClearMessages();
+
+ // Simulate a new connection being opened.
+ const int kPortId = 0;
+ extensions_v8::RendererExtensionBindings::HandleConnect(kPortId);
+
+ // Verify that we handled the new connection by posting a message.
+ const IPC::Message* post_msg =
+ render_thread_.sink().GetUniqueMessageMatching(
+ ViewHostMsg_ExtensionPostMessage::ID);
+ EXPECT_TRUE(post_msg);
+ ViewHostMsg_ExtensionPostMessage::Param post_params;
+ ViewHostMsg_ExtensionPostMessage::Read(post_msg, &post_params);
+ EXPECT_EQ("onconnect", post_params.b);
+
+ // Now simulate getting a message back from the channel opener.
+ render_thread_.sink().ClearMessages();
+ extensions_v8::RendererExtensionBindings::HandleMessage("42", kPortId);
+
+ // Verify that we got it.
+ const IPC::Message* alert_msg =
+ render_thread_.sink().GetUniqueMessageMatching(
+ ViewHostMsg_RunJavaScriptMessage::ID);
+ EXPECT_TRUE(alert_msg);
+ void* iter = IPC::SyncMessage::GetDataIterator(alert_msg);
+ ViewHostMsg_RunJavaScriptMessage::SendParam alert_param;
+ IPC::ReadParam(alert_msg, &iter, &alert_param);
+ EXPECT_EQ(L"got: 42", alert_param.a);
+}
diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd
index 48bd7b4..e805630 100755
--- a/chrome/renderer/renderer_resources.grd
+++ b/chrome/renderer/renderer_resources.grd
@@ -14,6 +14,7 @@
<include name="IDR_ERROR_NO_DETAILS_HTML" file="resources\error_no_details.html" type="BINDATA" />
<include name="IDR_GREASEMONKEY_API_JS" file="resources\greasemonkey_api.js" type="BINDATA" />
<include name="IDR_EXTENSION_PROCESS_BINDINGS_JS" file="resources\extension_process_bindings.js" type="BINDATA" />
+ <include name="IDR_RENDERER_EXTENSION_BINDINGS_JS" file="resources\renderer_extension_bindings.js" type="BINDATA" />
</includes>
</release>
</grit> \ No newline at end of file
diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js
new file mode 100644
index 0000000..401f82c
--- /dev/null
+++ b/chrome/renderer/resources/renderer_extension_bindings.js
@@ -0,0 +1,93 @@
+var chromium = chromium || {};
+(function () {
+ native function OpenChannelToExtension(id);
+ native function PostMessage(portId, msg);
+ native function RegisterScriptAPI(private);
+ // chromium: Public API.
+
+ // Represents info we know about a chrome extension.
+ chromium.Extension = function(id) {
+ this.id_ = id;
+ };
+
+ // Opens a channel to the extension for message passing.
+ chromium.Extension.prototype.openChannel = function() {
+ portId = OpenChannelToExtension(this.id_);
+ if (portId == -1)
+ throw new Error('No such extension \"' + this.id_ + '\"');
+ return new Port(portId);
+ };
+
+ // Adds a listener that fires when a renderer opens a channel to talk
+ // to us.
+ chromium.addConnectListener = function(callback) {
+ chromium.addEventListener('channel-connect',
+ function (e) { callback(e.data.port); });
+ };
+
+ // Adds a generic event listener.
+ chromium.addEventListener = function(type, callback) {
+ var listeners = getPrivateData().eventListeners;
+ if (!listeners[type])
+ listeners[type] = [];
+ listeners[type].push(callback);
+ };
+
+ // Dispatches the given event to anyone listening for that event.
+ chromium.dispatchEvent = function(type, data) {
+ var event = {type: type, data: data};
+ var listeners = getPrivateData().eventListeners;
+ for (var i in listeners[type]) {
+ listeners[type][i](event);
+ }
+ };
+
+ // Private API.
+
+ // Always access privateData through this function, to ensure that we
+ // have registered our native API. We do this lazily to avoid registering
+ // on pages that don't use these bindings.
+ function getPrivateData() {
+ if (!scriptAPI.registered_) {
+ RegisterScriptAPI(scriptAPI);
+ scriptAPI.registered_ = true;
+ }
+ return privateData;
+ }
+ var privateData = {
+ eventListeners: {},
+ ports: {}
+ };
+
+ // Represents a port through which we can send messages to another process.
+ var Port = function(portId) {
+ // TODO(mpcomplete): we probably want to hide this portId_ so
+ // it can't be guessed at. One idea is to expose v8's SetHiddenValue
+ // to our extension.
+ this.portId_ = portId;
+ getPrivateData().ports[portId] = this;
+ };
+
+ // Sends a message to the other side of the channel.
+ Port.prototype.postMessage = function(msg) {
+ PostMessage(this.portId_, msg);
+ };
+
+ // Script API: javascript APIs exposed to C++ only.
+ // This object allows our native code to call back to us through a
+ // private interface that isn't exposed to web content.
+ var scriptAPI = {};
+
+ // Called by native code when a channel has been opened to this process.
+ scriptAPI.dispatchOnConnect = function(portId) {
+ chromium.dispatchEvent('channel-connect', {port: new Port(portId)});
+ };
+
+ // Called by native code when a message has been sent over the given
+ // channel.
+ scriptAPI.dispatchOnMessage = function(msg, portId) {
+ var port = getPrivateData().ports[portId];
+ if (port && port.onMessage)
+ port.onMessage(msg, port);
+ };
+})();
diff --git a/chrome/test/data/extensions/good/extension1/1/toolstrip1.html b/chrome/test/data/extensions/good/extension1/1/toolstrip1.html
index b4fe99b..66ba2b3 100644
--- a/chrome/test/data/extensions/good/extension1/1/toolstrip1.html
+++ b/chrome/test/data/extensions/good/extension1/1/toolstrip1.html
@@ -15,8 +15,17 @@ body {
<div class="content">
<script>
alert('Sir, I exist');
- chromium.Extension.onMessage = function(msg) {
- alert('Incoming: ' + msg);
+
+ chromium.addConnectListener(function (port) {
+ port.onMessage = doOnMessage;
+ port.postMessage('extension onconnect');
+ });
+
+ function doOnMessage(msg, port) {
+ if (!port.didRespond) {
+ port.postMessage('extension msg ack: ' + msg);
+ port.didRespond = true;
+ }
}
</script>
<button onclick="alert('clicked')">HTML button</button>