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