| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "extensions/renderer/bindings/argument_spec.h" | 5 #include "extensions/renderer/bindings/argument_spec.h" |
| 6 | 6 |
| 7 #include "base/memory/ptr_util.h" | 7 #include "base/memory/ptr_util.h" |
| 8 #include "base/strings/string_piece.h" | 8 #include "base/strings/string_piece.h" |
| 9 #include "base/strings/string_util.h" | 9 #include "base/strings/string_util.h" |
| 10 #include "base/strings/stringprintf.h" | 10 #include "base/strings/stringprintf.h" |
| (...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 198 | 198 |
| 199 // Check if we should preserve null in objects. Right now, this is only used | 199 // Check if we should preserve null in objects. Right now, this is only used |
| 200 // on arguments of type object and any (in fact, it's only used in the storage | 200 // on arguments of type object and any (in fact, it's only used in the storage |
| 201 // API), but it could potentially make sense for lists or functions as well. | 201 // API), but it could potentially make sense for lists or functions as well. |
| 202 if (type_ == ArgumentType::OBJECT || type_ == ArgumentType::ANY) | 202 if (type_ == ArgumentType::OBJECT || type_ == ArgumentType::ANY) |
| 203 dict->GetBoolean("preserveNull", &preserve_null_); | 203 dict->GetBoolean("preserveNull", &preserve_null_); |
| 204 } | 204 } |
| 205 | 205 |
| 206 ArgumentSpec::~ArgumentSpec() {} | 206 ArgumentSpec::~ArgumentSpec() {} |
| 207 | 207 |
| 208 bool ArgumentSpec::IsCorrectType(v8::Local<v8::Value> value, |
| 209 const APITypeReferenceMap& refs, |
| 210 std::string* error) const { |
| 211 bool is_valid_type = false; |
| 212 |
| 213 switch (type_) { |
| 214 case ArgumentType::INTEGER: |
| 215 is_valid_type = value->IsInt32(); |
| 216 break; |
| 217 case ArgumentType::DOUBLE: |
| 218 is_valid_type = value->IsNumber(); |
| 219 break; |
| 220 case ArgumentType::BOOLEAN: |
| 221 is_valid_type = value->IsBoolean(); |
| 222 break; |
| 223 case ArgumentType::STRING: |
| 224 is_valid_type = value->IsString(); |
| 225 break; |
| 226 case ArgumentType::OBJECT: |
| 227 // Don't allow functions or arrays (even though they are technically |
| 228 // objects). This is to make it easier to match otherwise-ambiguous |
| 229 // signatures. For instance, if an API method has an optional object |
| 230 // parameter and then an optional callback, we wouldn't necessarily be |
| 231 // able to match the arguments if we allowed functions as objects. |
| 232 // TODO(devlin): What about other subclasses of Object, like Map and Set? |
| 233 is_valid_type = |
| 234 value->IsObject() && !value->IsFunction() && !value->IsArray(); |
| 235 break; |
| 236 case ArgumentType::LIST: |
| 237 is_valid_type = value->IsArray(); |
| 238 break; |
| 239 case ArgumentType::BINARY: |
| 240 is_valid_type = value->IsArrayBuffer() || value->IsArrayBufferView(); |
| 241 break; |
| 242 case ArgumentType::FUNCTION: |
| 243 is_valid_type = value->IsFunction(); |
| 244 break; |
| 245 case ArgumentType::ANY: |
| 246 is_valid_type = true; |
| 247 break; |
| 248 case ArgumentType::REF: { |
| 249 DCHECK(ref_); |
| 250 const ArgumentSpec* reference = refs.GetSpec(ref_.value()); |
| 251 DCHECK(reference) << ref_.value(); |
| 252 is_valid_type = reference->IsCorrectType(value, refs, error); |
| 253 break; |
| 254 } |
| 255 case ArgumentType::CHOICES: |
| 256 for (const auto& choice : choices_) { |
| 257 if (choice->IsCorrectType(value, refs, error)) { |
| 258 is_valid_type = true; |
| 259 break; |
| 260 } |
| 261 } |
| 262 break; |
| 263 } |
| 264 |
| 265 if (!is_valid_type) |
| 266 *error = GetInvalidTypeError(value); |
| 267 return is_valid_type; |
| 268 } |
| 269 |
| 208 bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, | 270 bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| 209 v8::Local<v8::Value> value, | 271 v8::Local<v8::Value> value, |
| 210 const APITypeReferenceMap& refs, | 272 const APITypeReferenceMap& refs, |
| 211 std::unique_ptr<base::Value>* out_value, | 273 std::unique_ptr<base::Value>* out_value, |
| 212 std::string* error) const { | 274 std::string* error) const { |
| 213 if (type_ == ArgumentType::FUNCTION) { | 275 // Note: for top-level arguments (i.e., those passed directly to the function, |
| 214 if (!value->IsFunction()) { | 276 // as opposed to a property on an object, or the item of an array), we will |
| 215 *error = GetInvalidTypeError(value); | 277 // have already checked the type. Doing so again should be nearly free, but |
| 278 // if we do find this to be an issue, we could avoid the second call. |
| 279 if (!IsCorrectType(value, refs, error)) |
| 280 return false; |
| 281 |
| 282 switch (type_) { |
| 283 case ArgumentType::INTEGER: |
| 284 case ArgumentType::DOUBLE: |
| 285 case ArgumentType::BOOLEAN: |
| 286 case ArgumentType::STRING: |
| 287 return ParseArgumentToFundamental(context, value, out_value, error); |
| 288 case ArgumentType::OBJECT: |
| 289 return ParseArgumentToObject(context, value.As<v8::Object>(), refs, |
| 290 out_value, error); |
| 291 case ArgumentType::LIST: |
| 292 return ParseArgumentToArray(context, value.As<v8::Array>(), refs, |
| 293 out_value, error); |
| 294 case ArgumentType::BINARY: |
| 295 return ParseArgumentToAny(context, value, out_value, error); |
| 296 case ArgumentType::FUNCTION: |
| 297 if (out_value) { |
| 298 // Certain APIs (contextMenus) have functions as parameters other than |
| 299 // the callback (contextMenus uses it for an onclick listener). Our |
| 300 // generated types have adapted to consider functions "objects" and |
| 301 // serialize them as dictionaries. |
| 302 // TODO(devlin): It'd be awfully nice to get rid of this eccentricity. |
| 303 *out_value = base::MakeUnique<base::DictionaryValue>(); |
| 304 } |
| 305 return true; |
| 306 case ArgumentType::REF: { |
| 307 DCHECK(ref_); |
| 308 const ArgumentSpec* reference = refs.GetSpec(ref_.value()); |
| 309 DCHECK(reference) << ref_.value(); |
| 310 return reference->ParseArgument(context, value, refs, out_value, error); |
| 311 } |
| 312 case ArgumentType::CHOICES: { |
| 313 for (const auto& choice : choices_) { |
| 314 if (choice->ParseArgument(context, value, refs, out_value, error)) |
| 315 return true; |
| 316 } |
| 317 *error = api_errors::InvalidChoice(); |
| 216 return false; | 318 return false; |
| 217 } | 319 } |
| 218 | 320 case ArgumentType::ANY: |
| 219 if (out_value) { | 321 return ParseArgumentToAny(context, value, out_value, error); |
| 220 // Certain APIs (contextMenus) have functions as parameters other than the | |
| 221 // callback (contextMenus uses it for an onclick listener). Our generated | |
| 222 // types have adapted to consider functions "objects" and serialize them | |
| 223 // as dictionaries. | |
| 224 // TODO(devlin): It'd be awfully nice to get rid of this eccentricity. | |
| 225 *out_value = base::MakeUnique<base::DictionaryValue>(); | |
| 226 } | |
| 227 return true; | |
| 228 } | 322 } |
| 229 | 323 |
| 230 if (type_ == ArgumentType::REF) { | |
| 231 DCHECK(ref_); | |
| 232 const ArgumentSpec* reference = refs.GetSpec(ref_.value()); | |
| 233 DCHECK(reference) << ref_.value(); | |
| 234 return reference->ParseArgument(context, value, refs, out_value, error); | |
| 235 } | |
| 236 | |
| 237 if (type_ == ArgumentType::CHOICES) { | |
| 238 for (const auto& choice : choices_) { | |
| 239 if (choice->ParseArgument(context, value, refs, out_value, error)) | |
| 240 return true; | |
| 241 } | |
| 242 *error = api_errors::InvalidChoice(); | |
| 243 return false; | |
| 244 } | |
| 245 | |
| 246 if (IsFundamentalType()) | |
| 247 return ParseArgumentToFundamental(context, value, out_value, error); | |
| 248 if (type_ == ArgumentType::OBJECT) { | |
| 249 // Don't allow functions or arrays (even though they are technically | |
| 250 // objects). This is to make it easier to match otherwise-ambiguous | |
| 251 // signatures. For instance, if an API method has an optional object | |
| 252 // parameter and then an optional callback, we wouldn't necessarily be able | |
| 253 // to match the arguments if we allowed functions as objects. | |
| 254 if (!value->IsObject() || value->IsFunction() || value->IsArray()) { | |
| 255 *error = GetInvalidTypeError(value); | |
| 256 return false; | |
| 257 } | |
| 258 v8::Local<v8::Object> object = value.As<v8::Object>(); | |
| 259 return ParseArgumentToObject(context, object, refs, out_value, error); | |
| 260 } | |
| 261 if (type_ == ArgumentType::LIST) { | |
| 262 if (!value->IsArray()) { | |
| 263 *error = GetInvalidTypeError(value); | |
| 264 return false; | |
| 265 } | |
| 266 v8::Local<v8::Array> array = value.As<v8::Array>(); | |
| 267 return ParseArgumentToArray(context, array, refs, out_value, error); | |
| 268 } | |
| 269 if (type_ == ArgumentType::BINARY) { | |
| 270 if (!value->IsArrayBuffer() && !value->IsArrayBufferView()) { | |
| 271 *error = GetInvalidTypeError(value); | |
| 272 return false; | |
| 273 } | |
| 274 return ParseArgumentToAny(context, value, out_value, error); | |
| 275 } | |
| 276 if (type_ == ArgumentType::ANY) | |
| 277 return ParseArgumentToAny(context, value, out_value, error); | |
| 278 NOTREACHED(); | 324 NOTREACHED(); |
| 279 return false; | 325 return false; |
| 280 } | 326 } |
| 281 | 327 |
| 282 const std::string& ArgumentSpec::GetTypeName() const { | 328 const std::string& ArgumentSpec::GetTypeName() const { |
| 283 if (!type_name_.empty()) | 329 if (!type_name_.empty()) |
| 284 return type_name_; | 330 return type_name_; |
| 285 | 331 |
| 286 switch (type_) { | 332 switch (type_) { |
| 287 case ArgumentType::INTEGER: | 333 case ArgumentType::INTEGER: |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 321 break; | 367 break; |
| 322 } | 368 } |
| 323 case ArgumentType::ANY: | 369 case ArgumentType::ANY: |
| 324 type_name_ = api_errors::kTypeAny; | 370 type_name_ = api_errors::kTypeAny; |
| 325 break; | 371 break; |
| 326 } | 372 } |
| 327 DCHECK(!type_name_.empty()); | 373 DCHECK(!type_name_.empty()); |
| 328 return type_name_; | 374 return type_name_; |
| 329 } | 375 } |
| 330 | 376 |
| 331 bool ArgumentSpec::IsFundamentalType() const { | |
| 332 return type_ == ArgumentType::INTEGER || type_ == ArgumentType::DOUBLE || | |
| 333 type_ == ArgumentType::BOOLEAN || type_ == ArgumentType::STRING; | |
| 334 } | |
| 335 | |
| 336 bool ArgumentSpec::ParseArgumentToFundamental( | 377 bool ArgumentSpec::ParseArgumentToFundamental( |
| 337 v8::Local<v8::Context> context, | 378 v8::Local<v8::Context> context, |
| 338 v8::Local<v8::Value> value, | 379 v8::Local<v8::Value> value, |
| 339 std::unique_ptr<base::Value>* out_value, | 380 std::unique_ptr<base::Value>* out_value, |
| 340 std::string* error) const { | 381 std::string* error) const { |
| 341 DCHECK(IsFundamentalType()); | |
| 342 | |
| 343 switch (type_) { | 382 switch (type_) { |
| 344 case ArgumentType::INTEGER: { | 383 case ArgumentType::INTEGER: { |
| 345 if (!value->IsInt32()) { | 384 DCHECK(value->IsInt32()); |
| 346 *error = GetInvalidTypeError(value); | |
| 347 return false; | |
| 348 } | |
| 349 int int_val = value.As<v8::Int32>()->Value(); | 385 int int_val = value.As<v8::Int32>()->Value(); |
| 350 if (!CheckFundamentalBounds(int_val, minimum_, maximum_, error)) | 386 if (!CheckFundamentalBounds(int_val, minimum_, maximum_, error)) |
| 351 return false; | 387 return false; |
| 352 if (out_value) | 388 if (out_value) |
| 353 *out_value = base::MakeUnique<base::Value>(int_val); | 389 *out_value = base::MakeUnique<base::Value>(int_val); |
| 354 return true; | 390 return true; |
| 355 } | 391 } |
| 356 case ArgumentType::DOUBLE: { | 392 case ArgumentType::DOUBLE: { |
| 357 if (!value->IsNumber()) { | 393 DCHECK(value->IsNumber()); |
| 358 *error = GetInvalidTypeError(value); | |
| 359 return false; | |
| 360 } | |
| 361 double double_val = value.As<v8::Number>()->Value(); | 394 double double_val = value.As<v8::Number>()->Value(); |
| 362 if (!CheckFundamentalBounds(double_val, minimum_, maximum_, error)) | 395 if (!CheckFundamentalBounds(double_val, minimum_, maximum_, error)) |
| 363 return false; | 396 return false; |
| 364 if (out_value) | 397 if (out_value) |
| 365 *out_value = base::MakeUnique<base::Value>(double_val); | 398 *out_value = base::MakeUnique<base::Value>(double_val); |
| 366 return true; | 399 return true; |
| 367 } | 400 } |
| 368 case ArgumentType::STRING: { | 401 case ArgumentType::STRING: { |
| 369 if (!value->IsString()) { | 402 DCHECK(value->IsString()); |
| 370 *error = GetInvalidTypeError(value); | |
| 371 return false; | |
| 372 } | |
| 373 | 403 |
| 374 v8::Local<v8::String> v8_string = value.As<v8::String>(); | 404 v8::Local<v8::String> v8_string = value.As<v8::String>(); |
| 375 size_t length = static_cast<size_t>(v8_string->Length()); | 405 size_t length = static_cast<size_t>(v8_string->Length()); |
| 376 if (min_length_ && length < *min_length_) { | 406 if (min_length_ && length < *min_length_) { |
| 377 *error = api_errors::TooFewStringChars(*min_length_, length); | 407 *error = api_errors::TooFewStringChars(*min_length_, length); |
| 378 return false; | 408 return false; |
| 379 } | 409 } |
| 380 | 410 |
| 381 if (max_length_ && length > *max_length_) { | 411 if (max_length_ && length > *max_length_) { |
| 382 *error = api_errors::TooManyStringChars(*max_length_, length); | 412 *error = api_errors::TooManyStringChars(*max_length_, length); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 397 return false; | 427 return false; |
| 398 } | 428 } |
| 399 if (out_value) { | 429 if (out_value) { |
| 400 // TODO(devlin): If base::Value ever takes a std::string&&, we | 430 // TODO(devlin): If base::Value ever takes a std::string&&, we |
| 401 // could use std::move to construct. | 431 // could use std::move to construct. |
| 402 *out_value = base::MakeUnique<base::Value>(s); | 432 *out_value = base::MakeUnique<base::Value>(s); |
| 403 } | 433 } |
| 404 return true; | 434 return true; |
| 405 } | 435 } |
| 406 case ArgumentType::BOOLEAN: { | 436 case ArgumentType::BOOLEAN: { |
| 407 if (!value->IsBoolean()) { | 437 DCHECK(value->IsBoolean()); |
| 408 *error = GetInvalidTypeError(value); | |
| 409 return false; | |
| 410 } | |
| 411 if (out_value) { | 438 if (out_value) { |
| 412 *out_value = | 439 *out_value = |
| 413 base::MakeUnique<base::Value>(value.As<v8::Boolean>()->Value()); | 440 base::MakeUnique<base::Value>(value.As<v8::Boolean>()->Value()); |
| 414 } | 441 } |
| 415 return true; | 442 return true; |
| 416 } | 443 } |
| 417 default: | 444 default: |
| 418 NOTREACHED(); | 445 NOTREACHED(); |
| 419 } | 446 } |
| 420 return false; | 447 return false; |
| (...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 634 return true; | 661 return true; |
| 635 } | 662 } |
| 636 | 663 |
| 637 std::string ArgumentSpec::GetInvalidTypeError( | 664 std::string ArgumentSpec::GetInvalidTypeError( |
| 638 v8::Local<v8::Value> value) const { | 665 v8::Local<v8::Value> value) const { |
| 639 return api_errors::InvalidType(GetTypeName().c_str(), | 666 return api_errors::InvalidType(GetTypeName().c_str(), |
| 640 GetV8ValueTypeString(value)); | 667 GetV8ValueTypeString(value)); |
| 641 } | 668 } |
| 642 | 669 |
| 643 } // namespace extensions | 670 } // namespace extensions |
| OLD | NEW |