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 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 |