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

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

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

Powered by Google App Engine
This is Rietveld 408576698