blob: 6f41e0b964e4e72542b77df736a0de93e616cd0c [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/renderer/bindings/api_event_listeners.h"
#include <algorithm>
#include <memory>
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/event_filtering_info.h"
#include "extensions/common/event_matcher.h"
#include "extensions/renderer/bindings/listener_tracker.h"
#include "gin/converter.h"
namespace extensions {
namespace {
// TODO(devlin): The EventFilter supports adding EventMatchers associated with
// an id. For now, we ignore it and add/return everything associated with this
// constant. We should rethink that.
const int kIgnoreRoutingId = 0;
const char kErrorTooManyListeners[] = "Too many listeners.";
// Pseudo-validates the given |filter| and converts it into a
// base::DictionaryValue. Returns true on success.
// TODO(devlin): This "validation" is pretty terrible. It matches the JS
// equivalent, but it's lousy and makes it easy for users to get it wrong.
// We should generate an argument spec for it and match it exactly.
bool ValidateFilter(v8::Local<v8::Context> context,
v8::Local<v8::Object> filter,
std::unique_ptr<base::DictionaryValue>* filter_dict,
std::string* error) {
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
if (filter.IsEmpty()) {
*filter_dict = std::make_unique<base::DictionaryValue>();
return true;
}
v8::Local<v8::Value> url_filter;
if (!filter->Get(context, gin::StringToSymbol(isolate, "url"))
.ToLocal(&url_filter)) {
return false;
}
if (!url_filter->IsUndefined() && !url_filter->IsArray()) {
*error = "filters.url should be an array.";
return false;
}
v8::Local<v8::Value> service_type;
if (!filter->Get(context, gin::StringToSymbol(isolate, "serviceType"))
.ToLocal(&service_type)) {
return false;
}
if (!service_type->IsUndefined() && !service_type->IsString()) {
*error = "filters.serviceType should be a string.";
return false;
}
std::unique_ptr<base::Value> value =
content::V8ValueConverter::Create()->FromV8Value(filter, context);
if (!value || !value->is_dict()) {
*error = "could not convert filter.";
return false;
}
*filter_dict = base::DictionaryValue::From(std::move(value));
return true;
}
} // namespace
UnfilteredEventListeners::UnfilteredEventListeners(
ListenersUpdated listeners_updated,
const std::string& event_name,
ContextOwnerIdGetter context_owner_id_getter,
int max_listeners,
bool supports_lazy_listeners,
ListenerTracker* listener_tracker)
: listeners_updated_(std::move(listeners_updated)),
event_name_(event_name),
context_owner_id_getter_(std::move(context_owner_id_getter)),
max_listeners_(max_listeners),
supports_lazy_listeners_(supports_lazy_listeners),
listener_tracker_(listener_tracker) {
DCHECK(max_listeners_ == binding::kNoListenerMax || max_listeners_ > 0);
DCHECK_EQ(listener_tracker_ == nullptr, context_owner_id_getter_.is_null())
<< "Managed events must have both a listener tracker and context owner; "
<< "unmanaged must have neither.";
}
UnfilteredEventListeners::~UnfilteredEventListeners() = default;
bool UnfilteredEventListeners::AddListener(v8::Local<v8::Function> listener,
v8::Local<v8::Object> filter,
v8::Local<v8::Context> context,
std::string* error) {
// |filter| should be checked before getting here.
DCHECK(filter.IsEmpty())
<< "Filtered events should use FilteredEventListeners";
if (HasListener(listener))
return false;
if (max_listeners_ != binding::kNoListenerMax &&
listeners_.size() >= static_cast<size_t>(max_listeners_)) {
*error = kErrorTooManyListeners;
return false;
}
listeners_.push_back(
v8::Global<v8::Function>(context->GetIsolate(), listener));
if (listeners_.size() == 1) {
// NOTE: |listener_tracker_| is null for unmanaged events, in which case we
// send no notifications.
if (listener_tracker_) {
LazilySetContextOwner(context);
bool was_first_listener_for_context_owner =
listener_tracker_->AddUnfilteredListener(context_owner_id_,
event_name_);
binding::EventListenersChanged changed =
was_first_listener_for_context_owner
? binding::EventListenersChanged::
kFirstUnfilteredListenerForContextOwnerAdded
: binding::EventListenersChanged::
kFirstUnfilteredListenerForContextAdded;
listeners_updated_.Run(event_name_, changed, nullptr,
supports_lazy_listeners_, context);
}
}
return true;
}
void UnfilteredEventListeners::RemoveListener(v8::Local<v8::Function> listener,
v8::Local<v8::Context> context) {
auto iter = std::find(listeners_.begin(), listeners_.end(), listener);
if (iter == listeners_.end())
return;
listeners_.erase(iter);
if (listeners_.empty()) {
bool update_lazy_listeners = supports_lazy_listeners_;
NotifyListenersEmpty(context, update_lazy_listeners);
}
}
bool UnfilteredEventListeners::HasListener(v8::Local<v8::Function> listener) {
return std::find(listeners_.begin(), listeners_.end(), listener) !=
listeners_.end();
}
size_t UnfilteredEventListeners::GetNumListeners() {
return listeners_.size();
}
std::vector<v8::Local<v8::Function>> UnfilteredEventListeners::GetListeners(
const EventFilteringInfo* filter,
v8::Local<v8::Context> context) {
std::vector<v8::Local<v8::Function>> listeners;
listeners.reserve(listeners_.size());
for (const auto& listener : listeners_)
listeners.push_back(listener.Get(context->GetIsolate()));
return listeners;
}
void UnfilteredEventListeners::Invalidate(v8::Local<v8::Context> context) {
if (!listeners_.empty()) {
listeners_.clear();
// We don't want to update stored lazy listeners in this case, since the
// extension didn't explicitly unregister interest in the event.
constexpr bool update_lazy_listeners = false;
NotifyListenersEmpty(context, update_lazy_listeners);
}
}
void UnfilteredEventListeners::LazilySetContextOwner(
v8::Local<v8::Context> context) {
if (context_owner_id_.empty()) {
DCHECK(context_owner_id_getter_);
context_owner_id_ = context_owner_id_getter_.Run(context);
DCHECK(!context_owner_id_.empty());
}
}
void UnfilteredEventListeners::NotifyListenersEmpty(
v8::Local<v8::Context> context,
bool update_lazy_listeners) {
DCHECK(listeners_.empty());
// NOTE: |listener_tracker_| is null for unmanaged events, in which case we
// send no notifications.
if (!listener_tracker_)
return;
DCHECK(!context_owner_id_.empty())
<< "The context owner must be instantiated if listeners were removed.";
bool was_last_listener_for_context_owner =
listener_tracker_->RemoveUnfilteredListener(context_owner_id_,
event_name_);
binding::EventListenersChanged changed =
was_last_listener_for_context_owner
? binding::EventListenersChanged::
kLastUnfilteredListenerForContextOwnerRemoved
: binding::EventListenersChanged::
kLastUnfilteredListenerForContextRemoved;
listeners_updated_.Run(event_name_, changed, nullptr, update_lazy_listeners,
context);
}
struct FilteredEventListeners::ListenerData {
bool operator==(v8::Local<v8::Function> other_function) const {
// Note that we only consider the listener function here, and not the
// filter. This implies that it's invalid to try and add the same
// function for multiple filters.
// TODO(devlin): It's always been this way, but should it be?
return function == other_function;
}
v8::Global<v8::Function> function;
int filter_id;
};
FilteredEventListeners::FilteredEventListeners(
ListenersUpdated listeners_updated,
const std::string& event_name,
ContextOwnerIdGetter context_owner_id_getter,
int max_listeners,
bool supports_lazy_listeners,
ListenerTracker* listener_tracker)
: listeners_updated_(std::move(listeners_updated)),
event_name_(event_name),
context_owner_id_getter_(std::move(context_owner_id_getter)),
max_listeners_(max_listeners),
supports_lazy_listeners_(supports_lazy_listeners),
listener_tracker_(listener_tracker) {
DCHECK(listener_tracker_);
DCHECK(context_owner_id_getter_);
}
FilteredEventListeners::~FilteredEventListeners() = default;
bool FilteredEventListeners::AddListener(v8::Local<v8::Function> listener,
v8::Local<v8::Object> filter,
v8::Local<v8::Context> context,
std::string* error) {
if (HasListener(listener))
return false;
if (max_listeners_ != binding::kNoListenerMax &&
listeners_.size() >= static_cast<size_t>(max_listeners_)) {
*error = kErrorTooManyListeners;
return false;
}
std::unique_ptr<base::DictionaryValue> filter_dict;
if (!ValidateFilter(context, filter, &filter_dict, error))
return false;
base::DictionaryValue* filter_weak = filter_dict.get();
int filter_id = -1;
bool was_first_of_kind = false;
LazilySetContextOwner(context);
std::tie(was_first_of_kind, filter_id) =
listener_tracker_->AddFilteredListener(context_owner_id_, event_name_,
std::move(filter_dict),
kIgnoreRoutingId);
if (filter_id == -1) {
*error = "Could not add listener";
return false;
}
listeners_.push_back(
{v8::Global<v8::Function>(context->GetIsolate(), listener), filter_id});
if (was_first_of_kind) {
listeners_updated_.Run(event_name_,
binding::EventListenersChanged::
kFirstListenerWithFilterForContextOwnerAdded,
filter_weak, supports_lazy_listeners_, context);
}
return true;
}
void FilteredEventListeners::RemoveListener(v8::Local<v8::Function> listener,
v8::Local<v8::Context> context) {
auto iter = std::find(listeners_.begin(), listeners_.end(), listener);
if (iter == listeners_.end())
return;
ListenerData data = std::move(*iter);
listeners_.erase(iter);
InvalidateListener(data, true, context);
}
bool FilteredEventListeners::HasListener(v8::Local<v8::Function> listener) {
return std::find(listeners_.begin(), listeners_.end(), listener) !=
listeners_.end();
}
size_t FilteredEventListeners::GetNumListeners() {
return listeners_.size();
}
std::vector<v8::Local<v8::Function>> FilteredEventListeners::GetListeners(
const EventFilteringInfo* filter,
v8::Local<v8::Context> context) {
std::set<int> ids = listener_tracker_->GetMatchingFilteredListeners(
event_name_, filter ? *filter : EventFilteringInfo(), kIgnoreRoutingId);
std::vector<v8::Local<v8::Function>> listeners;
listeners.reserve(ids.size());
for (const auto& listener : listeners_) {
if (ids.count(listener.filter_id))
listeners.push_back(listener.function.Get(context->GetIsolate()));
}
return listeners;
}
void FilteredEventListeners::Invalidate(v8::Local<v8::Context> context) {
for (const auto& listener : listeners_)
InvalidateListener(listener, false, context);
listeners_.clear();
}
void FilteredEventListeners::LazilySetContextOwner(
v8::Local<v8::Context> context) {
if (context_owner_id_.empty()) {
DCHECK(context_owner_id_getter_);
context_owner_id_ = context_owner_id_getter_.Run(context);
DCHECK(!context_owner_id_.empty());
}
}
void FilteredEventListeners::InvalidateListener(
const ListenerData& listener,
bool was_manual,
v8::Local<v8::Context> context) {
DCHECK(!context_owner_id_.empty())
<< "The context owner must be instantiated if listeners were removed.";
bool was_last_of_kind = false;
std::unique_ptr<base::DictionaryValue> filter;
std::tie(was_last_of_kind, filter) =
listener_tracker_->RemoveFilteredListener(context_owner_id_, event_name_,
listener.filter_id);
if (was_last_of_kind) {
listeners_updated_.Run(event_name_,
binding::EventListenersChanged::
kLastListenerWithFilterForContextOwnerRemoved,
filter.get(), was_manual && supports_lazy_listeners_,
context);
}
}
} // namespace extensions