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
7 #include "base/strings/string_piece.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/values.h"
11 #include "content/public/renderer/v8_value_converter.h"
12 #include "extensions/renderer/bindings/api_invocation_errors.h"
13 #include "extensions/renderer/bindings/api_type_reference_map.h"
14 #include "gin/converter.h"
15 #include "gin/data_object_builder.h"
16 #include "gin/dictionary.h"
17
18 namespace extensions {
19
20 namespace {
21
22 // Returns a type string for the given |value|.
GetV8ValueTypeString(v8::Local<v8::Value> value)23 const char* GetV8ValueTypeString(v8::Local<v8::Value> value) {
24 DCHECK(!value.IsEmpty());
25
26 if (value->IsNull())
27 return api_errors::kTypeNull;
28 if (value->IsUndefined())
29 return api_errors::kTypeUndefined;
30 if (value->IsInt32())
31 return api_errors::kTypeInteger;
32 if (value->IsNumber())
33 return api_errors::kTypeDouble;
34 if (value->IsBoolean())
35 return api_errors::kTypeBoolean;
36 if (value->IsString())
37 return api_errors::kTypeString;
38
39 // Note: check IsArray(), IsFunction(), and IsArrayBuffer[View]() before
40 // IsObject() since arrays, functions, and array buffers are objects.
41 if (value->IsArray())
42 return api_errors::kTypeList;
43 if (value->IsFunction())
44 return api_errors::kTypeFunction;
45 if (value->IsArrayBuffer() || value->IsArrayBufferView())
46 return api_errors::kTypeBinary;
47 if (value->IsObject())
48 return api_errors::kTypeObject;
49
50 // TODO(devlin): The list above isn't exhaustive (it's missing at least
51 // Symbol and Uint32). We may want to include those, since saying
52 // "expected int, found other" isn't super helpful. On the other hand, authors
53 // should be able to see what they passed.
54 return "other";
55 }
56
57 // Returns true if |value| is within the bounds specified by |minimum| and
58 // |maximum|, populating |error| otherwise.
59 template <class T>
CheckFundamentalBounds(T value,const base::Optional<int> & minimum,const base::Optional<int> & maximum,std::string * error)60 bool CheckFundamentalBounds(T value,
61 const base::Optional<int>& minimum,
62 const base::Optional<int>& maximum,
63 std::string* error) {
64 if (minimum && value < *minimum) {
65 *error = api_errors::NumberTooSmall(*minimum);
66 return false;
67 }
68 if (maximum && value > *maximum) {
69 *error = api_errors::NumberTooLarge(*maximum);
70 return false;
71 }
72 return true;
73 }
74
75 } // namespace
76
ArgumentSpec(const base::Value & value)77 ArgumentSpec::ArgumentSpec(const base::Value& value) {
78 const base::DictionaryValue* dict = nullptr;
79 CHECK(value.GetAsDictionary(&dict));
80 dict->GetBoolean("optional", &optional_);
81 dict->GetString("name", &name_);
82
83 InitializeType(dict);
84 }
85
ArgumentSpec(ArgumentType type)86 ArgumentSpec::ArgumentSpec(ArgumentType type) : type_(type) {}
87
InitializeType(const base::DictionaryValue * dict)88 void ArgumentSpec::InitializeType(const base::DictionaryValue* dict) {
89 std::string ref_string;
90 if (dict->GetString("$ref", &ref_string)) {
91 ref_ = std::move(ref_string);
92 type_ = ArgumentType::REF;
93 return;
94 }
95
96 {
97 const base::ListValue* choices = nullptr;
98 if (dict->GetList("choices", &choices)) {
99 DCHECK(!choices->empty());
100 type_ = ArgumentType::CHOICES;
101 choices_.reserve(choices->GetSize());
102 for (const auto& choice : *choices)
103 choices_.push_back(std::make_unique<ArgumentSpec>(choice));
104 return;
105 }
106 }
107
108 std::string type_string;
109 CHECK(dict->GetString("type", &type_string));
110 if (type_string == "integer")
111 type_ = ArgumentType::INTEGER;
112 else if (type_string == "number")
113 type_ = ArgumentType::DOUBLE;
114 else if (type_string == "object")
115 type_ = ArgumentType::OBJECT;
116 else if (type_string == "array")
117 type_ = ArgumentType::LIST;
118 else if (type_string == "boolean")
119 type_ = ArgumentType::BOOLEAN;
120 else if (type_string == "string")
121 type_ = ArgumentType::STRING;
122 else if (type_string == "binary")
123 type_ = ArgumentType::BINARY;
124 else if (type_string == "any")
125 type_ = ArgumentType::ANY;
126 else if (type_string == "function")
127 type_ = ArgumentType::FUNCTION;
128 else
129 NOTREACHED();
130
131 int min = 0;
132 if (dict->GetInteger("minimum", &min))
133 minimum_ = min;
134
135 int max = 0;
136 if (dict->GetInteger("maximum", &max))
137 maximum_ = max;
138
139 int min_length = 0;
140 if (dict->GetInteger("minLength", &min_length) ||
141 dict->GetInteger("minItems", &min_length)) {
142 DCHECK_GE(min_length, 0);
143 min_length_ = min_length;
144 }
145
146 int max_length = 0;
147 if (dict->GetInteger("maxLength", &max_length) ||
148 dict->GetInteger("maxItems", &max_length)) {
149 DCHECK_GE(max_length, 0);
150 max_length_ = max_length;
151 }
152
153 if (type_ == ArgumentType::OBJECT) {
154 const base::DictionaryValue* properties_value = nullptr;
155 if (dict->GetDictionary("properties", &properties_value)) {
156 for (base::DictionaryValue::Iterator iter(*properties_value);
157 !iter.IsAtEnd(); iter.Advance()) {
158 properties_[iter.key()] = std::make_unique<ArgumentSpec>(iter.value());
159 }
160 }
161 const base::DictionaryValue* additional_properties_value = nullptr;
162 if (dict->GetDictionary("additionalProperties",
163 &additional_properties_value)) {
164 additional_properties_ =
165 std::make_unique<ArgumentSpec>(*additional_properties_value);
166 // Additional properties are always optional.
167 additional_properties_->optional_ = true;
168 }
169 } else if (type_ == ArgumentType::LIST) {
170 const base::DictionaryValue* item_value = nullptr;
171 CHECK(dict->GetDictionary("items", &item_value));
172 list_element_type_ = std::make_unique<ArgumentSpec>(*item_value);
173 } else if (type_ == ArgumentType::STRING) {
174 // Technically, there's no reason enums couldn't be other objects (e.g.
175 // numbers), but right now they seem to be exclusively strings. We could
176 // always update this if need be.
177 const base::ListValue* enums = nullptr;
178 if (dict->GetList("enum", &enums)) {
179 size_t size = enums->GetSize();
180 CHECK_GT(size, 0u);
181 for (size_t i = 0; i < size; ++i) {
182 std::string enum_value;
183 // Enum entries come in two versions: a list of possible strings, and
184 // a dictionary with a field 'name'.
185 if (!enums->GetString(i, &enum_value)) {
186 const base::DictionaryValue* enum_value_dictionary = nullptr;
187 CHECK(enums->GetDictionary(i, &enum_value_dictionary));
188 CHECK(enum_value_dictionary->GetString("name", &enum_value));
189 }
190 enum_values_.insert(std::move(enum_value));
191 }
192 }
193 }
194
195 // Check if we should preserve null in objects. Right now, this is only used
196 // on arguments of type object and any (in fact, it's only used in the storage
197 // API), but it could potentially make sense for lists or functions as well.
198 if (type_ == ArgumentType::OBJECT || type_ == ArgumentType::ANY)
199 dict->GetBoolean("preserveNull", &preserve_null_);
200
201 if (type_ == ArgumentType::OBJECT || type_ == ArgumentType::BINARY) {
202 std::string instance_of;
203 if (dict->GetString("isInstanceOf", &instance_of))
204 instance_of_ = instance_of;
205 }
206 }
207
~ArgumentSpec()208 ArgumentSpec::~ArgumentSpec() {}
209
IsCorrectType(v8::Local<v8::Value> value,const APITypeReferenceMap & refs,std::string * error) const210 bool ArgumentSpec::IsCorrectType(v8::Local<v8::Value> value,
211 const APITypeReferenceMap& refs,
212 std::string* error) const {
213 bool is_valid_type = false;
214
215 switch (type_) {
216 case ArgumentType::INTEGER:
217 // -0 is treated internally as a double, but we classify it as an integer.
218 is_valid_type =
219 value->IsInt32() ||
220 (value->IsNumber() && value.As<v8::Number>()->Value() == 0.0);
221 break;
222 case ArgumentType::DOUBLE:
223 is_valid_type = value->IsNumber();
224 break;
225 case ArgumentType::BOOLEAN:
226 is_valid_type = value->IsBoolean();
227 break;
228 case ArgumentType::STRING:
229 is_valid_type = value->IsString();
230 break;
231 case ArgumentType::OBJECT:
232 // Don't allow functions or arrays (even though they are technically
233 // objects). This is to make it easier to match otherwise-ambiguous
234 // signatures. For instance, if an API method has an optional object
235 // parameter and then an optional callback, we wouldn't necessarily be
236 // able to match the arguments if we allowed functions as objects.
237 // TODO(devlin): What about other subclasses of Object, like Map and Set?
238 is_valid_type =
239 value->IsObject() && !value->IsFunction() && !value->IsArray();
240 break;
241 case ArgumentType::LIST:
242 is_valid_type = value->IsArray();
243 break;
244 case ArgumentType::BINARY:
245 is_valid_type = value->IsArrayBuffer() || value->IsArrayBufferView();
246 break;
247 case ArgumentType::FUNCTION:
248 is_valid_type = value->IsFunction();
249 break;
250 case ArgumentType::ANY:
251 is_valid_type = true;
252 break;
253 case ArgumentType::REF: {
254 DCHECK(ref_);
255 const ArgumentSpec* reference = refs.GetSpec(ref_.value());
256 DCHECK(reference) << ref_.value();
257 is_valid_type = reference->IsCorrectType(value, refs, error);
258 break;
259 }
260 case ArgumentType::CHOICES:
261 for (const auto& choice : choices_) {
262 if (choice->IsCorrectType(value, refs, error)) {
263 is_valid_type = true;
264 break;
265 }
266 }
267 break;
268 }
269
270 if (!is_valid_type)
271 *error = GetInvalidTypeError(value);
272 return is_valid_type;
273 }
274
ParseArgument(v8::Local<v8::Context> context,v8::Local<v8::Value> value,const APITypeReferenceMap & refs,std::unique_ptr<base::Value> * out_value,v8::Local<v8::Value> * v8_out_value,std::string * error) const275 bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context,
276 v8::Local<v8::Value> value,
277 const APITypeReferenceMap& refs,
278 std::unique_ptr<base::Value>* out_value,
279 v8::Local<v8::Value>* v8_out_value,
280 std::string* error) const {
281 // Note: for top-level arguments (i.e., those passed directly to the function,
282 // as opposed to a property on an object, or the item of an array), we will
283 // have already checked the type. Doing so again should be nearly free, but
284 // if we do find this to be an issue, we could avoid the second call.
285 if (!IsCorrectType(value, refs, error))
286 return false;
287
288 switch (type_) {
289 case ArgumentType::INTEGER:
290 case ArgumentType::DOUBLE:
291 case ArgumentType::BOOLEAN:
292 case ArgumentType::STRING:
293 return ParseArgumentToFundamental(context, value, out_value, v8_out_value,
294 error);
295 case ArgumentType::OBJECT:
296 return ParseArgumentToObject(context, value.As<v8::Object>(), refs,
297 out_value, v8_out_value, error);
298 case ArgumentType::LIST:
299 return ParseArgumentToArray(context, value.As<v8::Array>(), refs,
300 out_value, v8_out_value, error);
301 case ArgumentType::BINARY:
302 return ParseArgumentToAny(context, value, out_value, v8_out_value, error);
303 case ArgumentType::FUNCTION:
304 if (out_value) {
305 // Certain APIs (contextMenus) have functions as parameters other than
306 // the callback (contextMenus uses it for an onclick listener). Our
307 // generated types have adapted to consider functions "objects" and
308 // serialize them as dictionaries.
309 // TODO(devlin): It'd be awfully nice to get rid of this eccentricity.
310 *out_value = std::make_unique<base::DictionaryValue>();
311 }
312
313 if (v8_out_value)
314 *v8_out_value = value;
315
316 return true;
317 case ArgumentType::REF: {
318 DCHECK(ref_);
319 const ArgumentSpec* reference = refs.GetSpec(ref_.value());
320 DCHECK(reference) << ref_.value();
321 return reference->ParseArgument(context, value, refs, out_value,
322 v8_out_value, error);
323 }
324 case ArgumentType::CHOICES: {
325 for (const auto& choice : choices_) {
326 if (choice->ParseArgument(context, value, refs, out_value, v8_out_value,
327 error)) {
328 return true;
329 }
330 }
331 *error = api_errors::InvalidChoice();
332 return false;
333 }
334 case ArgumentType::ANY:
335 return ParseArgumentToAny(context, value, out_value, v8_out_value, error);
336 }
337
338 NOTREACHED();
339 return false;
340 }
341
GetTypeName() const342 const std::string& ArgumentSpec::GetTypeName() const {
343 if (!type_name_.empty())
344 return type_name_;
345
346 switch (type_) {
347 case ArgumentType::INTEGER:
348 type_name_ = api_errors::kTypeInteger;
349 break;
350 case ArgumentType::DOUBLE:
351 type_name_ = api_errors::kTypeDouble;
352 break;
353 case ArgumentType::BOOLEAN:
354 type_name_ = api_errors::kTypeBoolean;
355 break;
356 case ArgumentType::STRING:
357 type_name_ = api_errors::kTypeString;
358 break;
359 case ArgumentType::OBJECT:
360 type_name_ = instance_of_ ? *instance_of_ : api_errors::kTypeObject;
361 break;
362 case ArgumentType::LIST:
363 type_name_ = api_errors::kTypeList;
364 break;
365 case ArgumentType::BINARY:
366 type_name_ = api_errors::kTypeBinary;
367 break;
368 case ArgumentType::FUNCTION:
369 type_name_ = api_errors::kTypeFunction;
370 break;
371 case ArgumentType::REF:
372 type_name_ = ref_->c_str();
373 break;
374 case ArgumentType::CHOICES: {
375 std::vector<base::StringPiece> choices_strings;
376 choices_strings.reserve(choices_.size());
377 for (const auto& choice : choices_)
378 choices_strings.push_back(choice->GetTypeName());
379 type_name_ = base::StringPrintf(
380 "[%s]", base::JoinString(choices_strings, "|").c_str());
381 break;
382 }
383 case ArgumentType::ANY:
384 type_name_ = api_errors::kTypeAny;
385 break;
386 }
387 DCHECK(!type_name_.empty());
388 return type_name_;
389 }
390
ParseArgumentToFundamental(v8::Local<v8::Context> context,v8::Local<v8::Value> value,std::unique_ptr<base::Value> * out_value,v8::Local<v8::Value> * v8_out_value,std::string * error) const391 bool ArgumentSpec::ParseArgumentToFundamental(
392 v8::Local<v8::Context> context,
393 v8::Local<v8::Value> value,
394 std::unique_ptr<base::Value>* out_value,
395 v8::Local<v8::Value>* v8_out_value,
396 std::string* error) const {
397 switch (type_) {
398 case ArgumentType::INTEGER: {
399 DCHECK(value->IsNumber());
400 int int_val = 0;
401 if (value->IsInt32()) {
402 int_val = value.As<v8::Int32>()->Value();
403 } else {
404 // See comment in IsCorrectType().
405 DCHECK_EQ(0.0, value.As<v8::Number>()->Value());
406 int_val = 0;
407 }
408 if (!CheckFundamentalBounds(int_val, minimum_, maximum_, error))
409 return false;
410 if (out_value)
411 *out_value = std::make_unique<base::Value>(int_val);
412 if (v8_out_value)
413 *v8_out_value = v8::Integer::New(context->GetIsolate(), int_val);
414 return true;
415 }
416 case ArgumentType::DOUBLE: {
417 DCHECK(value->IsNumber());
418 double double_val = value.As<v8::Number>()->Value();
419 if (!CheckFundamentalBounds(double_val, minimum_, maximum_, error))
420 return false;
421 if (out_value)
422 *out_value = std::make_unique<base::Value>(double_val);
423 if (v8_out_value)
424 *v8_out_value = value;
425 return true;
426 }
427 case ArgumentType::STRING: {
428 DCHECK(value->IsString());
429
430 v8::Local<v8::String> v8_string = value.As<v8::String>();
431 size_t length = static_cast<size_t>(v8_string->Length());
432 if (min_length_ && length < *min_length_) {
433 *error = api_errors::TooFewStringChars(*min_length_, length);
434 return false;
435 }
436
437 if (max_length_ && length > *max_length_) {
438 *error = api_errors::TooManyStringChars(*max_length_, length);
439 return false;
440 }
441
442 if (!enum_values_.empty() || out_value) {
443 std::string str;
444 // We already checked that this is a string, so this should never fail.
445 CHECK(gin::Converter<std::string>::FromV8(context->GetIsolate(), value,
446 &str));
447 if (!enum_values_.empty() && enum_values_.count(str) == 0) {
448 *error = api_errors::InvalidEnumValue(enum_values_);
449 return false;
450 }
451
452 if (out_value)
453 *out_value = std::make_unique<base::Value>(std::move(str));
454 }
455
456 if (v8_out_value)
457 *v8_out_value = value;
458
459 return true;
460 }
461 case ArgumentType::BOOLEAN: {
462 DCHECK(value->IsBoolean());
463 if (out_value) {
464 *out_value =
465 std::make_unique<base::Value>(value.As<v8::Boolean>()->Value());
466 }
467 if (v8_out_value)
468 *v8_out_value = value;
469
470 return true;
471 }
472 default:
473 NOTREACHED();
474 }
475 return false;
476 }
477
ParseArgumentToObject(v8::Local<v8::Context> context,v8::Local<v8::Object> object,const APITypeReferenceMap & refs,std::unique_ptr<base::Value> * out_value,v8::Local<v8::Value> * v8_out_value,std::string * error) const478 bool ArgumentSpec::ParseArgumentToObject(
479 v8::Local<v8::Context> context,
480 v8::Local<v8::Object> object,
481 const APITypeReferenceMap& refs,
482 std::unique_ptr<base::Value>* out_value,
483 v8::Local<v8::Value>* v8_out_value,
484 std::string* error) const {
485 DCHECK_EQ(ArgumentType::OBJECT, type_);
486 std::unique_ptr<base::DictionaryValue> result;
487 // Only construct the result if we have an |out_value| to populate.
488 if (out_value)
489 result = std::make_unique<base::DictionaryValue>();
490
491 // We don't convert to a new object in two cases:
492 // - If instanceof is specified, we don't want to create a new data object,
493 // because then the object wouldn't be an instanceof the specified type.
494 // e.g., if a function is expecting a RegExp, we need to make sure the
495 // value passed in is, indeed, a RegExp, which won't be the case if we just
496 // copy the properties to a new object.
497 // - Some methods use additional_properties_ in order to allow for arbitrary
498 // types to be passed in (e.g., test.assertThrows allows a "self" property
499 // to be provided). Similar to above, if we just copy the property values,
500 // it may change the type of the object and break expectations.
501 // TODO(devlin): The latter case could be handled by specifying a different
502 // tag to indicate that we don't want to convert. This would be much clearer,
503 // and allow us to handle the other additional_properties_ cases. But first,
504 // we need to track down all the instances that use it.
505 bool convert_to_v8 = v8_out_value && !additional_properties_ && !instance_of_;
506 gin::DataObjectBuilder v8_result(context->GetIsolate());
507
508 v8::Local<v8::Array> own_property_names;
509 if (!object->GetOwnPropertyNames(context).ToLocal(&own_property_names)) {
510 *error = api_errors::ScriptThrewError();
511 return false;
512 }
513
514 // Track all properties we see from |properties_| to check if any are missing.
515 // Use ArgumentSpec* instead of std::string for comparison + copy efficiency.
516 std::set<const ArgumentSpec*> seen_properties;
517 uint32_t length = own_property_names->Length();
518 std::string property_error;
519 for (uint32_t i = 0; i < length; ++i) {
520 v8::Local<v8::Value> key;
521 if (!own_property_names->Get(context, i).ToLocal(&key)) {
522 *error = api_errors::ScriptThrewError();
523 return false;
524 }
525 // In JS, all keys are strings or numbers (or symbols, but those are
526 // excluded by GetOwnPropertyNames()). If you try to set anything else
527 // (e.g. an object), it is converted to a string.
528 DCHECK(key->IsString() || key->IsNumber());
529 v8::String::Utf8Value utf8_key(context->GetIsolate(), key);
530
531 ArgumentSpec* property_spec = nullptr;
532 auto iter = properties_.find(*utf8_key);
533 bool allow_unserializable = false;
534 if (iter != properties_.end()) {
535 property_spec = iter->second.get();
536 seen_properties.insert(property_spec);
537 } else if (additional_properties_) {
538 property_spec = additional_properties_.get();
539 // additionalProperties: {type: any} is often used to allow anything
540 // through, including things that would normally break serialization like
541 // functions, or even NaN. If the additional properties are of
542 // ArgumentType::ANY, allow anything, even if it doesn't serialize.
543 allow_unserializable = property_spec->type_ == ArgumentType::ANY;
544 } else {
545 *error = api_errors::UnexpectedProperty(*utf8_key);
546 return false;
547 }
548
549 v8::Local<v8::Value> prop_value;
550 // Fun: It's possible that a previous getter has removed the property from
551 // the object. This isn't that big of a deal, since it would only manifest
552 // in the case of some reasonably-crazy script objects, and it's probably
553 // not worth optimizing for the uncommon case to the detriment of the
554 // common (and either should be totally safe). We can always add a
555 // HasOwnProperty() check here in the future, if we desire.
556 // See also comment in ParseArgumentToArray() about passing in custom
557 // crazy values here.
558 if (!object->Get(context, key).ToLocal(&prop_value)) {
559 *error = api_errors::ScriptThrewError();
560 return false;
561 }
562
563 // Note: We don't serialize undefined, and only serialize null if it's part
564 // of the spec.
565 // TODO(devlin): This matches current behavior, but it is correct? And
566 // we treat undefined and null the same?
567 if (prop_value->IsUndefined() || prop_value->IsNull()) {
568 if (!property_spec->optional_) {
569 *error = api_errors::MissingRequiredProperty(*utf8_key);
570 return false;
571 }
572 if (preserve_null_ && prop_value->IsNull()) {
573 if (result) {
574 result->SetWithoutPathExpansion(*utf8_key,
575 std::make_unique<base::Value>());
576 }
577 if (convert_to_v8)
578 v8_result.Set(*utf8_key, prop_value);
579 }
580 continue;
581 }
582
583 std::unique_ptr<base::Value> property;
584 v8::Local<v8::Value> v8_property;
585 if (!property_spec->ParseArgument(
586 context, prop_value, refs, out_value ? &property : nullptr,
587 convert_to_v8 ? &v8_property : nullptr, &property_error)) {
588 if (allow_unserializable)
589 continue;
590 *error = api_errors::PropertyError(*utf8_key, property_error);
591 return false;
592 }
593 if (out_value)
594 result->SetWithoutPathExpansion(*utf8_key, std::move(property));
595 if (convert_to_v8)
596 v8_result.Set(*utf8_key, v8_property);
597 }
598
599 for (const auto& pair : properties_) {
600 const ArgumentSpec* spec = pair.second.get();
601 if (!spec->optional_ && seen_properties.count(spec) == 0) {
602 *error = api_errors::MissingRequiredProperty(pair.first.c_str());
603 return false;
604 }
605 }
606
607 if (instance_of_) {
608 // Check for the instance somewhere in the object's prototype chain.
609 // NOTE: This only checks that something in the prototype chain was
610 // constructed with the same name as the desired instance, but doesn't
611 // validate that it's the same constructor as the expected one. For
612 // instance, if we expect isInstanceOf == 'Date', script could pass in
613 // (function() {
614 // function Date() {}
615 // return new Date();
616 // })()
617 // Since the object contains 'Date' in its prototype chain, this check
618 // succeeds, even though the object is not of built-in type Date.
619 // Since this isn't (or at least shouldn't be) a security check, this is
620 // okay.
621 bool found = false;
622 v8::Local<v8::Value> next_check = object;
623 do {
624 v8::Local<v8::Object> current = next_check.As<v8::Object>();
625 v8::String::Utf8Value constructor(context->GetIsolate(),
626 current->GetConstructorName());
627 if (*instance_of_ ==
628 base::StringPiece(*constructor, constructor.length())) {
629 found = true;
630 break;
631 }
632 next_check = current->GetPrototype();
633 } while (next_check->IsObject());
634
635 if (!found) {
636 *error = api_errors::NotAnInstance(instance_of_->c_str());
637 return false;
638 }
639 }
640
641 if (out_value)
642 *out_value = std::move(result);
643
644 if (v8_out_value) {
645 if (convert_to_v8) {
646 v8::Local<v8::Object> converted = v8_result.Build();
647 // We set the object's prototype to Null() so that handlers avoid
648 // triggering any tricky getters or setters on Object.prototype.
649 CHECK(converted->SetPrototype(context, v8::Null(context->GetIsolate()))
650 .ToChecked());
651 *v8_out_value = converted;
652 } else {
653 *v8_out_value = object;
654 }
655 }
656
657 return true;
658 }
659
ParseArgumentToArray(v8::Local<v8::Context> context,v8::Local<v8::Array> value,const APITypeReferenceMap & refs,std::unique_ptr<base::Value> * out_value,v8::Local<v8::Value> * v8_out_value,std::string * error) const660 bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context,
661 v8::Local<v8::Array> value,
662 const APITypeReferenceMap& refs,
663 std::unique_ptr<base::Value>* out_value,
664 v8::Local<v8::Value>* v8_out_value,
665 std::string* error) const {
666 DCHECK_EQ(ArgumentType::LIST, type_);
667
668 uint32_t length = value->Length();
669 if (min_length_ && length < *min_length_) {
670 *error = api_errors::TooFewArrayItems(*min_length_, length);
671 return false;
672 }
673
674 if (max_length_ && length > *max_length_) {
675 *error = api_errors::TooManyArrayItems(*max_length_, length);
676 return false;
677 }
678
679 std::unique_ptr<base::ListValue> result;
680 // Only construct the result if we have an |out_value| to populate.
681 if (out_value)
682 result = std::make_unique<base::ListValue>();
683 v8::Local<v8::Array> v8_result;
684 if (v8_out_value)
685 v8_result = v8::Array::New(context->GetIsolate(), length);
686
687 std::string item_error;
688 for (uint32_t i = 0; i < length; ++i) {
689 v8::MaybeLocal<v8::Value> maybe_subvalue = value->Get(context, i);
690 v8::Local<v8::Value> subvalue;
691 // Note: This can fail in the case of a developer passing in the following:
692 // var a = [];
693 // Object.defineProperty(a, 0, { get: () => { throw new Error('foo'); } });
694 // Currently, this will cause the developer-specified error ('foo') to be
695 // thrown.
696 // TODO(devlin): This is probably fine, but it's worth contemplating
697 // catching the error and throwing our own.
698 if (!maybe_subvalue.ToLocal(&subvalue))
699 return false;
700 std::unique_ptr<base::Value> item;
701 v8::Local<v8::Value> v8_item;
702 if (!list_element_type_->ParseArgument(
703 context, subvalue, refs, out_value ? &item : nullptr,
704 v8_out_value ? &v8_item : nullptr, &item_error)) {
705 *error = api_errors::IndexError(i, item_error);
706 return false;
707 }
708 if (out_value)
709 result->Append(std::move(item));
710 if (v8_out_value) {
711 // This should never fail, since it's a newly-created array with
712 // CreateDataProperty().
713 CHECK(v8_result->CreateDataProperty(context, i, v8_item).ToChecked());
714 }
715 }
716
717 if (out_value)
718 *out_value = std::move(result);
719 if (v8_out_value)
720 *v8_out_value = v8_result;
721
722 return true;
723 }
724
ParseArgumentToAny(v8::Local<v8::Context> context,v8::Local<v8::Value> value,std::unique_ptr<base::Value> * out_value,v8::Local<v8::Value> * v8_out_value,std::string * error) const725 bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context,
726 v8::Local<v8::Value> value,
727 std::unique_ptr<base::Value>* out_value,
728 v8::Local<v8::Value>* v8_out_value,
729 std::string* error) const {
730 DCHECK(type_ == ArgumentType::ANY || type_ == ArgumentType::BINARY);
731 if (out_value) {
732 std::unique_ptr<content::V8ValueConverter> converter =
733 content::V8ValueConverter::Create();
734 converter->SetStripNullFromObjects(!preserve_null_);
735 converter->SetConvertNegativeZeroToInt(true);
736 // Note: don't allow functions. Functions are handled either by the specific
737 // type (ArgumentType::FUNCTION) or by allowing arbitrary optional
738 // arguments, which allows unserializable values.
739 // TODO(devlin): Is this correct? Or do we rely on an 'any' type of function
740 // being serialized in an odd-ball API?
741 std::unique_ptr<base::Value> converted =
742 converter->FromV8Value(value, context);
743 if (!converted) {
744 *error = api_errors::UnserializableValue();
745 return false;
746 }
747 if (type_ == ArgumentType::BINARY)
748 DCHECK_EQ(base::Value::Type::BINARY, converted->type());
749 *out_value = std::move(converted);
750 }
751 if (v8_out_value)
752 *v8_out_value = value;
753
754 return true;
755 }
756
GetInvalidTypeError(v8::Local<v8::Value> value) const757 std::string ArgumentSpec::GetInvalidTypeError(
758 v8::Local<v8::Value> value) const {
759 return api_errors::InvalidType(GetTypeName().c_str(),
760 GetV8ValueTypeString(value));
761 }
762
763 } // namespace extensions
764