diff options
author | mpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-02 23:56:11 +0000 |
---|---|---|
committer | mpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-02 23:56:11 +0000 |
commit | 75e5a879a500897b2409c49d4ef205e2e954c9fd (patch) | |
tree | 166ca53e8cc29520402efd7cc6ee2804edf744eb /chrome/renderer | |
parent | 720ac73783ea732c130ff6d11dffa41919a25809 (diff) | |
download | chromium_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-x | chrome/renderer/extensions/renderer_extension_bindings.cc | 186 | ||||
-rwxr-xr-x | chrome/renderer/extensions/renderer_extension_bindings.h | 15 | ||||
-rw-r--r-- | chrome/renderer/mock_render_thread.cc | 7 | ||||
-rw-r--r-- | chrome/renderer/mock_render_thread.h | 4 | ||||
-rw-r--r-- | chrome/renderer/render_thread.cc | 16 | ||||
-rw-r--r-- | chrome/renderer/render_thread.h | 3 | ||||
-rw-r--r-- | chrome/renderer/render_view.cc | 10 | ||||
-rw-r--r-- | chrome/renderer/render_view.h | 2 | ||||
-rw-r--r-- | chrome/renderer/render_view_unittest.cc | 90 | ||||
-rwxr-xr-x | chrome/renderer/renderer_resources.grd | 1 | ||||
-rw-r--r-- | chrome/renderer/resources/renderer_extension_bindings.js | 93 |
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); + }; +})(); |