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

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

Issue 2438623002: [Extensions Bindings] Add APIBindingsSystem (Closed)
Patch Set: rebase Created 4 years, 2 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/extension_api.h"
14 #include "extensions/renderer/api_binding.h"
15 #include "extensions/renderer/api_binding_test_util.h"
16 #include "gin/converter.h"
17 #include "gin/public/context_holder.h"
18 #include "gin/public/isolate_holder.h"
19 #include "gin/test/v8_test.h"
20 #include "gin/try_catch.h"
21
22 namespace extensions {
23
24 namespace {
25
26 // Fake API for testing.
27 const char kAlphaAPIName[] = "alpha";
28 const char kAlphaAPISpec[] =
29 "{"
30 " 'types': [{"
31 " 'id': 'objRef',"
32 " 'type': 'object',"
33 " 'properties': {"
34 " 'prop1': {'type': 'string'},"
35 " 'prop2': {'type': 'integer', 'optional': true}"
36 " }"
37 " }],"
38 " 'functions': [{"
39 " 'name': 'functionWithCallback',"
40 " 'parameters': [{"
41 " 'name': 'str',"
42 " 'type': 'string'"
43 " }, {"
44 " 'name': 'callback',"
45 " 'type': 'function'"
46 " }]"
47 " }, {"
48 " 'name': 'functionWithRefAndCallback',"
49 " 'parameters': [{"
50 " 'name': 'ref',"
51 " '$ref': 'objRef'"
52 " }, {"
53 " 'name': 'callback',"
54 " 'type': 'function'"
55 " }]"
56 " }]"
57 "}";
58
59 // Another fake API for testing.
60 const char kBetaAPIName[] = "beta";
61 const char kBetaAPISpec[] =
62 "{"
63 " 'functions': [{"
64 " 'name': 'simpleFunc',"
65 " 'parameters': [{'name': 'int', 'type': 'integer'}]"
66 " }]"
67 "}";
68
69 // Runs the given |function| on the |context|'s Global object.
70 void RunJSFunction(v8::Local<v8::Function> function,
71 v8::Local<v8::Context> context,
72 int argc,
73 v8::Local<v8::Value> argv[]) {
74 function->Call(context->Global(), argc, argv);
75 }
76
77 } // namespace
78
79 // The base class to test the APIBindingsSystem. This allows subclasses to
80 // retrieve API schemas differently.
81 class APIBindingsSystemTestBase : public gin::V8Test {
82 public:
83 // Returns the DictionaryValue representing the schema with the given API
84 // name.
85 virtual const base::DictionaryValue& GetAPISchema(
86 const std::string& api_name) = 0;
87
88 // Stores the request in |last_request_|.
89 void OnAPIRequest(std::unique_ptr<APIBindingsSystem::Request> request) {
90 ASSERT_FALSE(last_request_);
91 last_request_ = std::move(request);
92 }
93
94 protected:
95 APIBindingsSystemTestBase() {}
96 void SetUp() override;
97
98 void TearDown() override {
99 bindings_system_.reset();
100 holder_.reset();
101 gin::V8Test::TearDown();
102 }
103
104 // Checks that |last_request_| exists and was provided with the
105 // |expected_name| and |expected_arguments|.
106 void ValidateLastRequest(const std::string& expected_name,
107 const std::string& expected_arguments);
108
109 const APIBindingsSystem::Request* last_request() const {
110 return last_request_.get();
111 }
112 void reset_last_request() { last_request_.reset(); }
113 APIBindingsSystem* bindings_system() { return bindings_system_.get(); }
114
115 private:
116 std::unique_ptr<gin::ContextHolder> holder_;
117
118 // The APIBindingsSystem associated with the test. Safe to use across multiple
119 // contexts.
120 std::unique_ptr<APIBindingsSystem> bindings_system_;
121
122 // The last request to be received from the APIBindingsSystem, or null if
123 // there is none.
124 std::unique_ptr<APIBindingsSystem::Request> last_request_;
125
126 DISALLOW_COPY_AND_ASSIGN(APIBindingsSystemTestBase);
127 };
128
129 void APIBindingsSystemTestBase::SetUp() {
130 gin::V8Test::SetUp();
131 v8::HandleScope handle_scope(instance_->isolate());
132 holder_ = base::MakeUnique<gin::ContextHolder>(instance_->isolate());
133 holder_->SetContext(
134 v8::Local<v8::Context>::New(instance_->isolate(), context_));
135
136 bindings_system_ = base::MakeUnique<APIBindingsSystem>(
137 base::Bind(&RunJSFunction),
138 base::Bind(&APIBindingsSystemTestBase::GetAPISchema,
139 base::Unretained(this)),
140 base::Bind(&APIBindingsSystemTestBase::OnAPIRequest,
141 base::Unretained(this)));
142 }
143
144 void APIBindingsSystemTestBase::ValidateLastRequest(
145 const std::string& expected_name,
146 const std::string& expected_arguments) {
147 ASSERT_TRUE(last_request());
148 // Note that even if no arguments are provided by the API call, we should have
149 // an empty list.
150 ASSERT_TRUE(last_request()->arguments);
151 EXPECT_EQ(expected_name, last_request()->method_name);
152 EXPECT_EQ(ReplaceSingleQuotes(expected_arguments),
153 ValueToString(*last_request()->arguments));
154 }
155
156 // An implementation that works with fake/supplied APIs, for easy testability.
157 class APIBindingsSystemTest : public APIBindingsSystemTestBase {
158 protected:
159 APIBindingsSystemTest() {}
160
161 // Calls a function constructed from |script_source| on the given |object|.
162 void CallFunctionOnObject(v8::Local<v8::Context> context,
163 v8::Local<v8::Object> object,
164 const std::string& script_source);
165
166 private:
167 const base::DictionaryValue& GetAPISchema(
168 const std::string& api_name) override {
169 EXPECT_TRUE(base::ContainsKey(api_schemas_, api_name));
170 return *api_schemas_[api_name];
171 }
172 void SetUp() override;
173
174 // The API schemas for the fake APIs.
175 std::map<std::string, std::unique_ptr<base::DictionaryValue>> api_schemas_;
176
177 DISALLOW_COPY_AND_ASSIGN(APIBindingsSystemTest);
178 };
179
180 void APIBindingsSystemTest::CallFunctionOnObject(
181 v8::Local<v8::Context> context,
182 v8::Local<v8::Object> object,
183 const std::string& script_source) {
184 std::string wrapped_script_source =
185 base::StringPrintf("(function(obj) { %s })", script_source.c_str());
186 v8::Isolate* isolate = instance_->isolate();
187
188 v8::Local<v8::Function> func =
189 FunctionFromString(context, wrapped_script_source);
190 ASSERT_FALSE(func.IsEmpty());
191
192 v8::TryCatch try_catch(isolate);
193 v8::Local<v8::Value> argv[] = {object};
194 func->Call(v8::Undefined(isolate), 1, argv);
195 EXPECT_FALSE(try_catch.HasCaught())
196 << gin::V8ToString(try_catch.Message()->Get());
197 }
198
199 void APIBindingsSystemTest::SetUp() {
200 APIBindingsSystemTestBase::SetUp();
201
202 // Create the fake API schemas.
203 {
204 struct APIData {
205 const char* name;
206 const char* spec;
207 } api_data[] = {
208 {kAlphaAPIName, kAlphaAPISpec}, {kBetaAPIName, kBetaAPISpec},
209 };
210 for (const auto& api : api_data) {
211 std::unique_ptr<base::DictionaryValue> api_schema =
212 DictionaryValueFromString(api.spec);
213 ASSERT_TRUE(api_schema);
214 api_schemas_[api.name] = std::move(api_schema);
215 }
216 }
217 }
218
219 // Tests API object initialization, calling a method on the supplied APIs, and
220 // triggering the callback for the request.
221 TEST_F(APIBindingsSystemTest, TestInitializationAndCallbacks) {
222 v8::Isolate* isolate = instance_->isolate();
223 v8::HandleScope handle_scope(isolate);
224 v8::Local<v8::Context> context =
225 v8::Local<v8::Context>::New(isolate, context_);
226
227 v8::Local<v8::Object> alpha_api =
228 bindings_system()->CreateAPIInstance(kAlphaAPIName, context, isolate);
229 ASSERT_FALSE(alpha_api.IsEmpty());
230 v8::Local<v8::Object> beta_api =
231 bindings_system()->CreateAPIInstance(kBetaAPIName, context, isolate);
232 ASSERT_FALSE(beta_api.IsEmpty());
233
234 {
235 // Test a simple call -> response.
236 const char kTestCall[] =
237 "obj.functionWithCallback('foo', function() {\n"
238 " this.callbackArguments = Array.from(arguments);\n"
239 "});";
240 CallFunctionOnObject(context, alpha_api, kTestCall);
241
242 ValidateLastRequest("alpha.functionWithCallback", "['foo']");
243
244 const char kResponseArgsJson[] = "['response',1,{'key':42}]";
245 std::unique_ptr<base::ListValue> expected_args =
246 ListValueFromString(kResponseArgsJson);
247 bindings_system()->CompleteRequest(
248 last_request()->request_id, *expected_args);
249
250 v8::Local<v8::Value> res;
251 ASSERT_TRUE(
252 context->Global()
253 ->Get(context, gin::StringToV8(isolate, "callbackArguments"))
254 .ToLocal(&res));
255
256 std::unique_ptr<base::Value> out_val = V8ToBaseValue(res, context);
257 ASSERT_TRUE(out_val);
258 EXPECT_EQ(ReplaceSingleQuotes(kResponseArgsJson), ValueToString(*out_val));
259 reset_last_request();
260 }
261
262 {
263 // Test a call with references -> response.
264 const char kTestCall[] =
265 "obj.functionWithRefAndCallback({prop1: 'alpha', prop2: 42},\n"
266 " function() {\n"
267 " this.callbackArguments = Array.from(arguments);\n"
268 "});";
269
270 CallFunctionOnObject(context, alpha_api, kTestCall);
271
272 ValidateLastRequest("alpha.functionWithRefAndCallback",
273 "[{'prop1':'alpha','prop2':42}]");
274
275 bindings_system()->CompleteRequest(
276 last_request()->request_id, base::ListValue());
277
278 v8::Local<v8::Value> res;
279 ASSERT_TRUE(
280 context->Global()
281 ->Get(context, gin::StringToV8(isolate, "callbackArguments"))
282 .ToLocal(&res));
283
284 std::unique_ptr<base::Value> out_val = V8ToBaseValue(res, context);
285 ASSERT_TRUE(out_val);
286 EXPECT_EQ("[]", ValueToString(*out_val));
287 reset_last_request();
288 }
289
290 {
291 // Test a call -> response on the second API.
292 const char kTestCall[] = "obj.simpleFunc(2)";
293 CallFunctionOnObject(context, beta_api, kTestCall);
294 ValidateLastRequest("beta.simpleFunc", "[2]");
295 EXPECT_TRUE(last_request()->request_id.empty());
296 reset_last_request();
297 }
298 }
299
300 // An implementation using real API schemas.
301 class APIBindingsSystemTestWithRealAPI : public APIBindingsSystemTestBase {
302 protected:
303 APIBindingsSystemTestWithRealAPI() {}
304
305 // Executes the given |script_source| in |context|, expecting no exceptions.
306 void ExecuteScript(v8::Local<v8::Context> context,
307 const std::string& script_source);
308
309 // Executes the given |script_source| in |context| and compares a caught
310 // error to |expected_error|.
311 void ExecuteScriptAndExpectError(v8::Local<v8::Context> context,
312 const std::string& script_source,
313 const std::string& expected_error);
314
315 private:
316 const base::DictionaryValue& GetAPISchema(
317 const std::string& api_name) override {
318 const base::DictionaryValue* schema =
319 ExtensionAPI::GetSharedInstance()->GetSchema(api_name);
320 EXPECT_TRUE(schema);
321 return *schema;
322 }
323
324 DISALLOW_COPY_AND_ASSIGN(APIBindingsSystemTestWithRealAPI);
325 };
326
327 void APIBindingsSystemTestWithRealAPI::ExecuteScript(
328 v8::Local<v8::Context> context,
329 const std::string& script_source) {
330 v8::Isolate* isolate = instance_->isolate();
331
332 v8::TryCatch try_catch(isolate);
333 // V8ValueFromScriptSource runs the source and returns the result; here, we
334 // only care about running the source.
335 V8ValueFromScriptSource(context, script_source);
336 EXPECT_FALSE(try_catch.HasCaught())
337 << gin::V8ToString(try_catch.Message()->Get());
338 }
339
340 void APIBindingsSystemTestWithRealAPI::ExecuteScriptAndExpectError(
341 v8::Local<v8::Context> context,
342 const std::string& script_source,
343 const std::string& expected_error) {
344 v8::Isolate* isolate = instance_->isolate();
345
346 v8::TryCatch try_catch(isolate);
347 V8ValueFromScriptSource(context, script_source);
348 ASSERT_TRUE(try_catch.HasCaught()) << script_source;
349 EXPECT_EQ(expected_error, gin::V8ToString(try_catch.Message()->Get()));
350 }
351
352 // The following test demonstrates how APIBindingsSystem can be used with "real"
353 // Extension APIs; that is, using the raw Extension API schemas, rather than a
354 // substituted test schema. This is useful to both show how the system is
355 // intended to be used in the future as well as to make sure that it works with
356 // actual APIs.
357 TEST_F(APIBindingsSystemTestWithRealAPI, RealAPIs) {
358 v8::Isolate* isolate = instance_->isolate();
359 v8::HandleScope handle_scope(isolate);
360 v8::Local<v8::Context> context =
361 v8::Local<v8::Context>::New(isolate, context_);
362
363 v8::Local<v8::Object> chrome = v8::Object::New(isolate);
364 {
365 v8::Maybe<bool> res = context->Global()->Set(
366 context, gin::StringToV8(isolate, "chrome"), chrome);
367 ASSERT_TRUE(res.IsJust());
368 ASSERT_TRUE(res.FromJust());
369 }
370
371 auto add_api_to_chrome = [this, &chrome,
372 &context](const std::string& api_name) {
373 v8::Local<v8::Object> api = bindings_system()->CreateAPIInstance(
374 api_name, context, context->GetIsolate());
375 ASSERT_FALSE(api.IsEmpty()) << api_name;
376 v8::Maybe<bool> res = chrome->Set(
377 context, gin::StringToV8(context->GetIsolate(), api_name), api);
378 ASSERT_TRUE(res.IsJust());
379 ASSERT_TRUE(res.FromJust());
380 };
381
382 // Pick two relatively small APIs that don't have any custom bindings (which
383 // aren't supported yet).
384 add_api_to_chrome("idle");
385 add_api_to_chrome("power");
386
387 // Test passing methods.
388 {
389 const char kTestCall[] = "chrome.power.requestKeepAwake('display');";
390 ExecuteScript(context, kTestCall);
391 ValidateLastRequest("power.requestKeepAwake", "['display']");
392 EXPECT_TRUE(last_request()->request_id.empty());
393 reset_last_request();
394 }
395
396 {
397 const char kTestCall[] = "chrome.power.releaseKeepAwake()";
398 ExecuteScript(context, kTestCall);
399 ValidateLastRequest("power.releaseKeepAwake", "[]");
400 EXPECT_TRUE(last_request()->request_id.empty());
401 reset_last_request();
402 }
403
404 {
405 const char kTestCall[] = "chrome.idle.queryState(30, function() {})";
406 ExecuteScript(context, kTestCall);
407 ValidateLastRequest("idle.queryState", "[30]");
408 EXPECT_FALSE(last_request()->request_id.empty());
409 reset_last_request();
410 }
411
412 {
413 const char kTestCall[] = "chrome.idle.setDetectionInterval(30);";
414 ExecuteScript(context, kTestCall);
415 ValidateLastRequest("idle.setDetectionInterval", "[30]");
416 EXPECT_TRUE(last_request()->request_id.empty());
417 reset_last_request();
418 }
419
420 // Check catching errors.
421 const char kError[] = "Uncaught TypeError: Invalid invocation";
422 {
423 // "disp" is not an allowed enum value.
424 const char kTestCall[] = "chrome.power.requestKeepAwake('disp');";
425 ExecuteScriptAndExpectError(context, kTestCall, kError);
426 EXPECT_FALSE(last_request());
427 reset_last_request(); // Just to not pollute future results.
428 }
429
430 {
431 // The queryState() param has a minimum of 15.
432 const char kTestCall[] = "chrome.idle.queryState(10, function() {});";
433 ExecuteScriptAndExpectError(context, kTestCall, kError);
434 EXPECT_FALSE(last_request());
435 reset_last_request(); // Just to not pollute future results.
436 }
437 }
438
439 } // namespace extensions
OLDNEW
« extensions/renderer/api_bindings_system.h ('K') | « extensions/renderer/api_bindings_system.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698