aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/response.rs
diff options
context:
space:
mode:
authorSebastian C <sebsebmc@gmail.com>2025-04-25 03:49:21 -0500
committerGitHub <noreply@github.com>2025-04-25 08:49:21 +0000
commit281d942981d9518c9c9c60a942de5023a11436c0 (patch)
treed1c7bb1438850bf9bf557e75b95e2de7000a5742 /components/script/dom/response.rs
parentdc0c067c9b825ea9b2755b7c79b3ab1517fb5cac (diff)
downloadservo-281d942981d9518c9c9c60a942de5023a11436c0.tar.gz
servo-281d942981d9518c9c9c60a942de5023a11436c0.zip
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>
Diffstat (limited to 'components/script/dom/response.rs')
-rw-r--r--components/script/dom/response.rs248
1 files changed, 151 insertions, 97 deletions
diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs
index 283b7d615aa..cbdfbe94603 100644
--- a/components/script/dom/response.rs
+++ b/components/script/dom/response.rs
@@ -8,7 +8,7 @@ use std::str::FromStr;
use dom_struct::dom_struct;
use http::header::HeaderMap as HyperHeaders;
use hyper_serde::Serde;
-use js::rust::HandleObject;
+use js::rust::{HandleObject, HandleValue};
use net_traits::http_status::HttpStatus;
use servo_url::ServoUrl;
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::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
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::headers::{Guard, Headers, is_obs_text, is_vchar};
use crate::dom::promise::Promise;
use crate::dom::readablestream::ReadableStream;
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
-use crate::script_runtime::{CanGc, StreamConsumer};
+use crate::script_runtime::{CanGc, JSContext, StreamConsumer};
#[dom_struct]
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> {
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 {
- // https://fetch.spec.whatwg.org/#initialize-a-response
+ /// <https://fetch.spec.whatwg.org/#dom-response>
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
- body: Option<BodyInit>,
+ body_init: Option<BodyInit>,
init: &ResponseBinding::ResponseInit,
) -> Fallible<DomRoot<Response>> {
- // Step 1
- 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
- )));
- }
-
- // Step 2
- 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(),
- ));
- }
-
- let r = Response::new_with_proto(global, proto, can_gc);
-
- // Step 3 & 4
- *r.status.borrow_mut() = HttpStatus::new_raw(init.status, init.statusText.clone().into());
-
- // 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));
- }
+ // 1. Set this’s response to a new response.
+ // Our Response/Body types don't actually hold onto an internal fetch Response.
+ let response = Response::new_with_proto(global, proto, can_gc);
+
+ // 2. Set this’s headers to a new Headers object with this’s relevant realm,
+ // whose header list is this’s response’s header list and guard is "response".
+ response.Headers(can_gc).set_guard(Guard::Response);
+
+ // 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,
+ };
- Ok(r)
+ // 5. Perform *initialize a response* given this, init, and bodyWithType.
+ initialize_response(global, can_gc, body_with_type, init, response)
}
- // 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> {
- let r = Response::new(global, can_gc);
- *r.response_type.borrow_mut() = DOMResponseType::Error;
- r.Headers(can_gc).set_guard(Guard::Immutable);
- *r.status.borrow_mut() = HttpStatus::new_error();
- r
+ let response = Response::new(global, can_gc);
+ *response.response_type.borrow_mut() = DOMResponseType::Error;
+ response.Headers(can_gc).set_guard(Guard::Immutable);
+ *response.status.borrow_mut() = HttpStatus::new_error();
+ response
}
- // https://fetch.spec.whatwg.org/#dom-response-redirect
+ /// <https://fetch.spec.whatwg.org/#dom-response-redirect>
fn Redirect(
global: &GlobalScope,
url: USVString,
@@ -251,31 +202,60 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
// Step 4
// see Step 4 continued
- let r = Response::new(global, can_gc);
+ let response = Response::new(global, can_gc);
// Step 5
- *r.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
+ *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
// Step 6
let url_bytestring =
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)?;
// Step 4 continued
// 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
- Ok(r)
+ Ok(response)
+ }
+
+ /// <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
+ /// <https://fetch.spec.whatwg.org/#dom-response-type>
fn Type(&self) -> DOMResponseType {
*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 {
USVString(String::from(
(*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 {
return *self.redirected.borrow();
}
- // https://fetch.spec.whatwg.org/#dom-response-status
+ /// <https://fetch.spec.whatwg.org/#dom-response-status>
fn Status(&self) -> u16 {
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 {
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 {
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> {
self.headers_reflector
.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>> {
// Step 1
if self.is_locked() || self.is_disturbed() {
@@ -352,7 +332,7 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
Ok(new_response)
}
- // https://fetch.spec.whatwg.org/#dom-body-bodyused
+ /// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
fn BodyUsed(&self) -> bool {
self.is_disturbed()
}
@@ -362,27 +342,27 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
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> {
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> {
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> {
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> {
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> {
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 {
&url[..Position::AfterQuery]
}