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