Index: extensions/renderer/api_event_handler.cc |
diff --git a/extensions/renderer/api_event_handler.cc b/extensions/renderer/api_event_handler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..eb606654b7ad5a0fd82edcc17870c8423dd42b6f |
--- /dev/null |
+++ b/extensions/renderer/api_event_handler.cc |
@@ -0,0 +1,222 @@ |
+// Copyright 2016 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 "extensions/renderer/api_event_handler.h" |
+ |
+#include <algorithm> |
+ |
+#include "base/bind.h" |
+#include "base/logging.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/values.h" |
+#include "content/public/child/v8_value_converter.h" |
+#include "gin/arguments.h" |
+#include "gin/per_context_data.h" |
+ |
+namespace extensions { |
+ |
+namespace { |
+ |
+const char kExtensionAPIEventPerContextKey[] = "extension_api_events"; |
+const char kEventListenersKey[] = "event_listeners"; |
+ |
+void ForwardToGinHandler(const v8::FunctionCallbackInfo<v8::Value>& info) { |
+ gin::Arguments args(info); |
+ v8::Local<v8::External> external; |
+ CHECK(args.GetData(&external)); |
+ auto callback = |
+ static_cast<APIEventHandler::HandlerCallback*>(external->Value()); |
+ callback->Run(info.This(), &args); |
+} |
+ |
+} // namespace |
+ |
+APIEventHandler::APIEventPerContextData::APIEventPerContextData() = default; |
+APIEventHandler::APIEventPerContextData::~APIEventPerContextData() = default; |
+ |
+APIEventHandler::APIEventHandler(const binding::RunJSFunction& call_js) |
+ : call_js_(call_js), weak_factory_(this) {} |
+APIEventHandler::~APIEventHandler() {} |
+ |
+v8::Local<v8::Object> APIEventHandler::CreateEventInstance( |
jbroman
2016/11/01 18:59:24
High-level comment here: while we needed to get cl
jbroman
2016/11/01 19:14:33
One note here: a little more caution than I've imp
Devlin
2016/11/02 00:48:29
So, there's a few gotchas here and there with how
|
+ const std::string& event_name, |
+ v8::Local<v8::Context> context) { |
+ gin::PerContextData* per_context_data = gin::PerContextData::From(context); |
+ DCHECK(per_context_data); |
+ APIEventPerContextData* data = static_cast<APIEventPerContextData*>( |
+ per_context_data->GetUserData(kExtensionAPIEventPerContextKey)); |
+ if (!data) { |
+ auto api_data = base::MakeUnique<APIEventPerContextData>(); |
+ data = api_data.get(); |
+ per_context_data->SetUserData(kExtensionAPIEventPerContextKey, |
+ api_data.release()); |
+ } |
+ |
+ DCHECK(data->event_data.find(event_name) == data->event_data.end()); |
+ auto listeners = base::MakeUnique<EventListeners>(); |
+ |
+ if (event_template_.IsEmpty()) |
+ InitializeTemplate(context->GetIsolate(), data); |
+ |
+ v8::Local<v8::ObjectTemplate> local_template = |
+ event_template_.Get(context->GetIsolate()); |
+ v8::MaybeLocal<v8::Object> maybe_object = |
+ local_template->NewInstance(context); |
+ v8::Local<v8::Object> object; |
+ // TODO(devlin): Could this fail? |
+ CHECK(maybe_object.ToLocal(&object)); |
jbroman
2016/11/01 18:59:24
I believe this won't fail, because we know that si
Devlin
2016/11/02 00:48:29
Moot if we go with the latest patch set.
|
+ |
+ // We cache the EventListeners pointer on the event object for easy lookups |
+ // when adding, removing, or querying listeners from JS. |
+ v8::Maybe<bool> success = object->SetPrivate( |
+ context, v8::Private::ForApi(context->GetIsolate(), |
+ gin::StringToSymbol(context->GetIsolate(), |
+ kEventListenersKey)), |
+ v8::External::New(context->GetIsolate(), listeners.get())); |
+ DCHECK(success.IsJust()); |
+ DCHECK(success.FromJust()); |
+ |
+ data->event_data.insert(std::make_pair(event_name, std::move(listeners))); |
+ |
+ return object; |
+} |
+ |
+void APIEventHandler::InitializeTemplate(v8::Isolate* isolate, |
+ APIEventPerContextData* data) { |
+ v8::Local<v8::ObjectTemplate> event_template = |
+ v8::ObjectTemplate::New(isolate); |
+ |
+ { |
+ struct Method { |
+ const char* name; |
+ void (APIEventHandler::*handler)(v8::Local<v8::Object> object, |
+ gin::Arguments* args); |
+ } methods[] = { |
+ {"addListener", &APIEventHandler::AddListener}, |
+ {"removeListener", &APIEventHandler::RemoveListener}, |
+ {"hasListener", &APIEventHandler::HasListener}, |
+ {"hasListeners", &APIEventHandler::HasListeners}, |
+ }; |
+ for (const auto& method : methods) { |
+ auto handler_callback = base::MakeUnique<HandlerCallback>( |
+ base::Bind(method.handler, weak_factory_.GetWeakPtr())); |
+ // TODO(devlin): FunctionTemplate docs say "There can only be one function |
+ // created from a FunctionTemplate in a context." I'm assuming that means |
+ // we can't change the template, rather than we can only use it to |
+ // instantiate a single function object? |
jbroman
2016/11/01 18:59:24
It means that if you call v8::FunctionTemplate::Ge
Devlin
2016/11/02 00:48:29
Ditto
|
+ v8::Local<v8::FunctionTemplate> function = v8::FunctionTemplate::New( |
+ isolate, &ForwardToGinHandler, |
+ v8::External::New(isolate, handler_callback.get()), |
+ v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kThrow); |
jbroman
2016/11/01 18:59:24
If you use a signature, you can have V8 validate t
Devlin
2016/11/02 00:48:29
Ditto
|
+ data->callbacks.push_back(std::move(handler_callback)); |
+ event_template->Set(gin::StringToSymbol(isolate, method.name), function); |
+ } |
+ } |
+ |
+ event_template_ = v8::Global<v8::ObjectTemplate>(isolate, event_template); |
+} |
+ |
+void APIEventHandler::FireEventInContext(const std::string& event_name, |
+ v8::Local<v8::Context> context, |
+ const base::ListValue& args) { |
+ gin::PerContextData* per_context_data = gin::PerContextData::From(context); |
+ DCHECK(per_context_data); |
+ APIEventPerContextData* data = static_cast<APIEventPerContextData*>( |
+ per_context_data->GetUserData(kExtensionAPIEventPerContextKey)); |
+ if (!data) |
+ return; |
+ |
+ auto iter = data->event_data.find(event_name); |
+ if (iter == data->event_data.end()) |
+ return; |
+ |
+ EventListeners* listeners = iter->second.get(); |
+ |
+ std::vector<v8::Local<v8::Value>> v8_args; |
+ v8_args.reserve(args.GetSize()); |
+ std::unique_ptr<content::V8ValueConverter> converter( |
+ content::V8ValueConverter::create()); |
+ for (const auto& arg : args) |
+ v8_args.push_back(converter->ToV8Value(arg.get(), context)); |
+ |
+ for (auto& listener : *listeners) { |
+ call_js_.Run(listener.Get(context->GetIsolate()), context, v8_args.size(), |
+ v8_args.data()); |
+ } |
+} |
+ |
+const APIEventHandler::EventListeners& |
+APIEventHandler::GetEventListenersForTesting(const std::string& event_name, |
+ v8::Local<v8::Context> context) { |
+ gin::PerContextData* per_context_data = gin::PerContextData::From(context); |
+ DCHECK(per_context_data); |
+ APIEventPerContextData* data = static_cast<APIEventPerContextData*>( |
+ per_context_data->GetUserData(kExtensionAPIEventPerContextKey)); |
+ DCHECK(data); |
+ |
+ auto iter = data->event_data.find(event_name); |
+ DCHECK(iter != data->event_data.end()); |
+ return *iter->second; |
+} |
+ |
+APIEventHandler::EventListeners* APIEventHandler::GetListenersForObject( |
+ v8::Local<v8::Object> object, |
+ v8::Local<v8::Context> context) { |
+ v8::MaybeLocal<v8::Value> maybe_value = object->GetPrivate( |
+ context, v8::Private::ForApi(context->GetIsolate(), |
+ gin::StringToSymbol(context->GetIsolate(), |
+ kEventListenersKey))); |
+ v8::Local<v8::Value> value; |
+ CHECK(maybe_value.ToLocal(&value)); |
+ CHECK(value->IsExternal()); |
jbroman
2016/11/01 18:59:24
You can't rely on this working, at the moment. You
Devlin
2016/11/02 00:48:29
Also moot.
|
+ v8::Local<v8::External> external = v8::Local<v8::External>::Cast(value); |
+ return static_cast<EventListeners*>(external->Value()); |
+} |
+ |
+void APIEventHandler::AddListener(v8::Local<v8::Object> caller, |
+ gin::Arguments* args) { |
+ v8::Local<v8::Function> listener; |
+ if (args->Length() != 1 || !args->GetNext(&listener)) |
+ return; |
+ EventListeners* listeners = |
+ GetListenersForObject(caller, args->isolate()->GetCurrentContext()); |
jbroman
2016/11/01 18:59:24
Didn't catch this before, but getting the context
Devlin
2016/11/02 00:48:29
And moot :)
|
+ if (std::find(listeners->begin(), listeners->end(), listener) != |
+ listeners->end()) |
+ return; |
+ listeners->push_back(v8::Global<v8::Function>(args->isolate(), listener)); |
+} |
+ |
+void APIEventHandler::RemoveListener(v8::Local<v8::Object> caller, |
+ gin::Arguments* args) { |
+ v8::Local<v8::Function> listener; |
+ if (args->Length() != 1 || !args->GetNext(&listener)) |
+ return; |
+ EventListeners* listeners = |
+ GetListenersForObject(caller, args->isolate()->GetCurrentContext()); |
+ auto iter = std::find(listeners->begin(), listeners->end(), listener); |
+ if (iter != listeners->end()) |
+ listeners->erase(iter); |
+} |
+ |
+void APIEventHandler::HasListener(v8::Local<v8::Object> caller, |
+ gin::Arguments* args) { |
+ v8::Local<v8::Function> listener; |
+ if (args->Length() != 1 || !args->GetNext(&listener)) |
+ return; |
+ EventListeners* listeners = |
+ GetListenersForObject(caller, args->isolate()->GetCurrentContext()); |
+ auto iter = std::find(listeners->begin(), listeners->end(), listener); |
+ args->Return(iter != listeners->end()); |
+} |
+ |
+void APIEventHandler::HasListeners(v8::Local<v8::Object> caller, |
+ gin::Arguments* args) { |
+ if (args->Length() != 0) |
+ return; |
+ EventListeners* listeners = |
+ GetListenersForObject(caller, args->isolate()->GetCurrentContext()); |
+ args->Return(!listeners->empty()); |
+} |
+ |
+} // namespace extensions |