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

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

Issue 2947463002: [Extensions Bindings] Add a bindings/ subdirectory under renderer (Closed)
Patch Set: . Created 3 years, 5 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
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_bindings_system.h"
6
7 #include "base/bind.h"
8 #include "base/macros.h"
9 #include "base/memory/ptr_util.h"
10 #include "base/stl_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/values.h"
13 #include "extensions/common/event_filtering_info.h"
14 #include "extensions/common/extension_api.h"
15 #include "extensions/renderer/api_binding.h"
16 #include "extensions/renderer/api_binding_hooks.h"
17 #include "extensions/renderer/api_binding_hooks_test_delegate.h"
18 #include "extensions/renderer/api_binding_test_util.h"
19 #include "extensions/renderer/api_binding_types.h"
20 #include "extensions/renderer/api_bindings_system_unittest.h"
21 #include "extensions/renderer/api_invocation_errors.h"
22 #include "gin/arguments.h"
23 #include "gin/converter.h"
24 #include "gin/try_catch.h"
25
26 namespace extensions {
27
28 namespace {
29
30 // Fake API for testing.
31 const char kAlphaAPIName[] = "alpha";
32 const char kAlphaAPISpec[] =
33 "{"
34 " 'types': [{"
35 " 'id': 'alpha.objRef',"
36 " 'type': 'object',"
37 " 'properties': {"
38 " 'prop1': {'type': 'string'},"
39 " 'prop2': {'type': 'integer', 'optional': true}"
40 " }"
41 " }, {"
42 " 'id': 'alpha.enumRef',"
43 " 'type': 'string',"
44 " 'enum': ['cat', 'dog']"
45 " }],"
46 " 'functions': [{"
47 " 'name': 'functionWithCallback',"
48 " 'parameters': [{"
49 " 'name': 'str',"
50 " 'type': 'string'"
51 " }, {"
52 " 'name': 'callback',"
53 " 'type': 'function'"
54 " }]"
55 " }, {"
56 " 'name': 'functionWithRefAndCallback',"
57 " 'parameters': [{"
58 " 'name': 'ref',"
59 " '$ref': 'alpha.objRef'"
60 " }, {"
61 " 'name': 'callback',"
62 " 'type': 'function'"
63 " }]"
64 " }, {"
65 " 'name': 'functionWithEnum',"
66 " 'parameters': [{'name': 'e', '$ref': 'alpha.enumRef'}]"
67 " }],"
68 " 'events': [{"
69 " 'name': 'alphaEvent'"
70 " }, {"
71 " 'name': 'alphaOtherEvent'"
72 " }]"
73 "}";
74
75 // Another fake API for testing.
76 const char kBetaAPIName[] = "beta";
77 const char kBetaAPISpec[] =
78 "{"
79 " 'functions': [{"
80 " 'name': 'simpleFunc',"
81 " 'parameters': [{'name': 'int', 'type': 'integer'}]"
82 " }]"
83 "}";
84
85 const char kGammaAPIName[] = "gamma";
86 const char kGammaAPISpec[] =
87 "{"
88 " 'functions': [{"
89 " 'name': 'functionWithExternalRef',"
90 " 'parameters': [{ 'name': 'someRef', '$ref': 'alpha.objRef' }]"
91 " }]"
92 "}";
93
94 bool AllowAllAPIs(v8::Local<v8::Context> context, const std::string& name) {
95 return true;
96 }
97
98 } // namespace
99
100 APIBindingsSystemTest::APIBindingsSystemTest() {}
101 APIBindingsSystemTest::~APIBindingsSystemTest() = default;
102
103 void APIBindingsSystemTest::SetUp() {
104 APIBindingTest::SetUp();
105
106 // Create the fake API schemas.
107 for (const auto& api : GetAPIs()) {
108 std::unique_ptr<base::DictionaryValue> api_schema =
109 DictionaryValueFromString(api.spec);
110 ASSERT_TRUE(api_schema);
111 api_schemas_[api.name] = std::move(api_schema);
112 }
113
114 bindings_system_ = base::MakeUnique<APIBindingsSystem>(
115 base::Bind(&RunFunctionOnGlobalAndIgnoreResult),
116 base::Bind(&RunFunctionOnGlobalAndReturnHandle),
117 base::Bind(&APIBindingsSystemTest::GetAPISchema, base::Unretained(this)),
118 base::Bind(&AllowAllAPIs),
119 base::Bind(&APIBindingsSystemTest::OnAPIRequest, base::Unretained(this)),
120 base::Bind(&APIBindingsSystemTest::OnEventListenersChanged,
121 base::Unretained(this)),
122 APILastError(base::Bind(&APIBindingsSystemTest::GetLastErrorParent,
123 base::Unretained(this)),
124 APILastError::AddConsoleError()));
125 }
126
127 void APIBindingsSystemTest::TearDown() {
128 // Dispose all contexts now so that we call WillReleaseContext().
129 DisposeAllContexts();
130 bindings_system_.reset();
131 APIBindingTest::TearDown();
132 }
133
134 void APIBindingsSystemTest::OnWillDisposeContext(
135 v8::Local<v8::Context> context) {
136 bindings_system_->WillReleaseContext(context);
137 }
138
139 std::vector<APIBindingsSystemTest::FakeSpec> APIBindingsSystemTest::GetAPIs() {
140 return {
141 {kAlphaAPIName, kAlphaAPISpec},
142 {kBetaAPIName, kBetaAPISpec},
143 {kGammaAPIName, kGammaAPISpec},
144 };
145 }
146
147 v8::Local<v8::Object> APIBindingsSystemTest::GetLastErrorParent(
148 v8::Local<v8::Context> context) {
149 return v8::Local<v8::Object>();
150 }
151
152 const base::DictionaryValue& APIBindingsSystemTest::GetAPISchema(
153 const std::string& api_name) {
154 EXPECT_TRUE(base::ContainsKey(api_schemas_, api_name));
155 return *api_schemas_[api_name];
156 }
157
158 void APIBindingsSystemTest::OnAPIRequest(
159 std::unique_ptr<APIRequestHandler::Request> request,
160 v8::Local<v8::Context> context) {
161 ASSERT_FALSE(last_request_);
162 last_request_ = std::move(request);
163 }
164
165 void APIBindingsSystemTest::OnEventListenersChanged(
166 const std::string& event_name,
167 binding::EventListenersChanged changed,
168 const base::DictionaryValue* filter,
169 bool was_manual,
170 v8::Local<v8::Context> context) {}
171
172 void APIBindingsSystemTest::ValidateLastRequest(
173 const std::string& expected_name,
174 const std::string& expected_arguments) {
175 ASSERT_TRUE(last_request());
176 // Note that even if no arguments are provided by the API call, we should
177 // have an empty list.
178 ASSERT_TRUE(last_request()->arguments);
179 EXPECT_EQ(expected_name, last_request()->method_name);
180 EXPECT_EQ(ReplaceSingleQuotes(expected_arguments),
181 ValueToString(*last_request()->arguments));
182 }
183
184 v8::Local<v8::Value> APIBindingsSystemTest::CallFunctionOnObject(
185 v8::Local<v8::Context> context,
186 v8::Local<v8::Object> object,
187 const std::string& script_source) {
188 std::string wrapped_script_source =
189 base::StringPrintf("(function(obj) { %s })", script_source.c_str());
190
191 v8::Local<v8::Function> func =
192 FunctionFromString(context, wrapped_script_source);
193 // Use ADD_FAILURE() to avoid messing up the return type with ASSERT.
194 if (func.IsEmpty()) {
195 ADD_FAILURE() << script_source;
196 return v8::Local<v8::Value>();
197 }
198
199 v8::Local<v8::Value> argv[] = {object};
200 return RunFunction(func, context, 1, argv);
201 }
202
203 // Tests API object initialization, calling a method on the supplied APIs, and
204 // triggering the callback for the request.
205 TEST_F(APIBindingsSystemTest, TestInitializationAndCallbacks) {
206 v8::HandleScope handle_scope(isolate());
207 v8::Local<v8::Context> context = MainContext();
208
209 v8::Local<v8::Object> alpha_api =
210 bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
211 ASSERT_FALSE(alpha_api.IsEmpty());
212 v8::Local<v8::Object> beta_api =
213 bindings_system()->CreateAPIInstance(kBetaAPIName, context, nullptr);
214 ASSERT_FALSE(beta_api.IsEmpty());
215
216 {
217 // Test a simple call -> response.
218 const char kTestCall[] =
219 "obj.functionWithCallback('foo', function() {\n"
220 " this.callbackArguments = Array.from(arguments);\n"
221 "});";
222 CallFunctionOnObject(context, alpha_api, kTestCall);
223
224 ValidateLastRequest("alpha.functionWithCallback", "['foo']");
225
226 const char kResponseArgsJson[] = "['response',1,{'key':42}]";
227 std::unique_ptr<base::ListValue> expected_args =
228 ListValueFromString(kResponseArgsJson);
229 bindings_system()->CompleteRequest(last_request()->request_id,
230 *expected_args, std::string());
231
232 EXPECT_EQ(ReplaceSingleQuotes(kResponseArgsJson),
233 GetStringPropertyFromObject(context->Global(), context,
234 "callbackArguments"));
235 reset_last_request();
236 }
237
238 {
239 // Test a call with references -> response.
240 const char kTestCall[] =
241 "obj.functionWithRefAndCallback({prop1: 'alpha', prop2: 42},\n"
242 " function() {\n"
243 " this.callbackArguments = Array.from(arguments);\n"
244 "});";
245
246 CallFunctionOnObject(context, alpha_api, kTestCall);
247
248 ValidateLastRequest("alpha.functionWithRefAndCallback",
249 "[{'prop1':'alpha','prop2':42}]");
250
251 bindings_system()->CompleteRequest(last_request()->request_id,
252 base::ListValue(), std::string());
253
254 EXPECT_EQ("[]", GetStringPropertyFromObject(context->Global(), context,
255 "callbackArguments"));
256 reset_last_request();
257 }
258
259 {
260 // Test an invalid invocation -> throwing error.
261 const char kTestCall[] =
262 "(function(obj) { obj.functionWithEnum('mouse') })";
263 v8::Local<v8::Function> function = FunctionFromString(context, kTestCall);
264 v8::Local<v8::Value> args[] = {alpha_api};
265 RunFunctionAndExpectError(
266 function, context, arraysize(args), args,
267 "Uncaught TypeError: " +
268 api_errors::InvocationError(
269 "alpha.functionWithEnum", "alpha.enumRef e",
270 api_errors::ArgumentError(
271 "e", api_errors::InvalidEnumValue({"cat", "dog"}))));
272 EXPECT_FALSE(last_request());
273 reset_last_request(); // Just to not pollute future results.
274 }
275
276 {
277 // Test an event registration -> event occurrence.
278 const char kTestCall[] =
279 "obj.alphaEvent.addListener(function() {\n"
280 " this.eventArguments = Array.from(arguments);\n"
281 "});\n";
282 CallFunctionOnObject(context, alpha_api, kTestCall);
283
284 const char kResponseArgsJson[] = "['response',1,{'key':42}]";
285 std::unique_ptr<base::ListValue> expected_args =
286 ListValueFromString(kResponseArgsJson);
287 bindings_system()->FireEventInContext("alpha.alphaEvent", context,
288 *expected_args, EventFilteringInfo());
289
290 EXPECT_EQ(ReplaceSingleQuotes(kResponseArgsJson),
291 GetStringPropertyFromObject(context->Global(), context,
292 "eventArguments"));
293 }
294
295 {
296 // Test a call -> response on the second API.
297 const char kTestCall[] = "obj.simpleFunc(2)";
298 CallFunctionOnObject(context, beta_api, kTestCall);
299 ValidateLastRequest("beta.simpleFunc", "[2]");
300 reset_last_request();
301 }
302 }
303
304 // Tests adding a custom hook to an API.
305 TEST_F(APIBindingsSystemTest, TestCustomHooks) {
306 v8::HandleScope handle_scope(isolate());
307 v8::Local<v8::Context> context = MainContext();
308
309 bool did_call = false;
310 auto hook = [](bool* did_call, const APISignature* signature,
311 v8::Local<v8::Context> context,
312 std::vector<v8::Local<v8::Value>>* arguments,
313 const APITypeReferenceMap& type_refs) {
314 *did_call = true;
315 APIBindingHooks::RequestResult result(
316 APIBindingHooks::RequestResult::HANDLED);
317 if (arguments->size() != 2) { // ASSERT* messes with the return type.
318 EXPECT_EQ(2u, arguments->size());
319 return result;
320 }
321 std::string argument;
322 EXPECT_EQ("foo", gin::V8ToString(arguments->at(0)));
323 if (!arguments->at(1)->IsFunction()) {
324 EXPECT_TRUE(arguments->at(1)->IsFunction());
325 return result;
326 }
327 v8::Local<v8::String> response =
328 gin::StringToV8(context->GetIsolate(), "bar");
329 v8::Local<v8::Value> response_args[] = {response};
330 RunFunctionOnGlobal(arguments->at(1).As<v8::Function>(),
331 context, 1, response_args);
332 return result;
333 };
334
335 auto test_hooks = base::MakeUnique<APIBindingHooksTestDelegate>();
336 test_hooks->AddHandler("alpha.functionWithCallback",
337 base::Bind(hook, &did_call));
338 APIBindingHooks* binding_hooks =
339 bindings_system()->GetHooksForAPI(kAlphaAPIName);
340 binding_hooks->SetDelegate(std::move(test_hooks));
341
342 v8::Local<v8::Object> alpha_api =
343 bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
344 ASSERT_FALSE(alpha_api.IsEmpty());
345
346 {
347 // Test a simple call -> response.
348 const char kTestCall[] =
349 "obj.functionWithCallback('foo', function() {\n"
350 " this.callbackArguments = Array.from(arguments);\n"
351 "});";
352 CallFunctionOnObject(context, alpha_api, kTestCall);
353 EXPECT_TRUE(did_call);
354
355 EXPECT_EQ("[\"bar\"]",
356 GetStringPropertyFromObject(context->Global(), context,
357 "callbackArguments"));
358 }
359 }
360
361 // Tests the setCustomCallback hook.
362 TEST_F(APIBindingsSystemTest, TestSetCustomCallback) {
363 v8::HandleScope handle_scope(isolate());
364 v8::Local<v8::Context> context = MainContext();
365
366 const char kHook[] =
367 "(function(hooks) {\n"
368 " hooks.setCustomCallback(\n"
369 " 'functionWithCallback', (name, request, originalCallback,\n"
370 " firstResult, secondResult) => {\n"
371 " this.methodName = name;\n"
372 // TODO(devlin): Currently, we don't actually pass anything useful in for
373 // the |request| object. If/when we do, we should test it.
374 " this.results = [firstResult, secondResult];\n"
375 " originalCallback(secondResult);\n"
376 " });\n"
377 "})";
378
379 APIBindingHooks* hooks = nullptr;
380 v8::Local<v8::Object> alpha_api =
381 bindings_system()->CreateAPIInstance(kAlphaAPIName, context, &hooks);
382 ASSERT_FALSE(alpha_api.IsEmpty());
383 ASSERT_TRUE(hooks);
384 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
385 v8::Local<v8::Function> function = FunctionFromString(context, kHook);
386 v8::Local<v8::Value> args[] = {js_hooks};
387 RunFunctionOnGlobal(function, context, arraysize(args), args);
388
389 {
390 const char kTestCall[] =
391 "obj.functionWithCallback('foo', function() {\n"
392 " this.callbackArguments = Array.from(arguments);\n"
393 "});";
394 CallFunctionOnObject(context, alpha_api, kTestCall);
395
396 ValidateLastRequest("alpha.functionWithCallback", "['foo']");
397
398 std::unique_ptr<base::ListValue> response =
399 ListValueFromString("['alpha','beta']");
400 bindings_system()->CompleteRequest(last_request()->request_id, *response,
401 std::string());
402
403 EXPECT_EQ(
404 "\"alpha.functionWithCallback\"",
405 GetStringPropertyFromObject(context->Global(), context, "methodName"));
406 EXPECT_EQ(
407 "[\"alpha\",\"beta\"]",
408 GetStringPropertyFromObject(context->Global(), context, "results"));
409 EXPECT_EQ("[\"beta\"]",
410 GetStringPropertyFromObject(context->Global(), context,
411 "callbackArguments"));
412 }
413 }
414
415 // Test that references to other API's types works.
416 TEST_F(APIBindingsSystemTest, CrossAPIReferences) {
417 v8::HandleScope handle_scope(isolate());
418 v8::Local<v8::Context> context = MainContext();
419
420 // Instantiate gamma API. Note: It's important that we haven't instantiated
421 // alpha API yet, since this tests that we can lazily populate the type
422 // information.
423 v8::Local<v8::Object> gamma_api =
424 bindings_system()->CreateAPIInstance(kGammaAPIName, context, nullptr);
425 ASSERT_FALSE(gamma_api.IsEmpty());
426
427 {
428 // Test a simple call -> response.
429 const char kTestCall[] = "obj.functionWithExternalRef({prop1: 'foo'});";
430 CallFunctionOnObject(context, gamma_api, kTestCall);
431 ValidateLastRequest("gamma.functionWithExternalRef", "[{'prop1':'foo'}]");
432 reset_last_request();
433 }
434 }
435
436 TEST_F(APIBindingsSystemTest, TestCustomEvent) {
437 v8::HandleScope handle_scope(isolate());
438 v8::Local<v8::Context> context = MainContext();
439
440 auto create_custom_event = [](v8::Local<v8::Context> context,
441 const binding::RunJSFunctionSync& run_js,
442 const std::string& event_name) {
443 v8::Isolate* isolate = context->GetIsolate();
444 v8::Local<v8::Object> ret = v8::Object::New(isolate);
445 ret->Set(context, gin::StringToSymbol(isolate, "name"),
446 gin::StringToSymbol(isolate, event_name))
447 .ToChecked();
448 return ret.As<v8::Value>();
449 };
450
451 auto test_hooks = base::MakeUnique<APIBindingHooksTestDelegate>();
452 test_hooks->SetCustomEvent(base::Bind(create_custom_event));
453 APIBindingHooks* binding_hooks =
454 bindings_system()->GetHooksForAPI(kAlphaAPIName);
455 binding_hooks->SetDelegate(std::move(test_hooks));
456
457 v8::Local<v8::Object> api =
458 bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
459
460 v8::Local<v8::Value> event =
461 GetPropertyFromObject(api, context, "alphaEvent");
462 ASSERT_TRUE(event->IsObject());
463 EXPECT_EQ(
464 "\"alpha.alphaEvent\"",
465 GetStringPropertyFromObject(event.As<v8::Object>(), context, "name"));
466 v8::Local<v8::Value> event2 =
467 GetPropertyFromObject(api, context, "alphaEvent");
468 EXPECT_EQ(event, event2);
469
470 v8::Local<v8::Value> other_event =
471 GetPropertyFromObject(api, context, "alphaOtherEvent");
472 ASSERT_TRUE(other_event->IsObject());
473 EXPECT_EQ("\"alpha.alphaOtherEvent\"",
474 GetStringPropertyFromObject(other_event.As<v8::Object>(), context,
475 "name"));
476 EXPECT_NE(event, other_event);
477 }
478
479 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/renderer/api_bindings_system_unittest.h ('k') | extensions/renderer/api_event_handler.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698