event_emitter.cc 12 KB

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