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 |