// Copyright (c) 2017 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include <string> #include "atom/browser/api/event_subscriber.h" #include "atom/common/native_mate_converters/callback.h" namespace { // A FunctionTemplate lifetime is bound to the v8 context, so it can be safely // stored as a global here since there's only one for the main process. v8::Global<v8::FunctionTemplate> g_cached_template; struct JSHandlerData { JSHandlerData(v8::Isolate* isolate, mate::internal::EventSubscriberBase* subscriber) : handle_(isolate, v8::External::New(isolate, this)), subscriber_(subscriber) { handle_.SetWeak(this, GC, v8::WeakCallbackType::kFinalizer); } static void GC(const v8::WeakCallbackInfo<JSHandlerData>& data) { delete data.GetParameter(); } v8::Global<v8::External> handle_; mate::internal::EventSubscriberBase* subscriber_; }; void InvokeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) { v8::Locker locker(info.GetIsolate()); v8::HandleScope handle_scope(info.GetIsolate()); v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); v8::Context::Scope context_scope(context); mate::Arguments args(info); v8::Local<v8::Value> handler, event; args.GetNext(&handler); args.GetNext(&event); DCHECK(handler->IsExternal()); DCHECK(event->IsString()); JSHandlerData* handler_data = static_cast<JSHandlerData*>( v8::Local<v8::External>::Cast(handler)->Value()); handler_data->subscriber_->EventEmitted(mate::V8ToString(event), &args); } } // namespace namespace mate { namespace internal { EventSubscriberBase::EventSubscriberBase(v8::Isolate* isolate, v8::Local<v8::Object> emitter) : isolate_(isolate), emitter_(isolate, emitter) { if (g_cached_template.IsEmpty()) { g_cached_template = v8::Global<v8::FunctionTemplate>( isolate_, v8::FunctionTemplate::New(isolate_, InvokeCallback)); } } EventSubscriberBase::~EventSubscriberBase() { if (!isolate_) { return; } RemoveAllListeners(); emitter_.Reset(); DCHECK_EQ(js_handlers_.size(), 0u); } void EventSubscriberBase::On(const std::string& event_name) { DCHECK(js_handlers_.find(event_name) == js_handlers_.end()); v8::Locker locker(isolate_); v8::Isolate::Scope isolate_scope(isolate_); v8::HandleScope handle_scope(isolate_); auto fn_template = g_cached_template.Get(isolate_); auto event = mate::StringToV8(isolate_, event_name); auto js_handler_data = new JSHandlerData(isolate_, this); v8::Local<v8::Value> fn = internal::BindFunctionWith( isolate_, isolate_->GetCurrentContext(), fn_template->GetFunction(), js_handler_data->handle_.Get(isolate_), event); js_handlers_.insert( std::make_pair(event_name, v8::Global<v8::Value>(isolate_, fn))); internal::ValueVector converted_args = {event, fn}; internal::CallMethodWithArgs(isolate_, emitter_.Get(isolate_), "on", &converted_args); } void EventSubscriberBase::Off(const std::string& event_name) { v8::Locker locker(isolate_); v8::Isolate::Scope isolate_scope(isolate_); v8::HandleScope handle_scope(isolate_); auto js_handler = js_handlers_.find(event_name); DCHECK(js_handler != js_handlers_.end()); RemoveListener(js_handler); } void EventSubscriberBase::RemoveAllListeners() { v8::Locker locker(isolate_); v8::Isolate::Scope isolate_scope(isolate_); v8::HandleScope handle_scope(isolate_); while (!js_handlers_.empty()) { RemoveListener(js_handlers_.begin()); } } std::map<std::string, v8::Global<v8::Value>>::iterator EventSubscriberBase::RemoveListener( std::map<std::string, v8::Global<v8::Value>>::iterator it) { internal::ValueVector args = {StringToV8(isolate_, it->first), it->second.Get(isolate_)}; internal::CallMethodWithArgs( isolate_, v8::Local<v8::Object>::Cast(emitter_.Get(isolate_)), "removeListener", &args); it->second.Reset(); return js_handlers_.erase(it); } } // namespace internal } // namespace mate