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

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

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

Powered by Google App Engine
This is Rietveld 408576698