api_event_listeners.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Copyright 2017 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/api_event_listeners.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include "content/public/renderer/v8_value_converter.h"
  8. #include "extensions/common/event_matcher.h"
  9. #include "extensions/common/mojom/event_dispatcher.mojom.h"
  10. #include "extensions/renderer/bindings/listener_tracker.h"
  11. #include "gin/converter.h"
  12. namespace extensions {
  13. namespace {
  14. // TODO(devlin): The EventFilter supports adding EventMatchers associated with
  15. // an id. For now, we ignore it and add/return everything associated with this
  16. // constant. We should rethink that.
  17. const int kIgnoreRoutingId = 0;
  18. const char kErrorTooManyListeners[] = "Too many listeners.";
  19. // Pseudo-validates the given |filter| and converts it into a
  20. // base::DictionaryValue. Returns true on success.
  21. // TODO(devlin): This "validation" is pretty terrible. It matches the JS
  22. // equivalent, but it's lousy and makes it easy for users to get it wrong.
  23. // We should generate an argument spec for it and match it exactly.
  24. bool ValidateFilter(v8::Local<v8::Context> context,
  25. v8::Local<v8::Object> filter,
  26. std::unique_ptr<base::DictionaryValue>* filter_dict,
  27. std::string* error) {
  28. v8::Isolate* isolate = context->GetIsolate();
  29. v8::HandleScope handle_scope(isolate);
  30. if (filter.IsEmpty()) {
  31. *filter_dict = std::make_unique<base::DictionaryValue>();
  32. return true;
  33. }
  34. v8::Local<v8::Value> url_filter;
  35. if (!filter->Get(context, gin::StringToSymbol(isolate, "url"))
  36. .ToLocal(&url_filter)) {
  37. return false;
  38. }
  39. if (!url_filter->IsUndefined() && !url_filter->IsArray()) {
  40. *error = "filters.url should be an array.";
  41. return false;
  42. }
  43. v8::Local<v8::Value> service_type;
  44. if (!filter->Get(context, gin::StringToSymbol(isolate, "serviceType"))
  45. .ToLocal(&service_type)) {
  46. return false;
  47. }
  48. if (!service_type->IsUndefined() && !service_type->IsString()) {
  49. *error = "filters.serviceType should be a string.";
  50. return false;
  51. }
  52. std::unique_ptr<base::Value> value =
  53. content::V8ValueConverter::Create()->FromV8Value(filter, context);
  54. if (!value || !value->is_dict()) {
  55. *error = "could not convert filter.";
  56. return false;
  57. }
  58. *filter_dict = base::DictionaryValue::From(std::move(value));
  59. return true;
  60. }
  61. } // namespace
  62. UnfilteredEventListeners::UnfilteredEventListeners(
  63. ListenersUpdated listeners_updated,
  64. const std::string& event_name,
  65. ContextOwnerIdGetter context_owner_id_getter,
  66. int max_listeners,
  67. bool supports_lazy_listeners,
  68. ListenerTracker* listener_tracker)
  69. : listeners_updated_(std::move(listeners_updated)),
  70. event_name_(event_name),
  71. context_owner_id_getter_(std::move(context_owner_id_getter)),
  72. max_listeners_(max_listeners),
  73. supports_lazy_listeners_(supports_lazy_listeners),
  74. listener_tracker_(listener_tracker) {
  75. DCHECK(max_listeners_ == binding::kNoListenerMax || max_listeners_ > 0);
  76. DCHECK_EQ(listener_tracker_ == nullptr, context_owner_id_getter_.is_null())
  77. << "Managed events must have both a listener tracker and context owner; "
  78. << "unmanaged must have neither.";
  79. }
  80. UnfilteredEventListeners::~UnfilteredEventListeners() = default;
  81. bool UnfilteredEventListeners::AddListener(v8::Local<v8::Function> listener,
  82. v8::Local<v8::Object> filter,
  83. v8::Local<v8::Context> context,
  84. std::string* error) {
  85. // |filter| should be checked before getting here.
  86. DCHECK(filter.IsEmpty())
  87. << "Filtered events should use FilteredEventListeners";
  88. if (HasListener(listener))
  89. return false;
  90. if (max_listeners_ != binding::kNoListenerMax &&
  91. listeners_.size() >= static_cast<size_t>(max_listeners_)) {
  92. *error = kErrorTooManyListeners;
  93. return false;
  94. }
  95. listeners_.push_back(
  96. v8::Global<v8::Function>(context->GetIsolate(), listener));
  97. if (listeners_.size() == 1) {
  98. // NOTE: |listener_tracker_| is null for unmanaged events, in which case we
  99. // send no notifications.
  100. if (listener_tracker_) {
  101. LazilySetContextOwner(context);
  102. bool was_first_listener_for_context_owner =
  103. listener_tracker_->AddUnfilteredListener(context_owner_id_,
  104. event_name_);
  105. binding::EventListenersChanged changed =
  106. was_first_listener_for_context_owner
  107. ? binding::EventListenersChanged::
  108. kFirstUnfilteredListenerForContextOwnerAdded
  109. : binding::EventListenersChanged::
  110. kFirstUnfilteredListenerForContextAdded;
  111. listeners_updated_.Run(event_name_, changed, nullptr,
  112. supports_lazy_listeners_, context);
  113. }
  114. }
  115. return true;
  116. }
  117. void UnfilteredEventListeners::RemoveListener(v8::Local<v8::Function> listener,
  118. v8::Local<v8::Context> context) {
  119. auto iter = std::find(listeners_.begin(), listeners_.end(), listener);
  120. if (iter == listeners_.end())
  121. return;
  122. listeners_.erase(iter);
  123. if (listeners_.empty()) {
  124. bool update_lazy_listeners = supports_lazy_listeners_;
  125. NotifyListenersEmpty(context, update_lazy_listeners);
  126. }
  127. }
  128. bool UnfilteredEventListeners::HasListener(v8::Local<v8::Function> listener) {
  129. return std::find(listeners_.begin(), listeners_.end(), listener) !=
  130. listeners_.end();
  131. }
  132. size_t UnfilteredEventListeners::GetNumListeners() {
  133. return listeners_.size();
  134. }
  135. std::vector<v8::Local<v8::Function>> UnfilteredEventListeners::GetListeners(
  136. mojom::EventFilteringInfoPtr filter,
  137. v8::Local<v8::Context> context) {
  138. std::vector<v8::Local<v8::Function>> listeners;
  139. listeners.reserve(listeners_.size());
  140. for (const auto& listener : listeners_)
  141. listeners.push_back(listener.Get(context->GetIsolate()));
  142. return listeners;
  143. }
  144. void UnfilteredEventListeners::Invalidate(v8::Local<v8::Context> context) {
  145. if (!listeners_.empty()) {
  146. listeners_.clear();
  147. // We don't want to update stored lazy listeners in this case, since the
  148. // extension didn't explicitly unregister interest in the event.
  149. constexpr bool update_lazy_listeners = false;
  150. NotifyListenersEmpty(context, update_lazy_listeners);
  151. }
  152. }
  153. void UnfilteredEventListeners::LazilySetContextOwner(
  154. v8::Local<v8::Context> context) {
  155. if (context_owner_id_.empty()) {
  156. DCHECK(context_owner_id_getter_);
  157. context_owner_id_ = context_owner_id_getter_.Run(context);
  158. DCHECK(!context_owner_id_.empty());
  159. }
  160. }
  161. void UnfilteredEventListeners::NotifyListenersEmpty(
  162. v8::Local<v8::Context> context,
  163. bool update_lazy_listeners) {
  164. DCHECK(listeners_.empty());
  165. // NOTE: |listener_tracker_| is null for unmanaged events, in which case we
  166. // send no notifications.
  167. if (!listener_tracker_)
  168. return;
  169. DCHECK(!context_owner_id_.empty())
  170. << "The context owner must be instantiated if listeners were removed.";
  171. bool was_last_listener_for_context_owner =
  172. listener_tracker_->RemoveUnfilteredListener(context_owner_id_,
  173. event_name_);
  174. binding::EventListenersChanged changed =
  175. was_last_listener_for_context_owner
  176. ? binding::EventListenersChanged::
  177. kLastUnfilteredListenerForContextOwnerRemoved
  178. : binding::EventListenersChanged::
  179. kLastUnfilteredListenerForContextRemoved;
  180. listeners_updated_.Run(event_name_, changed, nullptr, update_lazy_listeners,
  181. context);
  182. }
  183. struct FilteredEventListeners::ListenerData {
  184. bool operator==(v8::Local<v8::Function> other_function) const {
  185. // Note that we only consider the listener function here, and not the
  186. // filter. This implies that it's invalid to try and add the same
  187. // function for multiple filters.
  188. // TODO(devlin): It's always been this way, but should it be?
  189. return function == other_function;
  190. }
  191. v8::Global<v8::Function> function;
  192. int filter_id;
  193. };
  194. FilteredEventListeners::FilteredEventListeners(
  195. ListenersUpdated listeners_updated,
  196. const std::string& event_name,
  197. ContextOwnerIdGetter context_owner_id_getter,
  198. int max_listeners,
  199. bool supports_lazy_listeners,
  200. ListenerTracker* listener_tracker)
  201. : listeners_updated_(std::move(listeners_updated)),
  202. event_name_(event_name),
  203. context_owner_id_getter_(std::move(context_owner_id_getter)),
  204. max_listeners_(max_listeners),
  205. supports_lazy_listeners_(supports_lazy_listeners),
  206. listener_tracker_(listener_tracker) {
  207. DCHECK(listener_tracker_);
  208. DCHECK(context_owner_id_getter_);
  209. }
  210. FilteredEventListeners::~FilteredEventListeners() = default;
  211. bool FilteredEventListeners::AddListener(v8::Local<v8::Function> listener,
  212. v8::Local<v8::Object> filter,
  213. v8::Local<v8::Context> context,
  214. std::string* error) {
  215. if (HasListener(listener))
  216. return false;
  217. if (max_listeners_ != binding::kNoListenerMax &&
  218. listeners_.size() >= static_cast<size_t>(max_listeners_)) {
  219. *error = kErrorTooManyListeners;
  220. return false;
  221. }
  222. std::unique_ptr<base::DictionaryValue> filter_dict;
  223. if (!ValidateFilter(context, filter, &filter_dict, error))
  224. return false;
  225. base::DictionaryValue* filter_weak = filter_dict.get();
  226. int filter_id = -1;
  227. bool was_first_of_kind = false;
  228. LazilySetContextOwner(context);
  229. std::tie(was_first_of_kind, filter_id) =
  230. listener_tracker_->AddFilteredListener(context_owner_id_, event_name_,
  231. std::move(filter_dict),
  232. kIgnoreRoutingId);
  233. if (filter_id == -1) {
  234. *error = "Could not add listener";
  235. return false;
  236. }
  237. listeners_.push_back(
  238. {v8::Global<v8::Function>(context->GetIsolate(), listener), filter_id});
  239. if (was_first_of_kind) {
  240. listeners_updated_.Run(event_name_,
  241. binding::EventListenersChanged::
  242. kFirstListenerWithFilterForContextOwnerAdded,
  243. filter_weak, supports_lazy_listeners_, context);
  244. }
  245. return true;
  246. }
  247. void FilteredEventListeners::RemoveListener(v8::Local<v8::Function> listener,
  248. v8::Local<v8::Context> context) {
  249. auto iter = std::find(listeners_.begin(), listeners_.end(), listener);
  250. if (iter == listeners_.end())
  251. return;
  252. ListenerData data = std::move(*iter);
  253. listeners_.erase(iter);
  254. InvalidateListener(data, true, context);
  255. }
  256. bool FilteredEventListeners::HasListener(v8::Local<v8::Function> listener) {
  257. return std::find(listeners_.begin(), listeners_.end(), listener) !=
  258. listeners_.end();
  259. }
  260. size_t FilteredEventListeners::GetNumListeners() {
  261. return listeners_.size();
  262. }
  263. std::vector<v8::Local<v8::Function>> FilteredEventListeners::GetListeners(
  264. mojom::EventFilteringInfoPtr filter,
  265. v8::Local<v8::Context> context) {
  266. std::set<int> ids = listener_tracker_->GetMatchingFilteredListeners(
  267. event_name_,
  268. filter ? std::move(filter) : mojom::EventFilteringInfo::New(),
  269. kIgnoreRoutingId);
  270. std::vector<v8::Local<v8::Function>> listeners;
  271. listeners.reserve(ids.size());
  272. for (const auto& listener : listeners_) {
  273. if (ids.count(listener.filter_id))
  274. listeners.push_back(listener.function.Get(context->GetIsolate()));
  275. }
  276. return listeners;
  277. }
  278. void FilteredEventListeners::Invalidate(v8::Local<v8::Context> context) {
  279. for (const auto& listener : listeners_)
  280. InvalidateListener(listener, false, context);
  281. listeners_.clear();
  282. }
  283. void FilteredEventListeners::LazilySetContextOwner(
  284. v8::Local<v8::Context> context) {
  285. if (context_owner_id_.empty()) {
  286. DCHECK(context_owner_id_getter_);
  287. context_owner_id_ = context_owner_id_getter_.Run(context);
  288. DCHECK(!context_owner_id_.empty());
  289. }
  290. }
  291. void FilteredEventListeners::InvalidateListener(
  292. const ListenerData& listener,
  293. bool was_manual,
  294. v8::Local<v8::Context> context) {
  295. DCHECK(!context_owner_id_.empty())
  296. << "The context owner must be instantiated if listeners were removed.";
  297. bool was_last_of_kind = false;
  298. std::unique_ptr<base::DictionaryValue> filter;
  299. std::tie(was_last_of_kind, filter) =
  300. listener_tracker_->RemoveFilteredListener(context_owner_id_, event_name_,
  301. listener.filter_id);
  302. if (was_last_of_kind) {
  303. listeners_updated_.Run(event_name_,
  304. binding::EventListenersChanged::
  305. kLastListenerWithFilterForContextOwnerRemoved,
  306. filter.get(), was_manual && supports_lazy_listeners_,
  307. context);
  308. }
  309. }
  310. } // namespace extensions