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/bindings/event_emitter.h"
6
7 #include <algorithm>
8
9 #include "extensions/renderer/bindings/api_binding_util.h"
10 #include "extensions/renderer/bindings/api_event_listeners.h"
11 #include "extensions/renderer/bindings/exception_handler.h"
12 #include "gin/data_object_builder.h"
13 #include "gin/object_template_builder.h"
14 #include "gin/per_context_data.h"
15
16 namespace extensions {
17
18 namespace {
19
20 constexpr const char kEmitterKey[] = "emitter";
21 constexpr const char kArgumentsKey[] = "arguments";
22 constexpr const char kFilterKey[] = "filter";
23 constexpr const char kEventEmitterTypeName[] = "Event";
24
25 } // namespace
26
27 gin::WrapperInfo EventEmitter::kWrapperInfo = {gin::kEmbedderNativeGin};
28
EventEmitter(bool supports_filters,std::unique_ptr<APIEventListeners> listeners,ExceptionHandler * exception_handler)29 EventEmitter::EventEmitter(bool supports_filters,
30 std::unique_ptr<APIEventListeners> listeners,
31 ExceptionHandler* exception_handler)
32 : supports_filters_(supports_filters),
33 listeners_(std::move(listeners)),
34 exception_handler_(exception_handler) {}
35
~EventEmitter()36 EventEmitter::~EventEmitter() {}
37
GetObjectTemplateBuilder(v8::Isolate * isolate)38 gin::ObjectTemplateBuilder EventEmitter::GetObjectTemplateBuilder(
39 v8::Isolate* isolate) {
40 return Wrappable<EventEmitter>::GetObjectTemplateBuilder(isolate)
41 .SetMethod("addListener", &EventEmitter::AddListener)
42 .SetMethod("removeListener", &EventEmitter::RemoveListener)
43 .SetMethod("hasListener", &EventEmitter::HasListener)
44 .SetMethod("hasListeners", &EventEmitter::HasListeners)
45 // The following methods aren't part of the public API, but are used
46 // by our custom bindings and exposed on the public event object. :(
47 // TODO(devlin): Once we convert all custom bindings that use these,
48 // they can be removed.
49 .SetMethod("dispatch", &EventEmitter::Dispatch);
50 }
51
GetTypeName()52 const char* EventEmitter::GetTypeName() {
53 return kEventEmitterTypeName;
54 }
55
Fire(v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * args,const EventFilteringInfo * filter,JSRunner::ResultCallback callback)56 void EventEmitter::Fire(v8::Local<v8::Context> context,
57 std::vector<v8::Local<v8::Value>>* args,
58 const EventFilteringInfo* filter,
59 JSRunner::ResultCallback callback) {
60 DispatchAsync(context, args, filter, std::move(callback));
61 }
62
FireSync(v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * args,const EventFilteringInfo * filter)63 v8::Local<v8::Value> EventEmitter::FireSync(
64 v8::Local<v8::Context> context,
65 std::vector<v8::Local<v8::Value>>* args,
66 const EventFilteringInfo* filter) {
67 DCHECK(context == context->GetIsolate()->GetCurrentContext());
68 return DispatchSync(context, args, filter);
69 }
70
Invalidate(v8::Local<v8::Context> context)71 void EventEmitter::Invalidate(v8::Local<v8::Context> context) {
72 valid_ = false;
73 listeners_->Invalidate(context);
74 }
75
GetNumListeners() const76 size_t EventEmitter::GetNumListeners() const {
77 return listeners_->GetNumListeners();
78 }
79
AddListener(gin::Arguments * arguments)80 void EventEmitter::AddListener(gin::Arguments* arguments) {
81 // If script from another context maintains a reference to this object, it's
82 // possible that functions can be called after this object's owning context
83 // is torn down and released by blink. We don't support this behavior, but
84 // we need to make sure nothing crashes, so early out of methods.
85 if (!valid_)
86 return;
87
88 v8::Local<v8::Function> listener;
89 // TODO(devlin): For some reason, we don't throw an error when someone calls
90 // add/removeListener with no argument. We probably should. For now, keep
91 // the status quo, but we should revisit this.
92 if (!arguments->GetNext(&listener))
93 return;
94
95 if (!arguments->PeekNext().IsEmpty() && !supports_filters_) {
96 arguments->ThrowTypeError("This event does not support filters");
97 return;
98 }
99
100 v8::Local<v8::Object> filter;
101 if (!arguments->PeekNext().IsEmpty() && !arguments->GetNext(&filter)) {
102 arguments->ThrowTypeError("Invalid invocation");
103 return;
104 }
105
106 v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
107 if (!gin::PerContextData::From(context))
108 return;
109
110 std::string error;
111 if (!listeners_->AddListener(listener, filter, context, &error) &&
112 !error.empty()) {
113 arguments->ThrowTypeError(error);
114 }
115 }
116
RemoveListener(gin::Arguments * arguments)117 void EventEmitter::RemoveListener(gin::Arguments* arguments) {
118 // See comment in AddListener().
119 if (!valid_)
120 return;
121
122 v8::Local<v8::Function> listener;
123 // See comment in AddListener().
124 if (!arguments->GetNext(&listener))
125 return;
126
127 listeners_->RemoveListener(listener, arguments->GetHolderCreationContext());
128 }
129
HasListener(v8::Local<v8::Function> listener)130 bool EventEmitter::HasListener(v8::Local<v8::Function> listener) {
131 return listeners_->HasListener(listener);
132 }
133
HasListeners()134 bool EventEmitter::HasListeners() {
135 return listeners_->GetNumListeners() != 0;
136 }
137
Dispatch(gin::Arguments * arguments)138 void EventEmitter::Dispatch(gin::Arguments* arguments) {
139 if (!valid_)
140 return;
141
142 if (listeners_->GetNumListeners() == 0)
143 return;
144
145 v8::Isolate* isolate = arguments->isolate();
146 v8::HandleScope handle_scope(isolate);
147 v8::Local<v8::Context> context = isolate->GetCurrentContext();
148 std::vector<v8::Local<v8::Value>> v8_args = arguments->GetAll();
149
150 // Since this is directly from JS, we know it should be safe to call
151 // synchronously and use the return result, so we don't use Fire().
152 arguments->Return(DispatchSync(context, &v8_args, nullptr));
153 }
154
DispatchSync(v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * args,const EventFilteringInfo * filter)155 v8::Local<v8::Value> EventEmitter::DispatchSync(
156 v8::Local<v8::Context> context,
157 std::vector<v8::Local<v8::Value>>* args,
158 const EventFilteringInfo* filter) {
159 // Note that |listeners_| can be modified during handling.
160 std::vector<v8::Local<v8::Function>> listeners =
161 listeners_->GetListeners(filter, context);
162
163 JSRunner* js_runner = JSRunner::Get(context);
164 v8::Isolate* isolate = context->GetIsolate();
165 DCHECK(context == isolate->GetCurrentContext());
166
167 // Gather results from each listener as we go along. This should only be
168 // called when running synchronous script is allowed, and some callers
169 // expect a return value of an array with entries for each of the results of
170 // the listeners.
171 // TODO(devlin): It'd be nice to refactor anything expecting a result here so
172 // we don't have to have this special logic, especially since script could
173 // potentially tweak the result object through prototype manipulation (which
174 // also means we should never use this for security decisions).
175 v8::Local<v8::Array> results = v8::Array::New(isolate);
176 uint32_t results_index = 0;
177
178 v8::TryCatch try_catch(isolate);
179 for (const auto& listener : listeners) {
180 // NOTE(devlin): Technically, any listener here could suspend JS execution
181 // (through e.g. calling alert() or print()). That should suspend this
182 // message loop as well (though a nested message loop will run). This is a
183 // bit ugly, but should hopefully be safe.
184 v8::MaybeLocal<v8::Value> maybe_result = js_runner->RunJSFunctionSync(
185 listener, context, args->size(), args->data());
186
187 // Any of the listeners could invalidate the context. If that happens,
188 // bail out.
189 if (!binding::IsContextValid(context))
190 return v8::Undefined(isolate);
191
192 v8::Local<v8::Value> listener_result;
193 if (maybe_result.ToLocal(&listener_result)) {
194 if (!listener_result->IsUndefined()) {
195 CHECK(
196 results
197 ->CreateDataProperty(context, results_index++, listener_result)
198 .ToChecked());
199 }
200 } else {
201 DCHECK(try_catch.HasCaught());
202 exception_handler_->HandleException(context, "Error in event handler",
203 &try_catch);
204 try_catch.Reset();
205 }
206 }
207
208 // Only return a value if there's at least one response. This is the behavior
209 // of the current JS implementation.
210 v8::Local<v8::Value> return_value;
211 if (results_index > 0) {
212 return_value = gin::DataObjectBuilder(isolate)
213 .Set("results", results.As<v8::Value>())
214 .Build();
215 } else {
216 return_value = v8::Undefined(isolate);
217 }
218
219 return return_value;
220 }
221
DispatchAsync(v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * args,const EventFilteringInfo * filter,JSRunner::ResultCallback callback)222 void EventEmitter::DispatchAsync(v8::Local<v8::Context> context,
223 std::vector<v8::Local<v8::Value>>* args,
224 const EventFilteringInfo* filter,
225 JSRunner::ResultCallback callback) {
226 v8::Isolate* isolate = context->GetIsolate();
227 v8::HandleScope handle_scope(isolate);
228 v8::Context::Scope context_scope(context);
229
230 // In order to dispatch (potentially) asynchronously (such as when script is
231 // suspended), use a helper function to run once JS is allowed to run,
232 // currying in the necessary information about the arguments and filter.
233 // We do this (rather than simply queuing up each listener and running them
234 // asynchronously) for a few reasons:
235 // - It allows us to catch exceptions when the listener is running.
236 // - Listeners could be removed between the time the event is received and the
237 // listeners are notified.
238 // - It allows us to group the listeners responses.
239
240 // We always set a filter id (rather than leaving filter undefined in the
241 // case of no filter being present) to avoid ever hitting the Object prototype
242 // chain when checking for it on the data value in DispatchAsyncHelper().
243 int filter_id = kInvalidFilterId;
244 if (filter) {
245 filter_id = next_filter_id_++;
246 pending_filters_.emplace(filter_id, *filter);
247 }
248
249 v8::Local<v8::Array> args_array = v8::Array::New(isolate, args->size());
250 for (size_t i = 0; i < args->size(); ++i) {
251 CHECK(args_array->CreateDataProperty(context, i, args->at(i)).ToChecked());
252 }
253
254 v8::Local<v8::Object> data =
255 gin::DataObjectBuilder(isolate)
256 .Set(kEmitterKey, GetWrapper(isolate).ToLocalChecked())
257 .Set(kArgumentsKey, args_array.As<v8::Value>())
258 .Set(kFilterKey, gin::ConvertToV8(isolate, filter_id))
259 .Build();
260 v8::Local<v8::Function> function;
261 // TODO(devlin): Function construction can fail in some weird cases (looking
262 // up the "prototype" property on parents, failing to instantiate properties
263 // on the function, etc). In *theory*, none of those apply here. Leave this as
264 // a CHECK for now to flush out any cases.
265 CHECK(v8::Function::New(context, &DispatchAsyncHelper, data)
266 .ToLocal(&function));
267
268 JSRunner::Get(context)->RunJSFunction(function, context, 0, nullptr,
269 std::move(callback));
270 }
271
272 // static
DispatchAsyncHelper(const v8::FunctionCallbackInfo<v8::Value> & info)273 void EventEmitter::DispatchAsyncHelper(
274 const v8::FunctionCallbackInfo<v8::Value>& info) {
275 v8::Isolate* isolate = info.GetIsolate();
276 v8::Local<v8::Context> context = isolate->GetCurrentContext();
277 if (!binding::IsContextValid(context))
278 return;
279
280 v8::Local<v8::Object> data = info.Data().As<v8::Object>();
281
282 v8::Local<v8::Value> emitter_value =
283 data->Get(context, gin::StringToSymbol(isolate, kEmitterKey))
284 .ToLocalChecked();
285 EventEmitter* emitter = nullptr;
286 gin::Converter<EventEmitter*>::FromV8(isolate, emitter_value, &emitter);
287 DCHECK(emitter);
288
289 v8::Local<v8::Value> filter_id_value =
290 data->Get(context, gin::StringToSymbol(isolate, kFilterKey))
291 .ToLocalChecked();
292 int filter_id = filter_id_value.As<v8::Int32>()->Value();
293 base::Optional<EventFilteringInfo> filter;
294 if (filter_id != kInvalidFilterId) {
295 auto filter_iter = emitter->pending_filters_.find(filter_id);
296 DCHECK(filter_iter != emitter->pending_filters_.end());
297 filter = std::move(filter_iter->second);
298 emitter->pending_filters_.erase(filter_iter);
299 }
300
301 v8::Local<v8::Value> arguments_value =
302 data->Get(context, gin::StringToSymbol(isolate, kArgumentsKey))
303 .ToLocalChecked();
304 DCHECK(arguments_value->IsArray());
305 v8::Local<v8::Array> arguments_array = arguments_value.As<v8::Array>();
306 std::vector<v8::Local<v8::Value>> arguments;
307 uint32_t arguments_count = arguments_array->Length();
308 arguments.reserve(arguments_count);
309 for (uint32_t i = 0; i < arguments_count; ++i)
310 arguments.push_back(arguments_array->Get(context, i).ToLocalChecked());
311
312 // We know that dispatching synchronously should be safe because this function
313 // was triggered by JS execution.
314 info.GetReturnValue().Set(emitter->DispatchSync(
315 context, &arguments, filter ? &filter.value() : nullptr));
316 }
317
318 } // namespace extensions
319