diff --git a/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime.rs b/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime.rs index 6235bf2619..a19aec44e2 100644 --- a/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime.rs @@ -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}; diff --git a/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/storage.rs b/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/storage.rs index 9313820fc0..bb3bebbb37 100644 --- a/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/storage.rs +++ b/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/storage.rs @@ -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>( diff --git a/moli-renderer-v8/src/script_vm/tests/webidl_fetch.rs b/moli-renderer-v8/src/script_vm/tests/webidl_fetch.rs index 8eacf6961e..f4b4c4bc5e 100644 --- a/moli-renderer-v8/src/script_vm/tests/webidl_fetch.rs +++ b/moli-renderer-v8/src/script_vm/tests/webidl_fetch.rs @@ -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/");