mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Implement static Response.json (#36589)
Implements https://fetch.spec.whatwg.org/#dom-response-json Restructured the constructor to follow the spec more closely with a separate "initialize the response" algorithm. Testing: There are existing WPT tests for this. --------- Signed-off-by: Sebastian C <sebsebmc@gmail.com>
This commit is contained in:
parent
dc0c067c9b
commit
281d942981
6 changed files with 215 additions and 179 deletions
|
@ -8,7 +8,7 @@ use std::str::FromStr;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use http::header::HeaderMap as HyperHeaders;
|
use http::header::HeaderMap as HyperHeaders;
|
||||||
use hyper_serde::Serde;
|
use hyper_serde::Serde;
|
||||||
use js::rust::HandleObject;
|
use js::rust::{HandleObject, HandleValue};
|
||||||
use net_traits::http_status::HttpStatus;
|
use net_traits::http_status::HttpStatus;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
use url::Position;
|
use url::Position;
|
||||||
|
@ -24,13 +24,13 @@ use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
|
||||||
use crate::dom::bindings::error::{Error, Fallible};
|
use crate::dom::bindings::error::{Error, Fallible};
|
||||||
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
|
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
|
||||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||||
use crate::dom::bindings::str::{ByteString, USVString};
|
use crate::dom::bindings::str::{ByteString, USVString, serialize_jsval_to_json_utf8};
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
|
use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
|
||||||
use crate::dom::promise::Promise;
|
use crate::dom::promise::Promise;
|
||||||
use crate::dom::readablestream::ReadableStream;
|
use crate::dom::readablestream::ReadableStream;
|
||||||
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
|
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
|
||||||
use crate::script_runtime::{CanGc, StreamConsumer};
|
use crate::script_runtime::{CanGc, JSContext, StreamConsumer};
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub(crate) struct Response {
|
pub(crate) struct Response {
|
||||||
|
@ -72,7 +72,7 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response
|
/// <https://fetch.spec.whatwg.org/#dom-response>
|
||||||
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
|
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
|
||||||
Self::new_with_proto(global, None, can_gc)
|
Self::new_with_proto(global, None, can_gc)
|
||||||
}
|
}
|
||||||
|
@ -142,92 +142,43 @@ fn is_null_body_status(status: u16) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseMethods<crate::DomTypeHolder> for Response {
|
impl ResponseMethods<crate::DomTypeHolder> for Response {
|
||||||
// https://fetch.spec.whatwg.org/#initialize-a-response
|
/// <https://fetch.spec.whatwg.org/#dom-response>
|
||||||
fn Constructor(
|
fn Constructor(
|
||||||
global: &GlobalScope,
|
global: &GlobalScope,
|
||||||
proto: Option<HandleObject>,
|
proto: Option<HandleObject>,
|
||||||
can_gc: CanGc,
|
can_gc: CanGc,
|
||||||
body: Option<BodyInit>,
|
body_init: Option<BodyInit>,
|
||||||
init: &ResponseBinding::ResponseInit,
|
init: &ResponseBinding::ResponseInit,
|
||||||
) -> Fallible<DomRoot<Response>> {
|
) -> Fallible<DomRoot<Response>> {
|
||||||
// Step 1
|
// 1. Set this’s response to a new response.
|
||||||
if init.status < 200 || init.status > 599 {
|
// Our Response/Body types don't actually hold onto an internal fetch Response.
|
||||||
return Err(Error::Range(format!(
|
let response = Response::new_with_proto(global, proto, can_gc);
|
||||||
"init's status member should be in the range 200 to 599, inclusive, but is {}",
|
|
||||||
init.status
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2
|
// 2. Set this’s headers to a new Headers object with this’s relevant realm,
|
||||||
if !is_valid_status_text(&init.statusText) {
|
// whose header list is this’s response’s header list and guard is "response".
|
||||||
return Err(Error::Type(
|
response.Headers(can_gc).set_guard(Guard::Response);
|
||||||
"init's statusText member does not match the reason-phrase token production"
|
|
||||||
.to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = Response::new_with_proto(global, proto, can_gc);
|
// 3. Let bodyWithType be null.
|
||||||
|
// 4. If body is non-null, then set bodyWithType to the result of extracting body.
|
||||||
|
let body_with_type = match body_init {
|
||||||
|
Some(body) => Some(body.extract(global, can_gc)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
// Step 3 & 4
|
// 5. Perform *initialize a response* given this, init, and bodyWithType.
|
||||||
*r.status.borrow_mut() = HttpStatus::new_raw(init.status, init.statusText.clone().into());
|
initialize_response(global, can_gc, body_with_type, init, response)
|
||||||
|
|
||||||
// Step 5
|
|
||||||
if let Some(ref headers_member) = init.headers {
|
|
||||||
r.Headers(can_gc).fill(Some(headers_member.clone()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 6
|
|
||||||
if let Some(ref body) = body {
|
|
||||||
// Step 6.1
|
|
||||||
if is_null_body_status(init.status) {
|
|
||||||
return Err(Error::Type(
|
|
||||||
"Body is non-null but init's status member is a null body status".to_string(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 6.2
|
|
||||||
let ExtractedBody {
|
|
||||||
stream,
|
|
||||||
total_bytes: _,
|
|
||||||
content_type,
|
|
||||||
source: _,
|
|
||||||
} = body.extract(global, can_gc)?;
|
|
||||||
|
|
||||||
r.body_stream.set(Some(&*stream));
|
|
||||||
|
|
||||||
// Step 6.3
|
|
||||||
if let Some(content_type_contents) = content_type {
|
|
||||||
if !r
|
|
||||||
.Headers(can_gc)
|
|
||||||
.Has(ByteString::new(b"Content-Type".to_vec()))
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
r.Headers(can_gc).Append(
|
|
||||||
ByteString::new(b"Content-Type".to_vec()),
|
|
||||||
ByteString::new(content_type_contents.as_bytes().to_vec()),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Reset FetchResponse to an in-memory stream with empty byte sequence here for
|
|
||||||
// no-init-body case
|
|
||||||
let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0), can_gc)?;
|
|
||||||
r.body_stream.set(Some(&*stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-error
|
/// <https://fetch.spec.whatwg.org/#dom-response-error>
|
||||||
fn Error(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
|
fn Error(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
|
||||||
let r = Response::new(global, can_gc);
|
let response = Response::new(global, can_gc);
|
||||||
*r.response_type.borrow_mut() = DOMResponseType::Error;
|
*response.response_type.borrow_mut() = DOMResponseType::Error;
|
||||||
r.Headers(can_gc).set_guard(Guard::Immutable);
|
response.Headers(can_gc).set_guard(Guard::Immutable);
|
||||||
*r.status.borrow_mut() = HttpStatus::new_error();
|
*response.status.borrow_mut() = HttpStatus::new_error();
|
||||||
r
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-redirect
|
/// <https://fetch.spec.whatwg.org/#dom-response-redirect>
|
||||||
fn Redirect(
|
fn Redirect(
|
||||||
global: &GlobalScope,
|
global: &GlobalScope,
|
||||||
url: USVString,
|
url: USVString,
|
||||||
|
@ -251,31 +202,60 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
|
||||||
|
|
||||||
// Step 4
|
// Step 4
|
||||||
// see Step 4 continued
|
// see Step 4 continued
|
||||||
let r = Response::new(global, can_gc);
|
let response = Response::new(global, can_gc);
|
||||||
|
|
||||||
// Step 5
|
// Step 5
|
||||||
*r.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
|
*response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
|
||||||
|
|
||||||
// Step 6
|
// Step 6
|
||||||
let url_bytestring =
|
let url_bytestring =
|
||||||
ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
|
ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
|
||||||
r.Headers(can_gc)
|
response
|
||||||
|
.Headers(can_gc)
|
||||||
.Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
|
.Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
|
||||||
|
|
||||||
// Step 4 continued
|
// Step 4 continued
|
||||||
// Headers Guard is set to Immutable here to prevent error in Step 6
|
// Headers Guard is set to Immutable here to prevent error in Step 6
|
||||||
r.Headers(can_gc).set_guard(Guard::Immutable);
|
response.Headers(can_gc).set_guard(Guard::Immutable);
|
||||||
|
|
||||||
// Step 7
|
// Step 7
|
||||||
Ok(r)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-type
|
/// <https://fetch.spec.whatwg.org/#dom-response-json>
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn CreateFromJson(
|
||||||
|
cx: JSContext,
|
||||||
|
global: &GlobalScope,
|
||||||
|
data: HandleValue,
|
||||||
|
init: &ResponseBinding::ResponseInit,
|
||||||
|
can_gc: CanGc,
|
||||||
|
) -> Fallible<DomRoot<Response>> {
|
||||||
|
// 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
|
||||||
|
let json_str = serialize_jsval_to_json_utf8(cx, data)?;
|
||||||
|
|
||||||
|
// 2. Let body be the result of extracting bytes
|
||||||
|
// The spec's definition of JSON bytes is a UTF-8 encoding so using a DOMString here handles
|
||||||
|
// the encoding part.
|
||||||
|
let body_init = BodyInit::String(json_str);
|
||||||
|
let mut body = body_init.extract(global, can_gc)?;
|
||||||
|
|
||||||
|
// 3. Let responseObject be the result of creating a Response object, given a new response,
|
||||||
|
// "response", and the current realm.
|
||||||
|
let response = Response::new(global, can_gc);
|
||||||
|
response.Headers(can_gc).set_guard(Guard::Response);
|
||||||
|
|
||||||
|
// 4. Perform initialize a response given responseObject, init, and (body, "application/json").
|
||||||
|
body.content_type = Some("application/json".into());
|
||||||
|
initialize_response(global, can_gc, Some(body), init, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://fetch.spec.whatwg.org/#dom-response-type>
|
||||||
fn Type(&self) -> DOMResponseType {
|
fn Type(&self) -> DOMResponseType {
|
||||||
*self.response_type.borrow() //into()
|
*self.response_type.borrow() //into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-url
|
/// <https://fetch.spec.whatwg.org/#dom-response-url>
|
||||||
fn Url(&self) -> USVString {
|
fn Url(&self) -> USVString {
|
||||||
USVString(String::from(
|
USVString(String::from(
|
||||||
(*self.url.borrow())
|
(*self.url.borrow())
|
||||||
|
@ -285,33 +265,33 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-redirected
|
/// <https://fetch.spec.whatwg.org/#dom-response-redirected>
|
||||||
fn Redirected(&self) -> bool {
|
fn Redirected(&self) -> bool {
|
||||||
return *self.redirected.borrow();
|
return *self.redirected.borrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-status
|
/// <https://fetch.spec.whatwg.org/#dom-response-status>
|
||||||
fn Status(&self) -> u16 {
|
fn Status(&self) -> u16 {
|
||||||
self.status.borrow().raw_code()
|
self.status.borrow().raw_code()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-ok
|
/// <https://fetch.spec.whatwg.org/#dom-response-ok>
|
||||||
fn Ok(&self) -> bool {
|
fn Ok(&self) -> bool {
|
||||||
self.status.borrow().is_success()
|
self.status.borrow().is_success()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-statustext
|
/// <https://fetch.spec.whatwg.org/#dom-response-statustext>
|
||||||
fn StatusText(&self) -> ByteString {
|
fn StatusText(&self) -> ByteString {
|
||||||
ByteString::new(self.status.borrow().message().to_vec())
|
ByteString::new(self.status.borrow().message().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-headers
|
/// <https://fetch.spec.whatwg.org/#dom-response-headers>
|
||||||
fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
|
fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
|
||||||
self.headers_reflector
|
self.headers_reflector
|
||||||
.or_init(|| Headers::for_response(&self.global(), can_gc))
|
.or_init(|| Headers::for_response(&self.global(), can_gc))
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-response-clone
|
/// <https://fetch.spec.whatwg.org/#dom-response-clone>
|
||||||
fn Clone(&self, can_gc: CanGc) -> Fallible<DomRoot<Response>> {
|
fn Clone(&self, can_gc: CanGc) -> Fallible<DomRoot<Response>> {
|
||||||
// Step 1
|
// Step 1
|
||||||
if self.is_locked() || self.is_disturbed() {
|
if self.is_locked() || self.is_disturbed() {
|
||||||
|
@ -352,7 +332,7 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
|
||||||
Ok(new_response)
|
Ok(new_response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
/// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
|
||||||
fn BodyUsed(&self) -> bool {
|
fn BodyUsed(&self) -> bool {
|
||||||
self.is_disturbed()
|
self.is_disturbed()
|
||||||
}
|
}
|
||||||
|
@ -362,27 +342,27 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
|
||||||
self.body()
|
self.body()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-text
|
/// <https://fetch.spec.whatwg.org/#dom-body-text>
|
||||||
fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
|
fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||||
consume_body(self, BodyType::Text, can_gc)
|
consume_body(self, BodyType::Text, can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-blob
|
/// <https://fetch.spec.whatwg.org/#dom-body-blob>
|
||||||
fn Blob(&self, can_gc: CanGc) -> Rc<Promise> {
|
fn Blob(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||||
consume_body(self, BodyType::Blob, can_gc)
|
consume_body(self, BodyType::Blob, can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-formdata
|
/// <https://fetch.spec.whatwg.org/#dom-body-formdata>
|
||||||
fn FormData(&self, can_gc: CanGc) -> Rc<Promise> {
|
fn FormData(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||||
consume_body(self, BodyType::FormData, can_gc)
|
consume_body(self, BodyType::FormData, can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-json
|
/// <https://fetch.spec.whatwg.org/#dom-body-json>
|
||||||
fn Json(&self, can_gc: CanGc) -> Rc<Promise> {
|
fn Json(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||||
consume_body(self, BodyType::Json, can_gc)
|
consume_body(self, BodyType::Json, can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
/// <https://fetch.spec.whatwg.org/#dom-body-arraybuffer>
|
||||||
fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
|
fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
|
||||||
consume_body(self, BodyType::ArrayBuffer, can_gc)
|
consume_body(self, BodyType::ArrayBuffer, can_gc)
|
||||||
}
|
}
|
||||||
|
@ -393,6 +373,80 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://fetch.spec.whatwg.org/#initialize-a-response>
|
||||||
|
fn initialize_response(
|
||||||
|
global: &GlobalScope,
|
||||||
|
can_gc: CanGc,
|
||||||
|
body: Option<ExtractedBody>,
|
||||||
|
init: &ResponseBinding::ResponseInit,
|
||||||
|
response: DomRoot<Response>,
|
||||||
|
) -> Result<DomRoot<Response>, Error> {
|
||||||
|
// 1. If init["status"] is not in the range 200 to 599, inclusive, then throw a RangeError.
|
||||||
|
if init.status < 200 || init.status > 599 {
|
||||||
|
return Err(Error::Range(format!(
|
||||||
|
"init's status member should be in the range 200 to 599, inclusive, but is {}",
|
||||||
|
init.status
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If init["statusText"] is not the empty string and does not match the reason-phrase token production,
|
||||||
|
// then throw a TypeError.
|
||||||
|
if !is_valid_status_text(&init.statusText) {
|
||||||
|
return Err(Error::Type(
|
||||||
|
"init's statusText member does not match the reason-phrase token production"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Set response’s response’s status to init["status"].
|
||||||
|
// 4. Set response’s response’s status message to init["statusText"].
|
||||||
|
*response.status.borrow_mut() =
|
||||||
|
HttpStatus::new_raw(init.status, init.statusText.clone().into());
|
||||||
|
|
||||||
|
// 5. If init["headers"] exists, then fill response’s headers with init["headers"].
|
||||||
|
if let Some(ref headers_member) = init.headers {
|
||||||
|
response
|
||||||
|
.Headers(can_gc)
|
||||||
|
.fill(Some(headers_member.clone()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. If body is non-null, then:
|
||||||
|
if let Some(ref body) = body {
|
||||||
|
// 6.1 If response’s status is a null body status, then throw a TypeError.
|
||||||
|
if is_null_body_status(init.status) {
|
||||||
|
return Err(Error::Type(
|
||||||
|
"Body is non-null but init's status member is a null body status".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 6.2 Set response’s body to body’s body.
|
||||||
|
response.body_stream.set(Some(&*body.stream));
|
||||||
|
|
||||||
|
// 6.3 If body’s type is non-null and response’s header list does not contain `Content-Type`,
|
||||||
|
// then append (`Content-Type`, body’s type) to response’s header list.
|
||||||
|
if let Some(content_type_contents) = &body.content_type {
|
||||||
|
if !response
|
||||||
|
.Headers(can_gc)
|
||||||
|
.Has(ByteString::new(b"Content-Type".to_vec()))
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
response.Headers(can_gc).Append(
|
||||||
|
ByteString::new(b"Content-Type".to_vec()),
|
||||||
|
ByteString::new(content_type_contents.as_bytes().to_vec()),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Reset FetchResponse to an in-memory stream with empty byte sequence here for
|
||||||
|
// no-init-body case. This is because the Response/Body types here do not hold onto a
|
||||||
|
// fetch Response object.
|
||||||
|
let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0), can_gc)?;
|
||||||
|
response.body_stream.set(Some(&*stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
fn serialize_without_fragment(url: &ServoUrl) -> &str {
|
fn serialize_without_fragment(url: &ServoUrl) -> &str {
|
||||||
&url[..Position::AfterQuery]
|
&url[..Position::AfterQuery]
|
||||||
}
|
}
|
||||||
|
|
|
@ -551,7 +551,7 @@ DOMInterfaces = {
|
||||||
},
|
},
|
||||||
|
|
||||||
'Response': {
|
'Response': {
|
||||||
'canGc': ['Error', 'Redirect', 'Clone', 'Text', 'Blob', 'FormData', 'Json', 'ArrayBuffer', 'Headers', 'Bytes'],
|
'canGc': ['Error', 'Redirect', 'Clone', 'CreateFromJson', 'Text', 'Blob', 'FormData', 'Json', 'ArrayBuffer', 'Headers', 'Bytes'],
|
||||||
},
|
},
|
||||||
|
|
||||||
'RTCPeerConnection': {
|
'RTCPeerConnection': {
|
||||||
|
|
|
@ -10,14 +10,19 @@ use std::marker::PhantomData;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::{fmt, ops, str};
|
use std::{fmt, ops, slice, str};
|
||||||
|
|
||||||
use cssparser::CowRcStr;
|
use cssparser::CowRcStr;
|
||||||
use html5ever::{LocalName, Namespace};
|
use html5ever::{LocalName, Namespace};
|
||||||
|
use js::rust::wrappers::ToJSON;
|
||||||
|
use js::rust::{HandleObject, HandleValue};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use stylo_atoms::Atom;
|
use stylo_atoms::Atom;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::script_runtime::JSContext as SafeJSContext;
|
||||||
|
|
||||||
/// Encapsulates the IDL `ByteString` type.
|
/// Encapsulates the IDL `ByteString` type.
|
||||||
#[derive(Clone, Debug, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
|
||||||
pub struct ByteString(Vec<u8>);
|
pub struct ByteString(Vec<u8>);
|
||||||
|
@ -293,6 +298,64 @@ impl DOMString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Because this converts to a DOMString it becomes UTF-8 encoded which is closer to
|
||||||
|
/// the spec definition of <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-json-bytes>
|
||||||
|
/// but we generally do not operate on anything that is truly a WTF-16 string.
|
||||||
|
///
|
||||||
|
/// <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string>
|
||||||
|
pub fn serialize_jsval_to_json_utf8(
|
||||||
|
cx: SafeJSContext,
|
||||||
|
data: HandleValue,
|
||||||
|
) -> Result<DOMString, Error> {
|
||||||
|
#[repr(C)]
|
||||||
|
struct ToJSONCallbackData {
|
||||||
|
string: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out_str = ToJSONCallbackData { string: None };
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe extern "C" fn write_callback(
|
||||||
|
string: *const u16,
|
||||||
|
len: u32,
|
||||||
|
data: *mut std::ffi::c_void,
|
||||||
|
) -> bool {
|
||||||
|
let data = data as *mut ToJSONCallbackData;
|
||||||
|
let string_chars = slice::from_raw_parts(string, len as usize);
|
||||||
|
(*data)
|
||||||
|
.string
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.push_str(&String::from_utf16_lossy(string_chars));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
|
||||||
|
unsafe {
|
||||||
|
let stringify_result = ToJSON(
|
||||||
|
*cx,
|
||||||
|
data,
|
||||||
|
HandleObject::null(),
|
||||||
|
HandleValue::null(),
|
||||||
|
Some(write_callback),
|
||||||
|
&mut out_str as *mut ToJSONCallbackData as *mut _,
|
||||||
|
);
|
||||||
|
// Note: ToJSON returns false when a JS error is thrown, so we need to return
|
||||||
|
// JSFailed to propagate the raised exception
|
||||||
|
if !stringify_result {
|
||||||
|
return Err(Error::JSFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If result is undefined, then throw a TypeError.
|
||||||
|
// Note: ToJSON will not call the callback if the data cannot be serialized.
|
||||||
|
// 3. Assert: result is a string.
|
||||||
|
// 4. Return result.
|
||||||
|
out_str
|
||||||
|
.string
|
||||||
|
.map(Into::into)
|
||||||
|
.ok_or_else(|| Error::Type("unable to serialize JSON".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
impl Borrow<str> for DOMString {
|
impl Borrow<str> for DOMString {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn borrow(&self) -> &str {
|
fn borrow(&self) -> &str {
|
||||||
|
|
|
@ -9,6 +9,7 @@ interface Response {
|
||||||
[Throws] constructor(optional BodyInit? body = null, optional ResponseInit init = {});
|
[Throws] constructor(optional BodyInit? body = null, optional ResponseInit init = {});
|
||||||
[NewObject] static Response error();
|
[NewObject] static Response error();
|
||||||
[NewObject, Throws] static Response redirect(USVString url, optional unsigned short status = 302);
|
[NewObject, Throws] static Response redirect(USVString url, optional unsigned short status = 302);
|
||||||
|
[NewObject, Throws, BinaryName="createFromJson"] static Response json(any data, optional ResponseInit init = {});
|
||||||
|
|
||||||
readonly attribute ResponseType type;
|
readonly attribute ResponseType type;
|
||||||
|
|
||||||
|
|
12
tests/wpt/meta/fetch/api/idlharness.any.js.ini
vendored
12
tests/wpt/meta/fetch/api/idlharness.any.js.ini
vendored
|
@ -29,12 +29,6 @@
|
||||||
[Request interface: new Request('about:blank') must inherit property "duplex" with the proper type]
|
[Request interface: new Request('about:blank') must inherit property "duplex" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Response interface: operation json(any, optional ResponseInit)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Response interface: calling json(any, optional ResponseInit) on new Response() with too few arguments must throw TypeError]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
||||||
[idlharness.any.sharedworker.html]
|
[idlharness.any.sharedworker.html]
|
||||||
expected: ERROR
|
expected: ERROR
|
||||||
|
@ -70,12 +64,6 @@
|
||||||
[Request interface: new Request('about:blank') must inherit property "duplex" with the proper type]
|
[Request interface: new Request('about:blank') must inherit property "duplex" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Response interface: operation json(any, optional ResponseInit)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Response interface: calling json(any, optional ResponseInit) on new Response() with too few arguments must throw TypeError]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
||||||
[idlharness.any.serviceworker.html]
|
[idlharness.any.serviceworker.html]
|
||||||
expected: ERROR
|
expected: ERROR
|
||||||
|
|
|
@ -1,73 +1,3 @@
|
||||||
[response-static-json.any.worker.html]
|
|
||||||
[Check response returned by static json() with init undefined]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"status":400}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"statusText":"foo"}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"headers":{}}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"headers":{"content-type":"foo/bar"}}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"headers":{"x-foo":"bar"}}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check static json() encodes JSON objects correctly]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check static json() propagates JSON serializer errors]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with input 𝌆]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with input U+df06U+d834]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with input U+dead]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
||||||
[response-static-json.any.html]
|
|
||||||
[Check response returned by static json() with init undefined]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"status":400}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"statusText":"foo"}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"headers":{}}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"headers":{"content-type":"foo/bar"}}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with init {"headers":{"x-foo":"bar"}}]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check static json() encodes JSON objects correctly]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check static json() propagates JSON serializer errors]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with input 𝌆]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with input U+df06U+d834]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Check response returned by static json() with input U+dead]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
||||||
[response-static-json.any.sharedworker.html]
|
[response-static-json.any.sharedworker.html]
|
||||||
expected: ERROR
|
expected: ERROR
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue