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/bindings/argument_spec.h"
6 #include "base/bind.h"
7 #include "base/callback.h"
8 #include "base/macros.h"
9 #include "base/stl_util.h"
10 #include "base/values.h"
11 #include "extensions/renderer/bindings/api_binding_test_util.h"
12 #include "extensions/renderer/bindings/api_invocation_errors.h"
13 #include "extensions/renderer/bindings/api_type_reference_map.h"
14 #include "extensions/renderer/bindings/argument_spec_builder.h"
15 #include "gin/converter.h"
16 #include "gin/dictionary.h"
17 #include "gin/public/isolate_holder.h"
18 #include "gin/test/v8_test.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20 #include "v8/include/v8.h"
21
22 namespace extensions {
23
24 using api_errors::IndexError;
25 using api_errors::InvalidType;
26 using api_errors::kTypeBoolean;
27 using api_errors::kTypeDouble;
28 using api_errors::kTypeFunction;
29 using api_errors::kTypeInteger;
30 using api_errors::kTypeList;
31 using api_errors::kTypeNull;
32 using api_errors::kTypeObject;
33 using api_errors::kTypeString;
34 using api_errors::kTypeUndefined;
35 using api_errors::MissingRequiredProperty;
36
37 using V8Validator = base::OnceCallback<void(v8::Local<v8::Value>)>;
38
39 class ArgumentSpecUnitTest : public gin::V8Test {
40 protected:
ArgumentSpecUnitTest()41 ArgumentSpecUnitTest()
42 : type_refs_(APITypeReferenceMap::InitializeTypeCallback()) {}
~ArgumentSpecUnitTest()43 ~ArgumentSpecUnitTest() override {}
44
45 enum class TestResult {
46 PASS,
47 FAIL,
48 THROW,
49 };
50
51 struct RunTestParams {
RunTestParamsextensions::ArgumentSpecUnitTest::RunTestParams52 RunTestParams(const ArgumentSpec& spec,
53 base::StringPiece script_source,
54 TestResult result)
55 : spec(spec), script_source(script_source), expected_result(result) {}
56
57 const ArgumentSpec& spec;
58 base::StringPiece script_source;
59 TestResult expected_result;
60 base::StringPiece expected_json;
61 base::StringPiece expected_error;
62 base::StringPiece expected_thrown_message;
63 const base::Value* expected_value = nullptr;
64 bool should_convert_to_base = true;
65 bool should_convert_to_v8 = false;
66 V8Validator validate_v8;
67 };
68
ExpectSuccess(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_json_single_quotes)69 void ExpectSuccess(const ArgumentSpec& spec,
70 const std::string& script_source,
71 const std::string& expected_json_single_quotes) {
72 RunTestParams params(spec, script_source, TestResult::PASS);
73 std::string expected_json =
74 ReplaceSingleQuotes(expected_json_single_quotes);
75 params.expected_json = expected_json;
76 RunTest(params);
77 }
78
ExpectSuccess(const ArgumentSpec & spec,const std::string & script_source,const base::Value & expected_value)79 void ExpectSuccess(const ArgumentSpec& spec,
80 const std::string& script_source,
81 const base::Value& expected_value) {
82 RunTestParams params(spec, script_source, TestResult::PASS);
83 params.expected_value = &expected_value;
84 RunTest(params);
85 }
86
ExpectSuccess(const ArgumentSpec & spec,const std::string & script_source,V8Validator validate_v8)87 void ExpectSuccess(const ArgumentSpec& spec,
88 const std::string& script_source,
89 V8Validator validate_v8) {
90 RunTestParams params(spec, script_source, TestResult::PASS);
91 params.should_convert_to_base = false;
92 params.should_convert_to_v8 = true;
93 params.validate_v8 = std::move(validate_v8);
94 RunTest(params);
95 }
96
ExpectSuccessWithNoConversion(const ArgumentSpec & spec,const std::string & script_source)97 void ExpectSuccessWithNoConversion(const ArgumentSpec& spec,
98 const std::string& script_source) {
99 RunTestParams params(spec, script_source, TestResult::PASS);
100 params.should_convert_to_base = false;
101 RunTest(params);
102 }
103
ExpectFailure(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_error)104 void ExpectFailure(const ArgumentSpec& spec,
105 const std::string& script_source,
106 const std::string& expected_error) {
107 RunTestParams params(spec, script_source, TestResult::FAIL);
108 params.expected_error = expected_error;
109 RunTest(params);
110 }
111
ExpectFailureWithNoConversion(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_error)112 void ExpectFailureWithNoConversion(const ArgumentSpec& spec,
113 const std::string& script_source,
114 const std::string& expected_error) {
115 RunTestParams params(spec, script_source, TestResult::FAIL);
116 params.should_convert_to_base = false;
117 params.expected_error = expected_error;
118 RunTest(params);
119 }
120
ExpectThrow(const ArgumentSpec & spec,const std::string & script_source,const std::string & expected_thrown_message)121 void ExpectThrow(const ArgumentSpec& spec,
122 const std::string& script_source,
123 const std::string& expected_thrown_message) {
124 RunTestParams params(spec, script_source, TestResult::THROW);
125 params.expected_thrown_message = expected_thrown_message;
126 RunTest(params);
127 }
128
AddTypeRef(const std::string & id,std::unique_ptr<ArgumentSpec> spec)129 void AddTypeRef(const std::string& id, std::unique_ptr<ArgumentSpec> spec) {
130 type_refs_.AddSpec(id, std::move(spec));
131 }
132
type_refs() const133 const APITypeReferenceMap& type_refs() const { return type_refs_; }
134
135 private:
136 void RunTest(RunTestParams& params);
137
138 APITypeReferenceMap type_refs_;
139
140 DISALLOW_COPY_AND_ASSIGN(ArgumentSpecUnitTest);
141 };
142
RunTest(RunTestParams & params)143 void ArgumentSpecUnitTest::RunTest(RunTestParams& params) {
144 v8::Isolate* isolate = instance_->isolate();
145 v8::HandleScope handle_scope(instance_->isolate());
146
147 v8::Local<v8::Context> context =
148 v8::Local<v8::Context>::New(instance_->isolate(), context_);
149 v8::TryCatch try_catch(isolate);
150 v8::Local<v8::Value> val =
151 V8ValueFromScriptSource(context, params.script_source);
152 ASSERT_FALSE(val.IsEmpty()) << params.script_source;
153
154 std::string error;
155 std::unique_ptr<base::Value> out_value;
156 v8::Local<v8::Value> v8_out_value;
157 bool did_succeed = params.spec.ParseArgument(
158 context, val, type_refs_,
159 params.should_convert_to_base ? &out_value : nullptr,
160 params.should_convert_to_v8 ? &v8_out_value : nullptr, &error);
161 bool should_succeed = params.expected_result == TestResult::PASS;
162 ASSERT_EQ(should_succeed, did_succeed)
163 << params.script_source << ", " << error;
164 ASSERT_EQ(did_succeed && params.should_convert_to_base, !!out_value);
165 ASSERT_EQ(did_succeed && params.should_convert_to_v8,
166 !v8_out_value.IsEmpty());
167 bool should_throw = params.expected_result == TestResult::THROW;
168 ASSERT_EQ(should_throw, try_catch.HasCaught()) << params.script_source;
169
170 if (!params.expected_error.empty())
171 EXPECT_EQ(params.expected_error, error) << params.script_source;
172
173 if (should_succeed) {
174 if (params.should_convert_to_base) {
175 ASSERT_TRUE(out_value);
176 if (params.expected_value) {
177 EXPECT_TRUE(params.expected_value->Equals(out_value.get()))
178 << params.script_source;
179 } else {
180 EXPECT_EQ(params.expected_json, ValueToString(*out_value));
181 }
182 }
183 if (params.should_convert_to_v8) {
184 ASSERT_FALSE(v8_out_value.IsEmpty()) << params.script_source;
185 if (!params.validate_v8.is_null())
186 std::move(params.validate_v8).Run(v8_out_value);
187 }
188 } else if (should_throw) {
189 EXPECT_EQ(params.expected_thrown_message,
190 gin::V8ToString(isolate, try_catch.Message()->Get()));
191 }
192 }
193
TEST_F(ArgumentSpecUnitTest,Test)194 TEST_F(ArgumentSpecUnitTest, Test) {
195 {
196 ArgumentSpec spec(*ValueFromString("{'type': 'integer'}"));
197 ExpectSuccess(spec, "1", "1");
198 ExpectSuccess(spec, "-1", "-1");
199 ExpectSuccess(spec, "0", "0");
200 ExpectSuccess(spec, "0.0", "0");
201 ExpectSuccess(spec, "-0.0", "0");
202 ExpectSuccess(spec, "-0.", "0");
203 ExpectSuccess(spec, "-.0", "0");
204 ExpectSuccess(spec, "-0", "0");
205 ExpectFailure(spec, "undefined", InvalidType(kTypeInteger, kTypeUndefined));
206 ExpectFailure(spec, "null", InvalidType(kTypeInteger, kTypeNull));
207 ExpectFailure(spec, "1.1", InvalidType(kTypeInteger, kTypeDouble));
208 ExpectFailure(spec, "'foo'", InvalidType(kTypeInteger, kTypeString));
209 ExpectFailure(spec, "'1'", InvalidType(kTypeInteger, kTypeString));
210 ExpectFailure(spec, "({})", InvalidType(kTypeInteger, kTypeObject));
211 ExpectFailure(spec, "[1]", InvalidType(kTypeInteger, kTypeList));
212 }
213
214 {
215 ArgumentSpec spec(*ValueFromString("{'type': 'number'}"));
216 ExpectSuccess(spec, "1", "1.0");
217 ExpectSuccess(spec, "-1", "-1.0");
218 ExpectSuccess(spec, "0", "0.0");
219 ExpectSuccess(spec, "1.1", "1.1");
220 ExpectSuccess(spec, "1.", "1.0");
221 ExpectSuccess(spec, ".1", "0.1");
222 ExpectFailure(spec, "undefined", InvalidType(kTypeDouble, kTypeUndefined));
223 ExpectFailure(spec, "null", InvalidType(kTypeDouble, kTypeNull));
224 ExpectFailure(spec, "'foo'", InvalidType(kTypeDouble, kTypeString));
225 ExpectFailure(spec, "'1.1'", InvalidType(kTypeDouble, kTypeString));
226 ExpectFailure(spec, "({})", InvalidType(kTypeDouble, kTypeObject));
227 ExpectFailure(spec, "[1.1]", InvalidType(kTypeDouble, kTypeList));
228 }
229
230 {
231 ArgumentSpec spec(*ValueFromString("{'type': 'integer', 'minimum': 1}"));
232 ExpectSuccess(spec, "2", "2");
233 ExpectSuccess(spec, "1", "1");
234 ExpectFailure(spec, "0", api_errors::NumberTooSmall(1));
235 ExpectFailure(spec, "-1", api_errors::NumberTooSmall(1));
236 }
237
238 {
239 ArgumentSpec spec(*ValueFromString("{'type': 'integer', 'maximum': 10}"));
240 ExpectSuccess(spec, "10", "10");
241 ExpectSuccess(spec, "1", "1");
242 ExpectFailure(spec, "11", api_errors::NumberTooLarge(10));
243 }
244
245 {
246 ArgumentSpec spec(*ValueFromString("{'type': 'string'}"));
247 ExpectSuccess(spec, "'foo'", "'foo'");
248 ExpectSuccess(spec, "''", "''");
249 ExpectFailure(spec, "1", InvalidType(kTypeString, kTypeInteger));
250 ExpectFailure(spec, "({})", InvalidType(kTypeString, kTypeObject));
251 ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList));
252 }
253
254 {
255 ArgumentSpec spec(
256 *ValueFromString("{'type': 'string', 'enum': ['foo', 'bar']}"));
257 std::set<std::string> valid_enums = {"foo", "bar"};
258 ExpectSuccess(spec, "'foo'", "'foo'");
259 ExpectSuccess(spec, "'bar'", "'bar'");
260 ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList));
261 ExpectFailure(spec, "'fo'", api_errors::InvalidEnumValue(valid_enums));
262 ExpectFailure(spec, "'foobar'", api_errors::InvalidEnumValue(valid_enums));
263 ExpectFailure(spec, "'baz'", api_errors::InvalidEnumValue(valid_enums));
264 ExpectFailure(spec, "''", api_errors::InvalidEnumValue(valid_enums));
265 }
266
267 {
268 ArgumentSpec spec(*ValueFromString(
269 "{'type': 'string', 'enum': [{'name': 'foo'}, {'name': 'bar'}]}"));
270 std::set<std::string> valid_enums = {"foo", "bar"};
271 ExpectSuccess(spec, "'foo'", "'foo'");
272 ExpectSuccess(spec, "'bar'", "'bar'");
273 ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList));
274 ExpectFailure(spec, "'fo'", api_errors::InvalidEnumValue(valid_enums));
275 ExpectFailure(spec, "'foobar'", api_errors::InvalidEnumValue(valid_enums));
276 ExpectFailure(spec, "'baz'", api_errors::InvalidEnumValue(valid_enums));
277 ExpectFailure(spec, "''", api_errors::InvalidEnumValue(valid_enums));
278 }
279
280 {
281 ArgumentSpec spec(*ValueFromString("{'type': 'boolean'}"));
282 ExpectSuccess(spec, "true", "true");
283 ExpectSuccess(spec, "false", "false");
284 ExpectFailure(spec, "1", InvalidType(kTypeBoolean, kTypeInteger));
285 ExpectFailure(spec, "'true'", InvalidType(kTypeBoolean, kTypeString));
286 ExpectFailure(spec, "null", InvalidType(kTypeBoolean, kTypeNull));
287 }
288
289 {
290 ArgumentSpec spec(
291 *ValueFromString("{'type': 'array', 'items': {'type': 'string'}}"));
292 ExpectSuccess(spec, "[]", "[]");
293 ExpectSuccess(spec, "['foo']", "['foo']");
294 ExpectSuccess(spec, "['foo', 'bar']", "['foo','bar']");
295 ExpectSuccess(spec, "var x = new Array(); x[0] = 'foo'; x;", "['foo']");
296 ExpectFailure(spec, "'foo'", InvalidType(kTypeList, kTypeString));
297 ExpectFailure(spec, "[1, 2]",
298 IndexError(0u, InvalidType(kTypeString, kTypeInteger)));
299 ExpectFailure(spec, "['foo', 1]",
300 IndexError(1u, InvalidType(kTypeString, kTypeInteger)));
301 ExpectFailure(spec,
302 "var x = ['a', 'b', 'c'];"
303 "x[4] = 'd';" // x[3] is undefined, violating the spec.
304 "x;",
305 IndexError(3u, InvalidType(kTypeString, kTypeUndefined)));
306 ExpectThrow(
307 spec,
308 "var x = [];"
309 "Object.defineProperty("
310 " x, 0,"
311 " { get: () => { throw new Error('Badness'); } });"
312 "x;",
313 "Uncaught Error: Badness");
314 }
315
316 {
317 const char kObjectSpec[] =
318 "{"
319 " 'type': 'object',"
320 " 'properties': {"
321 " 'prop1': {'type': 'string'},"
322 " 'prop2': {'type': 'integer', 'optional': true}"
323 " }"
324 "}";
325 ArgumentSpec spec(*ValueFromString(kObjectSpec));
326 ExpectSuccess(spec, "({prop1: 'foo', prop2: 2})",
327 "{'prop1':'foo','prop2':2}");
328 ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
329 ExpectSuccess(spec, "({prop1: 'foo', prop2: null})", "{'prop1':'foo'}");
330 ExpectSuccess(spec, "x = {}; x.prop1 = 'foo'; x;", "{'prop1':'foo'}");
331 ExpectFailure(spec, "({prop1: 'foo', prop2: 'bar'})",
332 api_errors::PropertyError(
333 "prop2", InvalidType(kTypeInteger, kTypeString)));
334 ExpectFailure(spec, "({prop2: 2})", MissingRequiredProperty("prop1"));
335 // Unknown properties are not allowed.
336 ExpectFailure(spec, "({prop1: 'foo', prop2: 2, prop3: 'blah'})",
337 api_errors::UnexpectedProperty("prop3"));
338 // We only consider properties on the object itself, not its prototype
339 // chain.
340 ExpectFailure(spec,
341 "function X() {}\n"
342 "X.prototype = { prop1: 'foo' };\n"
343 "var x = new X();\n"
344 "x;",
345 MissingRequiredProperty("prop1"));
346 ExpectFailure(spec,
347 "function X() {}\n"
348 "X.prototype = { prop1: 'foo' };\n"
349 "function Y() { this.__proto__ = X.prototype; }\n"
350 "var z = new Y();\n"
351 "z;",
352 MissingRequiredProperty("prop1"));
353 // Self-referential fun. Currently we don't have to worry about these much
354 // because the spec won't match at some point (and V8ValueConverter has
355 // cycle detection and will fail).
356 ExpectFailure(spec, "x = {}; x.prop1 = x; x;",
357 api_errors::PropertyError(
358 "prop1", InvalidType(kTypeString, kTypeObject)));
359 ExpectThrow(
360 spec,
361 "({ get prop1() { throw new Error('Badness'); }});",
362 "Uncaught Error: Badness");
363 ExpectThrow(spec,
364 "x = {prop1: 'foo'};\n"
365 "Object.defineProperty(\n"
366 " x, 'prop2',\n"
367 " {\n"
368 " get: () => { throw new Error('Badness'); },\n"
369 " enumerable: true,\n"
370 "});\n"
371 "x;",
372 "Uncaught Error: Badness");
373 // By default, properties from Object.defineProperty() aren't enumerable,
374 // so they will be ignored in our matching.
375 ExpectSuccess(spec,
376 "x = {prop1: 'foo'};\n"
377 "Object.defineProperty(\n"
378 " x, 'prop2',\n"
379 " { get: () => { throw new Error('Badness'); } });\n"
380 "x;",
381 "{'prop1':'foo'}");
382 }
383
384 {
385 const char kFunctionSpec[] = "{ 'type': 'function' }";
386 ArgumentSpec spec(*ValueFromString(kFunctionSpec));
387 // Functions are serialized as empty dictionaries.
388 ExpectSuccess(spec, "(function() {})", "{}");
389 ExpectSuccessWithNoConversion(spec, "(function() {})");
390 ExpectSuccessWithNoConversion(spec, "(function(a, b) { a(); b(); })");
391 ExpectSuccessWithNoConversion(spec, "(function(a, b) { a(); b(); })");
392 ExpectFailureWithNoConversion(spec, "({a: function() {}})",
393 InvalidType(kTypeFunction, kTypeObject));
394 ExpectFailureWithNoConversion(spec, "([function() {}])",
395 InvalidType(kTypeFunction, kTypeList));
396 ExpectFailureWithNoConversion(spec, "1",
397 InvalidType(kTypeFunction, kTypeInteger));
398 }
399
400 {
401 const char kBinarySpec[] = "{ 'type': 'binary' }";
402 ArgumentSpec spec(*ValueFromString(kBinarySpec));
403 // Simple case: empty ArrayBuffer -> empty BinaryValue.
404 ExpectSuccess(spec, "(new ArrayBuffer())",
405 base::Value(base::Value::Type::BINARY));
406 {
407 // A non-empty (but zero-filled) ArrayBufferView.
408 const char kBuffer[] = {0, 0, 0, 0};
409 std::unique_ptr<base::Value> expected_value =
410 base::Value::CreateWithCopiedBuffer(kBuffer, base::size(kBuffer));
411 ASSERT_TRUE(expected_value);
412 ExpectSuccessWithNoConversion(spec, "(new Int32Array(2))");
413 }
414 {
415 // Actual data.
416 const char kBuffer[] = {'p', 'i', 'n', 'g'};
417 std::unique_ptr<base::Value> expected_value =
418 base::Value::CreateWithCopiedBuffer(kBuffer, base::size(kBuffer));
419 ASSERT_TRUE(expected_value);
420 ExpectSuccess(spec,
421 "var b = new ArrayBuffer(4);\n"
422 "var v = new Uint8Array(b);\n"
423 "var s = 'ping';\n"
424 "for (var i = 0; i < s.length; ++i)\n"
425 " v[i] = s.charCodeAt(i);\n"
426 "b;",
427 *expected_value);
428 }
429 ExpectFailure(spec, "1",
430 InvalidType(api_errors::kTypeBinary, kTypeInteger));
431 }
432 {
433 const char kAnySpec[] = "{ 'type': 'any' }";
434 ArgumentSpec spec(*ValueFromString(kAnySpec));
435 ExpectSuccess(spec, "42", "42");
436 ExpectSuccess(spec, "'foo'", "'foo'");
437 ExpectSuccess(spec, "({prop1:'bar'})", "{'prop1':'bar'}");
438 ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
439 ExpectSuccess(spec, "[1, 'a']", "[1,'a']");
440 ExpectSuccess(spec, "null", base::Value());
441 ExpectSuccess(spec, "({prop1: 'alpha', prop2: null})", "{'prop1':'alpha'}");
442 ExpectSuccess(spec,
443 "x = {alpha: 'alpha'};\n"
444 "y = {beta: 'beta', x: x};\n"
445 "y;",
446 "{'beta':'beta','x':{'alpha':'alpha'}}");
447 // We don't serialize undefined.
448 // TODO(devlin): This matches current behavior, but should it? Part of the
449 // problem is that base::Values don't differentiate between undefined and
450 // null, which is a potentially important distinction. However, this means
451 // that in serialization of an object {a: 1, foo:undefined}, we lose the
452 // 'foo' property.
453 ExpectFailure(spec, "undefined", api_errors::UnserializableValue());
454
455 ExpectSuccess(spec, "({prop1: 1, prop2: undefined})", "{'prop1':1}");
456 }
457 }
458
TEST_F(ArgumentSpecUnitTest,TypeRefsTest)459 TEST_F(ArgumentSpecUnitTest, TypeRefsTest) {
460 const char kObjectType[] =
461 "{"
462 " 'id': 'refObj',"
463 " 'type': 'object',"
464 " 'properties': {"
465 " 'prop1': {'type': 'string'},"
466 " 'prop2': {'type': 'integer', 'optional': true}"
467 " }"
468 "}";
469 const char kEnumType[] =
470 "{'id': 'refEnum', 'type': 'string', 'enum': ['alpha', 'beta']}";
471 AddTypeRef("refObj",
472 std::make_unique<ArgumentSpec>(*ValueFromString(kObjectType)));
473 AddTypeRef("refEnum",
474 std::make_unique<ArgumentSpec>(*ValueFromString(kEnumType)));
475 std::set<std::string> valid_enums = {"alpha", "beta"};
476
477 {
478 const char kObjectWithRefEnumSpec[] =
479 "{"
480 " 'name': 'objWithRefEnum',"
481 " 'type': 'object',"
482 " 'properties': {"
483 " 'e': {'$ref': 'refEnum'},"
484 " 'sub': {'type': 'integer'}"
485 " }"
486 "}";
487 ArgumentSpec spec(*ValueFromString(kObjectWithRefEnumSpec));
488 ExpectSuccess(spec, "({e: 'alpha', sub: 1})", "{'e':'alpha','sub':1}");
489 ExpectSuccess(spec, "({e: 'beta', sub: 1})", "{'e':'beta','sub':1}");
490 ExpectFailure(spec, "({e: 'gamma', sub: 1})",
491 api_errors::PropertyError(
492 "e", api_errors::InvalidEnumValue(valid_enums)));
493 ExpectFailure(spec, "({e: 'alpha'})", MissingRequiredProperty("sub"));
494 }
495
496 {
497 const char kObjectWithRefObjectSpec[] =
498 "{"
499 " 'name': 'objWithRefObject',"
500 " 'type': 'object',"
501 " 'properties': {"
502 " 'o': {'$ref': 'refObj'}"
503 " }"
504 "}";
505 ArgumentSpec spec(*ValueFromString(kObjectWithRefObjectSpec));
506 ExpectSuccess(spec, "({o: {prop1: 'foo'}})", "{'o':{'prop1':'foo'}}");
507 ExpectSuccess(spec, "({o: {prop1: 'foo', prop2: 2}})",
508 "{'o':{'prop1':'foo','prop2':2}}");
509 ExpectFailure(
510 spec, "({o: {prop1: 1}})",
511 api_errors::PropertyError(
512 "o", api_errors::PropertyError(
513 "prop1", InvalidType(kTypeString, kTypeInteger))));
514 }
515
516 {
517 const char kRefEnumListSpec[] =
518 "{'type': 'array', 'items': {'$ref': 'refEnum'}}";
519 ArgumentSpec spec(*ValueFromString(kRefEnumListSpec));
520 ExpectSuccess(spec, "['alpha']", "['alpha']");
521 ExpectSuccess(spec, "['alpha', 'alpha']", "['alpha','alpha']");
522 ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']");
523 ExpectFailure(spec, "['alpha', 'beta', 'gamma']",
524 IndexError(2u, api_errors::InvalidEnumValue(valid_enums)));
525 }
526 }
527
TEST_F(ArgumentSpecUnitTest,TypeChoicesTest)528 TEST_F(ArgumentSpecUnitTest, TypeChoicesTest) {
529 {
530 const char kSimpleChoices[] =
531 "{'choices': [{'type': 'string'}, {'type': 'integer'}]}";
532 ArgumentSpec spec(*ValueFromString(kSimpleChoices));
533 ExpectSuccess(spec, "'alpha'", "'alpha'");
534 ExpectSuccess(spec, "42", "42");
535 const char kChoicesType[] = "[string|integer]";
536 ExpectFailure(spec, "true", InvalidType(kChoicesType, kTypeBoolean));
537 }
538
539 {
540 const char kComplexChoices[] =
541 "{"
542 " 'choices': ["
543 " {'type': 'array', 'items': {'type': 'string'}},"
544 " {'type': 'object', 'properties': {'prop1': {'type': 'string'}}}"
545 " ]"
546 "}";
547 ArgumentSpec spec(*ValueFromString(kComplexChoices));
548 ExpectSuccess(spec, "['alpha']", "['alpha']");
549 ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']");
550 ExpectSuccess(spec, "({prop1: 'alpha'})", "{'prop1':'alpha'}");
551
552 const char kChoicesType[] = "[array|object]";
553 ExpectFailure(spec, "({prop1: 1})", api_errors::InvalidChoice());
554 ExpectFailure(spec, "'alpha'", InvalidType(kChoicesType, kTypeString));
555 ExpectFailure(spec, "42", InvalidType(kChoicesType, kTypeInteger));
556 }
557 }
558
TEST_F(ArgumentSpecUnitTest,AdditionalPropertiesTest)559 TEST_F(ArgumentSpecUnitTest, AdditionalPropertiesTest) {
560 {
561 const char kOnlyAnyAdditionalProperties[] =
562 "{"
563 " 'type': 'object',"
564 " 'additionalProperties': {'type': 'any'}"
565 "}";
566 ArgumentSpec spec(*ValueFromString(kOnlyAnyAdditionalProperties));
567 ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})",
568 "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}");
569 ExpectSuccess(spec, "({})", "{}");
570 // Test some crazy keys.
571 ExpectSuccess(spec,
572 "var x = {};\n"
573 "var y = {prop1: 'alpha'};\n"
574 "y[42] = 'beta';\n"
575 "y[x] = 'gamma';\n"
576 "y[undefined] = 'delta';\n"
577 "y;",
578 "{'42':'beta','[object Object]':'gamma','prop1':'alpha',"
579 "'undefined':'delta'}");
580 // We (typically*, see "Fun case" below) don't serialize properties on an
581 // object prototype.
582 ExpectSuccess(spec,
583 "({\n"
584 " __proto__: {protoProp: 'proto'},\n"
585 " instanceProp: 'instance'\n"
586 "})",
587 "{'instanceProp':'instance'}");
588 // Fun case: Remove a property as a result of getting another. Currently,
589 // we don't check each property with HasOwnProperty() during iteration, so
590 // Fun case: Remove a property as a result of getting another. Currently,
591 // we don't check each property with HasOwnProperty() during iteration, so
592 // we still try to serialize it. But we don't serialize undefined, so in the
593 // case of the property not being defined on the prototype, this works as
594 // expected.
595 ExpectSuccess(spec,
596 "var x = {};\n"
597 "Object.defineProperty(\n"
598 " x, 'alpha',\n"
599 " {\n"
600 " enumerable: true,\n"
601 " get: () => { delete x.omega; return 'alpha'; }\n"
602 " });\n"
603 "x.omega = 'omega';\n"
604 "x;",
605 "{'alpha':'alpha'}");
606 // Fun case continued: If an object removes the property, and the property
607 // *is* present on the prototype, then we serialize the value from the
608 // prototype. This is inconsistent, but only manifests scripts are doing
609 // crazy things (and is still safe).
610 // TODO(devlin): We *could* add a HasOwnProperty() check, in which case
611 // the result of this call should be {'alpha':'alpha'}.
612 ExpectSuccess(spec,
613 "var x = {\n"
614 " __proto__: { omega: 'different omega' }\n"
615 "};\n"
616 "Object.defineProperty(\n"
617 " x, 'alpha',\n"
618 " {\n"
619 " enumerable: true,\n"
620 " get: () => { delete x.omega; return 'alpha'; }\n"
621 " });\n"
622 "x.omega = 'omega';\n"
623 "x;",
624 "{'alpha':'alpha','omega':'different omega'}");
625 }
626 {
627 const char kPropertiesAndAnyAdditionalProperties[] =
628 "{"
629 " 'type': 'object',"
630 " 'properties': {"
631 " 'prop1': {'type': 'string'}"
632 " },"
633 " 'additionalProperties': {'type': 'any'}"
634 "}";
635 ArgumentSpec spec(*ValueFromString(kPropertiesAndAnyAdditionalProperties));
636 ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})",
637 "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}");
638 // Additional properties are optional.
639 ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
640 ExpectFailure(spec, "({prop2: 42, prop3: {foo: 'bar'}})",
641 MissingRequiredProperty("prop1"));
642 ExpectFailure(spec, "({prop1: 42})",
643 api_errors::PropertyError(
644 "prop1", InvalidType(kTypeString, kTypeInteger)));
645 }
646 {
647 const char kTypedAdditionalProperties[] =
648 "{"
649 " 'type': 'object',"
650 " 'additionalProperties': {'type': 'string'}"
651 "}";
652 ArgumentSpec spec(*ValueFromString(kTypedAdditionalProperties));
653 ExpectSuccess(spec, "({prop1: 'alpha', prop2: 'beta', prop3: 'gamma'})",
654 "{'prop1':'alpha','prop2':'beta','prop3':'gamma'}");
655 ExpectFailure(spec, "({prop1: 'alpha', prop2: 42})",
656 api_errors::PropertyError(
657 "prop2", InvalidType(kTypeString, kTypeInteger)));
658 }
659 }
660
TEST_F(ArgumentSpecUnitTest,InstanceOfTest)661 TEST_F(ArgumentSpecUnitTest, InstanceOfTest) {
662 {
663 const char kInstanceOfRegExp[] =
664 "{"
665 " 'type': 'object',"
666 " 'isInstanceOf': 'RegExp'"
667 "}";
668 ArgumentSpec spec(*ValueFromString(kInstanceOfRegExp));
669 ExpectSuccess(spec, "(new RegExp())", "{}");
670 ExpectSuccess(spec, "({ __proto__: RegExp.prototype })", "{}");
671 ExpectSuccess(spec,
672 "(function() {\n"
673 " function subRegExp() {}\n"
674 " subRegExp.prototype = { __proto__: RegExp.prototype };\n"
675 " return new subRegExp();\n"
676 "})()",
677 "{}");
678 ExpectSuccess(spec,
679 "(function() {\n"
680 " function RegExp() {}\n"
681 " return new RegExp();\n"
682 "})()",
683 "{}");
684 ExpectFailure(spec, "({})", api_errors::NotAnInstance("RegExp"));
685 ExpectFailure(spec, "('')", InvalidType("RegExp", kTypeString));
686 ExpectFailure(spec, "('.*')", InvalidType("RegExp", kTypeString));
687 ExpectFailure(spec, "({ __proto__: Date.prototype })",
688 api_errors::NotAnInstance("RegExp"));
689 }
690
691 {
692 const char kInstanceOfCustomClass[] =
693 "{"
694 " 'type': 'object',"
695 " 'isInstanceOf': 'customClass'"
696 "}";
697 ArgumentSpec spec(*ValueFromString(kInstanceOfCustomClass));
698 ExpectSuccess(spec,
699 "(function() {\n"
700 " function customClass() {}\n"
701 " return new customClass();\n"
702 "})()",
703 "{}");
704 ExpectSuccess(spec,
705 "(function() {\n"
706 " function customClass() {}\n"
707 " function otherClass() {}\n"
708 " otherClass.prototype = \n"
709 " { __proto__: customClass.prototype };\n"
710 " return new otherClass();\n"
711 "})()",
712 "{}");
713 ExpectFailure(spec, "({})", api_errors::NotAnInstance("customClass"));
714 ExpectFailure(spec,
715 "(function() {\n"
716 " function otherClass() {}\n"
717 " return new otherClass();\n"
718 "})()",
719 api_errors::NotAnInstance("customClass"));
720 }
721 }
722
TEST_F(ArgumentSpecUnitTest,MinAndMaxLengths)723 TEST_F(ArgumentSpecUnitTest, MinAndMaxLengths) {
724 {
725 const char kMinLengthString[] = "{'type': 'string', 'minLength': 3}";
726 ArgumentSpec spec(*ValueFromString(kMinLengthString));
727 ExpectSuccess(spec, "'aaa'", "'aaa'");
728 ExpectSuccess(spec, "'aaaa'", "'aaaa'");
729 ExpectFailure(spec, "'aa'", api_errors::TooFewStringChars(3, 2));
730 ExpectFailure(spec, "''", api_errors::TooFewStringChars(3, 0));
731 }
732
733 {
734 const char kMaxLengthString[] = "{'type': 'string', 'maxLength': 3}";
735 ArgumentSpec spec(*ValueFromString(kMaxLengthString));
736 ExpectSuccess(spec, "'aaa'", "'aaa'");
737 ExpectSuccess(spec, "'aa'", "'aa'");
738 ExpectSuccess(spec, "''", "''");
739 ExpectFailure(spec, "'aaaa'", api_errors::TooManyStringChars(3, 4));
740 }
741
742 {
743 const char kMinLengthArray[] =
744 "{'type': 'array', 'items': {'type': 'integer'}, 'minItems': 3}";
745 ArgumentSpec spec(*ValueFromString(kMinLengthArray));
746 ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
747 ExpectSuccess(spec, "[1, 2, 3, 4]", "[1,2,3,4]");
748 ExpectFailure(spec, "[1, 2]", api_errors::TooFewArrayItems(3, 2));
749 ExpectFailure(spec, "[]", api_errors::TooFewArrayItems(3, 0));
750 }
751
752 {
753 const char kMaxLengthArray[] =
754 "{'type': 'array', 'items': {'type': 'integer'}, 'maxItems': 3}";
755 ArgumentSpec spec(*ValueFromString(kMaxLengthArray));
756 ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
757 ExpectSuccess(spec, "[1, 2]", "[1,2]");
758 ExpectSuccess(spec, "[]", "[]");
759 ExpectFailure(spec, "[1, 2, 3, 4]", api_errors::TooManyArrayItems(3, 4));
760 }
761 }
762
TEST_F(ArgumentSpecUnitTest,PreserveNull)763 TEST_F(ArgumentSpecUnitTest, PreserveNull) {
764 {
765 const char kObjectSpec[] =
766 "{"
767 " 'type': 'object',"
768 " 'additionalProperties': {'type': 'any'},"
769 " 'preserveNull': true"
770 "}";
771 ArgumentSpec spec(*ValueFromString(kObjectSpec));
772 ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}");
773 // Subproperties shouldn't preserve null (if not specified).
774 ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})",
775 "{'prop':{'subprop1':'foo'}}");
776 }
777
778 {
779 const char kObjectSpec[] =
780 "{"
781 " 'type': 'object',"
782 " 'additionalProperties': {'type': 'any', 'preserveNull': true},"
783 " 'preserveNull': true"
784 "}";
785 ArgumentSpec spec(*ValueFromString(kObjectSpec));
786 ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}");
787 // Here, subproperties should preserve null.
788 ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})",
789 "{'prop':{'subprop1':'foo','subprop2':null}}");
790 }
791
792 {
793 const char kObjectSpec[] =
794 "{"
795 " 'type': 'object',"
796 " 'properties': {'prop1': {'type': 'string', 'optional': true}},"
797 " 'preserveNull': true"
798 "}";
799 ArgumentSpec spec(*ValueFromString(kObjectSpec));
800 ExpectSuccess(spec, "({})", "{}");
801 ExpectSuccess(spec, "({prop1: null})", "{'prop1':null}");
802 ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
803 // Undefined should not be preserved.
804 ExpectSuccess(spec, "({prop1: undefined})", "{}");
805 // preserveNull shouldn't affect normal parsing restrictions.
806 ExpectFailure(spec, "({prop1: 1})",
807 api_errors::PropertyError(
808 "prop1", InvalidType(kTypeString, kTypeInteger)));
809 }
810 }
811
TEST_F(ArgumentSpecUnitTest,NaNFun)812 TEST_F(ArgumentSpecUnitTest, NaNFun) {
813 {
814 const char kAnySpec[] = "{'type': 'any'}";
815 ArgumentSpec spec(*ValueFromString(kAnySpec));
816 ExpectFailure(spec, "NaN", api_errors::UnserializableValue());
817 }
818
819 {
820 const char kObjectWithAnyPropertiesSpec[] =
821 "{'type': 'object', 'additionalProperties': {'type': 'any'}}";
822 ArgumentSpec spec(*ValueFromString(kObjectWithAnyPropertiesSpec));
823 ExpectSuccess(spec, "({foo: NaN, bar: 'baz'})", "{'bar':'baz'}");
824 }
825 }
826
TEST_F(ArgumentSpecUnitTest,GetTypeName)827 TEST_F(ArgumentSpecUnitTest, GetTypeName) {
828 struct {
829 ArgumentType type;
830 const char* expected_type_name;
831 } simple_cases[] = {
832 {ArgumentType::BOOLEAN, kTypeBoolean},
833 {ArgumentType::INTEGER, kTypeInteger},
834 {ArgumentType::OBJECT, kTypeObject},
835 {ArgumentType::LIST, kTypeList},
836 {ArgumentType::BINARY, api_errors::kTypeBinary},
837 {ArgumentType::FUNCTION, kTypeFunction},
838 {ArgumentType::ANY, api_errors::kTypeAny},
839 };
840
841 for (const auto& test_case : simple_cases) {
842 ArgumentSpec spec(test_case.type);
843 EXPECT_EQ(test_case.expected_type_name, spec.GetTypeName());
844 }
845
846 {
847 const char kRefName[] = "someRef";
848 ArgumentSpec ref_spec(ArgumentType::REF);
849 ref_spec.set_ref(kRefName);
850 EXPECT_EQ(kRefName, ref_spec.GetTypeName());
851 }
852
853 {
854 std::vector<std::unique_ptr<ArgumentSpec>> choices;
855 choices.push_back(std::make_unique<ArgumentSpec>(ArgumentType::INTEGER));
856 choices.push_back(std::make_unique<ArgumentSpec>(ArgumentType::STRING));
857 ArgumentSpec choices_spec(ArgumentType::CHOICES);
858 choices_spec.set_choices(std::move(choices));
859 EXPECT_EQ("[integer|string]", choices_spec.GetTypeName());
860 }
861 }
862
TEST_F(ArgumentSpecUnitTest,V8Conversion)863 TEST_F(ArgumentSpecUnitTest, V8Conversion) {
864 {
865 // Sanity check: a simple conversion.
866 ArgumentSpec spec(ArgumentType::INTEGER);
867 ExpectSuccess(spec, "1", base::BindOnce([](v8::Local<v8::Value> value) {
868 ASSERT_TRUE(value->IsInt32());
869 EXPECT_EQ(1, value.As<v8::Int32>()->Value());
870 }));
871 // The conversion should handle the -0 value (which is considered an
872 // integer but stored in v8 has a number) by converting it to a 0 integer.
873 ExpectSuccess(spec, "-0", base::BindOnce([](v8::Local<v8::Value> value) {
874 ASSERT_TRUE(value->IsInt32());
875 EXPECT_EQ(0, value.As<v8::Int32>()->Value());
876 }));
877 }
878
879 {
880 std::unique_ptr<ArgumentSpec> prop =
881 ArgumentSpecBuilder(ArgumentType::STRING, "str").Build();
882 std::unique_ptr<ArgumentSpec> spec =
883 ArgumentSpecBuilder(ArgumentType::OBJECT, "obj")
884 .AddProperty("str", std::move(prop))
885 .Build();
886 // The conversion to a v8 value should handle tricky cases like this, where
887 // a subtle getter would invalidate expectations if the value was passed
888 // directly. Since the conversion creates a new object (free of any sneaky
889 // getters), we don't need to repeatedly check for types.
890 constexpr char kTrickyInterceptors[] = R"(
891 ({
892 get str() {
893 if (this.checkedOnce)
894 return 42;
895 this.checkedOnce = true;
896 return 'a string';
897 }
898 }))";
899 ExpectSuccess(*spec, kTrickyInterceptors,
900 base::BindOnce([](v8::Local<v8::Value> value) {
901 ASSERT_TRUE(value->IsObject());
902 v8::Local<v8::Object> object = value.As<v8::Object>();
903 v8::Local<v8::Context> context = object->CreationContext();
904 gin::Dictionary dict(context->GetIsolate(), object);
905 std::string result;
906 ASSERT_TRUE(dict.Get("str", &result));
907 EXPECT_EQ("a string", result);
908 ASSERT_TRUE(dict.Get("str", &result));
909 // The value should remain constant (even though there's a
910 // subtle getter).
911 EXPECT_EQ("a string", result);
912 }));
913 }
914
915 {
916 std::unique_ptr<ArgumentSpec> prop =
917 ArgumentSpecBuilder(ArgumentType::STRING, "str").MakeOptional().Build();
918 std::unique_ptr<ArgumentSpec> spec =
919 ArgumentSpecBuilder(ArgumentType::OBJECT, "obj")
920 .AddProperty("str", std::move(prop))
921 .Build();
922 // The conversion to a v8 value should also protect set an undefined value
923 // on the result value for any absent optional properties. This protects
924 // against cases where an Object.prototype getter would be invoked when a
925 // handler tried to check the value of an argument.
926 constexpr const char kMessWithObjectPrototype[] =
927 R"((function() {
928 Object.defineProperty(
929 Object.prototype, 'str',
930 { get() { throw new Error('tricked!'); } });
931 }))";
932 v8::HandleScope handle_scope(instance_->isolate());
933 v8::Local<v8::Context> context =
934 v8::Local<v8::Context>::New(instance_->isolate(), context_);
935 v8::Local<v8::Function> mess_with_proto =
936 FunctionFromString(context, kMessWithObjectPrototype);
937 RunFunction(mess_with_proto, context, 0, nullptr);
938 ExpectSuccess(*spec, "({})", base::BindOnce([](v8::Local<v8::Value> value) {
939 ASSERT_TRUE(value->IsObject());
940 v8::Local<v8::Object> object = value.As<v8::Object>();
941 v8::Local<v8::Context> context = object->CreationContext();
942 // We expect a null prototype to ensure we avoid tricky getters/setters on
943 // the Object prototype.
944 EXPECT_TRUE(object->GetPrototype()->IsNull());
945 gin::Dictionary dict(context->GetIsolate(), object);
946 v8::Local<v8::Value> result;
947 ASSERT_TRUE(dict.Get("str", &result));
948 EXPECT_TRUE(result->IsUndefined());
949 }));
950 }
951
952 {
953 std::unique_ptr<ArgumentSpec> prop =
954 ArgumentSpecBuilder(ArgumentType::STRING, "prop")
955 .MakeOptional()
956 .Build();
957 std::unique_ptr<ArgumentSpec> preserve_null_spec =
958 ArgumentSpecBuilder(ArgumentType::OBJECT, "spec")
959 .AddProperty("prop", std::move(prop))
960 .PreserveNull()
961 .Build();
962 ExpectSuccess(
963 *preserve_null_spec, "({prop: null})",
964 base::BindOnce([](v8::Local<v8::Value> value) {
965 ASSERT_TRUE(value->IsObject());
966 v8::Local<v8::Object> object = value.As<v8::Object>();
967 v8::Local<v8::Context> context = object->CreationContext();
968 v8::Local<v8::Value> prop =
969 object
970 ->Get(context, gin::StringToV8(context->GetIsolate(), "prop"))
971 .ToLocalChecked();
972 EXPECT_TRUE(prop->IsNull());
973 }));
974 }
975 }
976
977 // Certain argument types (any, binary, and function) will be passed through
978 // directly to the v8 arguments because we won't be able to parse them properly.
TEST_F(ArgumentSpecUnitTest,TestV8ValuePassedThrough)979 TEST_F(ArgumentSpecUnitTest, TestV8ValuePassedThrough) {
980 v8::Isolate* isolate = instance_->isolate();
981 v8::HandleScope handle_scope(instance_->isolate());
982
983 v8::Local<v8::Context> context =
984 v8::Local<v8::Context>::New(instance_->isolate(), context_);
985 v8::TryCatch try_catch(isolate);
986
987 auto test_is_same_value = [this, context](const ArgumentSpec& spec,
988 const char* value_str) {
989 SCOPED_TRACE(value_str);
990 v8::Local<v8::Value> value_in = V8ValueFromScriptSource(context, value_str);
991 v8::Local<v8::Value> value_out;
992 std::string error;
993 ASSERT_TRUE(spec.ParseArgument(context, value_in, type_refs(), nullptr,
994 &value_out, &error));
995 ASSERT_FALSE(value_out.IsEmpty());
996 EXPECT_EQ(value_in, value_out);
997 };
998
999 // Functions are passed directly because we reply directly to the passed-in
1000 // function.
1001 test_is_same_value(ArgumentSpec(ArgumentType::FUNCTION), "(function() {})");
1002 // 'Any' and 'Binary' arguments are passed directly because we can't know if
1003 // we can copy them safely, accurately, and efficiently.
1004 test_is_same_value(ArgumentSpec(ArgumentType::ANY), "({foo: 'bar'})");
1005 test_is_same_value(ArgumentSpec(ArgumentType::BINARY), "(new ArrayBuffer())");
1006 // See comments in ArgumentSpec::ParseArgumentToObject() for why these are
1007 // passed directly.
1008 {
1009 std::unique_ptr<ArgumentSpec> instance_of_spec =
1010 ArgumentSpecBuilder(ArgumentType::OBJECT, "instance_of")
1011 .SetInstanceOf("RegExp")
1012 .Build();
1013 test_is_same_value(*instance_of_spec, "(new RegExp('hi'))");
1014 }
1015 {
1016 std::unique_ptr<ArgumentSpec> additional_properties_spec =
1017 ArgumentSpecBuilder(ArgumentType::OBJECT, "additional props")
1018 .SetAdditionalProperties(
1019 std::make_unique<ArgumentSpec>(ArgumentType::ANY))
1020 .Build();
1021 test_is_same_value(*additional_properties_spec, "({foo: 'bar'})");
1022 }
1023 }
1024
1025 } // namespace extensions
1026