Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(158)

Side by Side Diff: extensions/renderer/api_binding_unittest.cc

Issue 2947463002: [Extensions Bindings] Add a bindings/ subdirectory under renderer (Closed)
Patch Set: . Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « extensions/renderer/api_binding_types.cc ('k') | extensions/renderer/api_bindings_system.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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_binding.h"
6 #include "base/bind.h"
7 #include "base/memory/ptr_util.h"
8 #include "base/stl_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/values.h"
11 #include "extensions/renderer/api_binding_hooks.h"
12 #include "extensions/renderer/api_binding_hooks_test_delegate.h"
13 #include "extensions/renderer/api_binding_test.h"
14 #include "extensions/renderer/api_binding_test_util.h"
15 #include "extensions/renderer/api_event_handler.h"
16 #include "extensions/renderer/api_invocation_errors.h"
17 #include "extensions/renderer/api_request_handler.h"
18 #include "extensions/renderer/api_type_reference_map.h"
19 #include "extensions/renderer/binding_access_checker.h"
20 #include "gin/arguments.h"
21 #include "gin/converter.h"
22 #include "gin/public/context_holder.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "third_party/WebKit/public/web/WebScopedUserGesture.h"
25 #include "v8/include/v8.h"
26
27 namespace extensions {
28
29 using namespace api_errors;
30
31 namespace {
32
33 const char kBindingName[] = "test";
34
35 // Function spec; we use single quotes for readability and then replace them.
36 const char kFunctions[] =
37 "[{"
38 " 'name': 'oneString',"
39 " 'parameters': [{"
40 " 'type': 'string',"
41 " 'name': 'str'"
42 " }]"
43 "}, {"
44 " 'name': 'stringAndInt',"
45 " 'parameters': [{"
46 " 'type': 'string',"
47 " 'name': 'str'"
48 " }, {"
49 " 'type': 'integer',"
50 " 'name': 'int'"
51 " }]"
52 "}, {"
53 " 'name': 'oneObject',"
54 " 'parameters': [{"
55 " 'type': 'object',"
56 " 'name': 'foo',"
57 " 'properties': {"
58 " 'prop1': {'type': 'string'},"
59 " 'prop2': {'type': 'string', 'optional': true}"
60 " }"
61 " }]"
62 "}, {"
63 " 'name': 'intAndCallback',"
64 " 'parameters': [{"
65 " 'name': 'int',"
66 " 'type': 'integer'"
67 " }, {"
68 " 'name': 'callback',"
69 " 'type': 'function'"
70 " }]"
71 "}]";
72
73 bool AllowAllFeatures(v8::Local<v8::Context> context, const std::string& name) {
74 return true;
75 }
76
77 void OnEventListenersChanged(const std::string& event_name,
78 binding::EventListenersChanged change,
79 const base::DictionaryValue* filter,
80 bool was_manual,
81 v8::Local<v8::Context> context) {}
82
83 } // namespace
84
85 class APIBindingUnittest : public APIBindingTest {
86 public:
87 void OnFunctionCall(std::unique_ptr<APIRequestHandler::Request> request,
88 v8::Local<v8::Context> context) {
89 last_request_ = std::move(request);
90 }
91
92 protected:
93 APIBindingUnittest()
94 : type_refs_(APITypeReferenceMap::InitializeTypeCallback()) {}
95 void SetUp() override {
96 APIBindingTest::SetUp();
97 request_handler_ = base::MakeUnique<APIRequestHandler>(
98 base::Bind(&APIBindingUnittest::OnFunctionCall, base::Unretained(this)),
99 base::Bind(&RunFunctionOnGlobalAndIgnoreResult),
100 APILastError(APILastError::GetParent(),
101 APILastError::AddConsoleError()));
102 }
103
104 void TearDown() override {
105 DisposeAllContexts();
106 access_checker_.reset();
107 request_handler_.reset();
108 event_handler_.reset();
109 binding_.reset();
110 APIBindingTest::TearDown();
111 }
112
113 void OnWillDisposeContext(v8::Local<v8::Context> context) override {
114 event_handler_->InvalidateContext(context);
115 request_handler_->InvalidateContext(context);
116 }
117
118 void SetFunctions(const char* functions) {
119 binding_functions_ = ListValueFromString(functions);
120 ASSERT_TRUE(binding_functions_);
121 }
122
123 void SetEvents(const char* events) {
124 binding_events_ = ListValueFromString(events);
125 ASSERT_TRUE(binding_events_);
126 }
127
128 void SetTypes(const char* types) {
129 binding_types_ = ListValueFromString(types);
130 ASSERT_TRUE(binding_types_);
131 }
132
133 void SetProperties(const char* properties) {
134 binding_properties_ = DictionaryValueFromString(properties);
135 ASSERT_TRUE(binding_properties_);
136 }
137
138 void SetHooks(std::unique_ptr<APIBindingHooks> hooks) {
139 binding_hooks_ = std::move(hooks);
140 ASSERT_TRUE(binding_hooks_);
141 }
142
143 void SetHooksDelegate(
144 std::unique_ptr<APIBindingHooksDelegate> hooks_delegate) {
145 binding_hooks_delegate_ = std::move(hooks_delegate);
146 ASSERT_TRUE(binding_hooks_delegate_);
147 }
148
149 void SetCreateCustomType(const APIBinding::CreateCustomType& callback) {
150 create_custom_type_ = callback;
151 }
152
153 void SetAvailabilityCallback(
154 const BindingAccessChecker::AvailabilityCallback& callback) {
155 availability_callback_ = callback;
156 }
157
158 void InitializeBinding() {
159 if (!binding_hooks_) {
160 binding_hooks_ = base::MakeUnique<APIBindingHooks>(
161 kBindingName, binding::RunJSFunctionSync());
162 }
163 if (binding_hooks_delegate_)
164 binding_hooks_->SetDelegate(std::move(binding_hooks_delegate_));
165 if (!availability_callback_)
166 availability_callback_ = base::Bind(&AllowAllFeatures);
167 event_handler_ = base::MakeUnique<APIEventHandler>(
168 base::Bind(&RunFunctionOnGlobalAndIgnoreResult),
169 base::Bind(&RunFunctionOnGlobalAndReturnHandle),
170 base::Bind(&OnEventListenersChanged));
171 access_checker_ =
172 base::MakeUnique<BindingAccessChecker>(availability_callback_);
173 binding_ = base::MakeUnique<APIBinding>(
174 kBindingName, binding_functions_.get(), binding_types_.get(),
175 binding_events_.get(), binding_properties_.get(), create_custom_type_,
176 std::move(binding_hooks_), &type_refs_, request_handler_.get(),
177 event_handler_.get(), access_checker_.get());
178 EXPECT_EQ(!binding_types_.get(), type_refs_.empty());
179 }
180
181 void ExpectPass(v8::Local<v8::Object> object,
182 const std::string& script_source,
183 const std::string& expected_json_arguments_single_quotes,
184 bool expect_callback) {
185 ExpectPass(MainContext(), object, script_source,
186 expected_json_arguments_single_quotes, expect_callback);
187 }
188
189 void ExpectPass(v8::Local<v8::Context> context,
190 v8::Local<v8::Object> object,
191 const std::string& script_source,
192 const std::string& expected_json_arguments_single_quotes,
193 bool expect_callback) {
194 RunTest(context, object, script_source, true,
195 ReplaceSingleQuotes(expected_json_arguments_single_quotes),
196 expect_callback, std::string());
197 }
198
199 void ExpectFailure(v8::Local<v8::Object> object,
200 const std::string& script_source,
201 const std::string& expected_error) {
202 RunTest(MainContext(), object, script_source, false, std::string(), false,
203 "Uncaught TypeError: " + expected_error);
204 }
205
206 void ExpectThrow(v8::Local<v8::Object> object,
207 const std::string& script_source,
208 const std::string& expected_error) {
209 RunTest(MainContext(), object, script_source, false, std::string(), false,
210 "Uncaught Error: " + expected_error);
211 }
212
213 bool HandlerWasInvoked() const { return last_request_ != nullptr; }
214 const APIRequestHandler::Request* last_request() const {
215 return last_request_.get();
216 }
217 void reset_last_request() { last_request_.reset(); }
218 APIBinding* binding() { return binding_.get(); }
219 APIEventHandler* event_handler() { return event_handler_.get(); }
220 APIRequestHandler* request_handler() { return request_handler_.get(); }
221 const APITypeReferenceMap& type_refs() const { return type_refs_; }
222
223 private:
224 void RunTest(v8::Local<v8::Context> context,
225 v8::Local<v8::Object> object,
226 const std::string& script_source,
227 bool should_pass,
228 const std::string& expected_json_arguments,
229 bool expect_callback,
230 const std::string& expected_error);
231
232 std::unique_ptr<APIRequestHandler::Request> last_request_;
233 std::unique_ptr<APIBinding> binding_;
234 std::unique_ptr<APIEventHandler> event_handler_;
235 std::unique_ptr<APIRequestHandler> request_handler_;
236 std::unique_ptr<BindingAccessChecker> access_checker_;
237 APITypeReferenceMap type_refs_;
238
239 std::unique_ptr<base::ListValue> binding_functions_;
240 std::unique_ptr<base::ListValue> binding_events_;
241 std::unique_ptr<base::ListValue> binding_types_;
242 std::unique_ptr<base::DictionaryValue> binding_properties_;
243 std::unique_ptr<APIBindingHooks> binding_hooks_;
244 std::unique_ptr<APIBindingHooksDelegate> binding_hooks_delegate_;
245 APIBinding::CreateCustomType create_custom_type_;
246 BindingAccessChecker::AvailabilityCallback availability_callback_;
247
248 DISALLOW_COPY_AND_ASSIGN(APIBindingUnittest);
249 };
250
251 void APIBindingUnittest::RunTest(v8::Local<v8::Context> context,
252 v8::Local<v8::Object> object,
253 const std::string& script_source,
254 bool should_pass,
255 const std::string& expected_json_arguments,
256 bool expect_callback,
257 const std::string& expected_error) {
258 EXPECT_FALSE(last_request_);
259 std::string wrapped_script_source =
260 base::StringPrintf("(function(obj) { %s })", script_source.c_str());
261
262 v8::Local<v8::Function> func =
263 FunctionFromString(context, wrapped_script_source);
264 ASSERT_FALSE(func.IsEmpty());
265
266 v8::Local<v8::Value> argv[] = {object};
267
268 if (should_pass) {
269 RunFunction(func, context, 1, argv);
270 ASSERT_TRUE(last_request_) << script_source;
271 EXPECT_EQ(expected_json_arguments,
272 ValueToString(*last_request_->arguments));
273 EXPECT_EQ(expect_callback, last_request_->has_callback) << script_source;
274 } else {
275 RunFunctionAndExpectError(func, context, 1, argv, expected_error);
276 EXPECT_FALSE(last_request_);
277 }
278
279 last_request_.reset();
280 }
281
282 TEST_F(APIBindingUnittest, TestEmptyAPI) {
283 InitializeBinding();
284
285 v8::HandleScope handle_scope(isolate());
286 v8::Local<v8::Context> context = MainContext();
287
288 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
289 EXPECT_EQ(
290 0u,
291 binding_object->GetOwnPropertyNames(context).ToLocalChecked()->Length());
292 }
293
294 // Tests the basic call -> request flow of the API binding (ensuring that
295 // functions are set up correctly and correctly enforced).
296 TEST_F(APIBindingUnittest, TestBasicAPICalls) {
297 SetFunctions(kFunctions);
298 InitializeBinding();
299
300 v8::HandleScope handle_scope(isolate());
301 v8::Local<v8::Context> context = MainContext();
302
303 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
304
305 // Argument parsing is tested primarily in APISignature and ArgumentSpec
306 // tests, so do a few quick sanity checks...
307 ExpectPass(binding_object, "obj.oneString('foo');", "['foo']", false);
308 ExpectFailure(
309 binding_object, "obj.oneString(1);",
310 InvocationError(
311 "test.oneString", "string str",
312 ArgumentError("str", InvalidType(kTypeString, kTypeInteger))));
313 ExpectPass(binding_object, "obj.stringAndInt('foo', 1)", "['foo',1]", false);
314 ExpectFailure(
315 binding_object, "obj.stringAndInt(1)",
316 InvocationError(
317 "test.stringAndInt", "string str, integer int",
318 ArgumentError("str", InvalidType(kTypeString, kTypeInteger))));
319 ExpectPass(binding_object, "obj.intAndCallback(1, function() {})", "[1]",
320 true);
321 ExpectFailure(
322 binding_object, "obj.intAndCallback(function() {})",
323 InvocationError(
324 "test.intAndCallback", "integer int, function callback",
325 ArgumentError("int", InvalidType(kTypeInteger, kTypeFunction))));
326
327 // ...And an interesting case (throwing an error during parsing).
328 ExpectThrow(binding_object,
329 "obj.oneObject({ get prop1() { throw new Error('Badness'); } });",
330 "Badness");
331 }
332
333 // Test that "forIOThread" property in a function schema is respected.
334 TEST_F(APIBindingUnittest, IOThreadCalls) {
335 const char kFunctions[] =
336 "[{"
337 " 'name' : 'uiFunc1',"
338 " 'parameters' : []"
339 "}, {"
340 " 'name' : 'uiFunc2',"
341 " 'parameters' : [],"
342 " 'forIOThread' : false"
343 "}, {"
344 " 'name' : 'ioFunc',"
345 " 'parameters' : [],"
346 " 'forIOThread' : true"
347 "}]";
348 SetFunctions(kFunctions);
349 InitializeBinding();
350
351 v8::HandleScope handle_scope(isolate());
352 v8::Local<v8::Context> context = MainContext();
353 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
354
355 struct {
356 const char* func_name;
357 binding::RequestThread thread;
358 } test_cases[] = {
359 {"uiFunc1", binding::RequestThread::UI},
360 {"uiFunc2", binding::RequestThread::UI},
361 {"ioFunc", binding::RequestThread::IO},
362 };
363 const char kFunctionCall[] = "(function(obj) { obj.%s(); })";
364 v8::Local<v8::Value> argv[] = {binding_object};
365
366 for (const auto& test_case : test_cases) {
367 SCOPED_TRACE(base::StringPrintf("Testing case-%s", test_case.func_name));
368 v8::Local<v8::Function> func = FunctionFromString(
369 context, base::StringPrintf(kFunctionCall, test_case.func_name));
370 RunFunction(func, context, arraysize(argv), argv);
371 ASSERT_TRUE(last_request());
372 EXPECT_EQ(test_case.thread, last_request()->thread);
373 reset_last_request();
374 }
375 }
376
377 // Test that enum values are properly exposed on the binding object.
378 TEST_F(APIBindingUnittest, EnumValues) {
379 const char kTypes[] =
380 "[{"
381 " 'id': 'first',"
382 " 'type': 'string',"
383 " 'enum': ['alpha', 'camelCase', 'Hyphen-ated',"
384 " 'SCREAMING', 'nums123', '42nums']"
385 "}, {"
386 " 'id': 'last',"
387 " 'type': 'string',"
388 " 'enum': [{'name': 'omega'}]"
389 "}]";
390
391 SetTypes(kTypes);
392 InitializeBinding();
393
394 v8::HandleScope handle_scope(isolate());
395 v8::Local<v8::Context> context = MainContext();
396
397 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
398
399 const char kExpected[] =
400 "{'ALPHA':'alpha','CAMEL_CASE':'camelCase','HYPHEN_ATED':'Hyphen-ated',"
401 "'NUMS123':'nums123','SCREAMING':'SCREAMING','_42NUMS':'42nums'}";
402 EXPECT_EQ(ReplaceSingleQuotes(kExpected),
403 GetStringPropertyFromObject(binding_object, context, "first"));
404 EXPECT_EQ(ReplaceSingleQuotes("{'OMEGA':'omega'}"),
405 GetStringPropertyFromObject(binding_object, context, "last"));
406 }
407
408 // Test that empty enum entries are (unfortunately) allowed.
409 TEST_F(APIBindingUnittest, EnumWithEmptyEntry) {
410 const char kTypes[] =
411 "[{"
412 " 'id': 'enumWithEmpty',"
413 " 'type': 'string',"
414 " 'enum': [{'name': ''}, {'name': 'other'}]"
415 "}]";
416
417 SetTypes(kTypes);
418 InitializeBinding();
419
420 v8::HandleScope handle_scope(isolate());
421 v8::Local<v8::Context> context = MainContext();
422
423 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
424
425 EXPECT_EQ(
426 "{\"\":\"\",\"OTHER\":\"other\"}",
427 GetStringPropertyFromObject(binding_object, context, "enumWithEmpty"));
428 }
429
430 // Test that type references are correctly set up in the API.
431 TEST_F(APIBindingUnittest, TypeRefsTest) {
432 const char kTypes[] =
433 "[{"
434 " 'id': 'refObj',"
435 " 'type': 'object',"
436 " 'properties': {"
437 " 'prop1': {'type': 'string'},"
438 " 'prop2': {'type': 'integer', 'optional': true}"
439 " }"
440 "}, {"
441 " 'id': 'refEnum',"
442 " 'type': 'string',"
443 " 'enum': ['alpha', 'beta']"
444 "}]";
445 const char kRefFunctions[] =
446 "[{"
447 " 'name': 'takesRefObj',"
448 " 'parameters': [{"
449 " 'name': 'o',"
450 " '$ref': 'refObj'"
451 " }]"
452 "}, {"
453 " 'name': 'takesRefEnum',"
454 " 'parameters': [{"
455 " 'name': 'e',"
456 " '$ref': 'refEnum'"
457 " }]"
458 "}]";
459
460 SetFunctions(kRefFunctions);
461 SetTypes(kTypes);
462 InitializeBinding();
463 EXPECT_EQ(2u, type_refs().size());
464 EXPECT_TRUE(type_refs().GetSpec("refObj"));
465 EXPECT_TRUE(type_refs().GetSpec("refEnum"));
466
467 v8::HandleScope handle_scope(isolate());
468 v8::Local<v8::Context> context = MainContext();
469
470 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
471
472 // Parsing in general is tested in APISignature and ArgumentSpec tests, but
473 // we test that the binding a) correctly finds the definitions, and b) accepts
474 // properties from the API object.
475 ExpectPass(binding_object, "obj.takesRefObj({prop1: 'foo'})",
476 "[{'prop1':'foo'}]", false);
477 ExpectFailure(
478 binding_object, "obj.takesRefObj({prop1: 'foo', prop2: 'a'})",
479 InvocationError(
480 "test.takesRefObj", "refObj o",
481 ArgumentError(
482 "o",
483 PropertyError("prop2", InvalidType(kTypeInteger, kTypeString)))));
484 ExpectPass(binding_object, "obj.takesRefEnum('alpha')", "['alpha']", false);
485 ExpectPass(binding_object, "obj.takesRefEnum(obj.refEnum.BETA)", "['beta']",
486 false);
487 ExpectFailure(
488 binding_object, "obj.takesRefEnum('gamma')",
489 InvocationError("test.takesRefEnum", "refEnum e",
490 ArgumentError("e", InvalidEnumValue({"alpha", "beta"}))));
491 }
492
493 TEST_F(APIBindingUnittest, RestrictedAPIs) {
494 const char kFunctions[] =
495 "[{"
496 " 'name': 'allowedOne',"
497 " 'parameters': []"
498 "}, {"
499 " 'name': 'allowedTwo',"
500 " 'parameters': []"
501 "}, {"
502 " 'name': 'restrictedOne',"
503 " 'parameters': []"
504 "}, {"
505 " 'name': 'restrictedTwo',"
506 " 'parameters': []"
507 "}]";
508 SetFunctions(kFunctions);
509 const char kEvents[] =
510 "[{'name': 'allowedEvent'}, {'name': 'restrictedEvent'}]";
511 SetEvents(kEvents);
512 auto is_available = [](v8::Local<v8::Context> context,
513 const std::string& name) {
514 std::set<std::string> allowed = {"test.allowedOne", "test.allowedTwo",
515 "test.allowedEvent"};
516 std::set<std::string> restricted = {
517 "test.restrictedOne", "test.restrictedTwo", "test.restrictedEvent"};
518 EXPECT_TRUE(allowed.count(name) || restricted.count(name)) << name;
519 return allowed.count(name) != 0;
520 };
521 SetAvailabilityCallback(base::Bind(is_available));
522
523 InitializeBinding();
524
525 v8::HandleScope handle_scope(isolate());
526 v8::Local<v8::Context> context = MainContext();
527
528 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
529
530 auto is_defined = [&binding_object, context](const std::string& name) {
531 v8::Local<v8::Value> val =
532 GetPropertyFromObject(binding_object, context, name);
533 EXPECT_FALSE(val.IsEmpty());
534 return !val->IsUndefined() && !val->IsNull();
535 };
536
537 EXPECT_TRUE(is_defined("allowedOne"));
538 EXPECT_TRUE(is_defined("allowedTwo"));
539 EXPECT_TRUE(is_defined("allowedEvent"));
540 EXPECT_FALSE(is_defined("restrictedOne"));
541 EXPECT_FALSE(is_defined("restrictedTwo"));
542 EXPECT_FALSE(is_defined("restrictedEvent"));
543 }
544
545 // Tests that events specified in the API are created as properties of the API
546 // object.
547 TEST_F(APIBindingUnittest, TestEventCreation) {
548 SetEvents(
549 R"([
550 {'name': 'onFoo'},
551 {'name': 'onBar'},
552 {'name': 'onBaz', 'options': {'maxListeners': 1}}
553 ])");
554 SetFunctions(kFunctions);
555 InitializeBinding();
556
557 v8::HandleScope handle_scope(isolate());
558 v8::Local<v8::Context> context = MainContext();
559
560 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
561
562 // Event behavior is tested in the APIEventHandler unittests as well as the
563 // APIBindingsSystem tests, so we really only need to check that the events
564 // are being initialized on the object.
565 v8::Maybe<bool> has_on_foo =
566 binding_object->Has(context, gin::StringToV8(isolate(), "onFoo"));
567 EXPECT_TRUE(has_on_foo.IsJust());
568 EXPECT_TRUE(has_on_foo.FromJust());
569
570 v8::Maybe<bool> has_on_bar =
571 binding_object->Has(context, gin::StringToV8(isolate(), "onBar"));
572 EXPECT_TRUE(has_on_bar.IsJust());
573 EXPECT_TRUE(has_on_bar.FromJust());
574
575 v8::Maybe<bool> has_on_baz =
576 binding_object->Has(context, gin::StringToV8(isolate(), "onBaz"));
577 EXPECT_TRUE(has_on_baz.IsJust());
578 EXPECT_TRUE(has_on_baz.FromJust());
579
580 // Test that the maxListeners property is correctly used.
581 v8::Local<v8::Function> add_listener = FunctionFromString(
582 context, "(function(e) { e.addListener(function() {}); })");
583 v8::Local<v8::Value> args[] = {
584 GetPropertyFromObject(binding_object, context, "onBaz")};
585 RunFunction(add_listener, context, arraysize(args), args);
586 EXPECT_EQ(1u, event_handler()->GetNumEventListenersForTesting("test.onBaz",
587 context));
588 RunFunctionAndExpectError(add_listener, context, arraysize(args), args,
589 "Uncaught TypeError: Too many listeners.");
590 EXPECT_EQ(1u, event_handler()->GetNumEventListenersForTesting("test.onBaz",
591 context));
592
593 v8::Maybe<bool> has_nonexistent_event = binding_object->Has(
594 context, gin::StringToV8(isolate(), "onNonexistentEvent"));
595 EXPECT_TRUE(has_nonexistent_event.IsJust());
596 EXPECT_FALSE(has_nonexistent_event.FromJust());
597 }
598
599 TEST_F(APIBindingUnittest, TestProperties) {
600 SetProperties(
601 "{"
602 " 'prop1': { 'value': 17, 'type': 'integer' },"
603 " 'prop2': {"
604 " 'type': 'object',"
605 " 'properties': {"
606 " 'subprop1': { 'value': 'some value', 'type': 'string' },"
607 " 'subprop2': { 'value': true, 'type': 'boolean' }"
608 " }"
609 " }"
610 "}");
611 InitializeBinding();
612
613 v8::HandleScope handle_scope(isolate());
614 v8::Local<v8::Context> context = MainContext();
615 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
616 EXPECT_EQ("17",
617 GetStringPropertyFromObject(binding_object, context, "prop1"));
618 EXPECT_EQ(R"({"subprop1":"some value","subprop2":true})",
619 GetStringPropertyFromObject(binding_object, context, "prop2"));
620 }
621
622 TEST_F(APIBindingUnittest, TestRefProperties) {
623 SetProperties(
624 "{"
625 " 'alpha': {"
626 " '$ref': 'AlphaRef',"
627 " 'value': ['a']"
628 " },"
629 " 'beta': {"
630 " '$ref': 'BetaRef',"
631 " 'value': ['b']"
632 " }"
633 "}");
634 auto create_custom_type = [](v8::Isolate* isolate,
635 const std::string& type_name,
636 const std::string& property_name,
637 const base::ListValue* property_values) {
638 v8::Local<v8::Context> context = isolate->GetCurrentContext();
639 v8::Local<v8::Object> result = v8::Object::New(isolate);
640 if (type_name == "AlphaRef") {
641 EXPECT_EQ("alpha", property_name);
642 EXPECT_EQ("[\"a\"]", ValueToString(*property_values));
643 result
644 ->Set(context, gin::StringToSymbol(isolate, "alphaProp"),
645 gin::StringToV8(isolate, "alphaVal"))
646 .ToChecked();
647 } else if (type_name == "BetaRef") {
648 EXPECT_EQ("beta", property_name);
649 EXPECT_EQ("[\"b\"]", ValueToString(*property_values));
650 result
651 ->Set(context, gin::StringToSymbol(isolate, "betaProp"),
652 gin::StringToV8(isolate, "betaVal"))
653 .ToChecked();
654 } else {
655 EXPECT_TRUE(false) << type_name;
656 }
657 return result;
658 };
659
660 SetCreateCustomType(base::Bind(create_custom_type));
661
662 InitializeBinding();
663
664 v8::HandleScope handle_scope(isolate());
665 v8::Local<v8::Context> context = MainContext();
666 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
667 EXPECT_EQ(R"({"alphaProp":"alphaVal"})",
668 GetStringPropertyFromObject(binding_object, context, "alpha"));
669 EXPECT_EQ(
670 R"({"betaProp":"betaVal"})",
671 GetStringPropertyFromObject(binding_object, context, "beta"));
672 }
673
674 TEST_F(APIBindingUnittest, TestDisposedContext) {
675 SetFunctions(kFunctions);
676 InitializeBinding();
677
678 v8::HandleScope handle_scope(isolate());
679 v8::Local<v8::Context> context = MainContext();
680
681 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
682
683 v8::Local<v8::Function> func =
684 FunctionFromString(context, "(function(obj) { obj.oneString('foo'); })");
685 v8::Local<v8::Value> argv[] = {binding_object};
686 DisposeContext(context);
687 RunFunction(func, context, arraysize(argv), argv);
688 EXPECT_FALSE(HandlerWasInvoked());
689 // This test passes if this does not crash, even under AddressSanitizer
690 // builds.
691 }
692
693 TEST_F(APIBindingUnittest, MultipleContexts) {
694 v8::HandleScope handle_scope(isolate());
695 v8::Local<v8::Context> context_a = MainContext();
696 v8::Local<v8::Context> context_b = AddContext();
697
698 SetFunctions(kFunctions);
699 InitializeBinding();
700
701 v8::Local<v8::Object> binding_object_a = binding()->CreateInstance(context_a);
702 v8::Local<v8::Object> binding_object_b = binding()->CreateInstance(context_b);
703
704 ExpectPass(context_a, binding_object_a, "obj.oneString('foo');", "['foo']",
705 false);
706 ExpectPass(context_b, binding_object_b, "obj.oneString('foo');", "['foo']",
707 false);
708 DisposeContext(context_a);
709 ExpectPass(context_b, binding_object_b, "obj.oneString('foo');", "['foo']",
710 false);
711 }
712
713 // Tests adding custom hooks for an API method.
714 TEST_F(APIBindingUnittest, TestCustomHooks) {
715 SetFunctions(kFunctions);
716
717 // Register a hook for the test.oneString method.
718 auto hooks = base::MakeUnique<APIBindingHooksTestDelegate>();
719 bool did_call = false;
720 auto hook = [](bool* did_call, const APISignature* signature,
721 v8::Local<v8::Context> context,
722 std::vector<v8::Local<v8::Value>>* arguments,
723 const APITypeReferenceMap& ref_map) {
724 *did_call = true;
725 APIBindingHooks::RequestResult result(
726 APIBindingHooks::RequestResult::HANDLED);
727 if (arguments->size() != 1u) { // ASSERT* messes with the return type.
728 EXPECT_EQ(1u, arguments->size());
729 return result;
730 }
731 EXPECT_EQ("foo", gin::V8ToString(arguments->at(0)));
732 return result;
733 };
734 hooks->AddHandler("test.oneString", base::Bind(hook, &did_call));
735 SetHooksDelegate(std::move(hooks));
736
737 InitializeBinding();
738
739 v8::HandleScope handle_scope(isolate());
740 v8::Local<v8::Context> context = MainContext();
741
742 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
743
744 // First try calling the oneString() method, which has a custom hook
745 // installed.
746 v8::Local<v8::Function> func =
747 FunctionFromString(context, "(function(obj) { obj.oneString('foo'); })");
748 v8::Local<v8::Value> args[] = {binding_object};
749 RunFunction(func, context, 1, args);
750 EXPECT_TRUE(did_call);
751
752 // Other methods, like stringAndInt(), should behave normally.
753 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]",
754 false);
755 }
756
757 TEST_F(APIBindingUnittest, TestJSCustomHook) {
758 // Register a hook for the test.oneString method.
759 auto hooks = base::MakeUnique<APIBindingHooks>(
760 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle));
761
762 v8::HandleScope handle_scope(isolate());
763 v8::Local<v8::Context> context = MainContext();
764 {
765 const char kRegisterHook[] =
766 "(function(hooks) {\n"
767 " hooks.setHandleRequest('oneString', function() {\n"
768 " this.requestArguments = Array.from(arguments);\n"
769 " });\n"
770 "})";
771 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
772 v8::Local<v8::Function> function =
773 FunctionFromString(context, kRegisterHook);
774 v8::Local<v8::Value> args[] = {js_hooks};
775 RunFunctionOnGlobal(function, context, arraysize(args), args);
776 }
777
778 SetFunctions(kFunctions);
779 SetHooks(std::move(hooks));
780 InitializeBinding();
781
782 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
783
784 // First try calling with an invalid invocation. An error should be raised and
785 // the hook should never have been called, since the arguments didn't match.
786 ExpectFailure(
787 binding_object, "obj.oneString(1);",
788 InvocationError(
789 "test.oneString", "string str",
790 ArgumentError("str", InvalidType(kTypeString, kTypeInteger))));
791 v8::Local<v8::Value> property =
792 GetPropertyFromObject(context->Global(), context, "requestArguments");
793 ASSERT_FALSE(property.IsEmpty());
794 EXPECT_TRUE(property->IsUndefined());
795
796 // Try calling the oneString() method with valid arguments. The hook should
797 // be called.
798 v8::Local<v8::Function> func =
799 FunctionFromString(context, "(function(obj) { obj.oneString('foo'); })");
800 v8::Local<v8::Value> args[] = {binding_object};
801 RunFunction(func, context, 1, args);
802
803 EXPECT_EQ("[\"foo\"]", GetStringPropertyFromObject(
804 context->Global(), context, "requestArguments"));
805
806 // Other methods, like stringAndInt(), should behave normally.
807 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]",
808 false);
809 }
810
811 // Tests the updateArgumentsPreValidate hook.
812 TEST_F(APIBindingUnittest, TestUpdateArgumentsPreValidate) {
813 // Register a hook for the test.oneString method.
814 auto hooks = base::MakeUnique<APIBindingHooks>(
815 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle));
816
817 v8::HandleScope handle_scope(isolate());
818 v8::Local<v8::Context> context = MainContext();
819 const char kRegisterHook[] =
820 "(function(hooks) {\n"
821 " hooks.setUpdateArgumentsPreValidate('oneString', function() {\n"
822 " this.requestArguments = Array.from(arguments);\n"
823 " if (this.requestArguments[0] === true)\n"
824 " return ['hooked']\n"
825 " return this.requestArguments\n"
826 " });\n"
827 "})";
828 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
829 v8::Local<v8::Function> function = FunctionFromString(context, kRegisterHook);
830 v8::Local<v8::Value> args[] = {js_hooks};
831 RunFunctionOnGlobal(function, context, arraysize(args), args);
832
833 SetHooks(std::move(hooks));
834 SetFunctions(kFunctions);
835 InitializeBinding();
836
837 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
838
839 // Call the method with a hook. Since the hook updates arguments before
840 // validation, we should be able to pass in invalid arguments and still
841 // have the hook called.
842 ExpectFailure(
843 binding_object, "obj.oneString(false);",
844 InvocationError(
845 "test.oneString", "string str",
846 ArgumentError("str", InvalidType(kTypeString, kTypeBoolean))));
847 EXPECT_EQ("[false]", GetStringPropertyFromObject(
848 context->Global(), context, "requestArguments"));
849
850 ExpectPass(binding_object, "obj.oneString(true);", "['hooked']", false);
851 EXPECT_EQ("[true]", GetStringPropertyFromObject(
852 context->Global(), context, "requestArguments"));
853
854 // Other methods, like stringAndInt(), should behave normally.
855 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]",
856 false);
857 }
858
859 // Tests the updateArgumentsPreValidate hook.
860 TEST_F(APIBindingUnittest, TestThrowInUpdateArgumentsPreValidate) {
861 auto run_js_and_allow_error = [](v8::Local<v8::Function> function,
862 v8::Local<v8::Context> context,
863 int argc,
864 v8::Local<v8::Value> argv[]) {
865 v8::MaybeLocal<v8::Value> maybe_result =
866 function->Call(context, context->Global(), argc, argv);
867 v8::Global<v8::Value> result;
868 v8::Local<v8::Value> local;
869 if (maybe_result.ToLocal(&local))
870 result.Reset(context->GetIsolate(), local);
871 return result;
872 };
873
874 // Register a hook for the test.oneString method.
875 auto hooks = base::MakeUnique<APIBindingHooks>(
876 kBindingName, base::Bind(run_js_and_allow_error));
877
878 v8::HandleScope handle_scope(isolate());
879 v8::Local<v8::Context> context = MainContext();
880 {
881 const char kRegisterHook[] =
882 "(function(hooks) {\n"
883 " hooks.setUpdateArgumentsPreValidate('oneString', function() {\n"
884 " throw new Error('Custom Hook Error');\n"
885 " });\n"
886 "})";
887 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
888 v8::Local<v8::Function> function =
889 FunctionFromString(context, kRegisterHook);
890 v8::Local<v8::Value> args[] = {js_hooks};
891 RunFunctionOnGlobal(function, context, arraysize(args), args);
892 }
893
894 SetHooks(std::move(hooks));
895 SetFunctions(kFunctions);
896 InitializeBinding();
897
898 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
899
900 v8::Local<v8::Function> function =
901 FunctionFromString(context,
902 "(function(obj) { return obj.oneString('ping'); })");
903 v8::Local<v8::Value> args[] = {binding_object};
904 RunFunctionAndExpectError(function, context, v8::Undefined(isolate()),
905 arraysize(args), args,
906 "Uncaught Error: Custom Hook Error");
907
908 // Other methods, like stringAndInt(), should behave normally.
909 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]",
910 false);
911 }
912
913 // Tests that custom JS hooks can return results synchronously.
914 TEST_F(APIBindingUnittest, TestReturningResultFromCustomJSHook) {
915 // Register a hook for the test.oneString method.
916 auto hooks = base::MakeUnique<APIBindingHooks>(
917 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle));
918
919 v8::HandleScope handle_scope(isolate());
920 v8::Local<v8::Context> context = MainContext();
921 {
922 const char kRegisterHook[] =
923 "(function(hooks) {\n"
924 " hooks.setHandleRequest('oneString', str => {\n"
925 " return str + ' pong';\n"
926 " });\n"
927 "})";
928 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
929 v8::Local<v8::Function> function =
930 FunctionFromString(context, kRegisterHook);
931 v8::Local<v8::Value> args[] = {js_hooks};
932 RunFunctionOnGlobal(function, context, arraysize(args), args);
933 }
934
935 SetHooks(std::move(hooks));
936 SetFunctions(kFunctions);
937 InitializeBinding();
938
939 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
940
941 v8::Local<v8::Function> function =
942 FunctionFromString(context,
943 "(function(obj) { return obj.oneString('ping'); })");
944 v8::Local<v8::Value> args[] = {binding_object};
945 v8::Local<v8::Value> result =
946 RunFunction(function, context, arraysize(args), args);
947 ASSERT_FALSE(result.IsEmpty());
948 std::unique_ptr<base::Value> json_result = V8ToBaseValue(result, context);
949 ASSERT_TRUE(json_result);
950 EXPECT_EQ("\"ping pong\"", ValueToString(*json_result));
951 }
952
953 // Tests that JS custom hooks can throw exceptions for bad invocations.
954 TEST_F(APIBindingUnittest, TestThrowingFromCustomJSHook) {
955 // Our testing handlers for running functions expect a pre-determined success
956 // or failure. Since we're testing throwing exceptions here, we need a way of
957 // running that allows exceptions to be thrown, but we still expect most JS
958 // calls to succeed.
959 // TODO(devlin): This is a bit clunky. If we need to do this enough, we could
960 // figure out a different solution, like having a stack object for allowing
961 // errors/exceptions. But given this is the only place we need it so far, this
962 // is sufficient.
963 auto run_js_and_expect_error = [](v8::Local<v8::Function> function,
964 v8::Local<v8::Context> context,
965 int argc,
966 v8::Local<v8::Value> argv[]) {
967 v8::MaybeLocal<v8::Value> maybe_result =
968 function->Call(context, context->Global(), argc, argv);
969 v8::Global<v8::Value> result;
970 v8::Local<v8::Value> local;
971 if (maybe_result.ToLocal(&local))
972 result.Reset(context->GetIsolate(), local);
973 return result;
974 };
975 // Register a hook for the test.oneString method.
976 auto hooks = base::MakeUnique<APIBindingHooks>(
977 kBindingName, base::Bind(run_js_and_expect_error));
978
979 v8::HandleScope handle_scope(isolate());
980 v8::Local<v8::Context> context = MainContext();
981 {
982 const char kRegisterHook[] =
983 "(function(hooks) {\n"
984 " hooks.setHandleRequest('oneString', str => {\n"
985 " throw new Error('Custom Hook Error');\n"
986 " });\n"
987 "})";
988 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
989 v8::Local<v8::Function> function =
990 FunctionFromString(context, kRegisterHook);
991 v8::Local<v8::Value> args[] = {js_hooks};
992 RunFunctionOnGlobal(function, context, arraysize(args), args);
993 }
994
995 SetHooks(std::move(hooks));
996 SetFunctions(kFunctions);
997 InitializeBinding();
998
999 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1000
1001 v8::Local<v8::Function> function =
1002 FunctionFromString(context,
1003 "(function(obj) { return obj.oneString('ping'); })");
1004 v8::Local<v8::Value> args[] = {binding_object};
1005 RunFunctionAndExpectError(function, context, v8::Undefined(isolate()),
1006 arraysize(args), args,
1007 "Uncaught Error: Custom Hook Error");
1008 }
1009
1010 // Tests that native custom hooks can return results synchronously, or throw
1011 // exceptions for bad invocations.
1012 TEST_F(APIBindingUnittest,
1013 TestReturningResultAndThrowingExceptionFromCustomNativeHook) {
1014 v8::HandleScope handle_scope(isolate());
1015 v8::Local<v8::Context> context = MainContext();
1016
1017 // Register a hook for the test.oneString method.
1018 auto hooks = base::MakeUnique<APIBindingHooksTestDelegate>();
1019 bool did_call = false;
1020 auto hook = [](bool* did_call, const APISignature* signature,
1021 v8::Local<v8::Context> context,
1022 std::vector<v8::Local<v8::Value>>* arguments,
1023 const APITypeReferenceMap& ref_map) {
1024 APIBindingHooks::RequestResult result(
1025 APIBindingHooks::RequestResult::HANDLED);
1026 if (arguments->size() != 1u) { // ASSERT* messes with the return type.
1027 EXPECT_EQ(1u, arguments->size());
1028 return result;
1029 }
1030 v8::Isolate* isolate = context->GetIsolate();
1031 std::string arg_value = gin::V8ToString(arguments->at(0));
1032 if (arg_value == "throw") {
1033 isolate->ThrowException(v8::Exception::Error(
1034 gin::StringToV8(isolate, "Custom Hook Error")));
1035 result.code = APIBindingHooks::RequestResult::THROWN;
1036 return result;
1037 }
1038 result.return_value =
1039 gin::StringToV8(context->GetIsolate(), arg_value + " pong");
1040 return result;
1041 };
1042 hooks->AddHandler("test.oneString", base::Bind(hook, &did_call));
1043
1044 SetHooksDelegate(std::move(hooks));
1045 SetFunctions(kFunctions);
1046 InitializeBinding();
1047
1048 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1049
1050 {
1051 // Test an invocation that we expect to throw an exception.
1052 v8::Local<v8::Function> function =
1053 FunctionFromString(
1054 context, "(function(obj) { return obj.oneString('throw'); })");
1055 v8::Local<v8::Value> args[] = {binding_object};
1056 RunFunctionAndExpectError(function, context, v8::Undefined(isolate()),
1057 arraysize(args), args,
1058 "Uncaught Error: Custom Hook Error");
1059 }
1060
1061 {
1062 // Test an invocation we expect to succeed.
1063 v8::Local<v8::Function> function =
1064 FunctionFromString(context,
1065 "(function(obj) { return obj.oneString('ping'); })");
1066 v8::Local<v8::Value> args[] = {binding_object};
1067 v8::Local<v8::Value> result =
1068 RunFunction(function, context, arraysize(args), args);
1069 ASSERT_FALSE(result.IsEmpty());
1070 std::unique_ptr<base::Value> json_result = V8ToBaseValue(result, context);
1071 ASSERT_TRUE(json_result);
1072 EXPECT_EQ("\"ping pong\"", ValueToString(*json_result));
1073 }
1074 }
1075
1076 // Tests the updateArgumentsPostValidate hook.
1077 TEST_F(APIBindingUnittest, TestUpdateArgumentsPostValidate) {
1078 // Register a hook for the test.oneString method.
1079 auto hooks = base::MakeUnique<APIBindingHooks>(
1080 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle));
1081
1082 v8::HandleScope handle_scope(isolate());
1083 v8::Local<v8::Context> context = MainContext();
1084 {
1085 const char kRegisterHook[] =
1086 "(function(hooks) {\n"
1087 " hooks.setUpdateArgumentsPostValidate('oneString', function() {\n"
1088 " this.requestArguments = Array.from(arguments);\n"
1089 " return ['pong'];\n"
1090 " });\n"
1091 "})";
1092 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
1093 v8::Local<v8::Function> function =
1094 FunctionFromString(context, kRegisterHook);
1095 v8::Local<v8::Value> args[] = {js_hooks};
1096 RunFunctionOnGlobal(function, context, arraysize(args), args);
1097 }
1098
1099 SetHooks(std::move(hooks));
1100 SetFunctions(kFunctions);
1101 InitializeBinding();
1102
1103 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1104
1105 // Try calling the method with an invalid signature. Since it's invalid, we
1106 // should never enter the hook.
1107 ExpectFailure(
1108 binding_object, "obj.oneString(false);",
1109 InvocationError(
1110 "test.oneString", "string str",
1111 ArgumentError("str", InvalidType(kTypeString, kTypeBoolean))));
1112 EXPECT_EQ("undefined", GetStringPropertyFromObject(
1113 context->Global(), context, "requestArguments"));
1114
1115 // Call the method with a valid signature. The hook should be entered and
1116 // manipulate the arguments.
1117 ExpectPass(binding_object, "obj.oneString('ping');", "['pong']", false);
1118 EXPECT_EQ("[\"ping\"]", GetStringPropertyFromObject(
1119 context->Global(), context, "requestArguments"));
1120
1121 // Other methods, like stringAndInt(), should behave normally.
1122 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);",
1123 "['foo',42]", false);
1124 }
1125
1126 // Tests using setUpdateArgumentsPostValidate to return a list of arguments
1127 // that violates the function schema. Sadly, this should succeed. :(
1128 // See comment in api_binding.cc.
1129 TEST_F(APIBindingUnittest, TestUpdateArgumentsPostValidateViolatingSchema) {
1130 // Register a hook for the test.oneString method.
1131 auto hooks = base::MakeUnique<APIBindingHooks>(
1132 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle));
1133
1134 v8::HandleScope handle_scope(isolate());
1135 v8::Local<v8::Context> context = MainContext();
1136 {
1137 const char kRegisterHook[] =
1138 "(function(hooks) {\n"
1139 " hooks.setUpdateArgumentsPostValidate('oneString', function() {\n"
1140 " return [{}];\n"
1141 " });\n"
1142 "})";
1143 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
1144 v8::Local<v8::Function> function =
1145 FunctionFromString(context, kRegisterHook);
1146 v8::Local<v8::Value> args[] = {js_hooks};
1147 RunFunctionOnGlobal(function, context, arraysize(args), args);
1148 }
1149
1150 SetHooks(std::move(hooks));
1151 SetFunctions(kFunctions);
1152 InitializeBinding();
1153
1154 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1155
1156 // Call the method with a valid signature. The hook should be entered and
1157 // manipulate the arguments.
1158 ExpectPass(binding_object, "obj.oneString('ping');", "[{}]", false);
1159 }
1160
1161 // Test that user gestures are properly recorded when calling APIs.
1162 TEST_F(APIBindingUnittest, TestUserGestures) {
1163 SetFunctions(kFunctions);
1164 InitializeBinding();
1165
1166 v8::HandleScope handle_scope(isolate());
1167 v8::Local<v8::Context> context = MainContext();
1168
1169 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1170
1171 v8::Local<v8::Function> function =
1172 FunctionFromString(context, "(function(obj) { obj.oneString('foo');})");
1173 ASSERT_FALSE(function.IsEmpty());
1174
1175 v8::Local<v8::Value> argv[] = {binding_object};
1176 RunFunction(function, context, arraysize(argv), argv);
1177 ASSERT_TRUE(last_request());
1178 EXPECT_FALSE(last_request()->has_user_gesture);
1179 reset_last_request();
1180
1181 blink::WebScopedUserGesture user_gesture(nullptr);
1182 RunFunction(function, context, arraysize(argv), argv);
1183 ASSERT_TRUE(last_request());
1184 EXPECT_TRUE(last_request()->has_user_gesture);
1185
1186 reset_last_request();
1187 }
1188
1189 TEST_F(APIBindingUnittest, FilteredEvents) {
1190 const char kEvents[] =
1191 "[{"
1192 " 'name': 'unfilteredOne',"
1193 " 'parameters': []"
1194 "}, {"
1195 " 'name': 'unfilteredTwo',"
1196 " 'filters': [],"
1197 " 'parameters': []"
1198 "}, {"
1199 " 'name': 'unfilteredThree',"
1200 " 'options': {'supportsFilters': false},"
1201 " 'parameters': []"
1202 "}, {"
1203 " 'name': 'filteredOne',"
1204 " 'options': {'supportsFilters': true},"
1205 " 'parameters': []"
1206 "}, {"
1207 " 'name': 'filteredTwo',"
1208 " 'filters': ["
1209 " {'name': 'url', 'type': 'array', 'items': {'type': 'any'}}"
1210 " ],"
1211 " 'parameters': []"
1212 "}]";
1213 SetEvents(kEvents);
1214 InitializeBinding();
1215
1216 v8::HandleScope handle_scope(isolate());
1217 v8::Local<v8::Context> context = MainContext();
1218
1219 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1220
1221 const char kAddFilteredListener[] =
1222 "(function(evt) {\n"
1223 " evt.addListener(function() {},\n"
1224 " {url: [{pathContains: 'simple2.html'}]});\n"
1225 "})";
1226 v8::Local<v8::Function> function =
1227 FunctionFromString(context, kAddFilteredListener);
1228 ASSERT_FALSE(function.IsEmpty());
1229
1230 auto check_supports_filters = [context, binding_object, function](
1231 base::StringPiece name,
1232 bool expect_supports) {
1233 SCOPED_TRACE(name);
1234 v8::Local<v8::Value> event =
1235 GetPropertyFromObject(binding_object, context, name);
1236 v8::Local<v8::Value> args[] = {event};
1237 if (expect_supports) {
1238 RunFunction(function, context, context->Global(), arraysize(args), args);
1239 } else {
1240 RunFunctionAndExpectError(
1241 function, context, context->Global(), arraysize(args), args,
1242 "Uncaught TypeError: This event does not support filters");
1243 }
1244 };
1245
1246 check_supports_filters("unfilteredOne", false);
1247 check_supports_filters("unfilteredTwo", false);
1248 check_supports_filters("unfilteredThree", false);
1249 check_supports_filters("filteredOne", true);
1250 check_supports_filters("filteredTwo", true);
1251 }
1252
1253 TEST_F(APIBindingUnittest, HooksTemplateInitializer) {
1254 SetFunctions(kFunctions);
1255
1256 // Register a hook for the test.oneString method.
1257 auto hooks = base::MakeUnique<APIBindingHooksTestDelegate>();
1258 auto hook = [](v8::Isolate* isolate,
1259 v8::Local<v8::ObjectTemplate> object_template,
1260 const APITypeReferenceMap& type_refs) {
1261 object_template->Set(gin::StringToSymbol(isolate, "hookedProperty"),
1262 gin::ConvertToV8(isolate, 42));
1263 };
1264 hooks->SetTemplateInitializer(base::Bind(hook));
1265 SetHooksDelegate(std::move(hooks));
1266
1267 InitializeBinding();
1268
1269 v8::HandleScope handle_scope(isolate());
1270 v8::Local<v8::Context> context = MainContext();
1271
1272 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context);
1273
1274 // The extra property should be present on the binding object.
1275 EXPECT_EQ("42", GetStringPropertyFromObject(binding_object, context,
1276 "hookedProperty"));
1277 // Sanity check: other values should still be there.
1278 EXPECT_EQ("function",
1279 GetStringPropertyFromObject(binding_object, context, "oneString"));
1280 }
1281
1282 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/renderer/api_binding_types.cc ('k') | extensions/renderer/api_bindings_system.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698