blob: e86c12f22adab28db69cebe7ce2c2c8debf051b3 [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/event_emitter.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/stl_util.h"
#include "base/values.h"
#include "extensions/renderer/bindings/api_binding_test.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "extensions/renderer/bindings/api_event_listeners.h"
#include "extensions/renderer/bindings/exception_handler.h"
#include "extensions/renderer/bindings/listener_tracker.h"
#include "extensions/renderer/bindings/test_js_runner.h"
#include "gin/handle.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace extensions {
namespace {
APIEventListeners::ContextOwnerIdGetter CreateContextOwnerIdGetter() {
return base::BindRepeating(
[](v8::Local<v8::Context>) { return std::string("context"); });
}
} // namespace
class EventEmitterUnittest : public APIBindingTest {
public:
EventEmitterUnittest() = default;
~EventEmitterUnittest() override = default;
// A helper method to dispose of a context and set a flag.
void DisposeContextWrapper(bool* did_invalidate,
v8::Local<v8::Context> context) {
EXPECT_FALSE(*did_invalidate);
*did_invalidate = true;
DisposeContext(context);
}
private:
DISALLOW_COPY_AND_ASSIGN(EventEmitterUnittest);
};
TEST_F(EventEmitterUnittest, TestDispatchMethod) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ListenerTracker tracker;
auto listeners = std::make_unique<UnfilteredEventListeners>(
base::DoNothing(), "event", CreateContextOwnerIdGetter(),
binding::kNoListenerMax, true, &tracker);
auto log_error = [](std::vector<std::string>* errors,
v8::Local<v8::Context> context,
const std::string& error) { errors->push_back(error); };
std::vector<std::string> logged_errors;
ExceptionHandler exception_handler(
base::BindRepeating(log_error, &logged_errors));
gin::Handle<EventEmitter> event = gin::CreateHandle(
isolate(),
new EventEmitter(false, std::move(listeners), &exception_handler));
v8::Local<v8::Value> v8_event = event.ToV8();
const char kAddListener[] =
"(function(event, listener) { event.addListener(listener); })";
v8::Local<v8::Function> add_listener_function =
FunctionFromString(context, kAddListener);
auto add_listener = [context, v8_event,
add_listener_function](base::StringPiece listener) {
v8::Local<v8::Function> listener_function =
FunctionFromString(context, listener);
v8::Local<v8::Value> args[] = {v8_event, listener_function};
RunFunction(add_listener_function, context, base::size(args), args);
};
const char kListener1[] =
"(function() {\n"
" this.eventArgs1 = Array.from(arguments);\n"
" return 'listener1';\n"
"})";
add_listener(kListener1);
const char kListener2[] =
"(function() {\n"
" this.eventArgs2 = Array.from(arguments);\n"
" return {listener: 'listener2'};\n"
"})";
add_listener(kListener2);
// Listener3 throws, but shouldn't stop the event from reaching other
// listeners.
const char kListener3[] =
"(function() {\n"
" this.eventArgs3 = Array.from(arguments);\n"
" throw new Error('hahaha');\n"
"})";
add_listener(kListener3);
// Returning undefined should not be added to the array of results from
// dispatch.
const char kListener4[] =
"(function() {\n"
" this.eventArgs4 = Array.from(arguments);\n"
"})";
add_listener(kListener4);
const char kDispatch[] =
"(function(event) {\n"
" return event.dispatch('arg1', 2);\n"
"})";
v8::Local<v8::Value> dispatch_args[] = {v8_event};
TestJSRunner::AllowErrors allow_errors;
v8::Local<v8::Value> dispatch_result =
RunFunctionOnGlobal(FunctionFromString(context, kDispatch), context,
base::size(dispatch_args), dispatch_args);
const char kExpectedEventArgs[] = "[\"arg1\",2]";
for (const char* property :
{"eventArgs1", "eventArgs2", "eventArgs3", "eventArgs4"}) {
EXPECT_EQ(kExpectedEventArgs, GetStringPropertyFromObject(
context->Global(), context, property));
}
EXPECT_EQ("{\"results\":[\"listener1\",{\"listener\":\"listener2\"}]}",
V8ToString(dispatch_result, context));
ASSERT_EQ(1u, logged_errors.size());
EXPECT_THAT(logged_errors[0],
testing::StartsWith("Error in event handler: Error: hahaha"));
}
// Test dispatching an event when the first listener invalidates the context.
// Nothing should break, and we shouldn't continue to dispatch the event.
TEST_F(EventEmitterUnittest, ListenersDestroyingContext) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
struct ListenerClosureData {
EventEmitterUnittest& test;
bool did_invalidate_context;
} closure_data = {*this, false};
// A wrapper that just calls DisposeContextWrapper() on the curried in data.
auto listener_wrapper = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
ASSERT_TRUE(info.Data()->IsExternal());
auto& data = *static_cast<ListenerClosureData*>(
info.Data().As<v8::External>()->Value());
data.test.DisposeContextWrapper(&data.did_invalidate_context,
info.GetIsolate()->GetCurrentContext());
};
ListenerTracker tracker;
auto listeners = std::make_unique<UnfilteredEventListeners>(
base::DoNothing(), "event", CreateContextOwnerIdGetter(),
binding::kNoListenerMax, true, &tracker);
ExceptionHandler exception_handler(base::BindRepeating(
[](v8::Local<v8::Context> context, const std::string& error) {}));
gin::Handle<EventEmitter> event = gin::CreateHandle(
isolate(),
new EventEmitter(false, std::move(listeners), &exception_handler));
v8::Local<v8::Value> v8_event = event.ToV8();
const char kAddListener[] =
"(function(event, listener) { event.addListener(listener); })";
v8::Local<v8::Function> add_listener_function =
FunctionFromString(context, kAddListener);
// Queue up three listeners. The first triggered will invalidate the context.
// The others should never be triggered.
constexpr size_t kNumListeners = 3;
for (size_t i = 0; i < kNumListeners; ++i) {
v8::Local<v8::Function> listener =
v8::Function::New(context, listener_wrapper,
v8::External::New(isolate(), &closure_data))
.ToLocalChecked();
v8::Local<v8::Value> args[] = {v8_event, listener};
RunFunction(add_listener_function, context, base::size(args), args);
}
EXPECT_EQ(kNumListeners, event->GetNumListeners());
std::vector<v8::Local<v8::Value>> args;
event->Fire(context, &args, nullptr, JSRunner::ResultCallback());
EXPECT_TRUE(closure_data.did_invalidate_context);
}
} // namespace extensions