mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
fix(fetch): inherit Headers methods and observe initializer iterators
Use the intrinsic Headers prototype for internally created header objects. Remove own method copies and platform-object initializer shortcuts so author iterator overrides, exceptions and record properties remain observable. Source:66434f2877,c229deb38c
This commit is contained in:
@@ -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>(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String> {
|
||||
if value.is_null_or_undefined() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(object) = v8::Local::<v8::Object>::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>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
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 +35,6 @@ pub(super) fn url_search_params_pairs_from_constructor<'s>(
|
||||
return Some(Vec::new());
|
||||
}
|
||||
if let Ok(object) = v8::Local::<v8::Object>::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::<UrlSearchParamsSequencePair>(
|
||||
scope,
|
||||
value,
|
||||
@@ -64,17 +50,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(
|
||||
|
||||
@@ -5,7 +5,6 @@ mod store;
|
||||
use super::*;
|
||||
|
||||
pub(crate) use self::bindings::headers_constructor_callback;
|
||||
pub(super) use self::methods::install_headers_object_methods;
|
||||
pub(crate) use self::methods::install_headers_template_bindings;
|
||||
pub(crate) use self::store::headers_entries;
|
||||
pub(crate) use self::store::{HeadersGuard, filter_headers_for_guard};
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(super) use self::iteration::{
|
||||
pub(super) use self::mutation::{
|
||||
headers_append_callback, headers_delete_callback, headers_set_callback,
|
||||
};
|
||||
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject};
|
||||
use moli_webapi_declare::WebApiFunctionTemplate;
|
||||
|
||||
pub(super) fn require_headers_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -28,33 +28,6 @@ pub(super) fn require_headers_receiver<'s>(
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::Headers)]
|
||||
struct HeadersObjectMethodsDeclaration {
|
||||
#[webapi(method, enumerable, length = 1, callback = headers_get_callback)]
|
||||
get: (),
|
||||
#[webapi(method, enumerable, length = 1, callback = headers_has_callback)]
|
||||
has: (),
|
||||
#[webapi(method, enumerable, length = 0, callback = headers_get_set_cookie_callback)]
|
||||
get_set_cookie: (),
|
||||
#[webapi(method, enumerable, length = 2, callback = headers_set_callback)]
|
||||
set: (),
|
||||
#[webapi(method, enumerable, length = 1, callback = headers_delete_callback)]
|
||||
delete: (),
|
||||
#[webapi(method, enumerable, length = 2, callback = headers_append_callback)]
|
||||
append: (),
|
||||
#[webapi(method, enumerable, length = 0, callback = headers_keys_callback)]
|
||||
keys: (),
|
||||
#[webapi(method, enumerable, length = 0, callback = headers_values_callback)]
|
||||
values: (),
|
||||
#[webapi(method, enumerable, length = 0, callback = headers_entries_callback)]
|
||||
entries: (),
|
||||
#[webapi(alias = "entries", symbol = "iterator", enumerable)]
|
||||
iterator: (),
|
||||
#[webapi(method, enumerable, length = 1, callback = headers_for_each_callback)]
|
||||
for_each: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::Headers, enumerable)]
|
||||
struct HeadersPrototypeMethodsDeclaration {
|
||||
@@ -82,15 +55,6 @@ struct HeadersPrototypeMethodsDeclaration {
|
||||
for_each: (),
|
||||
}
|
||||
|
||||
pub(in crate::network_host) fn install_headers_object_methods<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
headers_obj: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
HeadersObjectMethodsDeclaration::default()
|
||||
.initialize(scope, headers_obj)
|
||||
.expect("Headers object methods declaration should initialize");
|
||||
}
|
||||
|
||||
pub(crate) fn install_headers_template_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
|
||||
@@ -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::<webidl::Record<webidl::ByteString, webidl::ByteString>>(
|
||||
scope,
|
||||
init_obj.into(),
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::web_api_interfaces;
|
||||
use moli_webapi_declare::WebApiObject;
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::Headers, prototype = "Object")]
|
||||
#[webapi(interface = web_api_interfaces::Headers)]
|
||||
struct HeadersStorageDeclaration {
|
||||
#[webapi(slot = HEADERS_ENTRIES_SLOT)]
|
||||
entries: String,
|
||||
|
||||
@@ -4,7 +4,6 @@ mod input;
|
||||
|
||||
use super::headers::{
|
||||
build_headers_object_with_state, filter_headers_for_guard, headers_entries_from_init,
|
||||
install_headers_object_methods,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -102,7 +102,6 @@ pub(crate) fn request_constructor_callback<'s>(
|
||||
request_headers_guard_for_mode(&state.mode),
|
||||
false,
|
||||
);
|
||||
install_headers_object_methods(scope, headers_obj);
|
||||
|
||||
let body_value = body_buffer
|
||||
.and_then(|buffer| {
|
||||
|
||||
@@ -5,7 +5,7 @@ mod materialize;
|
||||
|
||||
use super::headers::{
|
||||
HeadersGuard, build_headers_object_with_state, filter_headers_for_guard, headers_entries,
|
||||
headers_entries_from_init, install_headers_object_methods,
|
||||
headers_entries_from_init,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ pub(super) fn install_response_headers(
|
||||
let entries = filter_headers_for_guard(&entries, HeadersGuard::Response);
|
||||
let headers_obj =
|
||||
build_headers_object_with_state(scope, &entries, HeadersGuard::Response, false);
|
||||
install_headers_object_methods(scope, headers_obj);
|
||||
set_response_slot_value(scope, obj, RESPONSE_HEADERS_SLOT, headers_obj.into());
|
||||
}
|
||||
|
||||
|
||||
@@ -366,7 +366,6 @@ fn build_fetch_response_object_head<'s>(
|
||||
let internal_headers = filter_headers_for_guard(&head.headers, HeadersGuard::Response);
|
||||
let internal_headers_obj =
|
||||
build_headers_object_with_state(scope, &internal_headers, HeadersGuard::Response, true);
|
||||
install_headers_object_methods(scope, internal_headers_obj);
|
||||
set_response_slot_value(
|
||||
scope,
|
||||
obj,
|
||||
@@ -399,7 +398,6 @@ fn finish_fetch_response_object_with_body_stream<'s>(
|
||||
let headers = filter_headers_for_guard(header_entries, HeadersGuard::Response);
|
||||
let headers_obj =
|
||||
build_headers_object_with_state(scope, &headers, HeadersGuard::Response, true);
|
||||
install_headers_object_methods(scope, headers_obj);
|
||||
set_response_slot_value(scope, obj, RESPONSE_HEADERS_SLOT, headers_obj.into());
|
||||
|
||||
let body_value = if !filtered_response_exposes_body(filter) {
|
||||
@@ -442,7 +440,6 @@ pub(crate) fn build_filtered_cached_response_object<'s>(
|
||||
let headers = filter_headers_for_guard(&[], HeadersGuard::Response);
|
||||
let headers_obj =
|
||||
build_headers_object_with_state(scope, &headers, HeadersGuard::Response, true);
|
||||
install_headers_object_methods(scope, headers_obj);
|
||||
set_response_slot_value(scope, obj, RESPONSE_HEADERS_SLOT, headers_obj.into());
|
||||
set_filtered_response_internal_body_from_bytes(scope, obj, body);
|
||||
FetchResponseBodyDeclaration::new(String::new(), v8::null(scope).into())
|
||||
|
||||
@@ -6702,6 +6702,133 @@ 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_url_objects_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');
|
||||
}
|
||||
}
|
||||
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 headers_methods_parse_webidl_arguments() {
|
||||
let mut vm = new_storage_test_vm("https://headers-methods.test/");
|
||||
@@ -7029,6 +7156,117 @@ async fn form_data_and_url_search_params_for_each_use_callback_relevant_realm()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_and_response_headers_share_intrinsic_prototype_methods() {
|
||||
let mut vm = new_storage_test_vm("https://headers-object-prototype.test/");
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const check = (value, label) => { if (!value) throw new Error(label); };
|
||||
const NativeHeaders = Headers;
|
||||
const prototype = NativeHeaders.prototype;
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'Headers');
|
||||
const nativeGet = prototype.get;
|
||||
let lookups = 0;
|
||||
Object.defineProperty(globalThis, 'Headers', {configurable: true, get() {
|
||||
lookups++;
|
||||
throw new Error('public Headers lookup');
|
||||
}});
|
||||
try {
|
||||
const request = new Request('/request', {headers: {'x-test': 'request'}});
|
||||
const streamRequest = new Request('/stream', {method: 'POST', duplex: 'half',
|
||||
body: new ReadableStream({start(controller) {controller.close();}})});
|
||||
const response = new Response('body', {headers: {'x-test': 'response'}});
|
||||
const error = Response.error();
|
||||
const redirect = Response.redirect('/target');
|
||||
const holders = [request, request.clone(), streamRequest, streamRequest.clone(),
|
||||
response, response.clone(), error, error.clone(), redirect, redirect.clone()];
|
||||
const methods = ['get', 'has', 'getSetCookie', 'set', 'delete', 'append',
|
||||
'keys', 'values', 'entries', 'forEach', Symbol.iterator];
|
||||
for (const holder of holders) {
|
||||
const headers = holder.headers;
|
||||
check(headers === holder.headers, 'same Headers object');
|
||||
check(Object.getPrototypeOf(headers) === prototype && headers instanceof NativeHeaders,
|
||||
'intrinsic Headers prototype');
|
||||
check(Object.prototype.toString.call(headers) === '[object Headers]', 'Headers class');
|
||||
check(Reflect.ownKeys(headers).length === 0, 'no own methods');
|
||||
for (const key of methods) check(headers[key] === prototype[key], String(key));
|
||||
}
|
||||
check(nativeGet.call(request.headers, 'x-test') === 'request', 'request entries');
|
||||
check(nativeGet.call(response.headers, 'x-test') === 'response', 'response entries');
|
||||
prototype.get = function() { return this; };
|
||||
for (const holder of holders) check(holder.headers.get() === holder.headers,
|
||||
'prototype method changes must remain observable');
|
||||
const fresh = new Response('fresh').headers;
|
||||
check(fresh.get() === fresh, 'new objects share updated prototype methods');
|
||||
check(lookups === 0, 'factory must not consult replaced global');
|
||||
return 'ok';
|
||||
} finally {
|
||||
prototype.get = nativeGet;
|
||||
Object.defineProperty(globalThis, 'Headers', descriptor);
|
||||
}
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn response_headers_keep_receiver_realm_across_borrowed_getters() {
|
||||
let mut vm = new_storage_test_vm("https://headers-owner-realm.test/");
|
||||
vm.eval(
|
||||
r#"
|
||||
const root = document.documentElement || document.appendChild(document.createElement('html'));
|
||||
const body = document.body || root.appendChild(document.createElement('body'));
|
||||
globalThis.headersFrame = document.createElement('iframe');
|
||||
body.appendChild(headersFrame);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_initial_about_blank_child_completed_synchronously_for_test(&mut vm, "Headers realm")
|
||||
.await;
|
||||
let _ = materialize_single_child_default_realm_for_test(&mut vm, "Headers realm");
|
||||
let result = vm.eval(r#"
|
||||
(() => {
|
||||
const check = (value, label) => { if (!value) throw new Error(label); };
|
||||
const child = headersFrame.contentWindow;
|
||||
const parentHeaders = Headers;
|
||||
const childHeaders = child.Headers;
|
||||
const parentResponse = new Response('parent', {headers: {'x-realm': 'parent'}});
|
||||
const childResponse = new child.Response('child', {headers: {'x-realm': 'child'}});
|
||||
const parentGetter = Object.getOwnPropertyDescriptor(Response.prototype, 'headers').get;
|
||||
const childGetter = Object.getOwnPropertyDescriptor(child.Response.prototype, 'headers').get;
|
||||
const descriptors = [globalThis, child].map(realm => Object.getOwnPropertyDescriptor(realm, 'Headers'));
|
||||
for (const realm of [globalThis, child]) Object.defineProperty(realm, 'Headers', {
|
||||
configurable: true, get() { throw new Error('public Headers lookup'); }
|
||||
});
|
||||
try {
|
||||
for (const [response, ctor, getter, value] of [
|
||||
[parentResponse, parentHeaders, childGetter, 'parent'],
|
||||
[childResponse, childHeaders, parentGetter, 'child']
|
||||
]) {
|
||||
check(getter.call(response) === response.headers, 'borrowed getter returns associated Headers');
|
||||
for (const entry of [response]) {
|
||||
const headers = entry.headers;
|
||||
check(Object.getPrototypeOf(headers) === ctor.prototype && headers instanceof ctor,
|
||||
'Headers must use the response realm');
|
||||
check(headers.get === ctor.prototype.get && Reflect.ownKeys(headers).length === 0,
|
||||
'Headers must share realm prototype methods');
|
||||
check(headers.get('x-realm') === value, 'cross-realm entries');
|
||||
check(ctor.prototype.get.call(headers, 'x-realm') === value, 'branded receiver');
|
||||
}
|
||||
}
|
||||
return 'ok';
|
||||
} finally {
|
||||
[globalThis, child].forEach((realm, index) => Object.defineProperty(realm, 'Headers', descriptors[index]));
|
||||
}
|
||||
})()
|
||||
"#).unwrap();
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_prototype_methods_are_declared_operations() {
|
||||
let mut vm = new_storage_test_vm("https://headers-prototype-methods.test/");
|
||||
@@ -7164,7 +7402,7 @@ fn headers_methods_reject_incompatible_receivers() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_declared_methods_preserve_descriptors_and_iterator_alias() {
|
||||
fn response_headers_inherit_declared_methods_and_iterator_alias() {
|
||||
let mut vm = new_storage_test_vm("https://headers-declared-methods.test/");
|
||||
|
||||
let result = vm
|
||||
@@ -7172,6 +7410,9 @@ fn headers_declared_methods_preserve_descriptors_and_iterator_alias() {
|
||||
r#"
|
||||
(() => {
|
||||
const headers = new Response(null, { headers: [['X-A', '1']] }).headers;
|
||||
const prototype = Object.getPrototypeOf(headers);
|
||||
if (prototype !== Headers.prototype) throw new Error('Headers must use the intrinsic prototype');
|
||||
if (Reflect.ownKeys(headers).length !== 0) throw new Error('Headers methods must be inherited');
|
||||
const descriptors = [
|
||||
['get', 1],
|
||||
['has', 1],
|
||||
@@ -7184,7 +7425,7 @@ fn headers_declared_methods_preserve_descriptors_and_iterator_alias() {
|
||||
['entries', 0],
|
||||
['forEach', 1],
|
||||
].map(([name, expectedLength]) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(headers, name);
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
||||
return [
|
||||
name,
|
||||
typeof descriptor?.value,
|
||||
@@ -7196,7 +7437,7 @@ fn headers_declared_methods_preserve_descriptors_and_iterator_alias() {
|
||||
descriptor?.configurable,
|
||||
].join(':');
|
||||
});
|
||||
const iteratorDescriptor = Object.getOwnPropertyDescriptor(headers, Symbol.iterator);
|
||||
const iteratorDescriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.iterator);
|
||||
const iterated = Array.from(headers).map(([name, value]) => `${name}=${value}`).join(',');
|
||||
return JSON.stringify({
|
||||
descriptors,
|
||||
@@ -7218,7 +7459,7 @@ fn headers_declared_methods_preserve_descriptors_and_iterator_alias() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"descriptors":["get:function:get:1:1:true:true:true","has:function:has:1:1:true:true:true","getSetCookie:function:getSetCookie:0:0:true:true:true","set:function:set:2:2:true:true:true","delete:function:delete:1:1:true:true:true","append:function:append:2:2:true:true:true","keys:function:keys:0:0:true:true:true","values:function:values:0:0:true:true:true","entries:function:entries:0:0:true:true:true","forEach:function:forEach:1:1:true:true:true"],"iterator":"function:true:entries:0:true:true:true:x-a=1"}"#
|
||||
r#"{"descriptors":["get:function:get:1:1:true:true:true","has:function:has:1:1:true:true:true","getSetCookie:function:getSetCookie:0:0:true:true:true","set:function:set:2:2:true:true:true","delete:function:delete:1:1:true:true:true","append:function:append:2:2:true:true:true","keys:function:keys:0:0:true:true:true","values:function:values:0:0:true:true:true","entries:function:entries:0:0:true:true:true","forEach:function:forEach:1:1:true:true:true"],"iterator":"function:true:entries:0:false:true:true:x-a=1"}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user