Chromium Code Reviews| Index: extensions/renderer/argument_spec.cc |
| diff --git a/extensions/renderer/argument_spec.cc b/extensions/renderer/argument_spec.cc |
| index fd3e1a973a923855127aaa0f1ae9574b8ea9833f..3e5a527a1d0f978e6f6fb1f833dc65200f26b638 100644 |
| --- a/extensions/renderer/argument_spec.cc |
| +++ b/extensions/renderer/argument_spec.cc |
| @@ -5,9 +5,11 @@ |
| #include "extensions/renderer/argument_spec.h" |
| #include "base/memory/ptr_util.h" |
| +#include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "content/public/child/v8_value_converter.h" |
| +#include "extensions/renderer/api_invocation_errors.h" |
| #include "extensions/renderer/api_type_reference_map.h" |
| #include "gin/converter.h" |
| #include "gin/dictionary.h" |
| @@ -16,21 +18,52 @@ namespace extensions { |
| namespace { |
| +// Returns a type string for the given |value|. |
| +const char* GetV8ValueTypeString(v8::Local<v8::Value> value) { |
| + DCHECK(!value.IsEmpty()); |
| + |
| + if (value->IsNull()) |
| + return api_errors::kTypeNull; |
| + if (value->IsUndefined()) |
| + return api_errors::kTypeUndefined; |
| + if (value->IsInt32()) |
| + return api_errors::kTypeInteger; |
| + if (value->IsNumber()) |
| + return api_errors::kTypeDouble; |
| + if (value->IsBoolean()) |
| + return api_errors::kTypeBoolean; |
| + if (value->IsString()) |
| + return api_errors::kTypeString; |
| + |
| + // Note: check IsArray(), IsFunction(), and IsArrayBuffer[View]() before |
| + // IsObject() since arrays, functions, and array buffers are objects. |
| + if (value->IsArray()) |
| + return api_errors::kTypeList; |
| + if (value->IsFunction()) |
| + return api_errors::kTypeFunction; |
| + if (value->IsArrayBuffer() || value->IsArrayBufferView()) |
| + return api_errors::kTypeBinary; |
| + if (value->IsObject()) |
| + return api_errors::kTypeObject; |
| + |
| + return ""; |
| +} |
| + |
| +// Returns true if |value| is within the bounds specified by |minimum|, |
|
lazyboy
2017/04/25 18:23:12
within the bounds [|minimum|, |maximum|]
Devlin
2017/04/25 22:47:59
Done.
|
| +// populating |error| otherwise. |
| template <class T> |
| -bool ParseFundamentalValueHelper(v8::Local<v8::Value> arg, |
| - v8::Local<v8::Context> context, |
| - const base::Optional<int>& minimum, |
| - const base::Optional<int>& maximum, |
| - std::unique_ptr<base::Value>* out_value) { |
| - T val; |
| - if (!gin::Converter<T>::FromV8(context->GetIsolate(), arg, &val)) |
| - return false; |
| - if (minimum && val < *minimum) |
| +bool CheckFundamentalBounds(T value, |
| + const base::Optional<int>& minimum, |
| + const base::Optional<int>& maximum, |
| + std::string* error) { |
| + if (minimum && value < *minimum) { |
| + *error = api_errors::GetError(api_errors::kNumberTooSmall, *minimum); |
| return false; |
| - if (maximum && val > *maximum) |
| + } |
| + if (maximum && value > *maximum) { |
| + *error = api_errors::GetError(api_errors::kNumberTooLarge, *maximum); |
| return false; |
| - if (out_value) |
| - *out_value = base::MakeUnique<base::Value>(val); |
| + } |
| return true; |
| } |
| @@ -168,8 +201,10 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| std::unique_ptr<base::Value>* out_value, |
| std::string* error) const { |
| if (type_ == ArgumentType::FUNCTION) { |
| - if (!value->IsFunction()) |
| + if (!value->IsFunction()) { |
| + *error = GetInvalidTypeError(value); |
| return false; |
| + } |
| if (out_value) { |
| // Certain APIs (contextMenus) have functions as parameters other than the |
| @@ -194,7 +229,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| if (choice->ParseArgument(context, value, refs, out_value, error)) |
| return true; |
| } |
| - *error = "Did not match any of the choices"; |
| + *error = api_errors::kInvalidChoice; |
| return false; |
| } |
| @@ -207,7 +242,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| // parameter and then an optional callback, we wouldn't necessarily be able |
| // to match the arguments if we allowed functions as objects. |
| if (!value->IsObject() || value->IsFunction() || value->IsArray()) { |
| - *error = "Wrong type"; |
| + *error = GetInvalidTypeError(value); |
| return false; |
| } |
| v8::Local<v8::Object> object = value.As<v8::Object>(); |
| @@ -215,7 +250,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| } |
| if (type_ == ArgumentType::LIST) { |
| if (!value->IsArray()) { |
| - *error = "Wrong type"; |
| + *error = GetInvalidTypeError(value); |
| return false; |
| } |
| v8::Local<v8::Array> array = value.As<v8::Array>(); |
| @@ -223,7 +258,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| } |
| if (type_ == ArgumentType::BINARY) { |
| if (!value->IsArrayBuffer() && !value->IsArrayBufferView()) { |
| - *error = "Wrong type"; |
| + *error = GetInvalidTypeError(value); |
| return false; |
| } |
| return ParseArgumentToAny(context, value, out_value, error); |
| @@ -245,26 +280,49 @@ bool ArgumentSpec::ParseArgumentToFundamental( |
| std::unique_ptr<base::Value>* out_value, |
| std::string* error) const { |
| DCHECK(IsFundamentalType()); |
| + |
| switch (type_) { |
| - case ArgumentType::INTEGER: |
| - return ParseFundamentalValueHelper<int32_t>(value, context, minimum_, |
| - maximum_, out_value); |
| - case ArgumentType::DOUBLE: |
| - return ParseFundamentalValueHelper<double>(value, context, minimum_, |
| - maximum_, out_value); |
| + case ArgumentType::INTEGER: { |
| + if (!value->IsInt32()) { |
| + *error = GetInvalidTypeError(value); |
| + return false; |
| + } |
| + int int_val = value.As<v8::Int32>()->Value(); |
| + if (!CheckFundamentalBounds(int_val, minimum_, maximum_, error)) |
| + return false; |
| + if (out_value) |
| + *out_value = base::MakeUnique<base::Value>(int_val); |
| + return true; |
| + } |
| + case ArgumentType::DOUBLE: { |
| + if (!value->IsNumber()) { |
| + *error = GetInvalidTypeError(value); |
| + return false; |
| + } |
| + double double_val = value.As<v8::Number>()->Value(); |
| + if (!CheckFundamentalBounds(double_val, minimum_, maximum_, error)) |
| + return false; |
| + if (out_value) |
| + *out_value = base::MakeUnique<base::Value>(double_val); |
| + return true; |
| + } |
| case ArgumentType::STRING: { |
| - if (!value->IsString()) |
| + if (!value->IsString()) { |
| + *error = GetInvalidTypeError(value); |
| return false; |
| + } |
| v8::Local<v8::String> v8_string = value.As<v8::String>(); |
| size_t length = static_cast<size_t>(v8_string->Length()); |
| if (min_length_ && length < *min_length_) { |
| - *error = "Less than min length"; |
| + *error = |
| + api_errors::GetError(api_errors::kTooFewStringChars, *min_length_); |
| return false; |
| } |
| if (max_length_ && length > *max_length_) { |
| - *error = "Greater than max length"; |
| + *error = |
| + api_errors::GetError(api_errors::kTooManyStringChars, *max_length_); |
| return false; |
| } |
| @@ -277,8 +335,14 @@ bool ArgumentSpec::ParseArgumentToFundamental( |
| // We already checked that this is a string, so this should never fail. |
| CHECK(gin::Converter<std::string>::FromV8(context->GetIsolate(), value, |
| &s)); |
| - if (!enum_values_.empty() && enum_values_.count(s) == 0) |
| + if (!enum_values_.empty() && enum_values_.count(s) == 0) { |
| + std::vector<base::StringPiece> options(enum_values_.begin(), |
| + enum_values_.end()); |
| + std::string options_str = base::JoinString(options, ", "); |
| + *error = api_errors::GetError(api_errors::kInvalidEnumValue, |
| + options_str.c_str()); |
| return false; |
| + } |
| if (out_value) { |
| // TODO(devlin): If base::Value ever takes a std::string&&, we |
| // could use std::move to construct. |
| @@ -287,8 +351,10 @@ bool ArgumentSpec::ParseArgumentToFundamental( |
| return true; |
| } |
| case ArgumentType::BOOLEAN: { |
| - if (!value->IsBoolean()) |
| + if (!value->IsBoolean()) { |
| + *error = GetInvalidTypeError(value); |
| return false; |
| + } |
| if (out_value) { |
| *out_value = |
| base::MakeUnique<base::Value>(value.As<v8::Boolean>()->Value()); |
| @@ -314,17 +380,22 @@ bool ArgumentSpec::ParseArgumentToObject( |
| result = base::MakeUnique<base::DictionaryValue>(); |
| v8::Local<v8::Array> own_property_names; |
| - if (!object->GetOwnPropertyNames(context).ToLocal(&own_property_names)) |
| + if (!object->GetOwnPropertyNames(context).ToLocal(&own_property_names)) { |
| + *error = api_errors::kScriptThrewError; |
| return false; |
| + } |
| // Track all properties we see from |properties_| to check if any are missing. |
| // Use ArgumentSpec* instead of std::string for comparison + copy efficiency. |
| std::set<const ArgumentSpec*> seen_properties; |
| uint32_t length = own_property_names->Length(); |
| + std::string property_error; |
| for (uint32_t i = 0; i < length; ++i) { |
| v8::Local<v8::Value> key; |
| - if (!own_property_names->Get(context, i).ToLocal(&key)) |
| + if (!own_property_names->Get(context, i).ToLocal(&key)) { |
| + *error = api_errors::kScriptThrewError; |
| return false; |
| + } |
| // In JS, all keys are strings or numbers (or symbols, but those are |
| // excluded by GetOwnPropertyNames()). If you try to set anything else |
| // (e.g. an object), it is converted to a string. |
| @@ -339,7 +410,7 @@ bool ArgumentSpec::ParseArgumentToObject( |
| } else if (additional_properties_) { |
| property_spec = additional_properties_.get(); |
| } else { |
| - *error = base::StringPrintf("Unknown property: %s", *utf8_key); |
| + *error = api_errors::GetError(api_errors::kUnexpectedProperty, *utf8_key); |
| return false; |
| } |
| @@ -359,7 +430,8 @@ bool ArgumentSpec::ParseArgumentToObject( |
| // TODO(devlin): This matches current behavior, but it is correct? |
| if (prop_value->IsUndefined() || prop_value->IsNull()) { |
| if (!property_spec->optional_) { |
| - *error = base::StringPrintf("Missing key: %s", *utf8_key); |
| + *error = api_errors::GetError(api_errors::kMissingRequiredProperty, |
| + *utf8_key); |
| return false; |
| } |
| continue; |
| @@ -367,7 +439,9 @@ bool ArgumentSpec::ParseArgumentToObject( |
| std::unique_ptr<base::Value> property; |
| if (!property_spec->ParseArgument(context, prop_value, refs, |
| - result ? &property : nullptr, error)) { |
| + result ? &property : nullptr, |
| + &property_error)) { |
| + *error = api_errors::GetPropertyError(*utf8_key, property_error); |
| return false; |
| } |
| if (result) |
| @@ -377,7 +451,8 @@ bool ArgumentSpec::ParseArgumentToObject( |
| for (const auto& pair : properties_) { |
| const ArgumentSpec* spec = pair.second.get(); |
| if (!spec->optional_ && seen_properties.count(spec) == 0) { |
| - *error = "Missing key: " + pair.first; |
| + *error = api_errors::GetError(api_errors::kMissingRequiredProperty, |
| + pair.first.c_str()); |
| return false; |
| } |
| } |
| @@ -410,7 +485,8 @@ bool ArgumentSpec::ParseArgumentToObject( |
| } while (next_check->IsObject()); |
| if (!found) { |
| - *error = "Object is not of correct instance"; |
| + *error = api_errors::GetError(api_errors::kNotAnInstance, |
| + instance_of_->c_str()); |
| return false; |
| } |
| } |
| @@ -429,12 +505,12 @@ bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context, |
| uint32_t length = value->Length(); |
| if (min_length_ && length < *min_length_) { |
| - *error = "Less than min length"; |
| + *error = api_errors::GetError(api_errors::kTooFewArrayItems, *min_length_); |
| return false; |
| } |
| if (max_length_ && length > *max_length_) { |
| - *error = "Greater than max length"; |
| + *error = api_errors::GetError(api_errors::kTooManyArrayItems, *max_length_); |
| return false; |
| } |
| @@ -443,6 +519,7 @@ bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context, |
| if (out_value) |
| result = base::MakeUnique<base::ListValue>(); |
| + std::string item_error; |
| for (uint32_t i = 0; i < length; ++i) { |
| v8::MaybeLocal<v8::Value> maybe_subvalue = value->Get(context, i); |
| v8::Local<v8::Value> subvalue; |
| @@ -456,8 +533,9 @@ bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context, |
| if (!maybe_subvalue.ToLocal(&subvalue)) |
| return false; |
| std::unique_ptr<base::Value> item; |
| - if (!list_element_type_->ParseArgument(context, subvalue, refs, |
| - result ? &item : nullptr, error)) { |
| + if (!list_element_type_->ParseArgument( |
| + context, subvalue, refs, result ? &item : nullptr, &item_error)) { |
| + *error = api_errors::GetIndexError(i, item_error); |
| return false; |
| } |
| if (result) |
| @@ -479,7 +557,7 @@ bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context, |
| std::unique_ptr<base::Value> converted( |
| converter->FromV8Value(value, context)); |
| if (!converted) { |
| - *error = "Could not convert to 'any'."; |
| + *error = api_errors::kUnserializableValue; |
| return false; |
| } |
| if (type_ == ArgumentType::BINARY) |
| @@ -489,4 +567,44 @@ bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context, |
| return true; |
| } |
| +std::string ArgumentSpec::GetInvalidTypeError( |
| + v8::Local<v8::Value> value) const { |
| + base::StringPiece expected_type; |
| + switch (type_) { |
| + case ArgumentType::INTEGER: |
| + expected_type = api_errors::kTypeInteger; |
| + break; |
| + case ArgumentType::DOUBLE: |
| + expected_type = api_errors::kTypeDouble; |
| + break; |
| + case ArgumentType::BOOLEAN: |
| + expected_type = api_errors::kTypeBoolean; |
| + break; |
| + case ArgumentType::STRING: |
| + expected_type = api_errors::kTypeString; |
| + break; |
| + case ArgumentType::OBJECT: |
| + expected_type = instance_of_ ? *instance_of_ : api_errors::kTypeObject; |
| + break; |
| + case ArgumentType::LIST: |
| + expected_type = api_errors::kTypeList; |
| + break; |
| + case ArgumentType::BINARY: |
| + expected_type = api_errors::kTypeBinary; |
| + break; |
| + case ArgumentType::FUNCTION: |
| + expected_type = api_errors::kTypeFunction; |
| + break; |
| + case ArgumentType::REF: |
| + expected_type = *ref_; |
| + break; |
| + case ArgumentType::CHOICES: |
| + case ArgumentType::ANY: |
| + NOTREACHED(); |
| + } |
| + |
| + return api_errors::GetError(api_errors::kInvalidType, expected_type.data(), |
| + GetV8ValueTypeString(value)); |
| +} |
| + |
| } // namespace extensions |