fix(renderer): support URLSearchParams subclasses

This commit is contained in:
ldm0
2026-09-05 04:18:26 +08:00
committed by Donough Liu
parent e47538d58f
commit f23e7367ee
3 changed files with 47 additions and 4 deletions
@@ -1,7 +1,6 @@
use super::form_data_runtime::{form_data_entries, form_data_is_object};
use super::url_form::{
apply_url_update, callback_arg_url_like_string, callback_value_string,
object_prototype_matches, url_object_value,
apply_url_update, callback_arg_url_like_string, callback_value_string, url_object_value,
};
use super::*;
use crate::util::{get_private_object, get_private_value, set_private_value};
@@ -102,8 +102,9 @@ pub(super) fn url_search_params_is_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
) -> bool {
object_prototype_matches(scope, object, "URLSearchParams")
&& url_search_params_pairs_array(scope, object).is_some()
// The internal slot is the WebIDL brand. A constructed subclass has this
// slot even though its direct prototype is not URLSearchParams.prototype.
url_search_params_pairs_array(scope, object).is_some()
}
pub(crate) fn url_search_params_request_body<'s>(
@@ -8337,6 +8337,49 @@ fn webidl_iterator_prototypes_use_v8_intrinsics_after_public_tampering() {
assert_eq!(result, "");
}
#[test]
fn url_search_params_subclasses_retain_their_webidl_brand() {
let mut vm = new_storage_test_vm("https://url-search-params-subclass.test/");
let result = vm
.eval(
r#"
(() => {
class ReadonlyURLSearchParams extends URLSearchParams {
append() { throw new Error('readonly'); }
delete() { throw new Error('readonly'); }
set() { throw new Error('readonly'); }
sort() { throw new Error('readonly'); }
}
const params = new ReadonlyURLSearchParams('loc=fr&loc=de&empty=');
const seen = [];
params.forEach((value, name, owner) => {
seen.push(`${name}:${value}:${owner === params}`);
});
return JSON.stringify({
directPrototypeIsSubclass:
Object.getPrototypeOf(params) === ReadonlyURLSearchParams.prototype,
get: params.get('loc'),
getAll: params.getAll('loc').join(','),
has: params.has('empty'),
size: params.size,
entries: Array.from(params.entries()).map(pair => pair.join(':')).join(','),
seen,
serialized: params.toString()
});
})()
"#,
)
.expect("URLSearchParams subclass WebIDL operations should evaluate");
assert_eq!(
result,
r#"{"directPrototypeIsSubclass":true,"get":"fr","getAll":"fr,de","has":true,"size":3,"entries":"loc:fr,loc:de,empty:","seen":["loc:fr:true","loc:de:true","empty::true"],"serialized":"loc=fr&loc=de&empty="}"#
);
}
#[test]
fn url_and_search_params_declared_slots_ignore_prototype_spoofing() {
let mut vm = new_storage_test_vm("https://url-declared-slots.test/");