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 |