910e62b5创建于 1月15日历史提交
// Copyright 2016 The Chromium Authors
// 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_handler.h"

#include <optional>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/values.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/renderer/bindings/api_binding_test.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "extensions/renderer/bindings/exception_handler.h"
#include "extensions/renderer/bindings/test_js_runner.h"
#include "gin/arguments.h"
#include "gin/converter.h"
#include "gin/public/context_holder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"

namespace extensions {

namespace {

using MockEventChangeHandler = ::testing::StrictMock<
    base::MockCallback<APIEventListeners::ListenersUpdated>>;

std::string GetContextOwner(v8::Local<v8::Context> context) {
  return "context";
}

size_t GetNumListeners(v8::Isolate* isolate, v8::Local<v8::Object> event) {
  EventEmitter* emitter = nullptr;
  gin::Converter<EventEmitter*>::FromV8(isolate, event, &emitter);
  CHECK(emitter);
  return emitter->GetNumListenersForTesting();
}

// Note: Not function-local to RemoveListener() because it's used in one place
// that needs to circumvent RunFunction().
constexpr char kRemoveListenerFunction[] =
    "(function(event, listener) { event.removeListener(listener); })";

void AddListener(v8::Local<v8::Context> context,
                 v8::Local<v8::Function> listener,
                 v8::Local<v8::Object> event) {
  constexpr char kAddListenerFunction[] =
      "(function(event, listener) { event.addListener(listener); })";
  v8::Local<v8::Function> add_listener =
      FunctionFromString(context, kAddListenerFunction);
  v8::Local<v8::Value> argv[] = {event, listener};
  RunFunction(add_listener, context, std::size(argv), argv);
}

void AddFilteredListener(v8::Local<v8::Context> context,
                         v8::Local<v8::Function> listener,
                         v8::Local<v8::Object> event,
                         v8::Local<v8::Object> filter) {
  constexpr char kAddListenerFunction[] =
      R"((function(event, listener, filter) {
            event.addListener(listener, filter);
         }))";
  v8::Local<v8::Function> add_listener =
      FunctionFromString(context, kAddListenerFunction);
  v8::Local<v8::Value> argv[] = {event, listener, filter};
  RunFunction(add_listener, context, std::size(argv), argv);
}

void RemoveListener(v8::Local<v8::Context> context,
                    v8::Local<v8::Function> listener,
                    v8::Local<v8::Object> event) {
  v8::Local<v8::Function> remove_listener =
      FunctionFromString(context, kRemoveListenerFunction);
  v8::Local<v8::Value> argv[] = {event, listener};
  RunFunction(remove_listener, context, std::size(argv), argv);
}

class APIEventHandlerTest : public APIBindingTest {
 public:
  APIEventHandlerTest(const APIEventHandlerTest&) = delete;
  APIEventHandlerTest& operator=(const APIEventHandlerTest&) = delete;

 protected:
  APIEventHandlerTest() {}
  ~APIEventHandlerTest() override {}

  void SetUp() override {
    APIBindingTest::SetUp();
    handler_ = std::make_unique<APIEventHandler>(
        base::DoNothing(), base::BindRepeating(&GetContextOwner), nullptr);
  }

  void TearDown() override {
    DisposeAllContexts();
    handler_.reset();
    APIBindingTest::TearDown();
  }

  void OnWillDisposeContext(v8::Local<v8::Context> context) override {
    ASSERT_TRUE(handler_);
    handler_->InvalidateContext(context);
  }

  void SetHandler(std::unique_ptr<APIEventHandler> handler) {
    handler_ = std::move(handler);
  }

  APIEventHandler* handler() { return handler_.get(); }

 private:
  std::unique_ptr<APIEventHandler> handler_;
};

}  // namespace

// Tests adding, removing, and querying event listeners by calling the
// associated methods on the JS object.
TEST_F(APIEventHandlerTest, AddingRemovingAndQueryingEventListeners) {
  const char kEventName[] = "alpha";
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  EXPECT_EQ(0u, GetNumListeners(isolate(), event));

  const char kListenerFunction[] = "(function() {})";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());

  AddListener(context, listener_function, event);
  EXPECT_EQ(1u, GetNumListeners(isolate(), event));

  AddListener(context, listener_function, event);
  // Trying to add the same listener again should be a no-op.
  EXPECT_EQ(1u, GetNumListeners(isolate(), event));

  // Test hasListener returns true for a listener that is present.
  const char kHasListenerFunction[] =
      "(function(event, listener) { return event.hasListener(listener); })";
  v8::Local<v8::Function> has_listener_function =
      FunctionFromString(context, kHasListenerFunction);
  {
    v8::Local<v8::Value> argv[] = {event, listener_function};
    v8::Local<v8::Value> result =
        RunFunction(has_listener_function, context, std::size(argv), argv);
    bool has_listener = false;
    EXPECT_TRUE(gin::Converter<bool>::FromV8(isolate(), result, &has_listener));
    EXPECT_TRUE(has_listener);
  }

  // Test that hasListener returns false for a listener that isn't present.
  {
    v8::Local<v8::Function> not_a_listener =
        FunctionFromString(context, "(function() {})");
    v8::Local<v8::Value> argv[] = {event, not_a_listener};
    v8::Local<v8::Value> result =
        RunFunction(has_listener_function, context, std::size(argv), argv);
    bool has_listener = false;
    EXPECT_TRUE(gin::Converter<bool>::FromV8(isolate(), result, &has_listener));
    EXPECT_FALSE(has_listener);
  }

  // Test hasListeners returns true
  const char kHasListenersFunction[] =
      "(function(event) { return event.hasListeners(); })";
  v8::Local<v8::Function> has_listeners_function =
      FunctionFromString(context, kHasListenersFunction);
  {
    v8::Local<v8::Value> argv[] = {event};
    v8::Local<v8::Value> result =
        RunFunction(has_listeners_function, context, std::size(argv), argv);
    bool has_listeners = false;
    EXPECT_TRUE(
        gin::Converter<bool>::FromV8(isolate(), result, &has_listeners));
    EXPECT_TRUE(has_listeners);
  }

  RemoveListener(context, listener_function, event);
  EXPECT_EQ(0u, GetNumListeners(isolate(), event));

  {
    v8::Local<v8::Value> argv[] = {event};
    v8::Local<v8::Value> result =
        RunFunction(has_listeners_function, context, std::size(argv), argv);
    bool has_listeners = false;
    EXPECT_TRUE(
        gin::Converter<bool>::FromV8(isolate(), result, &has_listeners));
    EXPECT_FALSE(has_listeners);
  }
}

// Tests listening for and firing different events.
TEST_F(APIEventHandlerTest, FiringEvents) {
  const char kAlphaName[] = "alpha";
  const char kBetaName[] = "beta";
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  v8::Local<v8::Object> alpha_event = handler()->CreateEventInstance(
      kAlphaName, false, true, binding::kNoListenerMax, true, context);
  v8::Local<v8::Object> beta_event = handler()->CreateEventInstance(
      kBetaName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(alpha_event.IsEmpty());
  ASSERT_FALSE(beta_event.IsEmpty());

  const char kAlphaListenerFunction1[] =
      "(function() {\n"
      "  if (!this.alphaCount1) this.alphaCount1 = 0;\n"
      "  ++this.alphaCount1;\n"
      "});\n";
  v8::Local<v8::Function> alpha_listener1 =
      FunctionFromString(context, kAlphaListenerFunction1);
  const char kAlphaListenerFunction2[] =
      "(function() {\n"
      "  if (!this.alphaCount2) this.alphaCount2 = 0;\n"
      "  ++this.alphaCount2;\n"
      "});\n";
  v8::Local<v8::Function> alpha_listener2 =
      FunctionFromString(context, kAlphaListenerFunction2);
  const char kBetaListenerFunction[] =
      "(function() {\n"
      "  if (!this.betaCount) this.betaCount = 0;\n"
      "  ++this.betaCount;\n"
      "});\n";
  v8::Local<v8::Function> beta_listener =
      FunctionFromString(context, kBetaListenerFunction);
  ASSERT_FALSE(alpha_listener1.IsEmpty());
  ASSERT_FALSE(alpha_listener2.IsEmpty());
  ASSERT_FALSE(beta_listener.IsEmpty());

  AddListener(context, alpha_listener1, alpha_event);
  AddListener(context, alpha_listener2, alpha_event);
  AddListener(context, beta_listener, beta_event);

  EXPECT_EQ(2u, GetNumListeners(isolate(), alpha_event));
  EXPECT_EQ(1u, GetNumListeners(isolate(), beta_event));

  auto get_fired_count = [&context](const char* name) {
    v8::Local<v8::Value> res =
        GetPropertyFromObject(context->Global(), context, name);
    if (res->IsUndefined())
      return 0;
    int32_t count = 0;
    EXPECT_TRUE(
        gin::Converter<int32_t>::FromV8(v8::Isolate::GetCurrent(), res, &count))
        << name;
    return count;
  };

  EXPECT_EQ(0, get_fired_count("alphaCount1"));
  EXPECT_EQ(0, get_fired_count("alphaCount2"));
  EXPECT_EQ(0, get_fired_count("betaCount"));

  handler()->FireEventInContext(kAlphaName, context, base::Value::List(),
                                nullptr);
  EXPECT_EQ(2u, GetNumListeners(isolate(), alpha_event));
  EXPECT_EQ(1u, GetNumListeners(isolate(), beta_event));

  EXPECT_EQ(1, get_fired_count("alphaCount1"));
  EXPECT_EQ(1, get_fired_count("alphaCount2"));
  EXPECT_EQ(0, get_fired_count("betaCount"));

  handler()->FireEventInContext(kAlphaName, context, base::Value::List(),
                                nullptr);
  EXPECT_EQ(2, get_fired_count("alphaCount1"));
  EXPECT_EQ(2, get_fired_count("alphaCount2"));
  EXPECT_EQ(0, get_fired_count("betaCount"));

  handler()->FireEventInContext(kBetaName, context, base::Value::List(),
                                nullptr);
  EXPECT_EQ(2, get_fired_count("alphaCount1"));
  EXPECT_EQ(2, get_fired_count("alphaCount2"));
  EXPECT_EQ(1, get_fired_count("betaCount"));
}

// Tests firing events with arguments.
TEST_F(APIEventHandlerTest, EventArguments) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kListenerFunction[] =
      "(function() { this.eventArgs = Array.from(arguments); })";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());

  AddListener(context, listener_function, event);

  const char kArguments[] = "['foo',1,{'prop1':'bar'}]";
  base::Value::List event_args = ListValueFromString(kArguments);
  handler()->FireEventInContext(kEventName, context, event_args, nullptr);

  EXPECT_EQ(
      ReplaceSingleQuotes(kArguments),
      GetStringPropertyFromObject(context->Global(), context, "eventArgs"));
}

// Test dispatching events to multiple contexts.
TEST_F(APIEventHandlerTest, MultipleContexts) {
  v8::HandleScope handle_scope(isolate());

  v8::Local<v8::Context> context_a = MainContext();
  v8::Local<v8::Context> context_b = AddContext();

  const char kEventName[] = "onFoo";


  v8::Local<v8::Function> listener_a = FunctionFromString(
      context_a, "(function(arg) { this.eventArgs = arg + 'alpha'; })");
  ASSERT_FALSE(listener_a.IsEmpty());
  v8::Local<v8::Function> listener_b = FunctionFromString(
      context_b, "(function(arg) { this.eventArgs = arg + 'beta'; })");
  ASSERT_FALSE(listener_b.IsEmpty());

  // Create two instances of the same event in different contexts.
  v8::Local<v8::Object> event_a = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context_a);
  ASSERT_FALSE(event_a.IsEmpty());
  v8::Local<v8::Object> event_b = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context_b);
  ASSERT_FALSE(event_b.IsEmpty());

  // Add two separate listeners to the event, one in each context.
  AddListener(context_a, listener_a, event_a);
  EXPECT_EQ(1u, GetNumListeners(isolate(), event_a));
  EXPECT_EQ(0u, GetNumListeners(isolate(), event_b));

  AddListener(context_b, listener_b, event_b);
  EXPECT_EQ(1u, GetNumListeners(isolate(), event_a));
  EXPECT_EQ(1u, GetNumListeners(isolate(), event_b));

  // Dispatch the event in context_a - the listener in context_b should not be
  // notified.
  base::Value::List arguments_a = ListValueFromString("['result_a:']");
  handler()->FireEventInContext(kEventName, context_a, arguments_a, nullptr);
  {
    EXPECT_EQ("\"result_a:alpha\"",
              GetStringPropertyFromObject(context_a->Global(), context_a,
                                          "eventArgs"));
  }
  {
    EXPECT_EQ("undefined", GetStringPropertyFromObject(context_b->Global(),
                                                       context_b, "eventArgs"));
  }

  // Dispatch the event in context_b - the listener in context_a should not be
  // notified.
  base::Value::List arguments_b = ListValueFromString("['result_b:']");
  handler()->FireEventInContext(kEventName, context_b, arguments_b, nullptr);
  {
    EXPECT_EQ("\"result_a:alpha\"",
              GetStringPropertyFromObject(context_a->Global(), context_a,
                                          "eventArgs"));
  }
  {
    EXPECT_EQ("\"result_b:beta\"",
              GetStringPropertyFromObject(context_b->Global(), context_b,
                                          "eventArgs"));
  }
}

TEST_F(APIEventHandlerTest, DifferentCallingMethods) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kAddListenerOnNull[] =
      "(function(event) {\n"
      "  event.addListener.call(null, function() {});\n"
      "})";
  {
    v8::Local<v8::Value> args[] = {event};
    RunFunctionAndExpectError(
        FunctionFromString(context, kAddListenerOnNull), context, 1, args,
        "Uncaught TypeError: Illegal invocation: Function must be called on "
        "an object of type Event");
  }
  EXPECT_EQ(0u, GetNumListeners(isolate(), event));

  const char kAddListenerOnEvent[] =
      "(function(event) {\n"
      "  event.addListener.call(event, function() {});\n"
      "})";
  {
    v8::Local<v8::Value> args[] = {event};
    RunFunction(FunctionFromString(context, kAddListenerOnEvent),
                context, 1, args);
  }
  EXPECT_EQ(1u, GetNumListeners(isolate(), event));

  // Call addListener with a function that captures the event, creating a cycle.
  // If we don't properly clean up, the context will leak.
  const char kAddListenerOnEventWithCapture[] =
      "(function(event) {\n"
      "  event.addListener(function listener() {\n"
      "    event.hasListener(listener);\n"
      "  });\n"
      "})";
  {
    v8::Local<v8::Value> args[] = {event};
    RunFunction(FunctionFromString(context, kAddListenerOnEventWithCapture),
                context, 1, args);
  }
  EXPECT_EQ(2u, GetNumListeners(isolate(), event));
}

TEST_F(APIEventHandlerTest, TestDispatchFromJs) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      "alpha", false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kListenerFunction[] =
      "(function() {\n"
      "  this.eventArgs = Array.from(arguments);\n"
      "});";
  v8::Local<v8::Function> listener =
      FunctionFromString(context, kListenerFunction);

  AddListener(context, listener, event);

  v8::Local<v8::Function> fire_event_function =
      FunctionFromString(
          context,
          "(function(event) { event.dispatch(42, 'foo', {bar: 'baz'}); })");
  {
    v8::Local<v8::Value> argv[] = {event};
    RunFunctionOnGlobal(fire_event_function, context, std::size(argv), argv);
  }

  EXPECT_EQ("[42,\"foo\",{\"bar\":\"baz\"}]",
            GetStringPropertyFromObject(
                context->Global(), context, "eventArgs"));
}

// Test listeners that remove themselves in their handling of the event.
TEST_F(APIEventHandlerTest, RemovingListenersWhileHandlingEvent) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());
  {
    // Cache the event object on the global in order to allow for easy removal.
    v8::Local<v8::Function> set_event_on_global =
        FunctionFromString(
            context,
           "(function(event) { this.testEvent = event; })");
    v8::Local<v8::Value> args[] = {event};
    RunFunctionOnGlobal(set_event_on_global, context, std::size(args), args);
    EXPECT_EQ(event,
              GetPropertyFromObject(context->Global(), context, "testEvent"));
  }

  // A listener function that removes itself as a listener.
  const char kListenerFunction[] =
      "(function() {\n"
      "  return function listener() {\n"
      "    this.testEvent.removeListener(listener);\n"
      "  };\n"
      "})();";

  // Create and add a bunch of listeners.
  v8::LocalVector<v8::Function> listeners(isolate());
  const size_t kNumListeners = 20u;
  listeners.reserve(kNumListeners);
  for (size_t i = 0; i < kNumListeners; ++i)
    listeners.push_back(FunctionFromString(context, kListenerFunction));

  for (const auto& listener : listeners)
    AddListener(context, listener, event);

  // Fire the event. All listeners should be removed (and we shouldn't crash).
  EXPECT_EQ(kNumListeners, GetNumListeners(isolate(), event));
  handler()->FireEventInContext(kEventName, context, base::Value::List(),
                                nullptr);
  EXPECT_EQ(0u, GetNumListeners(isolate(), event));

  // TODO(devlin): Another possible test: register listener a and listener b,
  // where a removes b and b removes a. Theoretically, only one should be
  // notified. Investigate what we currently do in JS-style bindings.
}

// Test an event listener throwing an exception.
TEST_F(APIEventHandlerTest, TestEventListenersThrowingExceptions) {
  auto log_error =
      [](std::vector<std::string>* errors_out, v8::Local<v8::Context> context,
         const std::string& error) { errors_out->push_back(error); };

  std::vector<std::string> logged_errors;
  ExceptionHandler exception_handler(
      base::BindRepeating(log_error, &logged_errors));
  SetHandler(std::make_unique<APIEventHandler>(
      base::DoNothing(), base::BindRepeating(&GetContextOwner),
      &exception_handler));

  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  // A listener that will throw an exception. We guarantee that we throw the
  // exception first so that we don't rely on event listener ordering.
  const char kListenerFunction[] =
      "(function() {\n"
      "  if (!this.didThrow) {\n"
      "    this.didThrow = true;\n"
      "    throw new Error('Event handler error');\n"
      "  }\n"
      "  this.eventArgs = Array.from(arguments);\n"
      "});";

  for (int i = 0; i < 2; ++i) {
    v8::Local<v8::Function> listener =
        FunctionFromString(context, kListenerFunction);
    AddListener(context, listener, event);
  }
  EXPECT_EQ(2u, GetNumListeners(isolate(), event));

  base::Value::List event_args = ListValueFromString("[42]");

  {
    TestJSRunner::AllowErrors allow_errors;
    handler()->FireEventInContext(kEventName, context, event_args, nullptr);
  }

  // An exception should have been thrown by the first listener and the second
  // listener should have recorded the event arguments.
  EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
                                                "didThrow"));
  EXPECT_EQ("[42]", GetStringPropertyFromObject(context->Global(), context,
                                                "eventArgs"));
  ASSERT_EQ(1u, logged_errors.size());
  EXPECT_THAT(logged_errors[0],
              testing::StartsWith("Error in event handler: Error: "
                                  "Event handler error"));
}

// Tests being notified as listeners are added or removed from events.
TEST_F(APIEventHandlerTest, CallbackNotifications) {
  MockEventChangeHandler change_handler;
  SetHandler(std::make_unique<APIEventHandler>(
      change_handler.Get(), base::BindRepeating(&GetContextOwner), nullptr));

  v8::HandleScope handle_scope(isolate());

  v8::Local<v8::Context> context_a = MainContext();
  v8::Local<v8::Context> context_b = AddContext();

  const char kEventName1[] = "onFoo";
  const char kEventName2[] = "onBar";
  v8::Local<v8::Object> event1_a = handler()->CreateEventInstance(
      kEventName1, false, true, binding::kNoListenerMax, true, context_a);
  ASSERT_FALSE(event1_a.IsEmpty());
  v8::Local<v8::Object> event2_a = handler()->CreateEventInstance(
      kEventName2, false, true, binding::kNoListenerMax, true, context_a);
  ASSERT_FALSE(event2_a.IsEmpty());
  v8::Local<v8::Object> event1_b = handler()->CreateEventInstance(
      kEventName1, false, true, binding::kNoListenerMax, true, context_b);
  ASSERT_FALSE(event1_b.IsEmpty());

  // Add a listener to the first event. The APIEventHandler should notify
  // since it's a change in state (no listeners -> listeners).
  v8::Local<v8::Function> listener1 =
      FunctionFromString(context_a, "(function() {})");
  {
    EXPECT_CALL(change_handler,
                Run(kEventName1,
                    binding::EventListenersChanged::
                        kFirstUnfilteredListenerForContextOwnerAdded,
                    nullptr, true, context_a))
        .Times(1);
    AddListener(context_a, listener1, event1_a);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }
  EXPECT_EQ(1u, GetNumListeners(isolate(), event1_a));

  // Add a second listener to the same event. We should not be notified, since
  // the event already had listeners.
  v8::Local<v8::Function> listener2 =
      FunctionFromString(context_a, "(function() {})");
  AddListener(context_a, listener2, event1_a);
  EXPECT_EQ(2u, GetNumListeners(isolate(), event1_a));

  // Remove the first listener of the event. Again, since the event has
  // listeners, we shouldn't be notified.
  RemoveListener(context_a, listener1, event1_a);

  EXPECT_EQ(1u, GetNumListeners(isolate(), event1_a));

  // Remove the final listener from the event. We should be notified that the
  // event no longer has listeners.
  {
    EXPECT_CALL(change_handler,
                Run(kEventName1,
                    binding::EventListenersChanged::
                        kLastUnfilteredListenerForContextOwnerRemoved,
                    nullptr, true, context_a))
        .Times(1);
    RemoveListener(context_a, listener2, event1_a);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }
  EXPECT_EQ(0u, GetNumListeners(isolate(), event1_a));

  // Add a listener to a separate event to ensure we receive the right
  // notifications.
  v8::Local<v8::Function> listener3 =
      FunctionFromString(context_a, "(function() {})");
  {
    EXPECT_CALL(change_handler,
                Run(kEventName2,
                    binding::EventListenersChanged::
                        kFirstUnfilteredListenerForContextOwnerAdded,
                    nullptr, true, context_a))
        .Times(1);
    AddListener(context_a, listener3, event2_a);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }
  EXPECT_EQ(1u, GetNumListeners(isolate(), event2_a));

  {
    EXPECT_CALL(change_handler,
                Run(kEventName1,
                    binding::EventListenersChanged::
                        kFirstUnfilteredListenerForContextOwnerAdded,
                    nullptr, true, context_b))
        .Times(1);
    // And add a listener to an event in a different context to make sure the
    // associated context is correct.
    v8::Local<v8::Function> listener =
        FunctionFromString(context_b, "(function() {})");
    AddListener(context_b, listener, event1_b);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }
  EXPECT_EQ(1u, GetNumListeners(isolate(), event1_b));

  // When the contexts are invalidated, we should receive listener removed
  // notifications. Additionally, since this was the context being torn down,
  // rather than a removeListener call, was_manual should be false.
  EXPECT_CALL(change_handler,
              Run(kEventName2,
                  binding::EventListenersChanged::
                      kLastUnfilteredListenerForContextOwnerRemoved,
                  nullptr, false, context_a))
      .Times(1);
  DisposeContext(context_a);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);

  EXPECT_CALL(change_handler,
              Run(kEventName1,
                  binding::EventListenersChanged::
                      kLastUnfilteredListenerForContextOwnerRemoved,
                  nullptr, false, context_b))
      .Times(1);
  DisposeContext(context_b);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);
}

// Test registering an argument massager for a given event.
TEST_F(APIEventHandlerTest, TestArgumentMassagers) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kArgumentMassager[] =
      "(function(originalArgs, dispatch) {\n"
      "  this.originalArgs = originalArgs;\n"
      "  dispatch(['primary', 'secondary']);\n"
      "});";
  v8::Local<v8::Function> massager =
      FunctionFromString(context, kArgumentMassager);
  handler()->RegisterArgumentMassager(context, "alpha", massager);

  const char kListenerFunction[] =
      "(function() { this.eventArgs = Array.from(arguments); })";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());

  AddListener(context, listener_function, event);

  const char kArguments[] = "['first','second']";
  base::Value::List event_args = ListValueFromString(kArguments);
  handler()->FireEventInContext(kEventName, context, event_args, nullptr);

  EXPECT_EQ(
      "[\"first\",\"second\"]",
      GetStringPropertyFromObject(context->Global(), context, "originalArgs"));
  EXPECT_EQ(
      "[\"primary\",\"secondary\"]",
      GetStringPropertyFromObject(context->Global(), context, "eventArgs"));
}

TEST_F(APIEventHandlerTest, TestFilteredEventWithMassager) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  context->Global()
      ->Set(
          context,
          v8::String::NewFromUtf8(isolate(), "dispatchCount").ToLocalChecked(),
          v8::Integer::New(isolate(), 0))
      .Check();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, true, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kArgumentMassager[] = R"(
    (function(originalArgs, dispatch) {
        this.originalArgs = originalArgs;
        dispatch(['primary', 'secondary']);
    });
    )";
  v8::Local<v8::Function> massager =
      FunctionFromString(context, kArgumentMassager);
  handler()->RegisterArgumentMassager(context, kEventName, massager);

  const char kListenerFunction[] = R"(
        (function() {
            this.eventArgs = Array.from(arguments);
            dispatchCount++;
        })
        )";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());
  auto filter =
      V8ValueFromScriptSource(context, "({url: [{hostSuffix: 'test.com'}]})")
          .As<v8::Object>();

  mojom::EventFilteringInfoPtr matched_filter_info =
      mojom::EventFilteringInfo::New();
  matched_filter_info->url = GURL("https://test.com");
  mojom::EventFilteringInfoPtr unmatched_filter_info =
      mojom::EventFilteringInfo::New();
  unmatched_filter_info->url = GURL("https://testfoo.com");

  AddFilteredListener(context, listener_function, event, filter);

  const char kArgs[] = "['first','second']";
  base::Value::List event_args = ListValueFromString(kArgs);

  handler()->FireEventInContext(kEventName, context, event_args,
                                std::move(matched_filter_info));
  handler()->FireEventInContext(kEventName, context, event_args,
                                std::move(unmatched_filter_info));

  EXPECT_EQ(
      "[\"first\",\"second\"]",
      GetStringPropertyFromObject(context->Global(), context, "originalArgs"));
  EXPECT_EQ(
      "[\"primary\",\"secondary\"]",
      GetStringPropertyFromObject(context->Global(), context, "eventArgs"));
  EXPECT_EQ(1, GetBaseValuePropertyFromObject(context->Global(), context,
                                              "dispatchCount")
                   ->GetIfInt());
}

// Test registering an argument massager for a given event and dispatching
// asynchronously.
TEST_F(APIEventHandlerTest, TestArgumentMassagersAsyncDispatch) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kArgumentMassager[] =
      "(function(originalArgs, dispatch) {\n"
      "  this.originalArgs = originalArgs;\n"
      "  this.dispatch = dispatch;\n"
      "});";
  v8::Local<v8::Function> massager =
      FunctionFromString(context, kArgumentMassager);
  handler()->RegisterArgumentMassager(context, "alpha", massager);

  const char kListenerFunction[] =
      "(function() { this.eventArgs = Array.from(arguments); })";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());

  AddListener(context, listener_function, event);

  const char kArguments[] = "['first','second']";
  base::Value::List event_args = ListValueFromString(kArguments);
  handler()->FireEventInContext(kEventName, context, event_args, nullptr);

  // The massager should have been triggered, but since it doesn't call
  // dispatch(), the listener shouldn't have been notified.
  EXPECT_EQ(
      "[\"first\",\"second\"]",
      GetStringPropertyFromObject(context->Global(), context, "originalArgs"));
  EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(), context,
                                                     "eventArgs"));

  // Dispatch the event.
  v8::Local<v8::Value> dispatch_value =
      GetPropertyFromObject(context->Global(), context, "dispatch");
  ASSERT_FALSE(dispatch_value.IsEmpty());
  ASSERT_TRUE(dispatch_value->IsFunction());
  v8::Local<v8::Value> dispatch_args[] = {
      V8ValueFromScriptSource(context, "['primary', 'secondary']"),
  };
  RunFunction(dispatch_value.As<v8::Function>(), context,
              std::size(dispatch_args), dispatch_args);

  EXPECT_EQ(
      "[\"primary\",\"secondary\"]",
      GetStringPropertyFromObject(context->Global(), context, "eventArgs"));
}

// Test registering an argument massager and never dispatching.
TEST_F(APIEventHandlerTest, TestArgumentMassagersNeverDispatch) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  // A massager that never dispatches.
  const char kArgumentMassager[] = "(function(originalArgs, dispatch) {})";
  v8::Local<v8::Function> massager =
      FunctionFromString(context, kArgumentMassager);
  handler()->RegisterArgumentMassager(context, "alpha", massager);

  const char kListenerFunction[] = "(function() {})";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());

  AddListener(context, listener_function, event);

  handler()->FireEventInContext(kEventName, context, base::Value::List(),
                                nullptr);

  // Nothing should blow up. (We tested in the previous test that the event
  // isn't notified without calling dispatch, so all there is to test here is
  // that we don't crash.)
}

// Test that event results of dispatch are passed to the calling argument
// massager. Regression test for https://crbug.com/867310.
TEST_F(APIEventHandlerTest, TestArgumentMassagersDispatchResult) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);
  ASSERT_FALSE(event.IsEmpty());

  const char kArgumentMassager[] =
      R"((function(originalArgs, dispatch) {
           this.dispatchResult = dispatch(['primary']);
         });)";
  v8::Local<v8::Function> massager =
      FunctionFromString(context, kArgumentMassager);
  handler()->RegisterArgumentMassager(context, kEventName, massager);

  const char kListenerFunction[] =
      R"((function(arg) {
           let res = arg == 'primary' ? 'listenerSuccess' : 'listenerFailure';
           this.listenerResult = res;
           return res;
         });)";
  v8::Local<v8::Function> listener_function =
      FunctionFromString(context, kListenerFunction);
  ASSERT_FALSE(listener_function.IsEmpty());
  AddListener(context, listener_function, event);

  handler()->FireEventInContext(kEventName, context, base::Value::List(),
                                nullptr);

  EXPECT_EQ(
      R"({"results":["listenerSuccess"]})",
      GetStringPropertyFromObject(context->Global(), context,
                                  "dispatchResult"));
  EXPECT_EQ(
      R"("listenerSuccess")",
      GetStringPropertyFromObject(context->Global(), context,
                                  "listenerResult"));
}

// Test creating a custom event, as is done by a few of our custom bindings.
TEST_F(APIEventHandlerTest, TestCreateCustomEvent) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  MockEventChangeHandler change_handler;
  APIEventHandler handler(change_handler.Get(),
                          base::BindRepeating(&GetContextOwner), nullptr);

  v8::Local<v8::Object> event = handler.CreateAnonymousEventInstance(context);
  ASSERT_FALSE(event.IsEmpty());

  constexpr char kListenerFunction[] =
      R"((function() { this.eventArgs = Array.from(arguments); }))";
  v8::Local<v8::Function> listener =
      FunctionFromString(context, kListenerFunction);
  AddListener(context, listener, event);

  // Test dispatching to the listeners.
  const char kDispatchEventFunction[] =
      "(function(event) { event.dispatch(1, 2, 3); })";
  v8::Local<v8::Function> dispatch_function =
      FunctionFromString(context, kDispatchEventFunction);

  v8::Local<v8::Value> dispatch_argv[] = {event};
  RunFunction(dispatch_function, context, std::size(dispatch_argv),
              dispatch_argv);

  EXPECT_EQ("[1,2,3]", GetStringPropertyFromObject(context->Global(), context,
                                                   "eventArgs"));

  // Clean up so we can re-check eventArgs.
  ASSERT_TRUE(context->Global()
                  ->Delete(context, gin::StringToSymbol(isolate(), "eventArgs"))
                  .FromJust());

  // Invalidate the event and try dispatching again. Nothing should happen.
  handler.InvalidateCustomEvent(context, event);
  RunFunction(dispatch_function, context, std::size(dispatch_argv),
              dispatch_argv);
  EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(), context,
                                                     "eventArgs"));
}

// Test adding a custom event with a cyclic dependency. Nothing should leak.
TEST_F(APIEventHandlerTest, TestCreateCustomEventWithCyclicDependency) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  MockEventChangeHandler change_handler;
  APIEventHandler handler(change_handler.Get(),
                          base::BindRepeating(&GetContextOwner), nullptr);

  v8::Local<v8::Object> event = handler.CreateAnonymousEventInstance(context);
  ASSERT_FALSE(event.IsEmpty());

  const char kLocalAddListenerFunction[] =
      "(function(event) {\n"
      "  event.addListener(function() {}.bind(null, event));\n"
      "})";
  v8::Local<v8::Value> add_listener_argv[] = {event};
  RunFunction(FunctionFromString(context, kLocalAddListenerFunction), context,
              std::size(add_listener_argv), add_listener_argv);

  DisposeContext(context);
}

TEST_F(APIEventHandlerTest, TestUnmanagedEvents) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  auto fail_on_notified = [](const std::string& event_name,
                             binding::EventListenersChanged changed,
                             const base::Value::Dict* filter, bool was_manual,
                             v8::Local<v8::Context> context) { ADD_FAILURE(); };

  APIEventHandler handler(base::BindRepeating(fail_on_notified),
                          base::BindRepeating(&GetContextOwner), nullptr);

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event = handler.CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, false, context);

  const char kListener[] =
      R"((function() { this.eventArgs = Array.from(arguments); }))";
  v8::Local<v8::Function> listener = FunctionFromString(context, kListener);
  AddListener(context, listener, event);

  EXPECT_EQ(1u, GetNumListeners(isolate(), event));

  handler.FireEventInContext(kEventName, context,
                             ListValueFromString("[1, 'foo']"), nullptr);

  EXPECT_EQ("[1,\"foo\"]", GetStringPropertyFromObject(context->Global(),
                                                       context, "eventArgs"));

  RemoveListener(context, listener, event);

  EXPECT_EQ(0u, GetNumListeners(isolate(), event));
}

// Test callback notifications for events that don't support lazy listeners.
TEST_F(APIEventHandlerTest, TestEventsWithoutLazyListeners) {
  MockEventChangeHandler change_handler;
  APIEventHandler handler(change_handler.Get(),
                          base::BindRepeating(&GetContextOwner), nullptr);

  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  const char kLazyListenersSupported[] = "supportsLazyListeners";
  const char kLazyListenersNotSupported[] = "noLazyListeners";
  v8::Local<v8::Object> lazy_listeners_supported =
      handler.CreateEventInstance(kLazyListenersSupported, false, true,
                                  binding::kNoListenerMax, true, context);
  v8::Local<v8::Object> lazy_listeners_not_supported =
      handler.CreateEventInstance(kLazyListenersNotSupported, false, false,
                                  binding::kNoListenerMax, true, context);
  ASSERT_FALSE(lazy_listeners_not_supported.IsEmpty());

  v8::Local<v8::Function> listener =
      FunctionFromString(context, "(function() {})");
  {
    EXPECT_CALL(change_handler,
                Run(kLazyListenersSupported,
                    binding::EventListenersChanged::
                        kFirstUnfilteredListenerForContextOwnerAdded,
                    nullptr, true, context))
        .Times(1);
    AddListener(context, listener, lazy_listeners_supported);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }

  {
    EXPECT_CALL(change_handler,
                Run(kLazyListenersNotSupported,
                    binding::EventListenersChanged::
                        kFirstUnfilteredListenerForContextOwnerAdded,
                    nullptr, false, context))
        .Times(1);
    AddListener(context, listener, lazy_listeners_not_supported);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }

  {
    EXPECT_CALL(change_handler,
                Run(kLazyListenersSupported,
                    binding::EventListenersChanged::
                        kLastUnfilteredListenerForContextOwnerRemoved,
                    nullptr, true, context))
        .Times(1);
    RemoveListener(context, listener, lazy_listeners_supported);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }

  {
    EXPECT_CALL(change_handler,
                Run(kLazyListenersNotSupported,
                    binding::EventListenersChanged::
                        kLastUnfilteredListenerForContextOwnerRemoved,
                    nullptr, false, context))
        .Times(1);
    RemoveListener(context, listener, lazy_listeners_not_supported);
    ::testing::Mock::VerifyAndClearExpectations(&change_handler);
  }

  DisposeContext(context);
}

// Tests dispatching events while script is suspended.
TEST_F(APIEventHandlerTest, TestDispatchingEventsWhileScriptSuspended) {
  const char kEventName[] = "alpha";
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);

  const char kListenerFunction[] = "(function() { this.eventFired = true; });";
  v8::Local<v8::Function> listener =
      FunctionFromString(context, kListenerFunction);

  AddListener(context, listener, event);

  {
    // Suspend script and fire an event. The listener should *not* be notified
    // while script is suspended.
    TestJSRunner::Suspension script_suspension;
    handler()->FireEventInContext(kEventName, context, base::Value::List(),
                                  nullptr);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(),
                                                       context, "eventFired"));
  }

  // After script resumes, the listener should be notified.
  EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
                                                "eventFired"));
}

// Tests catching errors thrown by listeners while dispatching after script
// suspension.
TEST_F(APIEventHandlerTest,
       TestListenersThrowingExceptionsAfterScriptSuspension) {
  auto log_error =
      [](std::vector<std::string>* errors_out, v8::Local<v8::Context> context,
         const std::string& error) { errors_out->push_back(error); };

  std::vector<std::string> logged_errors;
  ExceptionHandler exception_handler(
      base::BindRepeating(log_error, &logged_errors));
  SetHandler(std::make_unique<APIEventHandler>(
      base::DoNothing(), base::BindRepeating(&GetContextOwner),
      &exception_handler));

  const char kEventName[] = "alpha";
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);

  const char kListenerFunction[] =
      "(function() {\n"
      "  this.eventFired = true;\n"
      "  throw new Error('hahaha');\n"
      "});";
  v8::Local<v8::Function> listener =
      FunctionFromString(context, kListenerFunction);

  AddListener(context, listener, event);

  TestJSRunner::AllowErrors allow_errors;
  {
    // Suspend script and fire an event. The listener should not be notified,
    // and no errors should be logged.
    TestJSRunner::Suspension script_suspension;
    handler()->FireEventInContext(kEventName, context, base::Value::List(),
                                  nullptr);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(),
                                                       context, "eventFired"));
    EXPECT_TRUE(logged_errors.empty());
  }

  // Once script resumes, the listener should have been notifed, and we should
  // have caught the error.
  EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
                                                "eventFired"));
  ASSERT_EQ(1u, logged_errors.size());
  EXPECT_THAT(logged_errors[0],
              testing::StartsWith("Error in event handler: Error: hahaha"));
}

// Tests dispatching events when listeners are removed between when an event
// was fired (during script suspension) and when the script runs.
TEST_F(APIEventHandlerTest,
       TestDispatchingEventAfterListenersRemovedAfterScriptSuspension) {
  const char kEventName[] = "alpha";
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context = MainContext();

  v8::Local<v8::Object> event = handler()->CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context);

  const char kListenerFunction1[] =
      "(function() { this.eventFired1 = true; });";
  const char kListenerFunction2[] =
      "(function() { this.eventFired2 = true; });";
  v8::Local<v8::Function> listener1 =
      FunctionFromString(context, kListenerFunction1);
  v8::Local<v8::Function> listener2 =
      FunctionFromString(context, kListenerFunction2);

  // Add two event listeners.
  AddListener(context, listener1, event);
  AddListener(context, listener2, event);
  EXPECT_EQ(2u, GetNumListeners(isolate(), event));

  {
    // Suspend script, and then queue up a call to remove the first listener.
    TestJSRunner::Suspension script_suspension;
    v8::Local<v8::Function> remove_listener_function =
        FunctionFromString(context, kRemoveListenerFunction);
    {
      v8::Local<v8::Value> args[] = {event, listener1};
      // Note: Use JSRunner() so that script suspension is respected.
      JSRunner::Get(context)->RunJSFunction(remove_listener_function, context,
                                            args);
    }

    // Since script has been suspended, there should still be two listeners, and
    // neither should have been notified.
    EXPECT_EQ(2u, GetNumListeners(isolate(), event));
    handler()->FireEventInContext(kEventName, context, base::Value::List(),
                                  nullptr);
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(),
                                                       context, "eventFired1"));
    EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(),
                                                       context, "eventFired2"));
  }

  // Once script resumes, the first listener should have been removed and the
  // event should have been fired. Since the listener was removed before the
  // event dispatch ran in JS, the first listener should *not* have been
  // notified.
  EXPECT_EQ(1u, GetNumListeners(isolate(), event));
  EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(), context,
                                                     "eventFired1"));
  EXPECT_EQ("true", GetStringPropertyFromObject(context->Global(), context,
                                                "eventFired2"));
}

// Test that notifications are properly fired for multiple events with the
// same context owner.
TEST_F(APIEventHandlerTest,
       TestListenersFromDifferentContextsWithTheSameOwner) {
  v8::HandleScope handle_scope(isolate());
  v8::Local<v8::Context> context_alpha1 = MainContext();
  v8::Local<v8::Context> context_alpha2 = AddContext();
  v8::Local<v8::Context> context_beta1 = AddContext();

  // Associate two v8::Contexts with the same owner, and a third with a separate
  // owner.
  auto get_context_owner = [context_alpha1, context_alpha2,
                            context_beta1](v8::Local<v8::Context> context) {
    if (context == context_alpha1 || context == context_alpha2)
      return std::string("alpha");
    if (context == context_beta1)
      return std::string("beta");
    ADD_FAILURE();
    return std::string();
  };

  MockEventChangeHandler change_handler;
  APIEventHandler handler(change_handler.Get(),
                          base::BindLambdaForTesting(get_context_owner),
                          nullptr);

  const char kEventName[] = "alpha";
  v8::Local<v8::Object> event_alpha1 = handler.CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context_alpha1);
  ASSERT_FALSE(event_alpha1.IsEmpty());
  v8::Local<v8::Object> event_alpha2 = handler.CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context_alpha2);
  ASSERT_FALSE(event_alpha2.IsEmpty());
  v8::Local<v8::Object> event_beta1 = handler.CreateEventInstance(
      kEventName, false, true, binding::kNoListenerMax, true, context_beta1);
  ASSERT_FALSE(event_beta1.IsEmpty());

  // Add a listener to the first event. The APIEventHandler should notify
  // since it's a change in state (no listeners -> listeners).
  v8::Local<v8::Function> listener_alpha1 =
      FunctionFromString(context_alpha1, "(function() {})");
  EXPECT_CALL(change_handler,
              Run(kEventName,
                  binding::EventListenersChanged::
                      kFirstUnfilteredListenerForContextOwnerAdded,
                  nullptr, true, context_alpha1))
      .Times(1);
  AddListener(context_alpha1, listener_alpha1, event_alpha1);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);

  // Adding a listener to the same event in a different context that is still
  // associated with the same owner should fire a notification for the context,
  // but not the context owner.
  EXPECT_CALL(change_handler, Run(kEventName,
                                  binding::EventListenersChanged::
                                      kFirstUnfilteredListenerForContextAdded,
                                  nullptr, true, context_alpha2))
      .Times(1);
  v8::Local<v8::Function> listener_alpha2 =
      FunctionFromString(context_alpha2, "(function() {})");
  AddListener(context_alpha2, listener_alpha2, event_alpha2);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);

  // Adding a listener in a separate context should fire a notification.
  v8::Local<v8::Function> listener_beta1 =
      FunctionFromString(context_alpha1, "(function() {})");
  EXPECT_CALL(change_handler,
              Run(kEventName,
                  binding::EventListenersChanged::
                      kFirstUnfilteredListenerForContextOwnerAdded,
                  nullptr, true, context_beta1))
      .Times(1);
  AddListener(context_beta1, listener_beta1, event_beta1);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);

  // Removing one of the listeners from the alpha context should notify about
  // the context, but not the context owner (since there are multiple listeners
  // for the context owner).
  EXPECT_CALL(change_handler, Run(kEventName,
                                  binding::EventListenersChanged::
                                      kLastUnfilteredListenerForContextRemoved,
                                  nullptr, true, context_alpha1))
      .Times(1);
  RemoveListener(context_alpha1, listener_alpha1, event_alpha1);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);

  // Removing the final listener should fire a notification for the context
  // owner.
  EXPECT_CALL(change_handler,
              Run(kEventName,
                  binding::EventListenersChanged::
                      kLastUnfilteredListenerForContextOwnerRemoved,
                  nullptr, true, context_alpha2))
      .Times(1);
  RemoveListener(context_alpha2, listener_alpha2, event_alpha2);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);

  // And removing the only listener for the beta context should fire a
  // notification.
  EXPECT_CALL(change_handler,
              Run(kEventName,
                  binding::EventListenersChanged::
                      kLastUnfilteredListenerForContextOwnerRemoved,
                  nullptr, true, context_beta1))
      .Times(1);
  RemoveListener(context_beta1, listener_beta1, event_beta1);
  ::testing::Mock::VerifyAndClearExpectations(&change_handler);
}

}  // namespace extensions