| // 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/api_event_listeners.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/memory/ptr_util.h" |
| #include "content/public/child/v8_value_converter.h" |
| #include "extensions/common/event_filter.h" |
| #include "extensions/common/event_filtering_info.h" |
| #include "extensions/common/event_matcher.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; |
| |
| // 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 = base::MakeUnique<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<content::V8ValueConverter> converter( |
| content::V8ValueConverter::create()); |
| std::unique_ptr<base::Value> value = converter->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( |
| const ListenersUpdated& listeners_updated) |
| : listeners_updated_(listeners_updated) {} |
| 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; |
| |
| listeners_.push_back( |
| v8::Global<v8::Function>(context->GetIsolate(), listener)); |
| if (listeners_.size() == 1) { |
| listeners_updated_.Run(binding::EventListenersChanged::HAS_LISTENERS, |
| nullptr, true, 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()) { |
| listeners_updated_.Run(binding::EventListenersChanged::NO_LISTENERS, |
| nullptr, true, context); |
| } |
| } |
| |
| 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(); |
| listeners_updated_.Run(binding::EventListenersChanged::NO_LISTENERS, |
| nullptr, false, 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( |
| const ListenersUpdated& listeners_updated, |
| const std::string& event_name, |
| EventFilter* event_filter) |
| : listeners_updated_(listeners_updated), |
| event_name_(event_name), |
| event_filter_(event_filter) {} |
| 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; |
| |
| std::unique_ptr<base::DictionaryValue> filter_dict; |
| if (!ValidateFilter(context, filter, &filter_dict, error)) |
| return false; |
| |
| int filter_id = event_filter_->AddEventMatcher( |
| event_name_, |
| base::MakeUnique<EventMatcher>(std::move(filter_dict), kIgnoreRoutingId)); |
| |
| if (filter_id == -1) { |
| *error = "Could not add listener"; |
| return false; |
| } |
| |
| const EventMatcher* matcher = event_filter_->GetEventMatcher(filter_id); |
| DCHECK(matcher); |
| listeners_.push_back( |
| {v8::Global<v8::Function>(context->GetIsolate(), listener), filter_id}); |
| if (value_counter_.Add(*matcher->value())) { |
| listeners_updated_.Run(binding::EventListenersChanged::HAS_LISTENERS, |
| matcher->value(), true, 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 = event_filter_->MatchEvent( |
| 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::InvalidateListener( |
| const ListenerData& listener, |
| bool was_manual, |
| v8::Local<v8::Context> context) { |
| EventMatcher* matcher = event_filter_->GetEventMatcher(listener.filter_id); |
| DCHECK(matcher); |
| if (value_counter_.Remove(*matcher->value())) { |
| listeners_updated_.Run(binding::EventListenersChanged::NO_LISTENERS, |
| matcher->value(), was_manual, context); |
| } |
| |
| event_filter_->RemoveEventMatcher(listener.filter_id); |
| } |
| |
| } // namespace extensions |