diff options
Diffstat (limited to 'chrome/renderer')
-rw-r--r-- | chrome/renderer/extensions/bindings_utils.cc | 110 | ||||
-rw-r--r-- | chrome/renderer/extensions/bindings_utils.h | 71 | ||||
-rw-r--r-- | chrome/renderer/extensions/event_bindings.cc | 171 | ||||
-rw-r--r-- | chrome/renderer/extensions/event_bindings.h | 6 | ||||
-rw-r--r-- | chrome/renderer/extensions/extension_api_client_unittest.cc | 5 | ||||
-rw-r--r-- | chrome/renderer/extensions/extension_process_bindings.cc | 175 | ||||
-rw-r--r-- | chrome/renderer/extensions/extension_process_bindings.h | 4 | ||||
-rw-r--r-- | chrome/renderer/extensions/renderer_extension_bindings.cc | 13 | ||||
-rw-r--r-- | chrome/renderer/js_only_v8_extensions.cc | 2 | ||||
-rw-r--r-- | chrome/renderer/render_view.cc | 10 | ||||
-rw-r--r-- | chrome/renderer/renderer_resources.grd | 2 | ||||
-rw-r--r-- | chrome/renderer/resources/event_bindings.js | 98 | ||||
-rw-r--r-- | chrome/renderer/resources/extension_process_bindings.js | 55 | ||||
-rw-r--r-- | chrome/renderer/resources/greasemonkey_api.js | 4 | ||||
-rw-r--r-- | chrome/renderer/resources/renderer_extension_bindings.js | 37 |
15 files changed, 418 insertions, 345 deletions
diff --git a/chrome/renderer/extensions/bindings_utils.cc b/chrome/renderer/extensions/bindings_utils.cc index 81e2058..fc9e98d 100644 --- a/chrome/renderer/extensions/bindings_utils.cc +++ b/chrome/renderer/extensions/bindings_utils.cc @@ -8,6 +8,103 @@ #include "chrome/renderer/render_view.h" #include "webkit/glue/webframe.h" +namespace bindings_utils { + +const char* kChromeHidden = "chromeHidden"; + +struct SingletonData { + ContextList contexts; + PendingRequestMap pending_requests; +}; + +// ExtensionBase + +v8::Handle<v8::FunctionTemplate> + ExtensionBase::GetNativeFunction(v8::Handle<v8::String> name) { + if (name->Equals(v8::String::New("GetChromeHidden"))) { + return v8::FunctionTemplate::New(GetChromeHidden); + } + + return v8::Handle<v8::FunctionTemplate>(); +} + +v8::Handle<v8::Value> ExtensionBase::GetChromeHidden( + const v8::Arguments& args) { + v8::Local<v8::Context> context = v8::Context::GetCurrent(); + v8::Local<v8::Object> global = context->Global(); + v8::Local<v8::Value> hidden = global->GetHiddenValue( + v8::String::New(kChromeHidden)); + + if (hidden.IsEmpty() || hidden->IsUndefined()) { + hidden = v8::Object::New(); + global->SetHiddenValue(v8::String::New(kChromeHidden), hidden); + } + + DCHECK(hidden->IsObject()); + return hidden; +} + +v8::Handle<v8::Value> ExtensionBase::StartRequest( + const v8::Arguments& args) { + // Get the current RenderView so that we can send a routed IPC message from + // the correct source. + RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); + if (!renderview) + return v8::Undefined(); + + if (args.Length() != 3 || !args[0]->IsString() || !args[1]->IsInt32() || + !args[2]->IsBoolean()) + return v8::Undefined(); + + std::string name = *v8::String::AsciiValue(args.Data()); + std::string json_args = *v8::String::Utf8Value(args[0]); + int request_id = args[1]->Int32Value(); + bool has_callback = args[2]->BooleanValue(); + + v8::Persistent<v8::Context> current_context = + v8::Persistent<v8::Context>::New(v8::Context::GetCurrent()); + DCHECK(!current_context.IsEmpty()); + GetPendingRequestMap()[request_id].reset(new PendingRequest( + current_context, *v8::String::AsciiValue(args.Data()))); + + renderview->SendExtensionRequest(name, json_args, request_id, has_callback); + + return v8::Undefined(); +} + +ContextList& GetContexts() { + return Singleton<SingletonData>::get()->contexts; +} + +ContextList GetContextsForExtension(const std::string& extension_id) { + ContextList& all_contexts = GetContexts(); + ContextList contexts; + + for (ContextList::iterator it = all_contexts.begin(); + it != all_contexts.end(); ++it) { + if ((*it)->extension_id == extension_id) + contexts.push_back(*it); + } + + return contexts; +} + +ContextList::iterator FindContext(v8::Handle<v8::Context> context) { + ContextList& all_contexts = GetContexts(); + + ContextList::iterator it = all_contexts.begin(); + for (; it != all_contexts.end(); ++it) { + if ((*it)->context == context) + break; + } + + return it; +} + +PendingRequestMap& GetPendingRequestMap() { + return Singleton<SingletonData>::get()->pending_requests; +} + RenderView* GetRenderViewForCurrentContext() { WebFrame* webframe = WebFrame::RetrieveFrameForCurrentContext(); DCHECK(webframe) << "RetrieveCurrentFrame called when not in a V8 context."; @@ -29,18 +126,23 @@ void CallFunctionInContext(v8::Handle<v8::Context> context, v8::Context::Scope context_scope(context); // Look up the function name, which may be a sub-property like - // "chrome.handleResponse_" in the global variable. - v8::Local<v8::Value> value = context->Global(); + // "Port.dispatchOnMessage" in the hidden global variable. + v8::Local<v8::Value> value = + context->Global()->GetHiddenValue(v8::String::New(kChromeHidden)); std::vector<std::string> components; SplitStringDontTrim(function_name, '.', &components); for (size_t i = 0; i < components.size(); ++i) { - if (value->IsObject()) + if (!value.IsEmpty() && value->IsObject()) value = value->ToObject()->Get(v8::String::New(components[i].c_str())); } - if (!value->IsFunction()) + if (value.IsEmpty() || !value->IsFunction()) { + NOTREACHED(); return; + } v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(value); if (!function.IsEmpty()) function->Call(v8::Object::New(), argc, argv); } + +} // namespace bindings_utils diff --git a/chrome/renderer/extensions/bindings_utils.h b/chrome/renderer/extensions/bindings_utils.h index b498cc5..1b21023 100644 --- a/chrome/renderer/extensions/bindings_utils.h +++ b/chrome/renderer/extensions/bindings_utils.h @@ -6,13 +6,44 @@ #define CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_ #include "app/resource_bundle.h" +#include "base/linked_ptr.h" #include "base/singleton.h" #include "base/string_piece.h" #include "v8/include/v8.h" +#include <list> #include <string> class RenderView; +class WebFrame; + +namespace bindings_utils { + +// This is a base class for chrome extension bindings. Common features that +// are shared by different modules go here. +class ExtensionBase : public v8::Extension { + public: + ExtensionBase(const char* name, + const char* source, + int dep_count, + const char** deps) + : v8::Extension(name, source, dep_count, deps) {} + + // Derived classes should call this at the end of their implementation in + // order to expose common native functions, like GetChromeHidden, to the + // v8 extension. + virtual v8::Handle<v8::FunctionTemplate> + GetNativeFunction(v8::Handle<v8::String> name); + + protected: + // Returns a hidden variable for use by the bindings that is unreachable + // by the page. + static v8::Handle<v8::Value> GetChromeHidden(const v8::Arguments& args); + + // Starts an API request to the browser, with an optional callback. The + // callback will be dispatched to EventBindings::HandleResponse. + static v8::Handle<v8::Value> StartRequest(const v8::Arguments& args); +}; template<int kResourceId> struct StringResourceTemplate { @@ -29,15 +60,51 @@ const char* GetStringResource() { Singleton< StringResourceTemplate<kResourceId> >::get()->resource.c_str(); } +// Contains information about a single javascript context. +struct ContextInfo { + v8::Persistent<v8::Context> context; + std::string extension_id; // empty if the context is not an extension + + ContextInfo(v8::Persistent<v8::Context> context, + const std::string& extension_id) + : context(context), extension_id(extension_id) {} +}; +typedef std::list< linked_ptr<ContextInfo> > ContextList; + +// Returns a mutable reference to the ContextList. +ContextList& GetContexts(); + +// Returns a (copied) list of contexts that have the given extension_id. +ContextList GetContextsForExtension(const std::string& extension_id); + +// Returns the ContextInfo item that has the given context. +ContextList::iterator FindContext(v8::Handle<v8::Context> context); + +// Contains info relevant to a pending API request. +struct PendingRequest { + public : + PendingRequest(v8::Persistent<v8::Context> context, const std::string& name) + : context(context), name(name) { + } + v8::Persistent<v8::Context> context; + std::string name; +}; +typedef std::map<int, linked_ptr<PendingRequest> > PendingRequestMap; + +// Returns a mutable reference to the PendingRequestMap. +PendingRequestMap& GetPendingRequestMap(); + // Returns the current RenderView, based on which V8 context is current. It is // an error to call this when not in a V8 context. RenderView* GetRenderViewForCurrentContext(); // Call the named javascript function with the given arguments in a context. -// The function name should be reachable from the global object, and can be a -// sub-property like "chrome.handleResponse_". +// The function name should be reachable from the chromeHidden object, and can +// be a sub-property like "Port.dispatchOnMessage". void CallFunctionInContext(v8::Handle<v8::Context> context, const std::string& function_name, int argc, v8::Handle<v8::Value>* argv); +} // namespace bindings_utils + #endif // CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_ diff --git a/chrome/renderer/extensions/event_bindings.cc b/chrome/renderer/extensions/event_bindings.cc index 7c988e2..240c330 100644 --- a/chrome/renderer/extensions/event_bindings.cc +++ b/chrome/renderer/extensions/event_bindings.cc @@ -7,32 +7,37 @@ #include "base/basictypes.h" #include "base/singleton.h" #include "chrome/common/render_messages.h" +#include "chrome/common/url_constants.h" #include "chrome/renderer/extensions/bindings_utils.h" #include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/js_only_v8_extensions.h" #include "chrome/renderer/render_thread.h" +#include "chrome/renderer/render_view.h" #include "grit/renderer_resources.h" #include "webkit/glue/webframe.h" +using bindings_utils::CallFunctionInContext; +using bindings_utils::ContextInfo; +using bindings_utils::ContextList; +using bindings_utils::GetContexts; +using bindings_utils::GetStringResource; +using bindings_utils::ExtensionBase; +using bindings_utils::GetPendingRequestMap; +using bindings_utils::PendingRequest; +using bindings_utils::PendingRequestMap; + namespace { // Keep a local cache of RenderThread so that we can mock it out for unit tests. static RenderThreadBase* render_thread = NULL; -static RenderThreadBase* GetRenderThread() { - return render_thread ? render_thread : RenderThread::current(); -} +// Set to true if these bindings are registered. Will be false when extensions +// are disabled. +static bool bindings_registered = false; -// 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; std::map<std::string, int> listener_count; }; -ContextList& GetRegisteredContexts() { - return Singleton<ExtensionData>::get()->contexts; -} int EventIncrementListenerCount(const std::string& event_name) { ExtensionData *data = Singleton<ExtensionData>::get(); return ++(data->listener_count[event_name]); @@ -42,12 +47,10 @@ int EventDecrementListenerCount(const std::string& event_name) { return --(data->listener_count[event_name]); } -const char* kContextAttachCount = "chromium.attachCount"; - -class ExtensionImpl : public v8::Extension { +class ExtensionImpl : public ExtensionBase { public: ExtensionImpl() - : v8::Extension(EventBindings::kName, + : ExtensionBase(EventBindings::kName, GetStringResource<IDR_EVENT_BINDINGS_JS>(), 0, NULL) { } @@ -59,8 +62,10 @@ class ExtensionImpl : public v8::Extension { return v8::FunctionTemplate::New(AttachEvent); } else if (name->Equals(v8::String::New("DetachEvent"))) { return v8::FunctionTemplate::New(DetachEvent); + } else if (name->Equals(v8::String::New("GetNextRequestId"))) { + return v8::FunctionTemplate::New(GetNextRequestId); } - return v8::Handle<v8::FunctionTemplate>(); + return ExtensionBase::GetNativeFunction(name); } // Attach an event name to an object. @@ -69,30 +74,10 @@ class ExtensionImpl : public v8::Extension { // TODO(erikkay) should enforce that event name is a string in the bindings DCHECK(args[0]->IsString() || args[0]->IsUndefined()); - v8::Persistent<v8::Context> context = - v8::Persistent<v8::Context>::New(v8::Context::GetCurrent()); - v8::Local<v8::Object> global = context->Global(); - - // Remember how many times this context has been attached, so we can - // register the context on first attach and unregister on last detach. - v8::Local<v8::Value> attach_count = global->GetHiddenValue( - v8::String::New(kContextAttachCount)); - int32_t account_count_value = - (!attach_count.IsEmpty() && attach_count->IsNumber()) ? - attach_count->Int32Value() : 0; - if (account_count_value == 0) { - // First time attaching. - GetRegisteredContexts().push_back(context); - context.MakeWeak(NULL, WeakContextCallback); - } - global->SetHiddenValue( - v8::String::New(kContextAttachCount), - v8::Integer::New(account_count_value + 1)); - if (args[0]->IsString()) { std::string event_name(*v8::String::AsciiValue(args[0])); if (EventIncrementListenerCount(event_name) == 1) { - GetRenderThread()->Send( + EventBindings::GetRenderThread()->Send( new ViewHostMsg_ExtensionAddListener(event_name)); } } @@ -105,25 +90,10 @@ class ExtensionImpl : public v8::Extension { // TODO(erikkay) should enforce that event name is a string in the bindings DCHECK(args[0]->IsString() || args[0]->IsUndefined()); - v8::Local<v8::Context> context = v8::Context::GetCurrent(); - v8::Local<v8::Object> global = context->Global(); - v8::Local<v8::Value> attach_count = global->GetHiddenValue( - v8::String::New(kContextAttachCount)); - DCHECK(!attach_count.IsEmpty() && attach_count->IsNumber()); - int32_t account_count_value = attach_count->Int32Value(); - DCHECK(account_count_value > 0); - if (account_count_value == 1) { - // Clean up after last detach. - UnregisterContext(context); - } - global->SetHiddenValue( - v8::String::New(kContextAttachCount), - v8::Integer::New(account_count_value - 1)); - if (args[0]->IsString()) { std::string event_name(*v8::String::AsciiValue(args[0])); if (EventDecrementListenerCount(event_name) == 0) { - GetRenderThread()->Send( + EventBindings::GetRenderThread()->Send( new ViewHostMsg_ExtensionRemoveListener(event_name)); } } @@ -131,24 +101,9 @@ class ExtensionImpl : public v8::Extension { return v8::Undefined(); } - // Called when a registered context is garbage collected. - static void UnregisterContext(v8::Handle<void> context) { - ContextList& contexts = GetRegisteredContexts(); - ContextList::iterator it = std::find(contexts.begin(), contexts.end(), - context); - if (it == contexts.end()) { - NOTREACHED(); - return; - } - - it->Dispose(); - it->Clear(); - contexts.erase(it); - } - - // Called when a registered context is garbage collected. - static void WeakContextCallback(v8::Persistent<v8::Value> obj, void*) { - UnregisterContext(obj); + static v8::Handle<v8::Value> GetNextRequestId(const v8::Arguments& args) { + static int next_request_id = 0; + return v8::Integer::New(next_request_id++); } }; @@ -157,6 +112,7 @@ class ExtensionImpl : public v8::Extension { const char* EventBindings::kName = "chrome/EventBindings"; v8::Extension* EventBindings::Get() { + bindings_registered = true; return new ExtensionImpl(); } @@ -166,25 +122,90 @@ void EventBindings::SetRenderThread(RenderThreadBase* thread) { } // static +RenderThreadBase* EventBindings::GetRenderThread() { + return render_thread ? render_thread : RenderThread::current(); +} + void EventBindings::HandleContextCreated(WebFrame* frame) { + if (!bindings_registered) + return; + v8::HandleScope handle_scope; v8::Local<v8::Context> context = frame->GetScriptContext(); DCHECK(!context.IsEmpty()); - // TODO(mpcomplete): register it + DCHECK(bindings_utils::FindContext(context) == GetContexts().end()); + + GURL url = frame->GetView()->GetMainFrame()->GetURL(); + std::string extension_id; + if (url.SchemeIs(chrome::kExtensionScheme)) + extension_id = url.host(); + + v8::Persistent<v8::Context> persistent_context = + v8::Persistent<v8::Context>::New(context); + GetContexts().push_back(linked_ptr<ContextInfo>( + new ContextInfo(persistent_context, extension_id))); } // static void EventBindings::HandleContextDestroyed(WebFrame* frame) { + if (!bindings_registered) + return; + v8::HandleScope handle_scope; v8::Local<v8::Context> context = frame->GetScriptContext(); DCHECK(!context.IsEmpty()); - // TODO(mpcomplete): unregister it, dispatch event + + ContextList::iterator it = bindings_utils::FindContext(context); + DCHECK(it != GetContexts().end()); + + // Notify the bindings that they're going away. + CallFunctionInContext(context, "dispatchOnUnload", 0, NULL); + + // Remove all pending requests for this context. + PendingRequestMap& pending_requests = GetPendingRequestMap(); + for (PendingRequestMap::iterator it = pending_requests.begin(); + it != pending_requests.end(); ) { + PendingRequestMap::iterator current = it++; + if (current->second->context == context) { + current->second->context.Dispose(); + current->second->context.Clear(); + pending_requests.erase(current); + } + } + + // Remove it from our registered contexts. + (*it)->context.Dispose(); + (*it)->context.Clear(); + GetContexts().erase(it); } +// static 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) { - CallFunctionInContext(*it, function_name, argc, argv); + v8::HandleScope handle_scope; + for (ContextList::iterator it = GetContexts().begin(); + it != GetContexts().end(); ++it) { + CallFunctionInContext((*it)->context, function_name, argc, argv); } } + +// static +void EventBindings::HandleResponse(int request_id, bool success, + const std::string& response, + const std::string& error) { + PendingRequest* request = GetPendingRequestMap()[request_id].get(); + if (!request) + return; // The frame went away. + + v8::HandleScope handle_scope; + v8::Handle<v8::Value> argv[5]; + argv[0] = v8::Integer::New(request_id); + argv[1] = v8::String::New(request->name.c_str()); + argv[2] = v8::Boolean::New(success); + argv[3] = v8::String::New(response.c_str()); + argv[4] = v8::String::New(error.c_str()); + CallFunctionInContext( + request->context, "handleResponse", arraysize(argv), argv); + + GetPendingRequestMap().erase(request_id); +} diff --git a/chrome/renderer/extensions/event_bindings.h b/chrome/renderer/extensions/event_bindings.h index a026c19..f2c5f88 100644 --- a/chrome/renderer/extensions/event_bindings.h +++ b/chrome/renderer/extensions/event_bindings.h @@ -20,6 +20,7 @@ class EventBindings { // Allow RenderThread to be mocked out. static void SetRenderThread(RenderThreadBase* thread); + static RenderThreadBase* GetRenderThread(); // Handle a script context coming / going away. static void HandleContextCreated(WebFrame* frame); @@ -30,6 +31,11 @@ class EventBindings { // "chromium.Event.dispatch_". static void CallFunction(const std::string& function_name, int argc, v8::Handle<v8::Value>* argv); + + // Handles a response to an API request. + static void HandleResponse(int request_id, bool success, + const std::string& response, + const std::string& error); }; #endif // CHROME_RENDERER_EXTENSIONS_EVENT_BINDINGS_H_ diff --git a/chrome/renderer/extensions/extension_api_client_unittest.cc b/chrome/renderer/extensions/extension_api_client_unittest.cc index dc37c5f..9638b27 100644 --- a/chrome/renderer/extensions/extension_api_client_unittest.cc +++ b/chrome/renderer/extensions/extension_api_client_unittest.cc @@ -7,7 +7,7 @@ #include "base/string_util.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/render_messages.h" -#include "chrome/renderer/extensions/extension_process_bindings.h" +#include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/extensions/renderer_extension_bindings.h" #include "chrome/test/render_view_test.h" #include "testing/gtest/include/gtest/gtest.h" @@ -91,8 +91,7 @@ TEST_F(ExtensionAPIClientTest, CallbackDispatching) { ASSERT_GE(callback_id, 0); // Now send the callback a response - ExtensionProcessBindings::HandleResponse( - callback_id, true, "{\"foo\":\"bar\"}", ""); + EventBindings::HandleResponse(callback_id, true, "{\"foo\":\"bar\"}", ""); // And verify that it worked ASSERT_EQ("pass", GetConsoleMessage()); diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index 1ce7ea2..6b568a1 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -5,7 +5,6 @@ #include "chrome/renderer/extensions/extension_process_bindings.h" #include "base/singleton.h" -#include "base/stl_util-inl.h" #include "chrome/common/render_messages.h" #include "chrome/common/url_constants.h" #include "chrome/renderer/extensions/bindings_utils.h" @@ -14,11 +13,13 @@ #include "chrome/renderer/js_only_v8_extensions.h" #include "chrome/renderer/render_view.h" #include "grit/renderer_resources.h" -#include "webkit/api/public/WebScriptSource.h" #include "webkit/glue/webframe.h" -using WebKit::WebScriptSource; -using WebKit::WebString; +using bindings_utils::GetStringResource; +using bindings_utils::ContextInfo; +using bindings_utils::ContextList; +using bindings_utils::GetContexts; +using bindings_utils::ExtensionBase; namespace { @@ -30,48 +31,17 @@ const char* kExtensionDeps[] = { RendererExtensionBindings::kName, }; -// Types for storage of per-renderer-singleton data structure that maps -// |extension_id| -> <List of v8 Contexts for the "views" of that extension> -typedef std::list< v8::Persistent<v8::Context> > ContextList; -typedef std::map<std::string, ContextList> ExtensionIdContextsMap; - -// Contains info relevant to a pending request. -struct CallContext { - public : - CallContext(v8::Persistent<v8::Context> context, const std::string& name) - : context(context), name(name) { - } - v8::Persistent<v8::Context> context; - std::string name; -}; -typedef std::map<int, CallContext*> PendingRequestMap; - struct SingletonData { std::set<std::string> function_names_; - ExtensionIdContextsMap contexts; - PendingRequestMap pending_requests; - - ~SingletonData() { - STLDeleteContainerPairSecondPointers(pending_requests.begin(), - pending_requests.end()); - } }; static std::set<std::string>* GetFunctionNameSet() { return &Singleton<SingletonData>()->function_names_; } -static ContextList& GetRegisteredContexts(std::string extension_id) { - return Singleton<SingletonData>::get()->contexts[extension_id]; -} - -static PendingRequestMap& GetPendingRequestMap() { - return Singleton<SingletonData>::get()->pending_requests; -} - -class ExtensionImpl : public v8::Extension { +class ExtensionImpl : public ExtensionBase { public: - ExtensionImpl() : v8::Extension( + ExtensionImpl() : ExtensionBase( kExtensionName, GetStringResource<IDR_EXTENSION_PROCESS_BINDINGS_JS>(), arraysize(kExtensionDeps), kExtensionDeps) {} @@ -86,86 +56,31 @@ class ExtensionImpl : public v8::Extension { v8::Handle<v8::String> name) { std::set<std::string>* names = GetFunctionNameSet(); - if (name->Equals(v8::String::New("GetNextRequestId"))) - return v8::FunctionTemplate::New(GetNextRequestId); - else if (name->Equals(v8::String::New("RegisterExtension"))) - return v8::FunctionTemplate::New(RegisterExtension); - else if (name->Equals(v8::String::New("UnregisterExtension"))) - return v8::FunctionTemplate::New(UnregisterExtension); - else if (name->Equals(v8::String::New("GetViews"))) + if (name->Equals(v8::String::New("GetViews"))) { return v8::FunctionTemplate::New(GetViews); - else if (names->find(*v8::String::AsciiValue(name)) != names->end()) - return v8::FunctionTemplate::New(StartRequest, name); - - return v8::Handle<v8::FunctionTemplate>(); - } - - private: - static v8::Handle<v8::Value> RegisterExtension(const v8::Arguments& args) { - RenderView* renderview = GetRenderViewForCurrentContext(); - DCHECK(renderview); - GURL url = renderview->webview()->GetMainFrame()->GetURL(); - DCHECK(url.scheme() == chrome::kExtensionScheme); - - v8::Persistent<v8::Context> current_context = - v8::Persistent<v8::Context>::New(v8::Context::GetCurrent()); - DCHECK(!current_context.IsEmpty()); - - std::string extension_id = url.host(); - GetRegisteredContexts(extension_id).push_back(current_context); - return v8::String::New(extension_id.c_str()); - } - - static v8::Handle<v8::Value> UnregisterExtension(const v8::Arguments& args) { - DCHECK_EQ(args.Length(), 1); - DCHECK(args[0]->IsString()); - - v8::Local<v8::Context> current_context = v8::Context::GetCurrent(); - DCHECK(!current_context.IsEmpty()); - - // Remove all pending requests for this context. - PendingRequestMap& pending_requests = GetPendingRequestMap(); - for (PendingRequestMap::iterator it = pending_requests.begin(); - it != pending_requests.end(); ) { - PendingRequestMap::iterator current = it++; - if (current->second->context == current_context) { - current->second->context.Dispose(); - current->second->context.Clear(); - delete current->second; - pending_requests.erase(current); - } + } else if (names->find(*v8::String::AsciiValue(name)) != names->end()) { + return v8::FunctionTemplate::New(ExtensionBase::StartRequest, name); } - std::string extension_id(*v8::String::Utf8Value(args[0])); - ContextList& contexts = GetRegisteredContexts(extension_id); - ContextList::iterator it = std::find(contexts.begin(), contexts.end(), - current_context); - if (it == contexts.end()) { - NOTREACHED(); - return v8::Undefined(); - } - - it->Dispose(); - it->Clear(); - contexts.erase(it); - - return v8::Undefined(); + return ExtensionBase::GetNativeFunction(name); } + private: static v8::Handle<v8::Value> GetViews(const v8::Arguments& args) { - RenderView* renderview = GetRenderViewForCurrentContext(); + RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); DCHECK(renderview); GURL url = renderview->webview()->GetMainFrame()->GetURL(); std::string extension_id = url.host(); - ContextList& contexts = GetRegisteredContexts(extension_id); + ContextList contexts = + bindings_utils::GetContextsForExtension(extension_id); DCHECK(contexts.size() > 0); v8::Local<v8::Array> views = v8::Array::New(contexts.size()); int index = 0; ContextList::const_iterator it = contexts.begin(); for (; it != contexts.end(); ++it) { - v8::Local<v8::Value> window = (*it)->Global()->Get( + v8::Local<v8::Value> window = (*it)->context->Global()->Get( v8::String::New("window")); DCHECK(!window.IsEmpty()); views->Set(v8::Integer::New(index), window); @@ -173,38 +88,6 @@ class ExtensionImpl : public v8::Extension { } return views; } - - static v8::Handle<v8::Value> GetNextRequestId(const v8::Arguments& args) { - static int next_request_id = 0; - return v8::Integer::New(next_request_id++); - } - - static v8::Handle<v8::Value> StartRequest(const v8::Arguments& args) { - // Get the current RenderView so that we can send a routed IPC message from - // the correct source. - RenderView* renderview = GetRenderViewForCurrentContext(); - if (!renderview) - return v8::Undefined(); - - if (args.Length() != 3 || !args[0]->IsString() || !args[1]->IsInt32() || - !args[2]->IsBoolean()) - return v8::Undefined(); - - std::string name = *v8::String::AsciiValue(args.Data()); - std::string json_args = *v8::String::Utf8Value(args[0]); - int request_id = args[1]->Int32Value(); - bool has_callback = args[2]->BooleanValue(); - - v8::Persistent<v8::Context> current_context = - v8::Persistent<v8::Context>::New(v8::Context::GetCurrent()); - DCHECK(!current_context.IsEmpty()); - GetPendingRequestMap()[request_id] = - new CallContext(current_context, *v8::String::AsciiValue(args.Data())); - - renderview->SendExtensionRequest(name, json_args, request_id, has_callback); - - return v8::Undefined(); - } }; } // namespace @@ -217,29 +100,3 @@ void ExtensionProcessBindings::SetFunctionNames( const std::vector<std::string>& names) { ExtensionImpl::SetFunctionNames(names); } - -void ExtensionProcessBindings::RegisterExtensionContext(WebFrame* frame) { - frame->ExecuteScript(WebScriptSource(WebString::fromUTF8( - "chrome.self.register_();"))); -} - -void ExtensionProcessBindings::HandleResponse(int request_id, bool success, - const std::string& response, - const std::string& error) { - CallContext* call = GetPendingRequestMap()[request_id]; - if (!call) - return; // The frame went away. - - v8::HandleScope handle_scope; - v8::Handle<v8::Value> argv[5]; - argv[0] = v8::Integer::New(request_id); - argv[1] = v8::String::New(call->name.c_str()); - argv[2] = v8::Boolean::New(success); - argv[3] = v8::String::New(response.c_str()); - argv[4] = v8::String::New(error.c_str()); - CallFunctionInContext(call->context, "chrome.handleResponse_", - arraysize(argv), argv); - - GetPendingRequestMap().erase(request_id); - delete call; -} diff --git a/chrome/renderer/extensions/extension_process_bindings.h b/chrome/renderer/extensions/extension_process_bindings.h index 9639090..f703914 100644 --- a/chrome/renderer/extensions/extension_process_bindings.h +++ b/chrome/renderer/extensions/extension_process_bindings.h @@ -18,10 +18,6 @@ class ExtensionProcessBindings { public: static void SetFunctionNames(const std::vector<std::string>& names); static v8::Extension* Get(); - static void RegisterExtensionContext(WebFrame* frame); - static void HandleResponse(int request_id, bool success, - const std::string& response, - const std::string& error); }; #endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_ diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc index 0931c8c..f719797 100644 --- a/chrome/renderer/extensions/renderer_extension_bindings.cc +++ b/chrome/renderer/extensions/renderer_extension_bindings.cc @@ -14,6 +14,9 @@ #include "chrome/renderer/render_view.h" #include "grit/renderer_resources.h" +using bindings_utils::GetStringResource; +using bindings_utils::ExtensionBase; + // Message passing API example (in a content script): // var extension = // new chrome.Extension('00123456789abcdef0123456789abcdef0123456'); @@ -28,10 +31,10 @@ namespace { const char* kExtensionDeps[] = { EventBindings::kName }; -class ExtensionImpl : public v8::Extension { +class ExtensionImpl : public ExtensionBase { public: ExtensionImpl() - : v8::Extension(RendererExtensionBindings::kName, + : ExtensionBase(RendererExtensionBindings::kName, GetStringResource<IDR_RENDERER_EXTENSION_BINDINGS_JS>(), arraysize(kExtensionDeps), kExtensionDeps) { } @@ -44,7 +47,7 @@ class ExtensionImpl : public v8::Extension { } else if (name->Equals(v8::String::New("PostMessage"))) { return v8::FunctionTemplate::New(PostMessage); } - return v8::Handle<v8::FunctionTemplate>(); + return ExtensionBase::GetNativeFunction(name); } // Creates a new messaging channel to the given extension. @@ -52,7 +55,7 @@ class ExtensionImpl : public v8::Extension { const v8::Arguments& args) { // Get the current RenderView so that we can send a routed IPC message from // the correct source. - RenderView* renderview = GetRenderViewForCurrentContext(); + RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); if (!renderview) return v8::Undefined(); @@ -68,7 +71,7 @@ class ExtensionImpl : public v8::Extension { // Sends a message along the given channel. static v8::Handle<v8::Value> PostMessage(const v8::Arguments& args) { - RenderView* renderview = GetRenderViewForCurrentContext(); + RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); if (!renderview) return v8::Undefined(); diff --git a/chrome/renderer/js_only_v8_extensions.cc b/chrome/renderer/js_only_v8_extensions.cc index 577f170..6d94b92 100644 --- a/chrome/renderer/js_only_v8_extensions.cc +++ b/chrome/renderer/js_only_v8_extensions.cc @@ -8,6 +8,8 @@ #include "grit/renderer_resources.h" #include "grit/webkit_resources.h" +using bindings_utils::GetStringResource; + // BaseJsV8Extension const char* BaseJsV8Extension::kName = "chrome/base"; v8::Extension* BaseJsV8Extension::Get() { diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index f62e9a8..b2c505a 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -36,7 +36,6 @@ #include "chrome/renderer/devtools_agent.h" #include "chrome/renderer/devtools_client.h" #include "chrome/renderer/extensions/event_bindings.h" -#include "chrome/renderer/extensions/extension_process_bindings.h" #include "chrome/renderer/localized_error.h" #include "chrome/renderer/media/audio_renderer_impl.h" #include "chrome/renderer/media/buffered_data_source.h" @@ -1458,12 +1457,6 @@ void RenderView::DocumentElementAvailable(WebFrame* frame) { if (frame->GetURL().SchemeIs(chrome::kExtensionScheme)) frame->GrantUniversalAccess(); - // Tell extensions to self-register their js contexts. - // TODO(rafaelw): This is kind of gross. We need a way to call through - // the glue layer to retrieve the current v8::Context. - if (frame->GetURL().SchemeIs(chrome::kExtensionScheme)) - ExtensionProcessBindings::RegisterExtensionContext(frame); - if (RenderThread::current()) // Will be NULL during unit tests. RenderThread::current()->user_script_slave()->InjectScripts( frame, UserScript::DOCUMENT_START); @@ -2824,8 +2817,7 @@ void RenderView::OnExtensionResponse(int request_id, bool success, const std::string& response, const std::string& error) { - ExtensionProcessBindings::HandleResponse(request_id, success, response, - error); + EventBindings::HandleResponse(request_id, success, response, error); } // Dump all load time histograms. diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index ab04582..874f57a 100644 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. 0 --> +without changes to the corresponding grd file. mp2 --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/renderer_resources.h" type="rc_header"> diff --git a/chrome/renderer/resources/event_bindings.js b/chrome/renderer/resources/event_bindings.js index f11ec78..78a1ceb 100644 --- a/chrome/renderer/resources/event_bindings.js +++ b/chrome/renderer/resources/event_bindings.js @@ -1,5 +1,5 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
+// 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. // ----------------------------------------------------------------------------- @@ -9,8 +9,12 @@ var chrome = chrome || {}; (function () { + native function GetChromeHidden(); native function AttachEvent(eventName); native function DetachEvent(eventName); + native function GetNextRequestId(); + + var chromeHidden = GetChromeHidden(); // Event object. If opt_eventName is provided, this object represents // the unique instance of that named event, and dispatching an event @@ -19,7 +23,7 @@ var chrome = chrome || {}; // Example: // chrome.tabs.onChanged = new chrome.Event("tab-changed"); // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); - // chrome.Event.dispatch_("tab-changed", "hi"); + // chromeHidden.Event.dispatch("tab-changed", "hi"); // will result in an alert dialog that says 'hi'. chrome.Event = function(opt_eventName) { this.eventName_ = opt_eventName; @@ -27,26 +31,31 @@ var chrome = chrome || {}; }; // A map of event names to the event object that is registered to that name. - chrome.Event.attached_ = {}; + var attachedNamedEvents = {}; + + // An array of all attached event objects, used for detaching on unload. + var allAttachedEvents = []; + + chromeHidden.Event = {}; // Dispatches a named event with the given JSON array, which is deserialized // before dispatch. The JSON array is the list of arguments that will be // sent with the event callback. - chrome.Event.dispatchJSON_ = function(name, args) { - if (chrome.Event.attached_[name]) { + chromeHidden.Event.dispatchJSON = function(name, args) { + if (attachedNamedEvents[name]) { if (args) { args = JSON.parse(args); } - chrome.Event.attached_[name].dispatch.apply( - chrome.Event.attached_[name], args); + attachedNamedEvents[name].dispatch.apply( + attachedNamedEvents[name], args); } }; // Dispatches a named event with the given arguments, supplied as an array. - chrome.Event.dispatch_ = function(name, args) { - if (chrome.Event.attached_[name]) { - chrome.Event.attached_[name].dispatch.apply( - chrome.Event.attached_[name], args); + chromeHidden.Event.dispatch = function(name, args) { + if (attachedNamedEvents[name]) { + attachedNamedEvents[name].dispatch.apply( + attachedNamedEvents[name], args); } }; @@ -105,31 +114,82 @@ var chrome = chrome || {}; // name. chrome.Event.prototype.attach_ = function() { AttachEvent(this.eventName_); - this.unloadHandler_ = this.detach_.bind(this); - window.addEventListener('unload', this.unloadHandler_, false); + allAttachedEvents[allAttachedEvents.length] = this; if (!this.eventName_) return; - if (chrome.Event.attached_[this.eventName_]) { + if (attachedNamedEvents[this.eventName_]) { throw new Error("chrome.Event '" + this.eventName_ + "' is already attached."); } - chrome.Event.attached_[this.eventName_] = this; + attachedNamedEvents[this.eventName_] = this; }; // Detaches this event object from its name. chrome.Event.prototype.detach_ = function() { - window.removeEventListener('unload', this.unloadHandler_, false); + var i = allAttachedEvents.indexOf(this); + if (i >= 0) + delete allAttachedEvents[i]; DetachEvent(this.eventName_); if (!this.eventName_) return; - if (!chrome.Event.attached_[this.eventName_]) { + if (!attachedNamedEvents[this.eventName_]) { throw new Error("chrome.Event '" + this.eventName_ + "' is not attached."); } - delete chrome.Event.attached_[this.eventName_]; + delete attachedNamedEvents[this.eventName_]; }; + + // Callback handling. + var callbacks = []; + chromeHidden.handleResponse = function(requestId, name, + success, response, error) { + try { + if (!success) { + if (!error) + error = "Unknown error." + console.error("Error during " + name + ": " + error); + return; + } + + if (callbacks[requestId]) { + if (response) { + callbacks[requestId](JSON.parse(response)); + } else { + callbacks[requestId](); + } + } + } finally { + delete callbacks[requestId]; + } + }; + + // Send an API request and optionally register a callback. + chromeHidden.sendRequest = function(request, args, callback) { + // JSON.stringify doesn't support a root object which is undefined. + if (args === undefined) + args = null; + var sargs = JSON.stringify(args); + var requestId = GetNextRequestId(); + var hasCallback = false; + if (callback) { + hasCallback = true; + callbacks[requestId] = callback; + } + request(sargs, requestId, hasCallback); + } + + // Special unload event: we don't use the DOM unload because that slows + // down tab shutdown. On the other hand, this might not always fire, since + // Chrome will terminate renderers on shutdown (SuddenTermination). + chromeHidden.onUnload = new chrome.Event(); + + chromeHidden.dispatchOnUnload = function() { + chromeHidden.onUnload.dispatch(); + for (var i in allAttachedEvents) + allAttachedEvents[i].detach_(); + } })(); diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 2cdf661..0fbec3f 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -9,9 +9,6 @@ var chrome; (function() { - native function GetNextRequestId(); - native function RegisterExtension(); - native function UnregisterExtension(); native function GetViews(); native function GetWindow(); native function GetCurrentWindow(); @@ -37,10 +34,13 @@ var chrome; native function CreateBookmark(); native function MoveBookmark(); native function SetBookmarkTitle(); + native function GetChromeHidden(); if (!chrome) chrome = {}; + var chromeHidden = GetChromeHidden(); + // Validate arguments. function validate(args, schemas) { if (args.length > schemas.length) @@ -72,45 +72,7 @@ var chrome; } } - // Callback handling. - // TODO(aa): This function should not be publicly exposed. Pass it into V8 - // instead and hold one per-context. See the way event_bindings.js works. - var callbacks = []; - chrome.handleResponse_ = function(requestId, name, success, response, error) { - try { - if (!success) { - if (!error) - error = "Unknown error." - console.error("Error during " + name + ": " + error); - return; - } - - if (callbacks[requestId]) { - if (response) { - callbacks[requestId](JSON.parse(response)); - } else { - callbacks[requestId](); - } - } - } finally { - delete callbacks[requestId]; - } - }; - - // Send an API request and optionally register a callback. - function sendRequest(request, args, callback) { - // JSON.stringify doesn't support a root object which is undefined. - if (args === undefined) - args = null; - var sargs = JSON.stringify(args); - var requestId = GetNextRequestId(); - var hasCallback = false; - if (callback) { - hasCallback = true; - callbacks[requestId] = callback; - } - request(sargs, requestId, hasCallback); - } + var sendRequest = chromeHidden.sendRequest; //---------------------------------------------------------------------------- @@ -528,16 +490,7 @@ var chrome; chrome.self = chrome.self || {}; chrome.self.onConnect = new chrome.Event("channel-connect"); - // Register - chrome.self.register_ = function() { - var extensionId = RegisterExtension(); - window.addEventListener('unload', function() { - UnregisterExtension(extensionId); }, false); - delete chrome.self.register_; - } - chrome.self.getViews = function() { return GetViews(); } })(); - diff --git a/chrome/renderer/resources/greasemonkey_api.js b/chrome/renderer/resources/greasemonkey_api.js index 3ce1d38..0ad24f0 100644 --- a/chrome/renderer/resources/greasemonkey_api.js +++ b/chrome/renderer/resources/greasemonkey_api.js @@ -1,5 +1,5 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
+// 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. // ----------------------------------------------------------------------------- diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js index 31b97ed..bf59671 100644 --- a/chrome/renderer/resources/renderer_extension_bindings.js +++ b/chrome/renderer/resources/renderer_extension_bindings.js @@ -11,44 +11,53 @@ var chrome = chrome || {}; (function () { native function OpenChannelToExtension(id); native function PostMessage(portId, msg); + native function GetChromeHidden(); + + var chromeHidden = GetChromeHidden(); + + // Map of port IDs to port object. + var ports = {}; // Port object. Represents a connection to another script context through // which messages can be passed. chrome.Port = function(portId) { - if (chrome.Port.ports_[portId]) { + if (ports[portId]) { throw new Error("Port '" + portId + "' already exists."); } this.portId_ = portId; // TODO(mpcomplete): readonly this.onDisconnect = new chrome.Event(); this.onMessage = new chrome.Event(); - chrome.Port.ports_[portId] = this; + ports[portId] = this; + + chromeHidden.onUnload.addListener(function() { + this.disconnect(); + }); }; - // Map of port IDs to port object. - chrome.Port.ports_ = {}; + chromeHidden.Port = {}; // Called by native code when a channel has been opened to this context. - chrome.Port.dispatchOnConnect_ = function(portId, tab) { + chromeHidden.Port.dispatchOnConnect = function(portId, tab) { var port = new chrome.Port(portId); if (tab) { tab = JSON.parse(tab); } port.tab = tab; - chrome.Event.dispatch_("channel-connect", [port]); + chromeHidden.Event.dispatch("channel-connect", [port]); }; // Called by native code when a channel has been closed. - chrome.Port.dispatchOnDisconnect_ = function(portId) { - var port = chrome.Port.ports_[portId]; + chromeHidden.Port.dispatchOnDisconnect = function(portId) { + var port = ports[portId]; if (port) { port.onDisconnect.dispatch(port); - delete chrome.Port.ports_[portId]; + delete ports[portId]; } }; // Called by native code when a message has been sent to the given port. - chrome.Port.dispatchOnMessage_ = function(msg, portId) { - var port = chrome.Port.ports_[portId]; + chromeHidden.Port.dispatchOnMessage = function(msg, portId) { + var port = ports[portId]; if (port) { if (msg) { msg = JSON.parse(msg); @@ -66,6 +75,12 @@ var chrome = chrome || {}; PostMessage(this.portId_, JSON.stringify(msg)); }; + // Disconnects the port from the other end. + chrome.Port.prototype.disconnect = function() { + delete ports[this.portId_]; + //CloseChannel(this.portId_); // TODO(mpcomplete) + } + // Extension object. chrome.Extension = function(id) { this.id_ = id; |