summaryrefslogtreecommitdiffstats
path: root/chrome/renderer
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 /chrome/renderer
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
Diffstat (limited to 'chrome/renderer')
-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
11 files changed, 360 insertions, 67 deletions
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);
+ };
+})();