| 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/argument_spec.h" | 5 #include "extensions/renderer/argument_spec.h" |
| 6 | 6 |
| 7 #include "base/memory/ptr_util.h" | 7 #include "base/memory/ptr_util.h" |
| 8 #include "base/values.h" | 8 #include "base/values.h" |
| 9 #include "content/public/child/v8_value_converter.h" | 9 #include "content/public/child/v8_value_converter.h" |
| 10 #include "gin/converter.h" | 10 #include "gin/converter.h" |
| 11 #include "gin/dictionary.h" | 11 #include "gin/dictionary.h" |
| 12 | 12 |
| 13 namespace extensions { | 13 namespace extensions { |
| 14 | 14 |
| 15 namespace { | 15 namespace { |
| 16 | 16 |
| 17 template <class T> | 17 template <class T> |
| 18 std::unique_ptr<base::Value> GetFundamentalConvertedValueHelper( | 18 bool ParseFundamentalValueHelper(v8::Local<v8::Value> arg, |
| 19 v8::Local<v8::Value> arg, | 19 v8::Local<v8::Context> context, |
| 20 v8::Local<v8::Context> context, | 20 const base::Optional<int>& minimum, |
| 21 const base::Optional<int>& minimum) { | 21 std::unique_ptr<base::Value>* out_value) { |
| 22 T val; | 22 T val; |
| 23 if (!gin::Converter<T>::FromV8(context->GetIsolate(), arg, &val)) | 23 if (!gin::Converter<T>::FromV8(context->GetIsolate(), arg, &val)) |
| 24 return nullptr; | 24 return false; |
| 25 if (minimum && val < minimum.value()) | 25 if (minimum && val < minimum.value()) |
| 26 return nullptr; | 26 return false; |
| 27 return base::MakeUnique<base::FundamentalValue>(val); | 27 if (out_value) |
| 28 *out_value = base::MakeUnique<base::FundamentalValue>(val); |
| 29 return true; |
| 28 } | 30 } |
| 29 | 31 |
| 30 } // namespace | 32 } // namespace |
| 31 | 33 |
| 32 ArgumentSpec::ArgumentSpec(const base::Value& value) | 34 ArgumentSpec::ArgumentSpec(const base::Value& value) |
| 33 : type_(ArgumentType::INTEGER), optional_(false) { | 35 : type_(ArgumentType::INTEGER), optional_(false) { |
| 34 const base::DictionaryValue* dict = nullptr; | 36 const base::DictionaryValue* dict = nullptr; |
| 35 CHECK(value.GetAsDictionary(&dict)); | 37 CHECK(value.GetAsDictionary(&dict)); |
| 36 dict->GetBoolean("optional", &optional_); | 38 dict->GetBoolean("optional", &optional_); |
| 37 dict->GetString("name", &name_); | 39 dict->GetString("name", &name_); |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 113 CHECK(enum_value_dictionary->GetString("name", &enum_value)); | 115 CHECK(enum_value_dictionary->GetString("name", &enum_value)); |
| 114 } | 116 } |
| 115 enum_values_.insert(std::move(enum_value)); | 117 enum_values_.insert(std::move(enum_value)); |
| 116 } | 118 } |
| 117 } | 119 } |
| 118 } | 120 } |
| 119 } | 121 } |
| 120 | 122 |
| 121 ArgumentSpec::~ArgumentSpec() {} | 123 ArgumentSpec::~ArgumentSpec() {} |
| 122 | 124 |
| 123 std::unique_ptr<base::Value> ArgumentSpec::ConvertArgument( | 125 bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, |
| 124 v8::Local<v8::Context> context, | 126 v8::Local<v8::Value> value, |
| 125 v8::Local<v8::Value> value, | 127 const RefMap& refs, |
| 126 const RefMap& refs, | 128 std::unique_ptr<base::Value>* out_value, |
| 127 std::string* error) const { | 129 std::string* error) const { |
| 128 // TODO(devlin): Support functions? | 130 // TODO(devlin): Support functions? |
| 129 DCHECK_NE(type_, ArgumentType::FUNCTION); | 131 DCHECK_NE(type_, ArgumentType::FUNCTION); |
| 130 if (type_ == ArgumentType::REF) { | 132 if (type_ == ArgumentType::REF) { |
| 131 DCHECK(ref_); | 133 DCHECK(ref_); |
| 132 auto iter = refs.find(ref_.value()); | 134 auto iter = refs.find(ref_.value()); |
| 133 DCHECK(iter != refs.end()) << ref_.value(); | 135 DCHECK(iter != refs.end()) << ref_.value(); |
| 134 return iter->second->ConvertArgument(context, value, refs, error); | 136 return iter->second->ParseArgument(context, value, refs, out_value, error); |
| 135 } | 137 } |
| 136 | 138 |
| 137 if (type_ == ArgumentType::CHOICES) { | 139 if (type_ == ArgumentType::CHOICES) { |
| 138 for (const auto& choice : choices_) { | 140 for (const auto& choice : choices_) { |
| 139 std::unique_ptr<base::Value> result = | 141 if (choice->ParseArgument(context, value, refs, out_value, error)) |
| 140 choice->ConvertArgument(context, value, refs, error); | 142 return true; |
| 141 if (result) | |
| 142 return result; | |
| 143 } | 143 } |
| 144 *error = "Did not match any of the choices"; | 144 *error = "Did not match any of the choices"; |
| 145 return nullptr; | 145 return false; |
| 146 } | 146 } |
| 147 | 147 |
| 148 if (IsFundamentalType()) | 148 if (IsFundamentalType()) |
| 149 return ConvertArgumentToFundamental(context, value, error); | 149 return ParseArgumentToFundamental(context, value, out_value, error); |
| 150 if (type_ == ArgumentType::OBJECT) { | 150 if (type_ == ArgumentType::OBJECT) { |
| 151 // TODO(devlin): Currently, this would accept an array (if that array had | 151 // TODO(devlin): Currently, this would accept an array (if that array had |
| 152 // all the requisite properties). Is that the right thing to do? | 152 // all the requisite properties). Is that the right thing to do? |
| 153 if (!value->IsObject()) { | 153 if (!value->IsObject()) { |
| 154 *error = "Wrong type"; | 154 *error = "Wrong type"; |
| 155 return nullptr; | 155 return false; |
| 156 } | 156 } |
| 157 v8::Local<v8::Object> object = value.As<v8::Object>(); | 157 v8::Local<v8::Object> object = value.As<v8::Object>(); |
| 158 return ConvertArgumentToObject(context, object, refs, error); | 158 return ParseArgumentToObject(context, object, refs, out_value, error); |
| 159 } | 159 } |
| 160 if (type_ == ArgumentType::LIST) { | 160 if (type_ == ArgumentType::LIST) { |
| 161 if (!value->IsArray()) { | 161 if (!value->IsArray()) { |
| 162 *error = "Wrong type"; | 162 *error = "Wrong type"; |
| 163 return nullptr; | 163 return false; |
| 164 } | 164 } |
| 165 v8::Local<v8::Array> array = value.As<v8::Array>(); | 165 v8::Local<v8::Array> array = value.As<v8::Array>(); |
| 166 return ConvertArgumentToArray(context, array, refs, error); | 166 return ParseArgumentToArray(context, array, refs, out_value, error); |
| 167 } | 167 } |
| 168 if (type_ == ArgumentType::ANY) | 168 if (type_ == ArgumentType::ANY) |
| 169 return ConvertArgumentToAny(context, value, error); | 169 return ParseArgumentToAny(context, value, out_value, error); |
| 170 NOTREACHED(); | 170 NOTREACHED(); |
| 171 return nullptr; | 171 return false; |
| 172 } | 172 } |
| 173 | 173 |
| 174 bool ArgumentSpec::IsFundamentalType() const { | 174 bool ArgumentSpec::IsFundamentalType() const { |
| 175 return type_ == ArgumentType::INTEGER || type_ == ArgumentType::DOUBLE || | 175 return type_ == ArgumentType::INTEGER || type_ == ArgumentType::DOUBLE || |
| 176 type_ == ArgumentType::BOOLEAN || type_ == ArgumentType::STRING; | 176 type_ == ArgumentType::BOOLEAN || type_ == ArgumentType::STRING; |
| 177 } | 177 } |
| 178 | 178 |
| 179 std::unique_ptr<base::Value> ArgumentSpec::ConvertArgumentToFundamental( | 179 bool ArgumentSpec::ParseArgumentToFundamental( |
| 180 v8::Local<v8::Context> context, | 180 v8::Local<v8::Context> context, |
| 181 v8::Local<v8::Value> value, | 181 v8::Local<v8::Value> value, |
| 182 std::unique_ptr<base::Value>* out_value, |
| 182 std::string* error) const { | 183 std::string* error) const { |
| 183 DCHECK(IsFundamentalType()); | 184 DCHECK(IsFundamentalType()); |
| 184 switch (type_) { | 185 switch (type_) { |
| 185 case ArgumentType::INTEGER: | 186 case ArgumentType::INTEGER: |
| 186 return GetFundamentalConvertedValueHelper<int32_t>(value, context, | 187 return ParseFundamentalValueHelper<int32_t>(value, context, minimum_, |
| 187 minimum_); | 188 out_value); |
| 188 case ArgumentType::DOUBLE: | 189 case ArgumentType::DOUBLE: |
| 189 return GetFundamentalConvertedValueHelper<double>(value, context, | 190 return ParseFundamentalValueHelper<double>(value, context, minimum_, |
| 190 minimum_); | 191 out_value); |
| 191 case ArgumentType::STRING: { | 192 case ArgumentType::STRING: { |
| 193 if (!value->IsString()) |
| 194 return false; |
| 195 // If we don't need to match enum values and don't need to convert, we're |
| 196 // done... |
| 197 if (!out_value && enum_values_.empty()) |
| 198 return true; |
| 199 // ...Otherwise, we need to convert to a std::string. |
| 192 std::string s; | 200 std::string s; |
| 193 // TODO(devlin): If base::StringValue ever takes a std::string&&, we could | 201 // We already checked that this is a string, so this should never fail. |
| 194 // use std::move to construct. | 202 CHECK(gin::Converter<std::string>::FromV8(context->GetIsolate(), value, |
| 195 if (!gin::Converter<std::string>::FromV8(context->GetIsolate(), | 203 &s)); |
| 196 value, &s) || | 204 if (!enum_values_.empty() && enum_values_.count(s) == 0) |
| 197 (!enum_values_.empty() && enum_values_.count(s) == 0)) { | 205 return false; |
| 198 return nullptr; | 206 if (out_value) { |
| 207 // TODO(devlin): If base::StringValue ever takes a std::string&&, we |
| 208 // could use std::move to construct. |
| 209 *out_value = base::MakeUnique<base::StringValue>(s); |
| 199 } | 210 } |
| 200 return base::MakeUnique<base::StringValue>(s); | 211 return true; |
| 201 } | 212 } |
| 202 case ArgumentType::BOOLEAN: { | 213 case ArgumentType::BOOLEAN: { |
| 203 bool b = false; | 214 if (!value->IsBoolean()) |
| 204 if (value->IsBoolean() && | 215 return false; |
| 205 gin::Converter<bool>::FromV8(context->GetIsolate(), value, &b)) { | 216 if (out_value) { |
| 206 return base::MakeUnique<base::FundamentalValue>(b); | 217 *out_value = base::MakeUnique<base::FundamentalValue>( |
| 218 value.As<v8::Boolean>()->Value()); |
| 207 } | 219 } |
| 208 return nullptr; | 220 return true; |
| 209 } | 221 } |
| 210 default: | 222 default: |
| 211 NOTREACHED(); | 223 NOTREACHED(); |
| 212 } | 224 } |
| 213 return nullptr; | 225 return false; |
| 214 } | 226 } |
| 215 | 227 |
| 216 std::unique_ptr<base::Value> ArgumentSpec::ConvertArgumentToObject( | 228 bool ArgumentSpec::ParseArgumentToObject( |
| 217 v8::Local<v8::Context> context, | 229 v8::Local<v8::Context> context, |
| 218 v8::Local<v8::Object> object, | 230 v8::Local<v8::Object> object, |
| 219 const RefMap& refs, | 231 const RefMap& refs, |
| 232 std::unique_ptr<base::Value>* out_value, |
| 220 std::string* error) const { | 233 std::string* error) const { |
| 221 DCHECK_EQ(ArgumentType::OBJECT, type_); | 234 DCHECK_EQ(ArgumentType::OBJECT, type_); |
| 222 auto result = base::MakeUnique<base::DictionaryValue>(); | 235 std::unique_ptr<base::DictionaryValue> result; |
| 236 // Only construct the result if we have an |out_value| to populate. |
| 237 if (out_value) |
| 238 result = base::MakeUnique<base::DictionaryValue>(); |
| 223 gin::Dictionary dictionary(context->GetIsolate(), object); | 239 gin::Dictionary dictionary(context->GetIsolate(), object); |
| 224 for (const auto& kv : properties_) { | 240 for (const auto& kv : properties_) { |
| 225 v8::Local<v8::Value> subvalue; | 241 v8::Local<v8::Value> subvalue; |
| 226 // See comment in ConvertArgumentToArray() about passing in custom crazy | 242 // See comment in ParseArgumentToArray() about passing in custom crazy |
| 227 // values here. | 243 // values here. |
| 228 // TODO(devlin): gin::Dictionary::Get() uses Isolate::GetCurrentContext() - | 244 // TODO(devlin): gin::Dictionary::Get() uses Isolate::GetCurrentContext() - |
| 229 // is that always right here, or should we use the v8::Object APIs and | 245 // is that always right here, or should we use the v8::Object APIs and |
| 230 // pass in |context|? | 246 // pass in |context|? |
| 231 // TODO(devlin): Hyper-optimization - Dictionary::Get() also creates a new | 247 // TODO(devlin): Hyper-optimization - Dictionary::Get() also creates a new |
| 232 // v8::String for each call. Hypothetically, we could cache these, or at | 248 // v8::String for each call. Hypothetically, we could cache these, or at |
| 233 // least use an internalized string. | 249 // least use an internalized string. |
| 234 if (!dictionary.Get(kv.first, &subvalue)) | 250 if (!dictionary.Get(kv.first, &subvalue)) |
| 235 return nullptr; | 251 return false; |
| 236 | 252 |
| 237 if (subvalue.IsEmpty() || subvalue->IsNull() || subvalue->IsUndefined()) { | 253 if (subvalue.IsEmpty() || subvalue->IsNull() || subvalue->IsUndefined()) { |
| 238 if (!kv.second->optional_) { | 254 if (!kv.second->optional_) { |
| 239 *error = "Missing key: " + kv.first; | 255 *error = "Missing key: " + kv.first; |
| 240 return nullptr; | 256 return false; |
| 241 } | 257 } |
| 242 continue; | 258 continue; |
| 243 } | 259 } |
| 244 std::unique_ptr<base::Value> property = | 260 std::unique_ptr<base::Value> property; |
| 245 kv.second->ConvertArgument(context, subvalue, refs, error); | 261 if (!kv.second->ParseArgument(context, subvalue, refs, |
| 246 if (!property) | 262 out_value ? &property : nullptr, error)) { |
| 247 return nullptr; | 263 return false; |
| 248 result->Set(kv.first, std::move(property)); | 264 } |
| 265 if (result) |
| 266 result->Set(kv.first, std::move(property)); |
| 249 } | 267 } |
| 250 return std::move(result); | 268 if (out_value) |
| 269 *out_value = std::move(result); |
| 270 return true; |
| 251 } | 271 } |
| 252 | 272 |
| 253 std::unique_ptr<base::Value> ArgumentSpec::ConvertArgumentToArray( | 273 bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context, |
| 254 v8::Local<v8::Context> context, | 274 v8::Local<v8::Array> value, |
| 255 v8::Local<v8::Array> value, | 275 const RefMap& refs, |
| 256 const RefMap& refs, | 276 std::unique_ptr<base::Value>* out_value, |
| 257 std::string* error) const { | 277 std::string* error) const { |
| 258 DCHECK_EQ(ArgumentType::LIST, type_); | 278 DCHECK_EQ(ArgumentType::LIST, type_); |
| 259 auto result = base::MakeUnique<base::ListValue>(); | 279 std::unique_ptr<base::ListValue> result; |
| 280 // Only construct the result if we have an |out_value| to populate. |
| 281 if (out_value) |
| 282 result = base::MakeUnique<base::ListValue>(); |
| 260 uint32_t length = value->Length(); | 283 uint32_t length = value->Length(); |
| 261 for (uint32_t i = 0; i < length; ++i) { | 284 for (uint32_t i = 0; i < length; ++i) { |
| 262 v8::MaybeLocal<v8::Value> maybe_subvalue = value->Get(context, i); | 285 v8::MaybeLocal<v8::Value> maybe_subvalue = value->Get(context, i); |
| 263 v8::Local<v8::Value> subvalue; | 286 v8::Local<v8::Value> subvalue; |
| 264 // Note: This can fail in the case of a developer passing in the following: | 287 // Note: This can fail in the case of a developer passing in the following: |
| 265 // var a = []; | 288 // var a = []; |
| 266 // Object.defineProperty(a, 0, { get: () => { throw new Error('foo'); } }); | 289 // Object.defineProperty(a, 0, { get: () => { throw new Error('foo'); } }); |
| 267 // Currently, this will cause the developer-specified error ('foo') to be | 290 // Currently, this will cause the developer-specified error ('foo') to be |
| 268 // thrown. | 291 // thrown. |
| 269 // TODO(devlin): This is probably fine, but it's worth contemplating | 292 // TODO(devlin): This is probably fine, but it's worth contemplating |
| 270 // catching the error and throwing our own. | 293 // catching the error and throwing our own. |
| 271 if (!maybe_subvalue.ToLocal(&subvalue)) | 294 if (!maybe_subvalue.ToLocal(&subvalue)) |
| 272 return nullptr; | 295 return false; |
| 273 std::unique_ptr<base::Value> item = | 296 std::unique_ptr<base::Value> item; |
| 274 list_element_type_->ConvertArgument(context, subvalue, refs, error); | 297 if (!list_element_type_->ParseArgument(context, subvalue, refs, |
| 275 if (!item) | 298 result ? &item : nullptr, error)) { |
| 276 return nullptr; | 299 return false; |
| 277 result->Append(std::move(item)); | 300 } |
| 301 if (result) |
| 302 result->Append(std::move(item)); |
| 278 } | 303 } |
| 279 return std::move(result); | 304 if (out_value) |
| 305 *out_value = std::move(result); |
| 306 return true; |
| 280 } | 307 } |
| 281 | 308 |
| 282 std::unique_ptr<base::Value> ArgumentSpec::ConvertArgumentToAny( | 309 bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context, |
| 283 v8::Local<v8::Context> context, | 310 v8::Local<v8::Value> value, |
| 284 v8::Local<v8::Value> value, | 311 std::unique_ptr<base::Value>* out_value, |
| 285 std::string* error) const { | 312 std::string* error) const { |
| 286 DCHECK_EQ(ArgumentType::ANY, type_); | 313 DCHECK_EQ(ArgumentType::ANY, type_); |
| 287 std::unique_ptr<content::V8ValueConverter> converter( | 314 if (out_value) { |
| 288 content::V8ValueConverter::create()); | 315 std::unique_ptr<content::V8ValueConverter> converter( |
| 289 std::unique_ptr<base::Value> converted( | 316 content::V8ValueConverter::create()); |
| 290 converter->FromV8Value(value, context)); | 317 std::unique_ptr<base::Value> converted( |
| 291 if (!converted) | 318 converter->FromV8Value(value, context)); |
| 292 *error = "Could not convert to 'any'."; | 319 if (!converted) { |
| 293 return converted; | 320 *error = "Could not convert to 'any'."; |
| 321 return false; |
| 322 } |
| 323 *out_value = std::move(converted); |
| 324 } |
| 325 return true; |
| 294 } | 326 } |
| 295 | 327 |
| 296 } // namespace extensions | 328 } // namespace extensions |
| OLD | NEW |