OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "extensions/renderer/api_event_handler.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/logging.h" | |
11 #include "base/memory/ptr_util.h" | |
12 #include "base/values.h" | |
13 #include "content/public/child/v8_value_converter.h" | |
14 #include "gin/arguments.h" | |
15 #include "gin/per_context_data.h" | |
16 | |
17 namespace extensions { | |
18 | |
19 namespace { | |
20 | |
21 const char kExtensionAPIEventPerContextKey[] = "extension_api_events"; | |
22 const char kEventListenersKey[] = "event_listeners"; | |
23 | |
24 void ForwardToGinHandler(const v8::FunctionCallbackInfo<v8::Value>& info) { | |
25 gin::Arguments args(info); | |
26 v8::Local<v8::External> external; | |
27 CHECK(args.GetData(&external)); | |
28 auto callback = | |
29 static_cast<APIEventHandler::HandlerCallback*>(external->Value()); | |
30 callback->Run(info.This(), &args); | |
31 } | |
32 | |
33 } // namespace | |
34 | |
35 APIEventHandler::APIEventPerContextData::APIEventPerContextData() = default; | |
36 APIEventHandler::APIEventPerContextData::~APIEventPerContextData() = default; | |
37 | |
38 APIEventHandler::APIEventHandler(const binding::RunJSFunction& call_js) | |
39 : call_js_(call_js), weak_factory_(this) {} | |
40 APIEventHandler::~APIEventHandler() {} | |
41 | |
42 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
| |
43 const std::string& event_name, | |
44 v8::Local<v8::Context> context) { | |
45 gin::PerContextData* per_context_data = gin::PerContextData::From(context); | |
46 DCHECK(per_context_data); | |
47 APIEventPerContextData* data = static_cast<APIEventPerContextData*>( | |
48 per_context_data->GetUserData(kExtensionAPIEventPerContextKey)); | |
49 if (!data) { | |
50 auto api_data = base::MakeUnique<APIEventPerContextData>(); | |
51 data = api_data.get(); | |
52 per_context_data->SetUserData(kExtensionAPIEventPerContextKey, | |
53 api_data.release()); | |
54 } | |
55 | |
56 DCHECK(data->event_data.find(event_name) == data->event_data.end()); | |
57 auto listeners = base::MakeUnique<EventListeners>(); | |
58 | |
59 if (event_template_.IsEmpty()) | |
60 InitializeTemplate(context->GetIsolate(), data); | |
61 | |
62 v8::Local<v8::ObjectTemplate> local_template = | |
63 event_template_.Get(context->GetIsolate()); | |
64 v8::MaybeLocal<v8::Object> maybe_object = | |
65 local_template->NewInstance(context); | |
66 v8::Local<v8::Object> object; | |
67 // TODO(devlin): Could this fail? | |
68 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.
| |
69 | |
70 // We cache the EventListeners pointer on the event object for easy lookups | |
71 // when adding, removing, or querying listeners from JS. | |
72 v8::Maybe<bool> success = object->SetPrivate( | |
73 context, v8::Private::ForApi(context->GetIsolate(), | |
74 gin::StringToSymbol(context->GetIsolate(), | |
75 kEventListenersKey)), | |
76 v8::External::New(context->GetIsolate(), listeners.get())); | |
77 DCHECK(success.IsJust()); | |
78 DCHECK(success.FromJust()); | |
79 | |
80 data->event_data.insert(std::make_pair(event_name, std::move(listeners))); | |
81 | |
82 return object; | |
83 } | |
84 | |
85 void APIEventHandler::InitializeTemplate(v8::Isolate* isolate, | |
86 APIEventPerContextData* data) { | |
87 v8::Local<v8::ObjectTemplate> event_template = | |
88 v8::ObjectTemplate::New(isolate); | |
89 | |
90 { | |
91 struct Method { | |
92 const char* name; | |
93 void (APIEventHandler::*handler)(v8::Local<v8::Object> object, | |
94 gin::Arguments* args); | |
95 } methods[] = { | |
96 {"addListener", &APIEventHandler::AddListener}, | |
97 {"removeListener", &APIEventHandler::RemoveListener}, | |
98 {"hasListener", &APIEventHandler::HasListener}, | |
99 {"hasListeners", &APIEventHandler::HasListeners}, | |
100 }; | |
101 for (const auto& method : methods) { | |
102 auto handler_callback = base::MakeUnique<HandlerCallback>( | |
103 base::Bind(method.handler, weak_factory_.GetWeakPtr())); | |
104 // TODO(devlin): FunctionTemplate docs say "There can only be one function | |
105 // created from a FunctionTemplate in a context." I'm assuming that means | |
106 // we can't change the template, rather than we can only use it to | |
107 // 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
| |
108 v8::Local<v8::FunctionTemplate> function = v8::FunctionTemplate::New( | |
109 isolate, &ForwardToGinHandler, | |
110 v8::External::New(isolate, handler_callback.get()), | |
111 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
| |
112 data->callbacks.push_back(std::move(handler_callback)); | |
113 event_template->Set(gin::StringToSymbol(isolate, method.name), function); | |
114 } | |
115 } | |
116 | |
117 event_template_ = v8::Global<v8::ObjectTemplate>(isolate, event_template); | |
118 } | |
119 | |
120 void APIEventHandler::FireEventInContext(const std::string& event_name, | |
121 v8::Local<v8::Context> context, | |
122 const base::ListValue& args) { | |
123 gin::PerContextData* per_context_data = gin::PerContextData::From(context); | |
124 DCHECK(per_context_data); | |
125 APIEventPerContextData* data = static_cast<APIEventPerContextData*>( | |
126 per_context_data->GetUserData(kExtensionAPIEventPerContextKey)); | |
127 if (!data) | |
128 return; | |
129 | |
130 auto iter = data->event_data.find(event_name); | |
131 if (iter == data->event_data.end()) | |
132 return; | |
133 | |
134 EventListeners* listeners = iter->second.get(); | |
135 | |
136 std::vector<v8::Local<v8::Value>> v8_args; | |
137 v8_args.reserve(args.GetSize()); | |
138 std::unique_ptr<content::V8ValueConverter> converter( | |
139 content::V8ValueConverter::create()); | |
140 for (const auto& arg : args) | |
141 v8_args.push_back(converter->ToV8Value(arg.get(), context)); | |
142 | |
143 for (auto& listener : *listeners) { | |
144 call_js_.Run(listener.Get(context->GetIsolate()), context, v8_args.size(), | |
145 v8_args.data()); | |
146 } | |
147 } | |
148 | |
149 const APIEventHandler::EventListeners& | |
150 APIEventHandler::GetEventListenersForTesting(const std::string& event_name, | |
151 v8::Local<v8::Context> context) { | |
152 gin::PerContextData* per_context_data = gin::PerContextData::From(context); | |
153 DCHECK(per_context_data); | |
154 APIEventPerContextData* data = static_cast<APIEventPerContextData*>( | |
155 per_context_data->GetUserData(kExtensionAPIEventPerContextKey)); | |
156 DCHECK(data); | |
157 | |
158 auto iter = data->event_data.find(event_name); | |
159 DCHECK(iter != data->event_data.end()); | |
160 return *iter->second; | |
161 } | |
162 | |
163 APIEventHandler::EventListeners* APIEventHandler::GetListenersForObject( | |
164 v8::Local<v8::Object> object, | |
165 v8::Local<v8::Context> context) { | |
166 v8::MaybeLocal<v8::Value> maybe_value = object->GetPrivate( | |
167 context, v8::Private::ForApi(context->GetIsolate(), | |
168 gin::StringToSymbol(context->GetIsolate(), | |
169 kEventListenersKey))); | |
170 v8::Local<v8::Value> value; | |
171 CHECK(maybe_value.ToLocal(&value)); | |
172 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.
| |
173 v8::Local<v8::External> external = v8::Local<v8::External>::Cast(value); | |
174 return static_cast<EventListeners*>(external->Value()); | |
175 } | |
176 | |
177 void APIEventHandler::AddListener(v8::Local<v8::Object> caller, | |
178 gin::Arguments* args) { | |
179 v8::Local<v8::Function> listener; | |
180 if (args->Length() != 1 || !args->GetNext(&listener)) | |
181 return; | |
182 EventListeners* listeners = | |
183 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 :)
| |
184 if (std::find(listeners->begin(), listeners->end(), listener) != | |
185 listeners->end()) | |
186 return; | |
187 listeners->push_back(v8::Global<v8::Function>(args->isolate(), listener)); | |
188 } | |
189 | |
190 void APIEventHandler::RemoveListener(v8::Local<v8::Object> caller, | |
191 gin::Arguments* args) { | |
192 v8::Local<v8::Function> listener; | |
193 if (args->Length() != 1 || !args->GetNext(&listener)) | |
194 return; | |
195 EventListeners* listeners = | |
196 GetListenersForObject(caller, args->isolate()->GetCurrentContext()); | |
197 auto iter = std::find(listeners->begin(), listeners->end(), listener); | |
198 if (iter != listeners->end()) | |
199 listeners->erase(iter); | |
200 } | |
201 | |
202 void APIEventHandler::HasListener(v8::Local<v8::Object> caller, | |
203 gin::Arguments* args) { | |
204 v8::Local<v8::Function> listener; | |
205 if (args->Length() != 1 || !args->GetNext(&listener)) | |
206 return; | |
207 EventListeners* listeners = | |
208 GetListenersForObject(caller, args->isolate()->GetCurrentContext()); | |
209 auto iter = std::find(listeners->begin(), listeners->end(), listener); | |
210 args->Return(iter != listeners->end()); | |
211 } | |
212 | |
213 void APIEventHandler::HasListeners(v8::Local<v8::Object> caller, | |
214 gin::Arguments* args) { | |
215 if (args->Length() != 0) | |
216 return; | |
217 EventListeners* listeners = | |
218 GetListenersForObject(caller, args->isolate()->GetCurrentContext()); | |
219 args->Return(!listeners->empty()); | |
220 } | |
221 | |
222 } // namespace extensions | |
OLD | NEW |