Skip to content

Commit eae9cc4

Browse files
fix: align null init, record Symbol keys, and receiver checks with WebIDL
- new URLSearchParams(null) parses the string "null" (so "null="). The IDL default "" applies only to undefined/missing; null goes through the union conversion, which has no null special case (the union is not nullable and a record is not a dictionary), so it lands in the USVString branch. Matches the whatwg-url reference implementation; Node treats null as empty and is the outlier, and WPT does not cover the case. - An own enumerable Symbol key in a record init throws a TypeError instead of being silently skipped: WebIDL record conversion takes the keys from [[OwnPropertyKeys]] filtered by enumerability only, and converting a Symbol key to a USVString throws. - The remaining members (get/getAll/has/append/set/delete/forEach/ sort/toString/size) brand-check their receiver and throw an "Illegal invocation" TypeError for a foreign object, consistent with entries()/keys()/values().
1 parent 65bf3bf commit eae9cc4

2 files changed

Lines changed: 60 additions & 19 deletions

File tree

test-app/app/src/main/assets/app/tests/testURLSearchParamsImpl.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,23 @@ describe("Test URLSearchParams ", function () {
406406
expect(function(){ new URLSearchParams(Symbol("x")); }).toThrow();
407407
});
408408

409-
it("Test URLSearchParams from null or undefined is empty", function(){
410-
expect(new URLSearchParams(null).toString()).toBe("");
409+
it("Test URLSearchParams from undefined or no argument is empty", function(){
411410
expect(new URLSearchParams(undefined).toString()).toBe("");
411+
expect(new URLSearchParams().toString()).toBe("");
412+
});
413+
414+
it("Test URLSearchParams from null parses as the string null", function(){
415+
// The IDL union has no null special case (the type is not nullable and
416+
// a record is not a dictionary), so null coerces like any primitive.
417+
const params = new URLSearchParams(null);
418+
expect(params.toString()).toBe("null=");
419+
expect(params.get("null")).toBe("");
420+
});
421+
422+
it("Test URLSearchParams throws when a record key is a Symbol", function(){
423+
// Per WebIDL record conversion every own enumerable key is converted to
424+
// a USVString, and converting a Symbol throws.
425+
expect(function(){ new URLSearchParams({ a: "1", [Symbol("x")]: "v" }); }).toThrow();
412426
});
413427

414428
// --- The name argument is a USVString: coerced, not assumed. ---
@@ -472,6 +486,19 @@ describe("Test URLSearchParams ", function () {
472486
expect(function(){ params.values.call({}); }).toThrow();
473487
});
474488

489+
it("Test URLSearchParams methods throw on a foreign receiver", function(){
490+
const params = new URLSearchParams("a=1");
491+
expect(function(){ params.get.call({}, "a"); }).toThrow();
492+
expect(function(){ params.getAll.call({}, "a"); }).toThrow();
493+
expect(function(){ params.has.call({}, "a"); }).toThrow();
494+
expect(function(){ params.append.call({}, "a", "b"); }).toThrow();
495+
expect(function(){ params.set.call({}, "a", "b"); }).toThrow();
496+
expect(function(){ params.delete.call({}, "a"); }).toThrow();
497+
expect(function(){ params.forEach.call({}, function(){}); }).toThrow();
498+
expect(function(){ params.sort.call({}); }).toThrow();
499+
expect(function(){ params.toString.call({}); }).toThrow();
500+
});
501+
475502
it("Test URLSearchParams iterator next() throws on a foreign receiver", function(){
476503
const params = new URLSearchParams("a=1");
477504
const iterator = params.entries();

test-app/runtime/src/main/cpp/URLSearchParamsImpl.cpp

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ namespace tns {
9999
// `(sequence<sequence<USVString>> or record<USVString, USVString> or USVString)`
100100
// that browsers/Node apply (https://url.spec.whatwg.org/#interface-urlsearchparams):
101101
// object with @@iterator -> sequence (Array, Map, Set, URLSearchParams, generator)
102-
// object without @@iterator-> record (own enumerable string keys, in order)
103-
// other primitive -> USVString (number/boolean/bigint -> string; Symbol throws)
104-
// null / undefined -> empty
102+
// object without @@iterator-> record (own enumerable keys, in order; a Symbol key throws)
103+
// other primitive / null -> USVString (number/boolean/bigint/null -> string; Symbol throws)
104+
// undefined / missing -> empty (the IDL default "")
105105
namespace {
106106
void ThrowTypeError(v8::Isolate *isolate, const char *message) {
107107
isolate->ThrowException(
@@ -448,12 +448,15 @@ namespace tns {
448448
}
449449

450450
// Record init form: a plain object of name -> value, iterated in own
451-
// enumerable string-key order. A value that cannot be coerced to a string
452-
// aborts with the JS exception left pending.
451+
// enumerable key order ([[OwnPropertyKeys]]: strings, then symbols). Per
452+
// WebIDL record conversion every enumerable key is converted to a
453+
// USVString, so an own enumerable Symbol key throws a TypeError
454+
// (ValueToString below raises it); symbols are NOT silently skipped. A
455+
// key or value that cannot be coerced to a string aborts with the JS
456+
// exception left pending.
453457
bool BuildFromRecord(v8::Local<v8::Context> context, v8::Local<v8::Object> object,
454458
ada::url_search_params &params) {
455-
auto filter = static_cast<v8::PropertyFilter>(
456-
v8::PropertyFilter::ONLY_ENUMERABLE | v8::PropertyFilter::SKIP_SYMBOLS);
459+
auto filter = v8::PropertyFilter::ONLY_ENUMERABLE;
457460
v8::Local<v8::Array> keys;
458461
if (!object->GetOwnPropertyNames(context, filter,
459462
v8::KeyConversionMode::kConvertToString).ToLocal(&keys)) {
@@ -516,17 +519,21 @@ namespace tns {
516519
ThrowTypeError(isolate, "URLSearchParams init Symbol.iterator is not a function");
517520
return;
518521
}
519-
} else if (!value->IsNullOrUndefined()) {
520-
// Other primitive (number / boolean / bigint / symbol): coerce to a
521-
// USVString and run the urlencoded string parser. A Symbol cannot be
522-
// converted and throws here, matching the spec.
522+
} else if (!value->IsUndefined()) {
523+
// Other primitive (number / boolean / bigint / symbol / null): coerce
524+
// to a USVString and run the urlencoded string parser. The WebIDL
525+
// union conversion has no null special case (the union is not
526+
// nullable and a record is not a dictionary), so
527+
// new URLSearchParams(null) parses the string "null", as the
528+
// reference implementation does. A Symbol cannot be converted and
529+
// throws here, matching the spec.
523530
std::string init;
524531
if (!ValueToString(context, value, init)) {
525532
return;
526533
}
527534
params = ada::url_search_params(init);
528535
}
529-
// null / undefined -> leave params empty
536+
// undefined / missing -> the IDL default "" -> leave params empty
530537

531538
auto searchParams = new URLSearchParamsImpl(params);
532539

@@ -541,6 +548,9 @@ namespace tns {
541548
void URLSearchParamsImpl::Append(const v8::FunctionCallbackInfo<v8::Value> &args) {
542549
URLSearchParamsImpl *ptr = GetPointer(args.This());
543550
if (ptr == nullptr) {
551+
// WebIDL brand check: every member requires a genuine URLSearchParams
552+
// receiver (same as entries()/keys()/values()).
553+
ThrowTypeError(args.GetIsolate(), "Illegal invocation");
544554
return;
545555
}
546556
// Both arguments are USVStrings (url.bs:3860); a Symbol or throwing
@@ -558,6 +568,7 @@ namespace tns {
558568
void URLSearchParamsImpl::Delete(const v8::FunctionCallbackInfo<v8::Value> &args) {
559569
URLSearchParamsImpl *ptr = GetPointer(args.This());
560570
if (ptr == nullptr) {
571+
ThrowTypeError(args.GetIsolate(), "Illegal invocation");
561572
return;
562573
}
563574
// The name is a USVString (url.bs:3861): coerce it like the optional
@@ -597,6 +608,7 @@ namespace tns {
597608
auto isolate = args.GetIsolate();
598609
auto context = isolate->GetCurrentContext();
599610
if (ptr == nullptr) {
611+
ThrowTypeError(isolate, "Illegal invocation");
600612
return;
601613
}
602614
auto callback = args[0].As<v8::Function>();
@@ -628,7 +640,7 @@ namespace tns {
628640
URLSearchParamsImpl *ptr = GetPointer(args.This());
629641
auto isolate = args.GetIsolate();
630642
if (ptr == nullptr) {
631-
args.GetReturnValue().SetUndefined();
643+
ThrowTypeError(isolate, "Illegal invocation");
632644
return;
633645
}
634646
// The name is a USVString (url.bs:3862); a Symbol or throwing toString
@@ -653,7 +665,7 @@ namespace tns {
653665
auto isolate = args.GetIsolate();
654666
auto context = isolate->GetCurrentContext();
655667
if (ptr == nullptr) {
656-
args.GetReturnValue().Set(v8::Array::New(isolate));
668+
ThrowTypeError(isolate, "Illegal invocation");
657669
return;
658670
}
659671
// The name is a USVString (url.bs:3863); a Symbol or throwing toString
@@ -674,7 +686,7 @@ namespace tns {
674686
void URLSearchParamsImpl::Has(const v8::FunctionCallbackInfo<v8::Value> &args) {
675687
URLSearchParamsImpl *ptr = GetPointer(args.This());
676688
if (ptr == nullptr) {
677-
args.GetReturnValue().Set(false);
689+
ThrowTypeError(args.GetIsolate(), "Illegal invocation");
678690
return;
679691
}
680692
// The name is a USVString (url.bs:3864): coerce it like the optional
@@ -713,6 +725,7 @@ namespace tns {
713725
void URLSearchParamsImpl::Set(const v8::FunctionCallbackInfo<v8::Value> &args) {
714726
URLSearchParamsImpl *ptr = GetPointer(args.This());
715727
if (ptr == nullptr) {
728+
ThrowTypeError(args.GetIsolate(), "Illegal invocation");
716729
return;
717730
}
718731
// Both arguments are USVStrings (url.bs:3865); a Symbol or throwing
@@ -731,7 +744,7 @@ namespace tns {
731744
const v8::PropertyCallbackInfo<v8::Value> &info) {
732745
URLSearchParamsImpl *ptr = GetPointer(info.This());
733746
if (ptr == nullptr) {
734-
info.GetReturnValue().Set(0);
747+
ThrowTypeError(info.GetIsolate(), "Illegal invocation");
735748
return;
736749
}
737750

@@ -743,6 +756,7 @@ namespace tns {
743756
void URLSearchParamsImpl::Sort(const v8::FunctionCallbackInfo<v8::Value> &args) {
744757
URLSearchParamsImpl *ptr = GetPointer(args.This());
745758
if (ptr == nullptr) {
759+
ThrowTypeError(args.GetIsolate(), "Illegal invocation");
746760
return;
747761
}
748762
ptr->GetURLSearchParams()->sort();
@@ -751,7 +765,7 @@ namespace tns {
751765
void URLSearchParamsImpl::ToString(const v8::FunctionCallbackInfo<v8::Value> &args) {
752766
URLSearchParamsImpl *ptr = GetPointer(args.This());
753767
if (ptr == nullptr) {
754-
args.GetReturnValue().SetEmptyString();
768+
ThrowTypeError(args.GetIsolate(), "Illegal invocation");
755769
return;
756770
}
757771
auto isolate = args.GetIsolate();

0 commit comments

Comments
 (0)