diff options
author | mpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-08 18:35:34 +0000 |
---|---|---|
committer | mpcomplete@google.com <mpcomplete@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-08 18:35:34 +0000 |
commit | a40caa97fbdae3760f52f95f6b265bd1f39b19ae (patch) | |
tree | b98dceab49c4efb854c9923660735cbf96addbcd | |
parent | 1b812ea42f713908a9034fcf2a26e8d4a8a86a04 (diff) | |
download | chromium_src-a40caa97fbdae3760f52f95f6b265bd1f39b19ae.zip chromium_src-a40caa97fbdae3760f52f95f6b265bd1f39b19ae.tar.gz chromium_src-a40caa97fbdae3760f52f95f6b265bd1f39b19ae.tar.bz2 |
Add aa's Event class to our javascript bindings and use it in our extension
message passing API.
Review URL: http://codereview.chromium.org/62069
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13371 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.cc | 1 | ||||
-rw-r--r-- | chrome/chrome.gyp | 3 | ||||
-rwxr-xr-x | chrome/renderer/extensions/bindings_utils.h | 28 | ||||
-rwxr-xr-x | chrome/renderer/extensions/event_bindings.cc | 119 | ||||
-rwxr-xr-x | chrome/renderer/extensions/event_bindings.h | 25 | ||||
-rwxr-xr-x | chrome/renderer/extensions/renderer_extension_bindings.cc | 118 | ||||
-rwxr-xr-x | chrome/renderer/extensions/renderer_extension_bindings.h | 1 | ||||
-rw-r--r-- | chrome/renderer/render_thread.cc | 14 | ||||
-rw-r--r-- | chrome/renderer/render_view_unittest.cc | 12 | ||||
-rw-r--r-- | chrome/renderer/renderer.vcproj | 12 | ||||
-rwxr-xr-x | chrome/renderer/renderer_resources.grd | 1 | ||||
-rw-r--r-- | chrome/renderer/resources/event_bindings.js | 120 | ||||
-rw-r--r-- | chrome/renderer/resources/renderer_extension_bindings.js | 113 | ||||
-rw-r--r-- | chrome/test/data/extensions/good/extension1/1/toolstrip1.html | 4 |
14 files changed, 383 insertions, 188 deletions
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc index 62498a0..807e457 100644 --- a/chrome/browser/renderer_host/browser_render_process_host.cc +++ b/chrome/browser/renderer_host/browser_render_process_host.cc @@ -267,6 +267,7 @@ bool BrowserRenderProcessHost::Init() { switches::kEnableVideo, switches::kEnableWebWorkers, switches::kEnableStatsTable, + switches::kEnableExtensions, }; for (size_t i = 0; i < arraysize(switch_names); ++i) { diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index cf3a9c3..99dee76 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1410,6 +1410,9 @@ 'app/breakpad_mac.h', 'renderer/automation/dom_automation_controller.cc', 'renderer/automation/dom_automation_controller.h', + 'renderer/extensions/bindings_utils.h', + 'renderer/extensions/event_bindings.cc', + 'renderer/extensions/event_bindings.h', 'renderer/extensions/extension_process_bindings.cc', 'renderer/extensions/extension_process_bindings.h', 'renderer/extensions/renderer_extension_bindings.cc', diff --git a/chrome/renderer/extensions/bindings_utils.h b/chrome/renderer/extensions/bindings_utils.h new file mode 100755 index 0000000..9e7d213 --- /dev/null +++ b/chrome/renderer/extensions/bindings_utils.h @@ -0,0 +1,28 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_ +#define CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_ + +#include "base/singleton.h" +#include "chrome/common/resource_bundle.h" + +#include <string> + +template<int kResourceId> +struct StringResourceTemplate { + StringResourceTemplate() + : resource(ResourceBundle::GetSharedInstance().GetRawDataResource( + kResourceId).as_string()) { + } + std::string resource; +}; + +template<int kResourceId> +const char* GetStringResource() { + return + Singleton< StringResourceTemplate<kResourceId> >::get()->resource.c_str(); +} + +#endif // CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_ diff --git a/chrome/renderer/extensions/event_bindings.cc b/chrome/renderer/extensions/event_bindings.cc new file mode 100755 index 0000000..0b45abba --- /dev/null +++ b/chrome/renderer/extensions/event_bindings.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/renderer/extensions/event_bindings.h" + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "chrome/renderer/extensions/bindings_utils.h" +#include "chrome/renderer/extensions/event_bindings.h" +#include "chrome/renderer/render_thread.h" +#include "grit/renderer_resources.h" + +namespace { + +// 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 ExtensionData { + ContextList contexts; +}; +ContextList& GetRegisteredContexts() { + return Singleton<ExtensionData>::get()->contexts; +} + +class ExtensionImpl : public v8::Extension { + public: + ExtensionImpl() + : v8::Extension(EventBindings::kName, + GetStringResource<IDR_EVENT_BINDINGS_JS>()) { + } + ~ExtensionImpl() {} + + virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( + v8::Handle<v8::String> name) { + if (name->Equals(v8::String::New("AttachEvent"))) { + return v8::FunctionTemplate::New(AttachEvent); + } else if (name->Equals(v8::String::New("DetachEvent"))) { + return v8::FunctionTemplate::New(DetachEvent); + } + return v8::Handle<v8::FunctionTemplate>(); + } + + // Attach an event name to an object. + // TODO(mpcomplete): I'm just using this to register the v8 Context right now. + // The idea is to eventually notify the browser about what events are being + // listened to, so it can dispatch appropriately. + static v8::Handle<v8::Value> AttachEvent(const v8::Arguments& args) { + const char* kContextRegistered = "chromium.extension.contextRegistered"; + + v8::Persistent<v8::Context> context = + v8::Persistent<v8::Context>::New(v8::Context::GetCurrent()); + v8::Local<v8::Object> global = context->Global(); + v8::Local<v8::Value> is_registered = global->GetHiddenValue( + v8::String::New(kContextRegistered)); + if (is_registered.IsEmpty() || is_registered->IsUndefined()) { + GetRegisteredContexts().push_back(context); + context.MakeWeak(NULL, WeakContextCallback); + global->SetHiddenValue( + v8::String::New(kContextRegistered), v8::Boolean::New(true)); + } + return v8::Undefined(); + } + + // TODO(mpcomplete): Implement this. This should do the reverse of + // AttachEvent, when that actually does what we plan on. + static v8::Handle<v8::Value> DetachEvent(const v8::Arguments& args) { + return v8::Undefined(); + } + + // 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 + +const char* EventBindings::kName = "chrome/EventBindings"; + +v8::Extension* EventBindings::Get() { + return new ExtensionImpl(); +} + +void EventBindings::CallFunction(const std::string& function_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; + + v8::Local<v8::Script> script = v8::Script::Compile( + v8::String::New(function_name.c_str())); + v8::Local<v8::Value> function_obj = script->Run(); + if (!function_obj->IsFunction()) + continue; + + v8::Local<v8::Function> function = + v8::Local<v8::Function>::Cast(function_obj); + if (!function.IsEmpty()) + function->Call(v8::Object::New(), argc, argv); + } +} diff --git a/chrome/renderer/extensions/event_bindings.h b/chrome/renderer/extensions/event_bindings.h new file mode 100755 index 0000000..dd91811 --- /dev/null +++ b/chrome/renderer/extensions/event_bindings.h @@ -0,0 +1,25 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_RENDERER_EXTENSIONS_EVENT_BINDINGS_H_ +#define CHROME_RENDERER_EXTENSIONS_EVENT_BINDINGS_H_ + +#include "v8/include/v8.h" + +#include <string> + +// This class deals with the javascript bindings related to Event objects. +class EventBindings { + public: + static const char* kName; // The v8::Extension name, for dependencies. + static v8::Extension* Get(); + + // Calls the given function in each registered context which is listening + // for events. The function can be an object property, ie: + // "chromium.Event.dispatch_". + static void CallFunction(const std::string& function_name, int argc, + v8::Handle<v8::Value>* argv); +}; + +#endif // CHROME_RENDERER_EXTENSIONS_EVENT_BINDINGS_H_ diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc index 8254bc6..572b00c 100755 --- a/chrome/renderer/extensions/renderer_extension_bindings.cc +++ b/chrome/renderer/extensions/renderer_extension_bindings.cc @@ -5,53 +5,38 @@ #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/extensions/bindings_utils.h" +#include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/render_thread.h" #include "grit/renderer_resources.h" -#include "webkit/glue/webframe.h" // 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) { +// var port = extension.connect(); +// port.postMessage('Can you hear me now?'); +// port.onmessage.addListener(function(msg, port) { // alert('response=' + msg); // port.postMessage('I got your reponse'); -// } +// }); namespace { -// 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(); -} - // We use the generic interface so that unit tests can inject a mock. RenderThreadBase* render_thread_ = NULL; const char* kExtensionName = "v8/RendererExtensionBindings"; -const char* kScriptAPI = "chromium.extensions.scriptAPI"; +const char* kExtensionDeps[] = { EventBindings::kName }; class ExtensionImpl : public v8::Extension { public: - ExtensionImpl() : v8::Extension(kExtensionName, GetSource()) {} + ExtensionImpl() + : v8::Extension(kExtensionName, + GetStringResource<IDR_RENDERER_EXTENSION_BINDINGS_JS>(), + arraysize(kExtensionDeps), kExtensionDeps) { + } ~ExtensionImpl() {} virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( @@ -60,8 +45,6 @@ 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>(); } @@ -89,77 +72,6 @@ class ExtensionImpl : public v8::Extension { } 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 @@ -175,7 +87,8 @@ 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); + EventBindings::CallFunction("chromium.Port.dispatchOnConnect_", + arraysize(argv), argv); } void RendererExtensionBindings::HandleMessage(const std::string& message, @@ -184,7 +97,8 @@ void RendererExtensionBindings::HandleMessage(const std::string& message, 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); + EventBindings::CallFunction("chromium.Port.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 7c951ec..b0d04de 100755 --- a/chrome/renderer/extensions/renderer_extension_bindings.h +++ b/chrome/renderer/extensions/renderer_extension_bindings.h @@ -10,7 +10,6 @@ #include <string> class RenderThreadBase; -class WebFrame; namespace extensions_v8 { diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc index 0b3c171..ea0cf94 100644 --- a/chrome/renderer/render_thread.cc +++ b/chrome/renderer/render_thread.cc @@ -25,6 +25,7 @@ #include "chrome/plugin/plugin_channel_base.h" #include "webkit/glue/weburlrequest.h" #endif +#include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/extensions/extension_process_bindings.h" #include "chrome/renderer/extensions/renderer_extension_bindings.h" #include "chrome/renderer/loadtimes_extension_bindings.h" @@ -286,13 +287,16 @@ void RenderThread::EnsureWebKitInitialized() { WebKit::registerExtension(extensions_v8::GearsExtension::Get()); WebKit::registerExtension(extensions_v8::IntervalExtension::Get()); WebKit::registerExtension(extensions_v8::LoadTimesExtension::Get()); - WebKit::registerExtension( - extensions_v8::RendererExtensionBindings::Get(this)); - - WebKit::registerExtension(extensions_v8::ExtensionProcessBindings::Get(), - WebKit::WebString::fromUTF8(chrome::kExtensionScheme)); const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kEnableExtensions)) { + WebKit::registerExtension(EventBindings::Get()); + WebKit::registerExtension( + extensions_v8::RendererExtensionBindings::Get(this)); + WebKit::registerExtension(extensions_v8::ExtensionProcessBindings::Get(), + WebKit::WebString::fromUTF8(chrome::kExtensionScheme)); + } + if (command_line.HasSwitch(switches::kPlaybackMode) || command_line.HasSwitch(switches::kRecordMode)) { WebKit::registerExtension(extensions_v8::PlaybackExtension::Get()); diff --git a/chrome/renderer/render_view_unittest.cc b/chrome/renderer/render_view_unittest.cc index c27bcbf..95a584bb 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/event_bindings.h" #include "chrome/renderer/extensions/renderer_extension_bindings.h" #include "chrome/renderer/mock_render_process.h" #include "chrome/renderer/mock_render_thread.h" @@ -66,6 +67,7 @@ class RenderViewTest : public testing::Test { // testing::Test virtual void SetUp() { WebKit::initialize(&webkitclient_); + WebKit::registerExtension(EventBindings::Get()); WebKit::registerExtension( extensions_v8::RendererExtensionBindings::Get(&render_thread_)); @@ -378,8 +380,8 @@ TEST_F(RenderViewTest, ExtensionMessagesOpenChannel) { LoadHTML("<body></body>"); ExecuteJavaScript( "var e = new chromium.Extension('foobar');" - "var port = e.openChannel();" - "port.onMessage = doOnMessage;" + "var port = e.connect();" + "port.onmessage.addListener(doOnMessage);" "port.postMessage('content ready');" "function doOnMessage(msg, port) {" " alert('content got: ' + msg);" @@ -420,10 +422,10 @@ TEST_F(RenderViewTest, ExtensionMessagesOpenChannel) { TEST_F(RenderViewTest, ExtensionMessagesOnConnect) { LoadHTML("<body></body>"); ExecuteJavaScript( - "chromium.addConnectListener(function (port) {" - " port.onMessage = doOnMessage;" + "chromium.onconnect.addListener(function (port) {" + " port.onmessage.addListener(doOnMessage);" " port.postMessage('onconnect');" - " });" + "});" "function doOnMessage(msg, port) {" " alert('got: ' + msg);" "}"); diff --git a/chrome/renderer/renderer.vcproj b/chrome/renderer/renderer.vcproj index d9ee179..fca563e 100644 --- a/chrome/renderer/renderer.vcproj +++ b/chrome/renderer/renderer.vcproj @@ -185,11 +185,19 @@ Name="extensions" > <File - RelativePath=".\extensions\extension_process_bindings.cc" + RelativePath=".\extensions\bindings_utils.h" + > + </File> + <File + RelativePath=".\extensions\event_bindings.cc" > </File> <File - RelativePath=".\extensions\extension_process_bindings.h" + RelativePath=".\extensions\event_bindings.h" + > + </File> + <File + RelativePath=".\extensions\extension_process_bindings.cc" > </File> <File diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index e805630..5589cc0 100755 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -15,6 +15,7 @@ <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" /> + <include name="IDR_EVENT_BINDINGS_JS" file="resources\event_bindings.js" type="BINDATA" /> </includes> </release> </grit>
\ No newline at end of file diff --git a/chrome/renderer/resources/event_bindings.js b/chrome/renderer/resources/event_bindings.js new file mode 100644 index 0000000..de5b3a9 --- /dev/null +++ b/chrome/renderer/resources/event_bindings.js @@ -0,0 +1,120 @@ +var chromium = chromium || {}; +(function () { + native function AttachEvent(eventName); + native function DetachEvent(eventName); + + // Event object. If opt_eventName is provided, this object represents + // the unique instance of that named event, and dispatching an event + // with that name will route through this object's listeners. + // + // Example: + // chromium.ontabchanged = new Event('tabchanged'); + // chromium.ontabchanged.addListener(function(data) { alert(data); }); + // chromium.Event.dispatch_('tabchanged', 'hi'); + // will result in an alert dialog that says 'hi'. + chromium.Event = function(opt_eventName) { + this.eventName_ = opt_eventName; + this.listeners_ = []; + }; + + // A map of event names to the event object that is registered to that name. + chromium.Event.attached_ = {}; + + // Dispatches a named event with the given JSON data, which is deserialized + // before dispatch. + chromium.Event.dispatchJSON_ = function(name, data) { + if (chromium.Event.attached_[name]) { + if (data) { + data = chromium.json.deserialize_(data); + } + chromium.Event.attached_[name].dispatch_(data); + } + }; + + // Dispatches a named event with the given object data. + chromium.Event.dispatch_ = function(name, data) { + if (chromium.Event.attached_[name]) { + chromium.Event.attached_[name].dispatch(data); + } + }; + + // Registers a callback to be called when this event is dispatched. + chromium.Event.prototype.addListener = function(cb) { + this.listeners_.push(cb); + if (this.listeners_.length == 1) { + this.attach_(); + } + }; + + // Unregisters a callback. + chromium.Event.prototype.removeListener = function(cb) { + var idx = this.findListener_(cb); + if (idx == -1) { + return; + } + + this.listeners_.splice(idx, 1); + if (this.listeners_.length == 0) { + this.detach_(); + } + }; + + // Test if the given callback is registered for this event. + chromium.Event.prototype.hasListener = function(cb) { + return this.findListeners_(cb) > -1; + }; + + // Returns the index of the given callback if registered, or -1 if not + // found. + chromium.Event.prototype.findListener_ = function(cb) { + for (var i = 0; i < this.listeners_.length; i++) { + if (this.listeners_[i] == cb) { + return i; + } + } + + return -1; + }; + + // Dispatches this event object to all listeners, passing all supplied + // arguments to this function each listener. + chromium.Event.prototype.dispatch = function(varargs) { + var args = Array.prototype.slice.call(arguments); + for (var i = 0; i < this.listeners_.length; i++) { + try { + this.listeners_[i].apply(null, args); + } catch (e) { + console.error(e); + } + } + }; + + // Attaches this event object to its name. Only one object can have a given + // name. + chromium.Event.prototype.attach_ = function() { + AttachEvent(this.eventName_); + if (!this.eventName_) + return; + + if (chromium.Event.attached_[this.eventName_]) { + throw new Error("chromium.Event '" + this.eventName_ + + "' is already attached."); + } + + chromium.Event.attached_[this.eventName_] = this; + }; + + // Detaches this event object from its name. + chromium.Event.prototype.detach_ = function() { + DetachEvent(this.eventName_); + if (!this.eventName_) + return; + + if (!chromium.Event.attached_[this.eventName_]) { + throw new Error("chromium.Event '" + this.eventName_ + + "' is not attached."); + } + + delete chromium.Event.attached_[this.eventName_]; + }; +})(); diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js index 401f82c..deb5c40 100644 --- a/chrome/renderer/resources/renderer_extension_bindings.js +++ b/chrome/renderer/resources/renderer_extension_bindings.js @@ -2,92 +2,63 @@ 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); + // Port object. Represents a connection to another script context through + // which messages can be passed. + chromium.Port = function(portId) { + if (chromium.Port.ports_[portId]) { + throw new Error("Port '" + portId + "' already exists."); + } + this.portId_ = portId; // TODO(mpcomplete): readonly + this.onmessage = new chromium.Event(); + chromium.Port.ports_[portId] = this; + // Note: this object will never get GCed. If we ever care, we could + // add an "ondetach" method to the onmessage Event that gets called + // when there are no more listeners. }; - // 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); }); - }; + // Map of port IDs to port object. + chromium.Port.ports_ = {}; - // Adds a generic event listener. - chromium.addEventListener = function(type, callback) { - var listeners = getPrivateData().eventListeners; - if (!listeners[type]) - listeners[type] = []; - listeners[type].push(callback); + // Called by native code when a channel has been opened to this context. + chromium.Port.dispatchOnConnect_ = function(portId) { + var port = new chromium.Port(portId); + chromium.Event.dispatch_("channel-connect", port); }; - // 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); + // Called by native code when a message has been sent to the given port. + chromium.Port.dispatchOnMessage_ = function(msg, portId) { + var port = chromium.Port.ports_[portId]; + if (port) { + port.onmessage.dispatch(msg, port); } }; - // 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: {} + // Sends a message asynchronously to the context on the other end of this + // port. + chromium.Port.prototype.postMessage = function(msg) { + PostMessage(this.portId_, msg); }; - // 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; + // Extension object. + chromium.Extension = function(id) { + this.id_ = id; }; - // Sends a message to the other side of the channel. - Port.prototype.postMessage = function(msg) { - PostMessage(this.portId_, msg); + // Opens a message channel to the extension. Returns a Port for + // message passing. + chromium.Extension.prototype.connect = function() { + var portId = OpenChannelToExtension(this.id_); + if (portId == -1) + throw new Error("No such extension: '" + this.id_ + "'"); + return new chromium.Port(portId); }; - // 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)}); + // Returns a resource URL that can be used to fetch a resource from this + // extension. + chromium.Extension.prototype.getURL = function(path) { + return "chrome-extension://" + this.id_ + "/" + path; }; - // 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); - }; + chromium.onconnect = new chromium.Event("channel-connect"); })(); diff --git a/chrome/test/data/extensions/good/extension1/1/toolstrip1.html b/chrome/test/data/extensions/good/extension1/1/toolstrip1.html index 66ba2b3..bbc4392 100644 --- a/chrome/test/data/extensions/good/extension1/1/toolstrip1.html +++ b/chrome/test/data/extensions/good/extension1/1/toolstrip1.html @@ -16,8 +16,8 @@ body { <script> alert('Sir, I exist'); - chromium.addConnectListener(function (port) { - port.onMessage = doOnMessage; + chromium.onconnect.addListener(function (port) { + port.onmessage.addListener(doOnMessage); port.postMessage('extension onconnect'); }); |