// 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/common/render_messages.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 "grit/renderer_resources.h" 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(); } // 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 > ContextList; struct ExtensionData { ContextList contexts; std::map listener_count; }; ContextList& GetRegisteredContexts() { return Singleton::get()->contexts; } int EventIncrementListenerCount(const std::string& event_name) { ExtensionData *data = Singleton::get(); return ++(data->listener_count[event_name]); } int EventDecrementListenerCount(const std::string& event_name) { ExtensionData *data = Singleton::get(); return --(data->listener_count[event_name]); } const char* kExtensionDeps[] = { JsonJsV8Extension::kName }; const char* kContextAttachCount = "chromium.attachCount"; class ExtensionImpl : public v8::Extension { public: ExtensionImpl() : v8::Extension(EventBindings::kName, GetStringResource(), arraysize(kExtensionDeps), kExtensionDeps) { } ~ExtensionImpl() {} virtual v8::Handle GetNativeFunction( v8::Handle 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(); } // Attach an event name to an object. static v8::Handle AttachEvent(const v8::Arguments& args) { DCHECK(args.Length() == 1); // TODO(erikkay) should enforce that event name is a string in the bindings DCHECK(args[0]->IsString() || args[0]->IsUndefined()); v8::Persistent context = v8::Persistent::New(v8::Context::GetCurrent()); v8::Local 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 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( new ViewHostMsg_ExtensionAddListener(event_name)); } } return v8::Undefined(); } static v8::Handle DetachEvent(const v8::Arguments& args) { DCHECK(args.Length() == 1); // TODO(erikkay) should enforce that event name is a string in the bindings DCHECK(args[0]->IsString() || args[0]->IsUndefined()); v8::Local context = v8::Context::GetCurrent(); v8::Local global = context->Global(); v8::Local 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( new ViewHostMsg_ExtensionRemoveListener(event_name)); } } return v8::Undefined(); } // Called when a registered context is garbage collected. static void UnregisterContext(v8::Handle 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 obj, void*) { UnregisterContext(obj); } }; } // namespace const char* EventBindings::kName = "chrome/EventBindings"; v8::Extension* EventBindings::Get() { return new ExtensionImpl(); } // static void EventBindings::SetRenderThread(RenderThreadBase* thread) { render_thread = thread; } void EventBindings::CallFunction(const std::string& function_name, int argc, v8::Handle* argv) { for (ContextList::iterator it = GetRegisteredContexts().begin(); it != GetRegisteredContexts().end(); ++it) { DCHECK(!it->IsEmpty()); v8::Context::Scope context_scope(*it); v8::Local global = (*it)->Global(); v8::Local script = v8::Script::Compile( v8::String::New(function_name.c_str())); v8::Local function_obj = script->Run(); if (!function_obj->IsFunction()) continue; v8::Local function = v8::Local::Cast(function_obj); if (!function.IsEmpty()) function->Call(v8::Object::New(), argc, argv); } }