diff --git a/moli-renderer-v8/src/context_bootstrap/form_data_runtime.rs b/moli-renderer-v8/src/context_bootstrap/form_data_runtime.rs index e43fba0a44..23443c310d 100644 --- a/moli-renderer-v8/src/context_bootstrap/form_data_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/form_data_runtime.rs @@ -15,7 +15,7 @@ pub(crate) use request_body::{ form_data_entries_multipart_body_with_prefix, form_data_request_body, }; pub(crate) use serialize::form_data_entries_to_string_pairs; -pub(in crate::context_bootstrap) use storage::{form_data_entries, form_data_is_object}; +pub(in crate::context_bootstrap) use storage::form_data_is_object; pub(super) use template::build_form_data_constructor_template; pub(crate) fn form_data_object_from_entries<'s>( diff --git a/moli-renderer-v8/src/context_bootstrap/url_form.rs b/moli-renderer-v8/src/context_bootstrap/url_form.rs index c9fb922a76..e34d86f9d1 100644 --- a/moli-renderer-v8/src/context_bootstrap/url_form.rs +++ b/moli-renderer-v8/src/context_bootstrap/url_form.rs @@ -6,8 +6,5 @@ mod callbacks; mod helpers; mod template; -pub(super) use helpers::{ - apply_url_update, callback_arg_url_like_string, callback_value_string, url_href_slot, - url_object_value, -}; +pub(super) use helpers::{apply_url_update, callback_value_string, url_object_value}; pub(super) use template::build_url_constructor_template; diff --git a/moli-renderer-v8/src/context_bootstrap/url_form/helpers.rs b/moli-renderer-v8/src/context_bootstrap/url_form/helpers.rs index a922ada112..37957893e3 100644 --- a/moli-renderer-v8/src/context_bootstrap/url_form/helpers.rs +++ b/moli-renderer-v8/src/context_bootstrap/url_form/helpers.rs @@ -11,21 +11,6 @@ pub(in crate::context_bootstrap) fn callback_value_string( .map(|value| value.to_rust_string_lossy(scope)) } -pub(in crate::context_bootstrap) fn callback_arg_url_like_string<'s>( - scope: &mut v8::PinScope<'s, '_>, - value: v8::Local<'s, v8::Value>, -) -> Option { - if value.is_null_or_undefined() { - return None; - } - if let Ok(object) = v8::Local::::try_from(value) - && let Some(href) = url_href_slot(scope, object) - { - return Some(href); - } - callback_value_string(scope, value) -} - pub(super) fn resolve_url_constructor_input( input: &str, base: Option<&str>, 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 a719bf1fde..82f9a469b4 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,4 @@ -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, url_object_value, -}; +use super::url_form::{apply_url_update, callback_value_string, url_object_value}; use super::*; use crate::util::{get_private_object, get_private_value, set_private_value}; use crate::web_api_interfaces; diff --git a/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/parse.rs b/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/parse.rs index 963c30a751..a79e6efcf8 100644 --- a/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/parse.rs +++ b/moli-renderer-v8/src/context_bootstrap/url_search_params_runtime/parse.rs @@ -1,6 +1,4 @@ -use super::storage::{url_search_params_is_object, url_search_params_pairs}; use super::*; -use crate::context_bootstrap::url_form::url_href_slot; use crate::webidl; use moli_url::search_params::{SearchParamPair, parse_search_params}; @@ -38,17 +36,6 @@ pub(super) fn url_search_params_pairs_from_constructor<'s>( return Some(Vec::new()); } if let Ok(object) = v8::Local::::try_from(value) { - if form_data_is_object(scope, object) { - return Some( - form_data_entries(scope, object) - .into_iter() - .filter_map(|(key, value)| { - callback_value_string(scope, v8::Local::new(scope, &value)) - .map(|value| (key, value)) - }) - .collect(), - ); - } let sequence = match webidl::convert_optional_sequence::( scope, value, @@ -64,17 +51,6 @@ pub(super) fn url_search_params_pairs_from_constructor<'s>( if let Some(sequence) = sequence { return Some(sequence.0.into_iter().map(|pair| pair.0).collect()); } - if url_search_params_is_object(scope, object) { - return Some(url_search_params_pairs(scope, object)); - } - if url_href_slot(scope, object).is_some() { - return Some( - callback_arg_url_like_string(scope, value) - .as_deref() - .map(parse_search_params) - .unwrap_or_default(), - ); - } return record_string_pairs(scope, object.into()); } Some( diff --git a/moli-renderer-v8/src/network_host/headers/store/init.rs b/moli-renderer-v8/src/network_host/headers/store/init.rs index 43ac8c3724..43028e2a66 100644 --- a/moli-renderer-v8/src/network_host/headers/store/init.rs +++ b/moli-renderer-v8/src/network_host/headers/store/init.rs @@ -1,4 +1,4 @@ -use super::entries::{headers_entries_if_present, normalized_header_entry_or_throw}; +use super::entries::normalized_header_entry_or_throw; use crate::webidl; pub(in crate::network_host) fn headers_entries_from_init<'s>( @@ -24,10 +24,6 @@ pub(in crate::network_host) fn headers_entries_from_init<'s>( return Ok(entries); } - if let Some(entries) = headers_entries_if_present(scope, init_obj) { - return Ok(entries); - } - let record = webidl::convert::>( scope, init_obj.into(), 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 48a28c5baf..e97e0ac1f4 100644 --- a/moli-renderer-v8/src/script_vm/tests/webidl_fetch.rs +++ b/moli-renderer-v8/src/script_vm/tests/webidl_fetch.rs @@ -6708,6 +6708,144 @@ fn vtt_cue_constructor_applies_webidl_conversion() { ); } +#[test] +fn webidl_initializer_unions_observe_iterators_on_platform_objects() { + let mut vm = new_storage_test_vm("https://initializer-union.test/"); + let result = vm.eval(r#" +(() => { + const check = (condition, label) => { if (!condition) throw new Error(label); }; + const equal = (actual, expected, label) => check( + JSON.stringify(actual) === JSON.stringify(expected), label + ': ' + JSON.stringify(actual)); + const factories = [ + ['Headers', () => new Headers([['x-original', 'original']])], + ['URLSearchParams', () => new URLSearchParams([['x-original', 'original']])], + ['FormData', () => { + const form = new FormData(); + form.append('x-original', 'original'); + return form; + }] + ]; + const consumers = [ + ['Headers', input => new Headers(input)], + ['URLSearchParams', input => new URLSearchParams(input)], + ['Request', input => new Request('https://initializer-union.test/', {headers: input}).headers], + ['Response', input => new Response(null, {headers: input}).headers] + ]; + for (const [target, create] of consumers) { + for (const [source, factory] of factories) { + for (const mode of ['original', 'custom', 'undefined', 'null', 'throw', 'noncallable']) { + const label = target + ' from ' + source + ' with ' + mode + ' iterator'; + const input = factory(); + const original = input[Symbol.iterator]; + const marker = {}; + const log = []; + Object.defineProperty(input, 'x-record', {enumerable: true, get() { + log.push('record'); + return 'record'; + }}); + Object.defineProperty(input, Symbol.iterator, {get() { + log.push('iterator'); + if (mode === 'throw') throw marker; + if (mode === 'noncallable') return 42; + if (mode === 'undefined') return undefined; + if (mode === 'null') return null; + if (mode === 'original') return original; + return function*() { + check(this === input, label + ' receiver'); + log.push('iterate'); + yield ['x-custom', 'custom']; + }; + }}); + if (mode === 'throw' || mode === 'noncallable') { + let caught; + try { create(input); } catch (error) { caught = error; } + check(mode === 'throw' ? caught === marker : caught instanceof TypeError, label + ' exception'); + } else { + const expected = mode === 'original' ? [['x-original', 'original']] : + mode === 'custom' ? [['x-custom', 'custom']] : [['x-record', 'record']]; + equal(Array.from(create(input)), expected, label); + } + const expectedLog = ['iterator']; + if (mode === 'custom') expectedLog.push('iterate'); + if (mode === 'undefined' || mode === 'null') expectedLog.push('record'); + equal(log, expectedLog, label + ' conversion order'); + } + } + } + return 'ok'; +})() +"#).unwrap(); + assert_eq!(result, "ok"); +} + +#[test] +fn webidl_initializer_unions_convert_platform_objects_without_iterators_as_records() { + let mut vm = new_storage_test_vm("https://initializer-record.test/"); + let result = vm.eval(r#" +(() => { + for (const Constructor of [Headers, URLSearchParams]) { + const url = new URL('https://initializer-record.test/path?query=value'); + Object.defineProperty(url, Symbol.toPrimitive, {value() { + throw new Error('URL object must use record conversion'); + }}); + if (Array.from(new Constructor(url)).length !== 0) throw new Error('empty URL record'); + url['x-record'] = 'record'; + if (JSON.stringify(Array.from(new Constructor(url))) !== '[["x-record","record"]]') { + throw new Error(Constructor.name + ' must use own URL properties'); + } + for (const factory of [() => new Headers(), () => new URLSearchParams(), () => new FormData()]) { + for (const value of [undefined, null]) { + const input = factory(); + let iteratorReads = 0; + Object.defineProperty(input, Symbol.iterator, {enumerable: true, get() { + iteratorReads++; + return value; + }}); + let caught; + try { new Constructor(input); } catch (error) { caught = error; } + if (!(caught instanceof TypeError) || iteratorReads !== 1) { + throw new Error(Constructor.name + ' must reject the enumerable Symbol key before reading its value'); + } + } + } + } + return 'ok'; +})() +"#).unwrap(); + assert_eq!(result, "ok"); +} + +#[test] +fn url_search_params_form_data_initializers_convert_file_values_through_the_iterator() { + let mut vm = new_storage_test_vm("https://form-data-initializer.test/"); + let result = vm.eval(r#" +(() => { + const file = new File(['contents'], 'entry.txt'); + const form = new FormData(); + form.append('a', 'first'); + form.append('file', file); + form.append('a', 'second'); + const params = new URLSearchParams(form); + form.append('later', 'ignored'); + if (JSON.stringify(Array.from(params)) !== '[["a","first"],["file","[object File]"],["a","second"]]') { + throw new Error('FormData entries must be copied in iteration order with File stringification'); + } + const marker = {}; + let conversions = 0; + Object.defineProperty(file, Symbol.toPrimitive, {value(hint) { + if (hint !== 'string') throw new Error('USVString must use the string hint'); + conversions++; + throw marker; + }}); + let caught; + try { new URLSearchParams(form); } catch (error) { caught = error; } + if (caught !== marker || conversions !== 1) throw new Error('File conversion exception must propagate'); + return 'ok'; +})() +"#).unwrap(); + assert_eq!(result, "ok"); +} + #[test] fn webidl_string_records_observe_descriptors_and_values_in_key_order() { let mut vm = new_storage_test_vm("https://webidl-record-order.test/");