@@ -165,6 +165,10 @@ void URLPattern::New(const FunctionCallbackInfo<Value>& args) { |
165 | 165 | input = input_buffer.ToString(); |
166 | 166 | } else if (args[0]->IsObject()) { |
167 | 167 | init = URLPatternInit::FromJsObject(env, args[0].As<Object>()); |
| 168 | + // If init does not have a value here, the implication is that an |
| 169 | + // error was thrown. Let's allow that to be handled now by returning |
| 170 | + // early. If we don't, the error thrown will be swallowed. |
| 171 | + if (!init) return; |
168 | 172 | } else { |
169 | 173 | THROW_ERR_INVALID_ARG_TYPE(env, "Input must be an object or a string"); |
170 | 174 | return; |
@@ -180,7 +184,9 @@ void URLPattern::New(const FunctionCallbackInfo<Value>& args) { |
180 | 184 | CHECK(!options.has_value()); |
181 | 185 | options = URLPatternOptions::FromJsObject(env, args[1].As<Object>()); |
182 | 186 | if (!options) { |
183 | | - THROW_ERR_INVALID_ARG_TYPE(env, "options.ignoreCase must be a boolean"); |
| 187 | + // If options does not have a value, we assume an error was |
| 188 | + // thrown and scheduled on the isolate. Return early to |
| 189 | + // propagate it. |
184 | 190 | return; |
185 | 191 | } |
186 | 192 | } else { |
@@ -197,7 +203,9 @@ void URLPattern::New(const FunctionCallbackInfo<Value>& args) { |
197 | 203 | CHECK(!options.has_value()); |
198 | 204 | options = URLPatternOptions::FromJsObject(env, args[2].As<Object>()); |
199 | 205 | if (!options) { |
200 | | - THROW_ERR_INVALID_ARG_TYPE(env, "options.ignoreCase must be a boolean"); |
| 206 | + // If options does not have a value, we assume an error was |
| 207 | + // thrown and scheduled on the isolate. Return early to |
| 208 | + // propagate it. |
201 | 209 | return; |
202 | 210 | } |
203 | 211 | } |
@@ -234,73 +242,29 @@ MaybeLocal<Value> URLPattern::URLPatternInit::ToJsObject( |
234 | 242 | auto isolate = env->isolate(); |
235 | 243 | auto context = env->context(); |
236 | 244 | auto result = Object::New(isolate); |
237 | | - if (init.protocol) { |
238 | | - Local<Value> protocol; |
239 | | - if (!ToV8Value(context, *init.protocol).ToLocal(&protocol) || |
240 | | - result->Set(context, env->protocol_string(), protocol).IsNothing()) { |
241 | | - return {}; |
242 | | - } |
243 | | - } |
244 | | - if (init.username) { |
245 | | - Local<Value> username; |
246 | | - if (!ToV8Value(context, *init.username).ToLocal(&username) || |
247 | | - result->Set(context, env->username_string(), username).IsNothing()) { |
248 | | - return {}; |
249 | | - } |
250 | | - } |
251 | | - if (init.password) { |
252 | | - Local<Value> password; |
253 | | - if (!ToV8Value(context, *init.password).ToLocal(&password) || |
254 | | - result->Set(context, env->password_string(), password).IsNothing()) { |
255 | | - return {}; |
256 | | - } |
257 | | - } |
258 | | - if (init.hostname) { |
259 | | - Local<Value> hostname; |
260 | | - if (!ToV8Value(context, *init.hostname).ToLocal(&hostname) || |
261 | | - result->Set(context, env->hostname_string(), hostname).IsNothing()) { |
262 | | - return {}; |
263 | | - } |
264 | | - } |
265 | | - if (init.port) { |
266 | | - Local<Value> port; |
267 | | - if (!ToV8Value(context, *init.port).ToLocal(&port) || |
268 | | - result->Set(context, env->port_string(), port).IsNothing()) { |
269 | | - return {}; |
270 | | - } |
271 | | - } |
272 | | - if (init.pathname) { |
273 | | - Local<Value> pathname; |
274 | | - if (!ToV8Value(context, *init.pathname).ToLocal(&pathname) || |
275 | | - result->Set(context, env->pathname_string(), pathname).IsNothing()) { |
276 | | - return {}; |
277 | | - } |
278 | | - } |
279 | | - if (init.search) { |
280 | | - Local<Value> search; |
281 | | - if (!ToV8Value(context, *init.search).ToLocal(&search) || |
282 | | - result->Set(context, env->search_string(), search).IsNothing()) { |
283 | | - return {}; |
284 | | - } |
285 | | - } |
286 | | - if (init.hash) { |
287 | | - Local<Value> hash; |
288 | | - if (!ToV8Value(context, *init.hash).ToLocal(&hash) || |
289 | | - result->Set(context, env->hash_string(), hash).IsNothing()) { |
290 | | - return {}; |
291 | | - } |
292 | | - } |
293 | | - if (init.base_url) { |
294 | | - Local<Value> base_url; |
295 | | - if (!ToV8Value(context, *init.base_url).ToLocal(&base_url) || |
296 | | - result->Set(context, env->base_url_string(), base_url).IsNothing()) { |
297 | | - return {}; |
298 | | - } |
| 245 | + |
| 246 | + const auto trySet = [&](auto name, const std::optional<std::string>& val) { |
| 247 | + if (!val) return true; |
| 248 | + Local<Value> temp; |
| 249 | + return ToV8Value(context, *val).ToLocal(&temp) && |
| 250 | + result->Set(context, name, temp).IsJust(); |
| 251 | + }; |
| 252 | + |
| 253 | + if (!trySet(env->protocol_string(), init.protocol) || |
| 254 | + !trySet(env->username_string(), init.username) || |
| 255 | + !trySet(env->password_string(), init.password) || |
| 256 | + !trySet(env->hostname_string(), init.hostname) || |
| 257 | + !trySet(env->port_string(), init.port) || |
| 258 | + !trySet(env->pathname_string(), init.pathname) || |
| 259 | + !trySet(env->search_string(), init.search) || |
| 260 | + !trySet(env->hash_string(), init.hash) || |
| 261 | + !trySet(env->base_url_string(), init.base_url)) { |
| 262 | + return {}; |
299 | 263 | } |
300 | 264 | return result; |
301 | 265 | } |
302 | 266 |
|
303 | | -ada::url_pattern_init URLPattern::URLPatternInit::FromJsObject( |
| 267 | +std::optional<ada::url_pattern_init> URLPattern::URLPatternInit::FromJsObject( |
304 | 268 | Environment* env, Local<Object> obj) { |
305 | 269 | ada::url_pattern_init init{}; |
306 | 270 | Local<String> components[] = { |
@@ -344,6 +308,10 @@ ada::url_pattern_init URLPattern::URLPatternInit::FromJsObject( |
344 | 308 | Utf8Value utf8_value(isolate, value); |
345 | 309 | set_parameter(key.ToStringView(), utf8_value.ToStringView()); |
346 | 310 | } |
| 311 | + } else { |
| 312 | + // If ToLocal failed then we assume an error occurred, |
| 313 | + // bail out early to propagate the error. |
| 314 | + return std::nullopt; |
347 | 315 | } |
348 | 316 | } |
349 | 317 | return init; |
@@ -355,12 +323,8 @@ MaybeLocal<Object> URLPattern::URLPatternComponentResult::ToJSObject( |
355 | 323 | auto context = env->context(); |
356 | 324 | auto parsed_group = Object::New(isolate); |
357 | 325 | for (const auto& [group_key, group_value] : result.groups) { |
358 | | - Local<String> key; |
359 | | - if (!String::NewFromUtf8(isolate, |
360 | | - group_key.c_str(), |
361 | | - NewStringType::kNormal, |
362 | | - group_key.size()) |
363 | | - .ToLocal(&key)) { |
| 326 | + Local<Value> key; |
| 327 | + if (!ToV8Value(context, group_key).ToLocal(&key)) { |
364 | 328 | return {}; |
365 | 329 | } |
366 | 330 | Local<Value> value; |
@@ -371,7 +335,9 @@ MaybeLocal<Object> URLPattern::URLPatternComponentResult::ToJSObject( |
371 | 335 | } else { |
372 | 336 | value = Undefined(isolate); |
373 | 337 | } |
374 | | - USE(parsed_group->Set(context, key, value)); |
| 338 | + if (parsed_group->Set(context, key, value).IsNothing()) { |
| 339 | + return {}; |
| 340 | + } |
375 | 341 | } |
376 | 342 | Local<Value> input; |
377 | 343 | if (!ToV8Value(env->context(), result.input).ToLocal(&input)) { |
@@ -418,34 +384,20 @@ MaybeLocal<Value> URLPattern::URLPatternResult::ToJSValue( |
418 | 384 | LocalVector<Value> values(isolate, arraysize(names)); |
419 | 385 | values[0] = Array::New(isolate, inputs.data(), inputs.size()); |
420 | 386 | if (!URLPatternComponentResult::ToJSObject(env, result.protocol) |
421 | | - .ToLocal(&values[1])) { |
422 | | - return {}; |
423 | | - } |
424 | | - if (!URLPatternComponentResult::ToJSObject(env, result.username) |
425 | | - .ToLocal(&values[2])) { |
426 | | - return {}; |
427 | | - } |
428 | | - if (!URLPatternComponentResult::ToJSObject(env, result.password) |
429 | | - .ToLocal(&values[3])) { |
430 | | - return {}; |
431 | | - } |
432 | | - if (!URLPatternComponentResult::ToJSObject(env, result.hostname) |
433 | | - .ToLocal(&values[4])) { |
434 | | - return {}; |
435 | | - } |
436 | | - if (!URLPatternComponentResult::ToJSObject(env, result.port) |
437 | | - .ToLocal(&values[5])) { |
438 | | - return {}; |
439 | | - } |
440 | | - if (!URLPatternComponentResult::ToJSObject(env, result.pathname) |
441 | | - .ToLocal(&values[6])) { |
442 | | - return {}; |
443 | | - } |
444 | | - if (!URLPatternComponentResult::ToJSObject(env, result.search) |
445 | | - .ToLocal(&values[7])) { |
446 | | - return {}; |
447 | | - } |
448 | | - if (!URLPatternComponentResult::ToJSObject(env, result.hash) |
| 387 | + .ToLocal(&values[1]) || |
| 388 | + !URLPatternComponentResult::ToJSObject(env, result.username) |
| 389 | + .ToLocal(&values[2]) || |
| 390 | + !URLPatternComponentResult::ToJSObject(env, result.password) |
| 391 | + .ToLocal(&values[3]) || |
| 392 | + !URLPatternComponentResult::ToJSObject(env, result.hostname) |
| 393 | + .ToLocal(&values[4]) || |
| 394 | + !URLPatternComponentResult::ToJSObject(env, result.port) |
| 395 | + .ToLocal(&values[5]) || |
| 396 | + !URLPatternComponentResult::ToJSObject(env, result.pathname) |
| 397 | + .ToLocal(&values[6]) || |
| 398 | + !URLPatternComponentResult::ToJSObject(env, result.search) |
| 399 | + .ToLocal(&values[7]) || |
| 400 | + !URLPatternComponentResult::ToJSObject(env, result.hash) |
449 | 401 | .ToLocal(&values[8])) { |
450 | 402 | return {}; |
451 | 403 | } |
@@ -461,9 +413,15 @@ URLPattern::URLPatternOptions::FromJsObject(Environment* env, |
461 | 413 | if (obj->Get(env->context(), env->ignore_case_string()) |
462 | 414 | .ToLocal(&ignore_case)) { |
463 | 415 | if (!ignore_case->IsBoolean()) { |
| 416 | + THROW_ERR_INVALID_ARG_TYPE(env, "options.ignoreCase must be a boolean"); |
464 | 417 | return std::nullopt; |
465 | 418 | } |
466 | 419 | options.ignore_case = ignore_case->IsTrue(); |
| 420 | + } else { |
| 421 | + // If ToLocal returns false, the assumption is that getting the |
| 422 | + // ignore_case_string threw an error, let's propagate that now |
| 423 | + // by returning std::nullopt. |
| 424 | + return std::nullopt; |
467 | 425 | } |
468 | 426 | return options; |
469 | 427 | } |
@@ -553,7 +511,9 @@ void URLPattern::Exec(const FunctionCallbackInfo<Value>& args) { |
553 | 511 | input_base = input_value.ToString(); |
554 | 512 | input = std::string_view(input_base); |
555 | 513 | } else if (args[0]->IsObject()) { |
556 | | - input = URLPatternInit::FromJsObject(env, args[0].As<Object>()); |
| 514 | + auto maybeInput = URLPatternInit::FromJsObject(env, args[0].As<Object>()); |
| 515 | + if (!maybeInput.has_value()) return; |
| 516 | + input = std::move(*maybeInput); |
557 | 517 | } else { |
558 | 518 | THROW_ERR_INVALID_ARG_TYPE( |
559 | 519 | env, "URLPattern input needs to be a string or an object"); |
@@ -594,7 +554,9 @@ void URLPattern::Test(const FunctionCallbackInfo<Value>& args) { |
594 | 554 | input_base = input_value.ToString(); |
595 | 555 | input = std::string_view(input_base); |
596 | 556 | } else if (args[0]->IsObject()) { |
597 | | - input = URLPatternInit::FromJsObject(env, args[0].As<Object>()); |
| 557 | + auto maybeInput = URLPatternInit::FromJsObject(env, args[0].As<Object>()); |
| 558 | + if (!maybeInput.has_value()) return; |
| 559 | + input = std::move(*maybeInput); |
598 | 560 | } else { |
599 | 561 | THROW_ERR_INVALID_ARG_TYPE( |
600 | 562 | env, "URLPattern input needs to be a string or an object"); |
|