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