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 |