| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/renderer/extensions/event_bindings.h" | 5 #include "chrome/renderer/extensions/event_bindings.h" |
| 6 | 6 |
| 7 #include "base/basictypes.h" | 7 #include "base/basictypes.h" |
| 8 #include "base/singleton.h" | 8 #include "base/singleton.h" |
| 9 #include "chrome/common/render_messages.h" | 9 #include "chrome/common/render_messages.h" |
| 10 #include "chrome/common/url_constants.h" |
| 10 #include "chrome/renderer/extensions/bindings_utils.h" | 11 #include "chrome/renderer/extensions/bindings_utils.h" |
| 11 #include "chrome/renderer/extensions/event_bindings.h" | 12 #include "chrome/renderer/extensions/event_bindings.h" |
| 12 #include "chrome/renderer/js_only_v8_extensions.h" | 13 #include "chrome/renderer/js_only_v8_extensions.h" |
| 13 #include "chrome/renderer/render_thread.h" | 14 #include "chrome/renderer/render_thread.h" |
| 15 #include "chrome/renderer/render_view.h" |
| 14 #include "grit/renderer_resources.h" | 16 #include "grit/renderer_resources.h" |
| 15 #include "webkit/glue/webframe.h" | 17 #include "webkit/glue/webframe.h" |
| 16 | 18 |
| 19 using bindings_utils::CallFunctionInContext; |
| 20 using bindings_utils::ContextInfo; |
| 21 using bindings_utils::ContextList; |
| 22 using bindings_utils::GetContexts; |
| 23 using bindings_utils::GetStringResource; |
| 24 using bindings_utils::ExtensionBase; |
| 25 using bindings_utils::GetPendingRequestMap; |
| 26 using bindings_utils::PendingRequest; |
| 27 using bindings_utils::PendingRequestMap; |
| 28 |
| 17 namespace { | 29 namespace { |
| 18 | 30 |
| 19 // Keep a local cache of RenderThread so that we can mock it out for unit tests. | 31 // Keep a local cache of RenderThread so that we can mock it out for unit tests. |
| 20 static RenderThreadBase* render_thread = NULL; | 32 static RenderThreadBase* render_thread = NULL; |
| 21 | 33 |
| 22 static RenderThreadBase* GetRenderThread() { | 34 // Set to true if these bindings are registered. Will be false when extensions |
| 23 return render_thread ? render_thread : RenderThread::current(); | 35 // are disabled. |
| 24 } | 36 static bool bindings_registered = false; |
| 25 | 37 |
| 26 // Keep a list of contexts that have registered themselves with us. This lets | |
| 27 // us know where to dispatch events when we receive them. | |
| 28 typedef std::list< v8::Persistent<v8::Context> > ContextList; | |
| 29 struct ExtensionData { | 38 struct ExtensionData { |
| 30 ContextList contexts; | |
| 31 std::map<std::string, int> listener_count; | 39 std::map<std::string, int> listener_count; |
| 32 }; | 40 }; |
| 33 ContextList& GetRegisteredContexts() { | |
| 34 return Singleton<ExtensionData>::get()->contexts; | |
| 35 } | |
| 36 int EventIncrementListenerCount(const std::string& event_name) { | 41 int EventIncrementListenerCount(const std::string& event_name) { |
| 37 ExtensionData *data = Singleton<ExtensionData>::get(); | 42 ExtensionData *data = Singleton<ExtensionData>::get(); |
| 38 return ++(data->listener_count[event_name]); | 43 return ++(data->listener_count[event_name]); |
| 39 } | 44 } |
| 40 int EventDecrementListenerCount(const std::string& event_name) { | 45 int EventDecrementListenerCount(const std::string& event_name) { |
| 41 ExtensionData *data = Singleton<ExtensionData>::get(); | 46 ExtensionData *data = Singleton<ExtensionData>::get(); |
| 42 return --(data->listener_count[event_name]); | 47 return --(data->listener_count[event_name]); |
| 43 } | 48 } |
| 44 | 49 |
| 45 const char* kContextAttachCount = "chromium.attachCount"; | 50 class ExtensionImpl : public ExtensionBase { |
| 46 | |
| 47 class ExtensionImpl : public v8::Extension { | |
| 48 public: | 51 public: |
| 49 ExtensionImpl() | 52 ExtensionImpl() |
| 50 : v8::Extension(EventBindings::kName, | 53 : ExtensionBase(EventBindings::kName, |
| 51 GetStringResource<IDR_EVENT_BINDINGS_JS>(), | 54 GetStringResource<IDR_EVENT_BINDINGS_JS>(), |
| 52 0, NULL) { | 55 0, NULL) { |
| 53 } | 56 } |
| 54 ~ExtensionImpl() {} | 57 ~ExtensionImpl() {} |
| 55 | 58 |
| 56 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( | 59 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( |
| 57 v8::Handle<v8::String> name) { | 60 v8::Handle<v8::String> name) { |
| 58 if (name->Equals(v8::String::New("AttachEvent"))) { | 61 if (name->Equals(v8::String::New("AttachEvent"))) { |
| 59 return v8::FunctionTemplate::New(AttachEvent); | 62 return v8::FunctionTemplate::New(AttachEvent); |
| 60 } else if (name->Equals(v8::String::New("DetachEvent"))) { | 63 } else if (name->Equals(v8::String::New("DetachEvent"))) { |
| 61 return v8::FunctionTemplate::New(DetachEvent); | 64 return v8::FunctionTemplate::New(DetachEvent); |
| 65 } else if (name->Equals(v8::String::New("GetNextRequestId"))) { |
| 66 return v8::FunctionTemplate::New(GetNextRequestId); |
| 62 } | 67 } |
| 63 return v8::Handle<v8::FunctionTemplate>(); | 68 return ExtensionBase::GetNativeFunction(name); |
| 64 } | 69 } |
| 65 | 70 |
| 66 // Attach an event name to an object. | 71 // Attach an event name to an object. |
| 67 static v8::Handle<v8::Value> AttachEvent(const v8::Arguments& args) { | 72 static v8::Handle<v8::Value> AttachEvent(const v8::Arguments& args) { |
| 68 DCHECK(args.Length() == 1); | 73 DCHECK(args.Length() == 1); |
| 69 // TODO(erikkay) should enforce that event name is a string in the bindings | 74 // TODO(erikkay) should enforce that event name is a string in the bindings |
| 70 DCHECK(args[0]->IsString() || args[0]->IsUndefined()); | 75 DCHECK(args[0]->IsString() || args[0]->IsUndefined()); |
| 71 | 76 |
| 72 v8::Persistent<v8::Context> context = | |
| 73 v8::Persistent<v8::Context>::New(v8::Context::GetCurrent()); | |
| 74 v8::Local<v8::Object> global = context->Global(); | |
| 75 | |
| 76 // Remember how many times this context has been attached, so we can | |
| 77 // register the context on first attach and unregister on last detach. | |
| 78 v8::Local<v8::Value> attach_count = global->GetHiddenValue( | |
| 79 v8::String::New(kContextAttachCount)); | |
| 80 int32_t account_count_value = | |
| 81 (!attach_count.IsEmpty() && attach_count->IsNumber()) ? | |
| 82 attach_count->Int32Value() : 0; | |
| 83 if (account_count_value == 0) { | |
| 84 // First time attaching. | |
| 85 GetRegisteredContexts().push_back(context); | |
| 86 context.MakeWeak(NULL, WeakContextCallback); | |
| 87 } | |
| 88 global->SetHiddenValue( | |
| 89 v8::String::New(kContextAttachCount), | |
| 90 v8::Integer::New(account_count_value + 1)); | |
| 91 | |
| 92 if (args[0]->IsString()) { | 77 if (args[0]->IsString()) { |
| 93 std::string event_name(*v8::String::AsciiValue(args[0])); | 78 std::string event_name(*v8::String::AsciiValue(args[0])); |
| 94 if (EventIncrementListenerCount(event_name) == 1) { | 79 if (EventIncrementListenerCount(event_name) == 1) { |
| 95 GetRenderThread()->Send( | 80 EventBindings::GetRenderThread()->Send( |
| 96 new ViewHostMsg_ExtensionAddListener(event_name)); | 81 new ViewHostMsg_ExtensionAddListener(event_name)); |
| 97 } | 82 } |
| 98 } | 83 } |
| 99 | 84 |
| 100 return v8::Undefined(); | 85 return v8::Undefined(); |
| 101 } | 86 } |
| 102 | 87 |
| 103 static v8::Handle<v8::Value> DetachEvent(const v8::Arguments& args) { | 88 static v8::Handle<v8::Value> DetachEvent(const v8::Arguments& args) { |
| 104 DCHECK(args.Length() == 1); | 89 DCHECK(args.Length() == 1); |
| 105 // TODO(erikkay) should enforce that event name is a string in the bindings | 90 // TODO(erikkay) should enforce that event name is a string in the bindings |
| 106 DCHECK(args[0]->IsString() || args[0]->IsUndefined()); | 91 DCHECK(args[0]->IsString() || args[0]->IsUndefined()); |
| 107 | 92 |
| 108 v8::Local<v8::Context> context = v8::Context::GetCurrent(); | |
| 109 v8::Local<v8::Object> global = context->Global(); | |
| 110 v8::Local<v8::Value> attach_count = global->GetHiddenValue( | |
| 111 v8::String::New(kContextAttachCount)); | |
| 112 DCHECK(!attach_count.IsEmpty() && attach_count->IsNumber()); | |
| 113 int32_t account_count_value = attach_count->Int32Value(); | |
| 114 DCHECK(account_count_value > 0); | |
| 115 if (account_count_value == 1) { | |
| 116 // Clean up after last detach. | |
| 117 UnregisterContext(context); | |
| 118 } | |
| 119 global->SetHiddenValue( | |
| 120 v8::String::New(kContextAttachCount), | |
| 121 v8::Integer::New(account_count_value - 1)); | |
| 122 | |
| 123 if (args[0]->IsString()) { | 93 if (args[0]->IsString()) { |
| 124 std::string event_name(*v8::String::AsciiValue(args[0])); | 94 std::string event_name(*v8::String::AsciiValue(args[0])); |
| 125 if (EventDecrementListenerCount(event_name) == 0) { | 95 if (EventDecrementListenerCount(event_name) == 0) { |
| 126 GetRenderThread()->Send( | 96 EventBindings::GetRenderThread()->Send( |
| 127 new ViewHostMsg_ExtensionRemoveListener(event_name)); | 97 new ViewHostMsg_ExtensionRemoveListener(event_name)); |
| 128 } | 98 } |
| 129 } | 99 } |
| 130 | 100 |
| 131 return v8::Undefined(); | 101 return v8::Undefined(); |
| 132 } | 102 } |
| 133 | 103 |
| 134 // Called when a registered context is garbage collected. | 104 static v8::Handle<v8::Value> GetNextRequestId(const v8::Arguments& args) { |
| 135 static void UnregisterContext(v8::Handle<void> context) { | 105 static int next_request_id = 0; |
| 136 ContextList& contexts = GetRegisteredContexts(); | 106 return v8::Integer::New(next_request_id++); |
| 137 ContextList::iterator it = std::find(contexts.begin(), contexts.end(), | |
| 138 context); | |
| 139 if (it == contexts.end()) { | |
| 140 NOTREACHED(); | |
| 141 return; | |
| 142 } | |
| 143 | |
| 144 it->Dispose(); | |
| 145 it->Clear(); | |
| 146 contexts.erase(it); | |
| 147 } | |
| 148 | |
| 149 // Called when a registered context is garbage collected. | |
| 150 static void WeakContextCallback(v8::Persistent<v8::Value> obj, void*) { | |
| 151 UnregisterContext(obj); | |
| 152 } | 107 } |
| 153 }; | 108 }; |
| 154 | 109 |
| 155 } // namespace | 110 } // namespace |
| 156 | 111 |
| 157 const char* EventBindings::kName = "chrome/EventBindings"; | 112 const char* EventBindings::kName = "chrome/EventBindings"; |
| 158 | 113 |
| 159 v8::Extension* EventBindings::Get() { | 114 v8::Extension* EventBindings::Get() { |
| 115 bindings_registered = true; |
| 160 return new ExtensionImpl(); | 116 return new ExtensionImpl(); |
| 161 } | 117 } |
| 162 | 118 |
| 163 // static | 119 // static |
| 164 void EventBindings::SetRenderThread(RenderThreadBase* thread) { | 120 void EventBindings::SetRenderThread(RenderThreadBase* thread) { |
| 165 render_thread = thread; | 121 render_thread = thread; |
| 166 } | 122 } |
| 167 | 123 |
| 168 // static | 124 // static |
| 125 RenderThreadBase* EventBindings::GetRenderThread() { |
| 126 return render_thread ? render_thread : RenderThread::current(); |
| 127 } |
| 128 |
| 169 void EventBindings::HandleContextCreated(WebFrame* frame) { | 129 void EventBindings::HandleContextCreated(WebFrame* frame) { |
| 130 if (!bindings_registered) |
| 131 return; |
| 132 |
| 170 v8::HandleScope handle_scope; | 133 v8::HandleScope handle_scope; |
| 171 v8::Local<v8::Context> context = frame->GetScriptContext(); | 134 v8::Local<v8::Context> context = frame->GetScriptContext(); |
| 172 DCHECK(!context.IsEmpty()); | 135 DCHECK(!context.IsEmpty()); |
| 173 // TODO(mpcomplete): register it | 136 DCHECK(bindings_utils::FindContext(context) == GetContexts().end()); |
| 137 |
| 138 GURL url = frame->GetView()->GetMainFrame()->GetURL(); |
| 139 std::string extension_id; |
| 140 if (url.SchemeIs(chrome::kExtensionScheme)) |
| 141 extension_id = url.host(); |
| 142 |
| 143 v8::Persistent<v8::Context> persistent_context = |
| 144 v8::Persistent<v8::Context>::New(context); |
| 145 GetContexts().push_back(linked_ptr<ContextInfo>( |
| 146 new ContextInfo(persistent_context, extension_id))); |
| 174 } | 147 } |
| 175 | 148 |
| 176 // static | 149 // static |
| 177 void EventBindings::HandleContextDestroyed(WebFrame* frame) { | 150 void EventBindings::HandleContextDestroyed(WebFrame* frame) { |
| 151 if (!bindings_registered) |
| 152 return; |
| 153 |
| 178 v8::HandleScope handle_scope; | 154 v8::HandleScope handle_scope; |
| 179 v8::Local<v8::Context> context = frame->GetScriptContext(); | 155 v8::Local<v8::Context> context = frame->GetScriptContext(); |
| 180 DCHECK(!context.IsEmpty()); | 156 DCHECK(!context.IsEmpty()); |
| 181 // TODO(mpcomplete): unregister it, dispatch event | 157 |
| 158 ContextList::iterator it = bindings_utils::FindContext(context); |
| 159 DCHECK(it != GetContexts().end()); |
| 160 |
| 161 // Notify the bindings that they're going away. |
| 162 CallFunctionInContext(context, "dispatchOnUnload", 0, NULL); |
| 163 |
| 164 // Remove all pending requests for this context. |
| 165 PendingRequestMap& pending_requests = GetPendingRequestMap(); |
| 166 for (PendingRequestMap::iterator it = pending_requests.begin(); |
| 167 it != pending_requests.end(); ) { |
| 168 PendingRequestMap::iterator current = it++; |
| 169 if (current->second->context == context) { |
| 170 current->second->context.Dispose(); |
| 171 current->second->context.Clear(); |
| 172 pending_requests.erase(current); |
| 173 } |
| 174 } |
| 175 |
| 176 // Remove it from our registered contexts. |
| 177 (*it)->context.Dispose(); |
| 178 (*it)->context.Clear(); |
| 179 GetContexts().erase(it); |
| 182 } | 180 } |
| 183 | 181 |
| 182 // static |
| 184 void EventBindings::CallFunction(const std::string& function_name, | 183 void EventBindings::CallFunction(const std::string& function_name, |
| 185 int argc, v8::Handle<v8::Value>* argv) { | 184 int argc, v8::Handle<v8::Value>* argv) { |
| 186 for (ContextList::iterator it = GetRegisteredContexts().begin(); | 185 v8::HandleScope handle_scope; |
| 187 it != GetRegisteredContexts().end(); ++it) { | 186 for (ContextList::iterator it = GetContexts().begin(); |
| 188 CallFunctionInContext(*it, function_name, argc, argv); | 187 it != GetContexts().end(); ++it) { |
| 188 CallFunctionInContext((*it)->context, function_name, argc, argv); |
| 189 } | 189 } |
| 190 } | 190 } |
| 191 |
| 192 // static |
| 193 void EventBindings::HandleResponse(int request_id, bool success, |
| 194 const std::string& response, |
| 195 const std::string& error) { |
| 196 PendingRequest* request = GetPendingRequestMap()[request_id].get(); |
| 197 if (!request) |
| 198 return; // The frame went away. |
| 199 |
| 200 v8::HandleScope handle_scope; |
| 201 v8::Handle<v8::Value> argv[5]; |
| 202 argv[0] = v8::Integer::New(request_id); |
| 203 argv[1] = v8::String::New(request->name.c_str()); |
| 204 argv[2] = v8::Boolean::New(success); |
| 205 argv[3] = v8::String::New(response.c_str()); |
| 206 argv[4] = v8::String::New(error.c_str()); |
| 207 CallFunctionInContext( |
| 208 request->context, "handleResponse", arraysize(argv), argv); |
| 209 |
| 210 GetPendingRequestMap().erase(request_id); |
| 211 } |
| OLD | NEW |