Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 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 | |
| 5 #include "extensions/renderer/api_event_handler.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/memory/ptr_util.h" | |
| 9 #include "base/strings/stringprintf.h" | |
| 10 #include "base/values.h" | |
| 11 #include "extensions/renderer/api_binding_test_util.h" | |
| 12 #include "gin/converter.h" | |
| 13 #include "gin/public/context_holder.h" | |
| 14 #include "gin/public/isolate_holder.h" | |
| 15 #include "gin/test/v8_test.h" | |
| 16 #include "gin/try_catch.h" | |
| 17 | |
| 18 namespace extensions { | |
| 19 | |
| 20 class APIEventHandlerTest : public gin::V8Test { | |
| 21 protected: | |
| 22 APIEventHandlerTest() {} | |
| 23 ~APIEventHandlerTest() override {} | |
| 24 | |
| 25 void SetUp() override { | |
| 26 gin::V8Test::SetUp(); | |
| 27 v8::HandleScope handle_scope(instance_->isolate()); | |
| 28 holder_ = base::MakeUnique<gin::ContextHolder>(instance_->isolate()); | |
| 29 holder_->SetContext( | |
| 30 v8::Local<v8::Context>::New(instance_->isolate(), context_)); | |
| 31 } | |
| 32 | |
| 33 void TearDown() override { | |
| 34 // Garbage collect everything so that we find any issues where we might be | |
| 35 // double-freeing. | |
| 36 // '5' is a magic number stolen from Blink; arbitrarily large enough to | |
| 37 // hopefully clean up all the various paths. | |
| 38 for (int i = 0; i < 5; i++) { | |
| 39 instance_->isolate()->RequestGarbageCollectionForTesting( | |
| 40 v8::Isolate::kFullGarbageCollection); | |
| 41 } | |
| 42 | |
| 43 holder_.reset(); | |
| 44 gin::V8Test::TearDown(); | |
|
Devlin
2016/11/04 17:59:30
After this point, the context and PerContextData s
jbroman
2016/11/04 18:33:04
That's a good question. I'm not aware of a general
Devlin
2016/11/05 01:24:19
Great idea! That worked - passes as-is and fails
| |
| 45 } | |
| 46 | |
| 47 void CallFunctionOnObject(v8::Local<v8::Context> context, | |
| 48 v8::Local<v8::Object> object, | |
| 49 const std::string& script_source) { | |
| 50 std::string wrapped_script_source = | |
| 51 base::StringPrintf("(function(obj) { %s })", script_source.c_str()); | |
| 52 v8::Local<v8::Function> func = | |
| 53 FunctionFromString(context, wrapped_script_source); | |
| 54 ASSERT_FALSE(func.IsEmpty()); | |
| 55 | |
| 56 v8::Local<v8::Value> argv[] = {object}; | |
| 57 RunFunction(func, context, object, 1, argv); | |
| 58 } | |
| 59 | |
| 60 private: | |
| 61 std::unique_ptr<gin::ContextHolder> holder_; | |
| 62 | |
| 63 DISALLOW_COPY_AND_ASSIGN(APIEventHandlerTest); | |
| 64 }; | |
| 65 | |
| 66 // Tests adding, removing, and querying event listeners by calling the | |
| 67 // associated methods on the JS object. | |
| 68 TEST_F(APIEventHandlerTest, AddingRemovingAndQueryingEventListeners) { | |
| 69 const char kEventName[] = "alpha"; | |
| 70 v8::Isolate* isolate = instance_->isolate(); | |
| 71 v8::HandleScope handle_scope(instance_->isolate()); | |
| 72 v8::Local<v8::Context> context = | |
| 73 v8::Local<v8::Context>::New(instance_->isolate(), context_); | |
| 74 | |
| 75 APIEventHandler handler(base::Bind(&RunFunctionOnGlobalAndIgnoreResult)); | |
| 76 v8::Local<v8::Object> event = | |
| 77 handler.CreateEventInstance(kEventName, context); | |
| 78 ASSERT_FALSE(event.IsEmpty()); | |
| 79 | |
| 80 EXPECT_EQ(0u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 81 | |
| 82 const char kListenerFunction[] = "(function() {})"; | |
| 83 v8::Local<v8::Function> listener_function = | |
| 84 FunctionFromString(context, kListenerFunction); | |
| 85 ASSERT_FALSE(listener_function.IsEmpty()); | |
| 86 | |
| 87 const char kAddListenerFunction[] = | |
| 88 "(function(event, listener) { event.addListener(listener); })"; | |
| 89 v8::Local<v8::Function> add_listener_function = | |
| 90 FunctionFromString(context, kAddListenerFunction); | |
| 91 | |
| 92 { | |
| 93 v8::Local<v8::Value> argv[] = {event, listener_function}; | |
| 94 RunFunction(add_listener_function, context, arraysize(argv), argv); | |
| 95 } | |
| 96 // There should only be one listener on the event. | |
| 97 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 98 | |
| 99 { | |
| 100 v8::Local<v8::Value> argv[] = {event, listener_function}; | |
| 101 RunFunction(add_listener_function, context, arraysize(argv), argv); | |
| 102 } | |
| 103 // Trying to add the same listener again should be a no-op. | |
| 104 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 105 | |
| 106 // Test hasListener returns true for a listener that is present. | |
| 107 const char kHasListenerFunction[] = | |
| 108 "(function(event, listener) { return event.hasListener(listener); })"; | |
| 109 v8::Local<v8::Function> has_listener_function = | |
| 110 FunctionFromString(context, kHasListenerFunction); | |
| 111 { | |
| 112 v8::Local<v8::Value> argv[] = {event, listener_function}; | |
| 113 v8::Local<v8::Value> result = | |
| 114 RunFunction(has_listener_function, context, arraysize(argv), argv); | |
| 115 bool has_listener = false; | |
| 116 EXPECT_TRUE(gin::Converter<bool>::FromV8(isolate, result, &has_listener)); | |
| 117 EXPECT_TRUE(has_listener); | |
| 118 } | |
| 119 | |
| 120 // Test that hasListener returns false for a listener that isn't present. | |
| 121 { | |
| 122 v8::Local<v8::Function> not_a_listener = | |
| 123 FunctionFromString(context, "(function() {})"); | |
| 124 v8::Local<v8::Value> argv[] = {event, not_a_listener}; | |
| 125 v8::Local<v8::Value> result = | |
| 126 RunFunction(has_listener_function, context, arraysize(argv), argv); | |
| 127 bool has_listener = false; | |
| 128 EXPECT_TRUE(gin::Converter<bool>::FromV8(isolate, result, &has_listener)); | |
| 129 EXPECT_FALSE(has_listener); | |
| 130 } | |
| 131 | |
| 132 // Test hasListeners returns true | |
| 133 const char kHasListenersFunction[] = | |
| 134 "(function(event) { return event.hasListeners(); })"; | |
| 135 v8::Local<v8::Function> has_listeners_function = | |
| 136 FunctionFromString(context, kHasListenersFunction); | |
| 137 { | |
| 138 v8::Local<v8::Value> argv[] = {event}; | |
| 139 v8::Local<v8::Value> result = | |
| 140 RunFunction(has_listeners_function, context, arraysize(argv), argv); | |
| 141 bool has_listeners = false; | |
| 142 EXPECT_TRUE(gin::Converter<bool>::FromV8(isolate, result, &has_listeners)); | |
| 143 EXPECT_TRUE(has_listeners); | |
| 144 } | |
| 145 | |
| 146 const char kRemoveListenerFunction[] = | |
| 147 "(function(event, listener) { event.removeListener(listener); })"; | |
| 148 v8::Local<v8::Function> remove_listener_function = | |
| 149 FunctionFromString(context, kRemoveListenerFunction); | |
| 150 { | |
| 151 v8::Local<v8::Value> argv[] = {event, listener_function}; | |
| 152 RunFunction(remove_listener_function, context, arraysize(argv), argv); | |
| 153 } | |
| 154 EXPECT_EQ(0u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 155 | |
| 156 { | |
| 157 v8::Local<v8::Value> argv[] = {event}; | |
| 158 v8::Local<v8::Value> result = | |
| 159 RunFunction(has_listeners_function, context, arraysize(argv), argv); | |
| 160 bool has_listeners = false; | |
| 161 EXPECT_TRUE(gin::Converter<bool>::FromV8(isolate, result, &has_listeners)); | |
| 162 EXPECT_FALSE(has_listeners); | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 // Tests listening for and firing different events. | |
| 167 TEST_F(APIEventHandlerTest, FiringEvents) { | |
| 168 const char kAlphaName[] = "alpha"; | |
| 169 const char kBetaName[] = "beta"; | |
| 170 v8::HandleScope handle_scope(instance_->isolate()); | |
| 171 v8::Local<v8::Context> context = | |
| 172 v8::Local<v8::Context>::New(instance_->isolate(), context_); | |
| 173 | |
| 174 APIEventHandler handler(base::Bind(&RunFunctionOnGlobalAndIgnoreResult)); | |
| 175 v8::Local<v8::Object> alpha_event = | |
| 176 handler.CreateEventInstance(kAlphaName, context); | |
| 177 v8::Local<v8::Object> beta_event = | |
| 178 handler.CreateEventInstance(kBetaName, context); | |
| 179 ASSERT_FALSE(alpha_event.IsEmpty()); | |
| 180 ASSERT_FALSE(beta_event.IsEmpty()); | |
| 181 | |
| 182 const char kAlphaListenerFunction1[] = | |
| 183 "(function() {\n" | |
| 184 " if (!this.alphaCount1) this.alphaCount1 = 0;\n" | |
| 185 " ++this.alphaCount1;\n" | |
| 186 "});\n"; | |
| 187 v8::Local<v8::Function> alpha_listener1 = | |
| 188 FunctionFromString(context, kAlphaListenerFunction1); | |
| 189 const char kAlphaListenerFunction2[] = | |
| 190 "(function() {\n" | |
| 191 " if (!this.alphaCount2) this.alphaCount2 = 0;\n" | |
| 192 " ++this.alphaCount2;\n" | |
| 193 "});\n"; | |
| 194 v8::Local<v8::Function> alpha_listener2 = | |
| 195 FunctionFromString(context, kAlphaListenerFunction2); | |
| 196 const char kBetaListenerFunction[] = | |
| 197 "(function() {\n" | |
| 198 " if (!this.betaCount) this.betaCount = 0;\n" | |
| 199 " ++this.betaCount;\n" | |
| 200 "});\n"; | |
| 201 v8::Local<v8::Function> beta_listener = | |
| 202 FunctionFromString(context, kBetaListenerFunction); | |
| 203 ASSERT_FALSE(alpha_listener1.IsEmpty()); | |
| 204 ASSERT_FALSE(alpha_listener2.IsEmpty()); | |
| 205 ASSERT_FALSE(beta_listener.IsEmpty()); | |
| 206 | |
| 207 { | |
| 208 const char kAddListenerFunction[] = | |
| 209 "(function(event, listener) { event.addListener(listener); })"; | |
| 210 v8::Local<v8::Function> add_listener_function = | |
| 211 FunctionFromString(context, kAddListenerFunction); | |
| 212 { | |
| 213 v8::Local<v8::Value> argv[] = {alpha_event, alpha_listener1}; | |
| 214 RunFunction(add_listener_function, context, arraysize(argv), argv); | |
| 215 } | |
| 216 { | |
| 217 v8::Local<v8::Value> argv[] = {alpha_event, alpha_listener2}; | |
| 218 RunFunction(add_listener_function, context, arraysize(argv), argv); | |
| 219 } | |
| 220 { | |
| 221 v8::Local<v8::Value> argv[] = {beta_event, beta_listener}; | |
| 222 RunFunction(add_listener_function, context, arraysize(argv), argv); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 EXPECT_EQ(2u, handler.GetNumEventListenersForTesting(kAlphaName, context)); | |
| 227 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kBetaName, context)); | |
| 228 | |
| 229 auto get_fired_count = [&context](const char* name) { | |
| 230 v8::Local<v8::Value> res = | |
| 231 GetPropertyFromObject(context->Global(), context, name); | |
| 232 if (res->IsUndefined()) | |
| 233 return 0; | |
| 234 int32_t count = 0; | |
| 235 EXPECT_TRUE( | |
| 236 gin::Converter<int32_t>::FromV8(context->GetIsolate(), res, &count)) | |
| 237 << name; | |
| 238 return count; | |
| 239 }; | |
| 240 | |
| 241 EXPECT_EQ(0, get_fired_count("alphaCount1")); | |
| 242 EXPECT_EQ(0, get_fired_count("alphaCount2")); | |
| 243 EXPECT_EQ(0, get_fired_count("betaCount")); | |
| 244 | |
| 245 handler.FireEventInContext(kAlphaName, context, base::ListValue()); | |
| 246 EXPECT_EQ(2u, handler.GetNumEventListenersForTesting(kAlphaName, context)); | |
| 247 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kBetaName, context)); | |
| 248 | |
| 249 EXPECT_EQ(1, get_fired_count("alphaCount1")); | |
| 250 EXPECT_EQ(1, get_fired_count("alphaCount2")); | |
| 251 EXPECT_EQ(0, get_fired_count("betaCount")); | |
| 252 | |
| 253 handler.FireEventInContext(kAlphaName, context, base::ListValue()); | |
| 254 EXPECT_EQ(2, get_fired_count("alphaCount1")); | |
| 255 EXPECT_EQ(2, get_fired_count("alphaCount2")); | |
| 256 EXPECT_EQ(0, get_fired_count("betaCount")); | |
| 257 | |
| 258 handler.FireEventInContext(kBetaName, context, base::ListValue()); | |
| 259 EXPECT_EQ(2, get_fired_count("alphaCount1")); | |
| 260 EXPECT_EQ(2, get_fired_count("alphaCount2")); | |
| 261 EXPECT_EQ(1, get_fired_count("betaCount")); | |
| 262 } | |
| 263 | |
| 264 // Tests firing events with arguments. | |
| 265 TEST_F(APIEventHandlerTest, EventArguments) { | |
| 266 v8::Isolate* isolate = instance_->isolate(); | |
| 267 v8::HandleScope handle_scope(isolate); | |
| 268 v8::Local<v8::Context> context = | |
| 269 v8::Local<v8::Context>::New(isolate, context_); | |
| 270 | |
| 271 const char kEventName[] = "alpha"; | |
| 272 APIEventHandler handler(base::Bind(&RunFunctionOnGlobalAndIgnoreResult)); | |
| 273 v8::Local<v8::Object> event = | |
| 274 handler.CreateEventInstance(kEventName, context); | |
| 275 ASSERT_FALSE(event.IsEmpty()); | |
| 276 | |
| 277 const char kListenerFunction[] = | |
| 278 "(function() { this.eventArgs = Array.from(arguments); })"; | |
| 279 v8::Local<v8::Function> listener_function = | |
| 280 FunctionFromString(context, kListenerFunction); | |
| 281 ASSERT_FALSE(listener_function.IsEmpty()); | |
| 282 | |
| 283 { | |
| 284 const char kAddListenerFunction[] = | |
| 285 "(function(event, listener) { event.addListener(listener); })"; | |
| 286 v8::Local<v8::Function> add_listener_function = | |
| 287 FunctionFromString(context, kAddListenerFunction); | |
| 288 v8::Local<v8::Value> argv[] = {event, listener_function}; | |
| 289 RunFunction(add_listener_function, context, arraysize(argv), argv); | |
| 290 } | |
| 291 | |
| 292 const char kArguments[] = "['foo',1,{'prop1':'bar'}]"; | |
| 293 std::unique_ptr<base::ListValue> event_args = ListValueFromString(kArguments); | |
| 294 ASSERT_TRUE(event_args); | |
| 295 handler.FireEventInContext(kEventName, context, *event_args); | |
| 296 | |
| 297 std::unique_ptr<base::Value> result = | |
| 298 GetBaseValuePropertyFromObject(context->Global(), context, "eventArgs"); | |
| 299 ASSERT_TRUE(result); | |
| 300 EXPECT_EQ(ReplaceSingleQuotes(kArguments), ValueToString(*result)); | |
| 301 } | |
| 302 | |
| 303 // Test dispatching events to multiple contexts. | |
| 304 TEST_F(APIEventHandlerTest, MultipleContexts) { | |
| 305 v8::Isolate* isolate = instance_->isolate(); | |
| 306 v8::HandleScope handle_scope(instance_->isolate()); | |
| 307 | |
| 308 v8::Local<v8::Context> context_a = | |
| 309 v8::Local<v8::Context>::New(isolate, context_); | |
| 310 v8::Local<v8::Context> context_b = v8::Context::New(isolate); | |
| 311 gin::ContextHolder holder_b(isolate); | |
| 312 holder_b.SetContext(context_b); | |
| 313 | |
| 314 const char kEventName[] = "onFoo"; | |
| 315 | |
| 316 APIEventHandler handler(base::Bind(&RunFunctionOnGlobalAndIgnoreResult)); | |
| 317 | |
| 318 v8::Local<v8::Function> listener_a = FunctionFromString( | |
| 319 context_a, "(function(arg) { this.eventArgs = arg + 'alpha'; })"); | |
| 320 ASSERT_FALSE(listener_a.IsEmpty()); | |
| 321 v8::Local<v8::Function> listener_b = FunctionFromString( | |
| 322 context_b, "(function(arg) { this.eventArgs = arg + 'beta'; })"); | |
| 323 ASSERT_FALSE(listener_b.IsEmpty()); | |
| 324 | |
| 325 // Create two instances of the same event in different contexts. | |
| 326 v8::Local<v8::Object> event_a = | |
| 327 handler.CreateEventInstance(kEventName, context_a); | |
| 328 ASSERT_FALSE(event_a.IsEmpty()); | |
| 329 v8::Local<v8::Object> event_b = | |
| 330 handler.CreateEventInstance(kEventName, context_b); | |
| 331 ASSERT_FALSE(event_b.IsEmpty()); | |
| 332 | |
| 333 // Add two separate listeners to the event, one in each context. | |
| 334 const char kAddListenerFunction[] = | |
| 335 "(function(event, listener) { event.addListener(listener); })"; | |
| 336 { | |
| 337 v8::Local<v8::Function> add_listener_a = | |
| 338 FunctionFromString(context_a, kAddListenerFunction); | |
| 339 v8::Local<v8::Value> argv[] = {event_a, listener_a}; | |
| 340 RunFunction(add_listener_a, context_a, arraysize(argv), argv); | |
| 341 } | |
| 342 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kEventName, context_a)); | |
| 343 EXPECT_EQ(0u, handler.GetNumEventListenersForTesting(kEventName, context_b)); | |
| 344 | |
| 345 { | |
| 346 v8::Local<v8::Function> add_listener_b = | |
| 347 FunctionFromString(context_b, kAddListenerFunction); | |
| 348 v8::Local<v8::Value> argv[] = {event_b, listener_b}; | |
| 349 RunFunction(add_listener_b, context_b, arraysize(argv), argv); | |
| 350 } | |
| 351 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kEventName, context_a)); | |
| 352 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kEventName, context_b)); | |
| 353 | |
| 354 // Dispatch the event in context_a - the listener in context_b should not be | |
| 355 // notified. | |
| 356 std::unique_ptr<base::ListValue> arguments_a = | |
| 357 ListValueFromString("['result_a:']"); | |
| 358 ASSERT_TRUE(arguments_a); | |
| 359 | |
| 360 handler.FireEventInContext(kEventName, context_a, *arguments_a); | |
| 361 { | |
| 362 std::unique_ptr<base::Value> result_a = GetBaseValuePropertyFromObject( | |
| 363 context_a->Global(), context_a, "eventArgs"); | |
| 364 ASSERT_TRUE(result_a); | |
| 365 EXPECT_EQ("\"result_a:alpha\"", ValueToString(*result_a)); | |
| 366 } | |
| 367 { | |
| 368 v8::Local<v8::Value> result_b = | |
| 369 GetPropertyFromObject(context_b->Global(), context_b, "eventArgs"); | |
| 370 ASSERT_FALSE(result_b.IsEmpty()); | |
| 371 EXPECT_TRUE(result_b->IsUndefined()); | |
| 372 } | |
| 373 | |
| 374 // Dispatch the event in context_b - the listener in context_a should not be | |
| 375 // notified. | |
| 376 std::unique_ptr<base::ListValue> arguments_b = | |
| 377 ListValueFromString("['result_b:']"); | |
| 378 ASSERT_TRUE(arguments_b); | |
| 379 handler.FireEventInContext(kEventName, context_b, *arguments_b); | |
| 380 { | |
| 381 std::unique_ptr<base::Value> result_a = GetBaseValuePropertyFromObject( | |
| 382 context_a->Global(), context_a, "eventArgs"); | |
| 383 ASSERT_TRUE(result_a); | |
| 384 EXPECT_EQ("\"result_a:alpha\"", ValueToString(*result_a)); | |
| 385 } | |
| 386 { | |
| 387 std::unique_ptr<base::Value> result_b = GetBaseValuePropertyFromObject( | |
| 388 context_b->Global(), context_b, "eventArgs"); | |
| 389 ASSERT_TRUE(result_b); | |
| 390 EXPECT_EQ("\"result_b:beta\"", ValueToString(*result_b)); | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 TEST_F(APIEventHandlerTest, EventArgumentsFoo) { | |
| 395 v8::Isolate* isolate = instance_->isolate(); | |
| 396 v8::HandleScope handle_scope(isolate); | |
| 397 v8::Local<v8::Context> context = | |
| 398 v8::Local<v8::Context>::New(isolate, context_); | |
| 399 | |
| 400 const char kEventName[] = "alpha"; | |
| 401 APIEventHandler handler(base::Bind(&RunFunctionOnGlobalAndIgnoreResult)); | |
| 402 v8::Local<v8::Object> event = | |
| 403 handler.CreateEventInstance(kEventName, context); | |
| 404 ASSERT_FALSE(event.IsEmpty()); | |
| 405 | |
| 406 const char kAddListenerOnNull[] = | |
| 407 "(function(event) {\n" | |
| 408 " event.addListener.call(null, function() {});\n" | |
| 409 "})"; | |
| 410 { | |
| 411 v8::Local<v8::Value> args[] = {event}; | |
| 412 // TODO(devlin): This is the generic type error that gin throws. It's not | |
| 413 // very descriptive, nor does it match the web (which would just say e.g. | |
| 414 // "Illegal invocation"). Might be worth updating later. | |
| 415 RunFunctionAndExpectError( | |
| 416 FunctionFromString(context, kAddListenerOnNull), | |
| 417 context, 1, args, | |
| 418 "Uncaught TypeError: Error processing argument at index -1," | |
| 419 " conversion failure from undefined"); | |
| 420 } | |
| 421 EXPECT_EQ(0u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 422 | |
| 423 const char kAddListenerOnEvent[] = | |
| 424 "(function(event) {\n" | |
| 425 " event.addListener.call(event, function() {});\n" | |
| 426 "})"; | |
| 427 { | |
| 428 v8::Local<v8::Value> args[] = {event}; | |
| 429 RunFunction(FunctionFromString(context, kAddListenerOnEvent), | |
| 430 context, 1, args); | |
| 431 } | |
| 432 EXPECT_EQ(1u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 433 | |
| 434 const char kAddListenerOnEventWithCapture[] = | |
| 435 "(function(event) {\n" | |
| 436 " event.addListener(function listener() {\n" | |
| 437 " event.hasListener(listener);\n" | |
| 438 " });\n" | |
| 439 "})"; | |
| 440 { | |
| 441 v8::Local<v8::Value> args[] = {event}; | |
| 442 RunFunction(FunctionFromString(context, kAddListenerOnEventWithCapture), | |
| 443 context, 1, args); | |
| 444 } | |
| 445 EXPECT_EQ(2u, handler.GetNumEventListenersForTesting(kEventName, context)); | |
| 446 } | |
| 447 | |
| 448 } // namespace extensions | |
| OLD | NEW |