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 |