aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2016-09-08 18:58:05 -0500
committerGitHub <noreply@github.com>2016-09-08 18:58:05 -0500
commit5a5a76cc5db830d2e622d4e0924837383b64dfa2 (patch)
tree9f104dbe418b8c42e5767b566700d59ceea84318 /components
parentaa011ea2759683ede76639a1ea889c93c21d1cb8 (diff)
parentfaf32a7cfbaf568bbfeb4f2572ea96b6a30d231e (diff)
downloadservo-5a5a76cc5db830d2e622d4e0924837383b64dfa2.tar.gz
servo-5a5a76cc5db830d2e622d4e0924837383b64dfa2.zip
Auto merge of #13058 - malisas:malisa-responseAPI, r=Manishearth,jdm
Response API <!-- Please describe your changes on the following line: --> This PR adds the [dom::Response](https://fetch.spec.whatwg.org/#response-class) implementation and addresses #11896. The relevant passing tests` expectations have been updated. In order to allow non-UTF-8-encoded status messages, `net_traits::response::Response`'s `raw_status` field has been changed from type [`Option<RawStatus>`](https://doc.servo.org/hyper/http/struct.RawStatus.html) to type `Option<(u16, Vec<u8>)>`. As a result, a few other files which rely on the `raw_status` field were affected and updated. TODOs: - The `body` and `trailer` methods. Relies on implementation of `ReadableStream` and `Promise`s. - Similarly, replace the dummy constructor `_body: Option<USVString>` argument with `body: ResponseBodyInit`. - Currently, whenever `r's response's header list` or `r's Headers object` are mentioned, I always modify the `headers_reflector` field (of type dom::Headers, or `r's Headers object`) and not the corresponding hyper::Headers list in the net_traits::Response field. A completely accurate interpretation of the spec might consider making both of these lists the same thing via a reference. [Discussion](https://github.com/whatwg/fetch/issues/358) was [had](https://github.com/servo/servo/pull/12884). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [X] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13058) <!-- Reviewable:end -->
Diffstat (limited to 'components')
-rw-r--r--components/devtools/Cargo.toml1
-rw-r--r--components/devtools/actors/network_event.rs8
-rw-r--r--components/devtools/lib.rs1
-rw-r--r--components/devtools_traits/lib.rs3
-rw-r--r--components/net/about_loader.rs3
-rw-r--r--components/net/blob_loader.rs3
-rw-r--r--components/net/fetch/methods.rs7
-rw-r--r--components/net/http_loader.rs7
-rw-r--r--components/net_traits/lib.rs5
-rw-r--r--components/net_traits/response.rs20
-rw-r--r--components/script/dom/bindings/trace.rs5
-rw-r--r--components/script/dom/headers.rs16
-rw-r--r--components/script/dom/htmllinkelement.rs3
-rw-r--r--components/script/dom/htmlmediaelement.rs3
-rw-r--r--components/script/dom/htmlscriptelement.rs4
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/response.rs290
-rw-r--r--components/script/dom/webidls/Response.webidl37
-rw-r--r--components/script/dom/xmlhttprequest.rs9
-rw-r--r--components/servo/Cargo.lock1
20 files changed, 385 insertions, 42 deletions
diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml
index 230c16af6c2..f17488c575d 100644
--- a/components/devtools/Cargo.toml
+++ b/components/devtools/Cargo.toml
@@ -11,6 +11,7 @@ path = "lib.rs"
[dependencies]
devtools_traits = {path = "../devtools_traits"}
+encoding = "0.2"
hyper = "0.9.9"
hyper_serde = "0.1.4"
ipc-channel = "0.5"
diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs
index ba2793af8b7..9281356f7e5 100644
--- a/components/devtools/actors/network_event.rs
+++ b/components/devtools/actors/network_event.rs
@@ -11,12 +11,15 @@ extern crate hyper;
use actor::{Actor, ActorMessageStatus, ActorRegistry};
use devtools_traits::HttpRequest as DevtoolsHttpRequest;
use devtools_traits::HttpResponse as DevtoolsHttpResponse;
+use encoding::all::UTF_8;
+use encoding::types::{DecoderTrap, Encoding};
use hyper::header::Headers;
use hyper::header::{ContentType, Cookie};
use hyper::http::RawStatus;
use hyper::method::Method;
use protocol::JsonPacketStream;
use serde_json::Value;
+use std::borrow::Cow;
use std::collections::BTreeMap;
use std::net::TcpStream;
use time;
@@ -360,7 +363,10 @@ impl NetworkEventActor {
pub fn add_response(&mut self, response: DevtoolsHttpResponse) {
self.response.headers = response.headers.clone();
- self.response.status = response.status.clone();
+ self.response.status = response.status.as_ref().map(|&(s, ref st)| {
+ let status_text = UTF_8.decode(st, DecoderTrap::Replace).unwrap();
+ RawStatus(s, Cow::from(status_text))
+ });
self.response.body = response.body.clone();
}
diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs
index 17e36f95c0b..0ec2a713cde 100644
--- a/components/devtools/lib.rs
+++ b/components/devtools/lib.rs
@@ -21,6 +21,7 @@
#![deny(unsafe_code)]
extern crate devtools_traits;
+extern crate encoding;
extern crate hyper;
extern crate ipc_channel;
#[macro_use]
diff --git a/components/devtools_traits/lib.rs b/components/devtools_traits/lib.rs
index ecaec49995b..4edf19d6f02 100644
--- a/components/devtools_traits/lib.rs
+++ b/components/devtools_traits/lib.rs
@@ -27,7 +27,6 @@ extern crate time;
extern crate url;
use hyper::header::Headers;
-use hyper::http::RawStatus;
use hyper::method::Method;
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
@@ -304,7 +303,7 @@ pub struct HttpRequest {
#[derive(Debug, PartialEq)]
pub struct HttpResponse {
pub headers: Option<Headers>,
- pub status: Option<RawStatus>,
+ pub status: Option<(u16, Vec<u8>)>,
pub body: Option<Vec<u8>>,
pub pipeline_id: PipelineId,
}
diff --git a/components/net/about_loader.rs b/components/net/about_loader.rs
index 29f598f3cdf..26ec34d2e6c 100644
--- a/components/net/about_loader.rs
+++ b/components/net/about_loader.rs
@@ -4,7 +4,6 @@
use file_loader;
use hyper::header::ContentType;
-use hyper::http::RawStatus;
use hyper::mime::{Mime, SubLevel, TopLevel};
use hyper_serde::Serde;
use mime_classifier::MimeClassifier;
@@ -38,7 +37,7 @@ pub fn factory(mut load_data: LoadData,
Some(Serde(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])))),
charset: Some("utf-8".to_owned()),
headers: None,
- status: Some(Serde(RawStatus(200, "OK".into()))),
+ status: Some((200, b"OK".to_vec())),
https_state: HttpsState::None,
referrer: None,
};
diff --git a/components/net/blob_loader.rs b/components/net/blob_loader.rs
index 5cad922802a..1fe15d9a4a5 100644
--- a/components/net/blob_loader.rs
+++ b/components/net/blob_loader.rs
@@ -5,7 +5,6 @@
use filemanager_thread::{FileManager, UIProvider};
use hyper::header::{DispositionType, ContentDisposition, DispositionParam};
use hyper::header::{Headers, ContentType, ContentLength, Charset};
-use hyper::http::RawStatus;
use hyper_serde::Serde;
use ipc_channel::ipc;
use mime::{Mime, Attr};
@@ -72,7 +71,7 @@ fn load_blob<UI: 'static + UIProvider>
charset: charset.map(|c| c.as_str().to_string()),
headers: Some(Serde(headers)),
// https://w3c.github.io/FileAPI/#TwoHundredOK
- status: Some(Serde(RawStatus(200, "OK".into()))),
+ status: Some((200, b"OK".to_vec())),
https_state: HttpsState::None,
referrer: None,
};
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs
index da2dda147fb..b9caa134712 100644
--- a/components/net/fetch/methods.rs
+++ b/components/net/fetch/methods.rs
@@ -973,8 +973,9 @@ fn http_network_fetch(request: Rc<Request>,
Ok((res, msg)) => {
response.url = Some(url.clone());
response.status = Some(res.response.status);
- response.raw_status = Some(res.response.status_raw().clone());
- response.headers = res.response.headers.clone();
+ response.raw_status = Some((res.response.status_raw().0,
+ res.response.status_raw().1.as_bytes().to_vec()));
+ response.headers = res.response.headers.clone();
let res_body = response.body.clone();
@@ -1001,7 +1002,7 @@ fn http_network_fetch(request: Rc<Request>,
send_response_to_devtools(
&sender, request_id.unwrap(),
meta_headers.map(Serde::into_inner),
- meta_status.map(Serde::into_inner),
+ meta_status,
pipeline_id);
}
}
diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs
index 62df61bcd68..a2fd9f87f48 100644
--- a/components/net/http_loader.rs
+++ b/components/net/http_loader.rs
@@ -629,7 +629,7 @@ pub fn send_request_to_devtools(msg: ChromeToDevtoolsControlMsg,
pub fn send_response_to_devtools(devtools_chan: &Sender<DevtoolsControlMsg>,
request_id: String,
headers: Option<Headers>,
- status: Option<RawStatus>,
+ status: Option<(u16, Vec<u8>)>,
pipeline_id: PipelineId) {
let response = DevtoolsHttpResponse { headers: headers, status: status, body: None, pipeline_id: pipeline_id };
let net_event_response = NetworkEvent::HttpResponse(response);
@@ -1081,7 +1081,8 @@ pub fn load<A, B>(load_data: &LoadData,
None => None
});
metadata.headers = Some(Serde(adjusted_headers));
- metadata.status = Some(Serde(response.status_raw().clone()));
+ metadata.status = Some((response.status_raw().0,
+ response.status_raw().1.as_bytes().to_vec()));
metadata.https_state = if doc_url.scheme() == "https" {
HttpsState::Modern
} else {
@@ -1097,7 +1098,7 @@ pub fn load<A, B>(load_data: &LoadData,
send_response_to_devtools(
&chan, request_id.unwrap(),
metadata.headers.clone().map(Serde::into_inner),
- metadata.status.clone().map(Serde::into_inner),
+ metadata.status.clone(),
pipeline_id);
}
}
diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs
index a778d3ce76b..036fb54d940 100644
--- a/components/net_traits/lib.rs
+++ b/components/net_traits/lib.rs
@@ -560,9 +560,8 @@ pub struct Metadata {
/// Headers
pub headers: Option<Serde<Headers>>,
- #[ignore_heap_size_of = "Defined in hyper"]
/// HTTP Status
- pub status: Option<Serde<RawStatus>>,
+ pub status: Option<(u16, Vec<u8>)>,
/// Is successful HTTPS connection
pub https_state: HttpsState,
@@ -580,7 +579,7 @@ impl Metadata {
charset: None,
headers: None,
// https://fetch.spec.whatwg.org/#concept-response-status-message
- status: Some(Serde(RawStatus(200, "OK".into()))),
+ status: Some((200, b"OK".to_vec())),
https_state: HttpsState::None,
referrer: None,
}
diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs
index 37b0a585780..4305abd4fb6 100644
--- a/components/net_traits/response.rs
+++ b/components/net_traits/response.rs
@@ -5,7 +5,6 @@
//! The [Response](https://fetch.spec.whatwg.org/#responses) object
//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
use hyper::header::{AccessControlExposeHeaders, ContentType, Headers};
-use hyper::http::RawStatus;
use hyper::status::StatusCode;
use hyper_serde::Serde;
use std::ascii::AsciiExt;
@@ -15,7 +14,7 @@ use url::Url;
use {Metadata, NetworkError};
/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
-#[derive(Clone, PartialEq, Copy, Debug, Deserialize, Serialize)]
+#[derive(Clone, PartialEq, Copy, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum ResponseType {
Basic,
CORS,
@@ -26,7 +25,7 @@ pub enum ResponseType {
}
/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
-#[derive(Clone, Copy, Deserialize, Serialize)]
+#[derive(Clone, Copy, Deserialize, Serialize, HeapSizeOf)]
pub enum TerminationReason {
EndUserAbort,
Fatal,
@@ -35,7 +34,7 @@ pub enum TerminationReason {
/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, HeapSizeOf)]
pub enum ResponseBody {
Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
Receiving(Vec<u8>),
@@ -53,7 +52,7 @@ impl ResponseBody {
/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
-#[derive(Clone, Debug, Deserialize, Serialize)]
+#[derive(Clone, Debug, Deserialize, Serialize, HeapSizeOf)]
pub enum CacheState {
None,
Local,
@@ -76,16 +75,19 @@ pub enum ResponseMsg {
}
/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
-#[derive(Clone)]
+#[derive(Clone, HeapSizeOf)]
pub struct Response {
pub response_type: ResponseType,
pub termination_reason: Option<TerminationReason>,
pub url: Option<Url>,
pub url_list: RefCell<Vec<Url>>,
/// `None` can be considered a StatusCode of `0`.
+ #[ignore_heap_size_of = "Defined in hyper"]
pub status: Option<StatusCode>,
- pub raw_status: Option<RawStatus>,
+ pub raw_status: Option<(u16, Vec<u8>)>,
+ #[ignore_heap_size_of = "Defined in hyper"]
pub headers: Headers,
+ #[ignore_heap_size_of = "Mutex heap size undefined"]
pub body: Arc<Mutex<ResponseBody>>,
pub cache_state: CacheState,
pub https_state: HttpsState,
@@ -104,7 +106,7 @@ impl Response {
url: None,
url_list: RefCell::new(Vec::new()),
status: Some(StatusCode::Ok),
- raw_status: Some(RawStatus(200, "OK".into())),
+ raw_status: Some((200, b"OK".to_vec())),
headers: Headers::new(),
body: Arc::new(Mutex::new(ResponseBody::Empty)),
cache_state: CacheState::None,
@@ -239,7 +241,7 @@ impl Response {
None => None
});
metadata.headers = Some(Serde(self.headers.clone()));
- metadata.status = self.raw_status.clone().map(Serde);
+ metadata.status = self.raw_status.clone();
metadata.https_state = self.https_state;
return Ok(metadata);
}
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
index 507e19e4bcb..49a27d3271a 100644
--- a/components/script/dom/bindings/trace.rs
+++ b/components/script/dom/bindings/trace.rs
@@ -50,6 +50,7 @@ use html5ever::tree_builder::QuirksMode;
use hyper::header::Headers;
use hyper::method::Method;
use hyper::mime::Mime;
+use hyper::status::StatusCode;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use js::glue::{CallObjectTracer, CallUnbarrieredObjectTracer, CallValueTracer};
use js::jsapi::{GCTraceKindToAscii, Heap, TraceKind, JSObject, JSTracer};
@@ -62,6 +63,7 @@ use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread};
use net_traits::request::Request;
use net_traits::response::HttpsState;
+use net_traits::response::{Response, ResponseBody};
use net_traits::storage_thread::StorageType;
use net_traits::{Metadata, NetworkError, ResourceThreads};
use offscreen_gl_context::GLLimits;
@@ -339,7 +341,10 @@ no_jsmanaged_fields!(SharedRt);
no_jsmanaged_fields!(TouchpadPressurePhase);
no_jsmanaged_fields!(USVString);
no_jsmanaged_fields!(ReferrerPolicy);
+no_jsmanaged_fields!(Response);
+no_jsmanaged_fields!(ResponseBody);
no_jsmanaged_fields!(ResourceThreads);
+no_jsmanaged_fields!(StatusCode);
no_jsmanaged_fields!(SystemTime);
no_jsmanaged_fields!(RelativePos);
no_jsmanaged_fields!(OpaqueStyleAndLayoutData);
diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs
index 7b45956c039..abfa959c8e6 100644
--- a/components/script/dom/headers.rs
+++ b/components/script/dom/headers.rs
@@ -209,7 +209,13 @@ impl Headers {
headers_for_request
}
- pub fn set_guard(&self, new_guard: Guard) {
+ pub fn for_response(global: GlobalRef) -> Root<Headers> {
+ let headers_for_response = Headers::new(global);
+ headers_for_response.guard.set(Guard::Response);
+ headers_for_response
+ }
+
+ pub fn set_guard(&self, new_guard: Guard) {
self.guard.set(new_guard)
}
@@ -346,7 +352,7 @@ pub fn is_forbidden_header_name(name: &str) -> bool {
// [3] https://tools.ietf.org/html/rfc7230#section-3.2.6
// [4] https://www.rfc-editor.org/errata_search.php?rfc=7230
fn validate_name_and_value(name: ByteString, value: ByteString)
- -> Result<(String, Vec<u8>), Error> {
+ -> Fallible<(String, Vec<u8>)> {
let valid_name = try!(validate_name(name));
if !is_field_content(&value) {
return Err(Error::Type("Value is not valid".to_string()));
@@ -354,7 +360,7 @@ fn validate_name_and_value(name: ByteString, value: ByteString)
Ok((valid_name, value.into()))
}
-fn validate_name(name: ByteString) -> Result<String, Error> {
+fn validate_name(name: ByteString) -> Fallible<String> {
if !is_field_name(&name) {
return Err(Error::Type("Name is not valid".to_string()));
}
@@ -444,7 +450,7 @@ fn is_field_vchar(x: u8) -> bool {
}
// https://tools.ietf.org/html/rfc5234#appendix-B.1
-fn is_vchar(x: u8) -> bool {
+pub fn is_vchar(x: u8) -> bool {
match x {
0x21...0x7E => true,
_ => false,
@@ -452,7 +458,7 @@ fn is_vchar(x: u8) -> bool {
}
// http://tools.ietf.org/html/rfc7230#section-3.2.6
-fn is_obs_text(x: u8) -> bool {
+pub fn is_obs_text(x: u8) -> bool {
match x {
0x80...0xFF => true,
_ => false,
diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs
index 9264bcb0490..07cd47cbcec 100644
--- a/components/script/dom/htmllinkelement.rs
+++ b/components/script/dom/htmllinkelement.rs
@@ -22,7 +22,6 @@ use dom::virtualmethods::VirtualMethods;
use encoding::EncodingRef;
use encoding::all::UTF_8;
use hyper::header::ContentType;
-use hyper::http::RawStatus;
use hyper::mime::{Mime, TopLevel, SubLevel};
use hyper_serde::Serde;
use ipc_channel::ipc;
@@ -335,7 +334,7 @@ impl AsyncResponseListener for StylesheetContext {
document.invalidate_stylesheets();
// FIXME: Revisit once consensus is reached at: https://github.com/whatwg/html/issues/1142
- successful = metadata.status.map_or(false, |Serde(RawStatus(code, _))| code == 200);
+ successful = metadata.status.map_or(false, |(code, _)| code == 200);
}
if elem.parser_inserted.get() {
diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs
index d26ac6a044e..c2db2b0b96f 100644
--- a/components/script/dom/htmlmediaelement.rs
+++ b/components/script/dom/htmlmediaelement.rs
@@ -24,7 +24,6 @@ use dom::htmlsourceelement::HTMLSourceElement;
use dom::mediaerror::MediaError;
use dom::node::{window_from_node, document_from_node, Node, UnbindContext};
use dom::virtualmethods::VirtualMethods;
-use hyper_serde::Serde;
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError};
@@ -66,7 +65,7 @@ impl AsyncResponseListener for HTMLMediaElementContext {
.as_ref()
.and_then(|m| m.status
.as_ref()
- .map(|&Serde(ref s)| s.0 < 200 || s.0 >= 300))
+ .map(|&(s, _)| s < 200 || s >= 300))
.unwrap_or(false);
if is_failure {
// Ensure that the element doesn't receive any further notifications
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs
index 6a6fe7d50b4..a7f5c5e3666 100644
--- a/components/script/dom/htmlscriptelement.rs
+++ b/components/script/dom/htmlscriptelement.rs
@@ -26,8 +26,6 @@ use dom::window::ScriptHelpers;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{DecoderTrap, EncodingRef};
use html5ever::tree_builder::NextParserState;
-use hyper::http::RawStatus;
-use hyper_serde::Serde;
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use js::jsval::UndefinedValue;
@@ -159,7 +157,7 @@ impl AsyncResponseListener for ScriptContext {
let status_code = self.metadata.as_ref().and_then(|m| {
match m.status {
- Some(Serde(RawStatus(c, _))) => Some(c),
+ Some((c, _)) => Some(c),
_ => None,
}
}).unwrap_or(0);
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index 07e4d6227be..f38d1e8949c 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -372,6 +372,7 @@ pub mod progressevent;
pub mod radionodelist;
pub mod range;
pub mod request;
+pub mod response;
pub mod screen;
pub mod serviceworker;
pub mod serviceworkercontainer;
diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs
new file mode 100644
index 00000000000..acfc181283a
--- /dev/null
+++ b/components/script/dom/response.rs
@@ -0,0 +1,290 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use core::cell::Cell;
+use dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
+use dom::bindings::codegen::Bindings::ResponseBinding;
+use dom::bindings::codegen::Bindings::ResponseBinding::{ResponseMethods, ResponseType as DOMResponseType};
+use dom::bindings::error::{Error, Fallible};
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JS, MutNullableHeap, Root};
+use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
+use dom::bindings::str::{ByteString, USVString};
+use dom::headers::{Headers, Guard};
+use dom::headers::{is_vchar, is_obs_text};
+use hyper::status::StatusCode;
+use net_traits::response::{ResponseBody as NetTraitsResponseBody};
+use std::str::FromStr;
+use url::Position;
+use url::Url;
+
+#[dom_struct]
+pub struct Response {
+ reflector_: Reflector,
+ headers_reflector: MutNullableHeap<JS<Headers>>,
+ mime_type: DOMRefCell<Vec<u8>>,
+ body_used: Cell<bool>,
+ /// `None` can be considered a StatusCode of `0`.
+ #[ignore_heap_size_of = "Defined in hyper"]
+ status: DOMRefCell<Option<StatusCode>>,
+ raw_status: DOMRefCell<Option<(u16, Vec<u8>)>>,
+ response_type: DOMRefCell<DOMResponseType>,
+ url: DOMRefCell<Option<Url>>,
+ url_list: DOMRefCell<Vec<Url>>,
+ // For now use the existing NetTraitsResponseBody enum, until body
+ // is implemented.
+ body: DOMRefCell<NetTraitsResponseBody>,
+}
+
+impl Response {
+ pub fn new_inherited() -> Response {
+ Response {
+ reflector_: Reflector::new(),
+ headers_reflector: Default::default(),
+ mime_type: DOMRefCell::new("".to_string().into_bytes()),
+ body_used: Cell::new(false),
+ status: DOMRefCell::new(Some(StatusCode::Ok)),
+ raw_status: DOMRefCell::new(Some((200, b"OK".to_vec()))),
+ response_type: DOMRefCell::new(DOMResponseType::Default),
+ url: DOMRefCell::new(None),
+ url_list: DOMRefCell::new(vec![]),
+ body: DOMRefCell::new(NetTraitsResponseBody::Empty),
+ }
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response
+ pub fn new(global: GlobalRef) -> Root<Response> {
+ reflect_dom_object(box Response::new_inherited(), global, ResponseBinding::Wrap)
+ }
+
+ pub fn Constructor(global: GlobalRef, _body: Option<USVString>, init: &ResponseBinding::ResponseInit)
+ -> Fallible<Root<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()));
+ }
+
+ // Step 3
+ let r = Response::new(global);
+
+ // Step 4
+ *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status));
+
+ // Step 5
+ *r.raw_status.borrow_mut() = Some((init.status, init.statusText.clone().into()));
+
+ // Step 6
+ if let Some(ref headers_member) = init.headers {
+ // Step 6.1
+ // TODO: Figure out how/if we should make r's response's
+ // header list and r's Headers object the same thing. For
+ // now just working with r's Headers object. Also, the
+ // header list should already be empty so this step may be
+ // unnecessary.
+ r.Headers().empty_header_list();
+
+ // Step 6.2
+ try!(r.Headers().fill(Some(headers_member.clone())));
+ }
+
+ // Step 7
+ if let Some(_) = _body {
+ // Step 7.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 7.2
+ let content_type: Option<ByteString> = None;
+
+ // Step 7.3
+ // TODO: Extract body and implement step 7.3.
+
+ // Step 7.4
+ if let Some(content_type_contents) = content_type {
+ if !r.Headers().Has(ByteString::new(b"Content-Type".to_vec())).unwrap() {
+ try!(r.Headers().Append(ByteString::new(b"Content-Type".to_vec()), content_type_contents));
+ }
+ };
+ }
+
+ // Step 8
+ *r.mime_type.borrow_mut() = r.Headers().extract_mime_type();
+
+ // Step 9
+ // TODO: `entry settings object` is not implemented in Servo yet.
+
+ // Step 10
+ // TODO: Write this step once Promises are merged in
+
+ // Step 11
+ Ok(r)
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-error
+ pub fn Error(global: GlobalRef) -> Root<Response> {
+ let r = Response::new(global);
+ *r.response_type.borrow_mut() = DOMResponseType::Error;
+ r.Headers().set_guard(Guard::Immutable);
+ *r.raw_status.borrow_mut() = Some((0, b"".to_vec()));
+ r
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-redirect
+ pub fn Redirect(global: GlobalRef, url: USVString, status: u16) -> Fallible<Root<Response>> {
+ // Step 1
+ // TODO: `entry settings object` is not implemented in Servo yet.
+ let base_url = global.get_url();
+ let parsed_url = base_url.join(&url.0);
+
+ // Step 2
+ let url = match parsed_url {
+ Ok(url) => url,
+ Err(_) => return Err(Error::Type("Url could not be parsed".to_string())),
+ };
+
+ // Step 3
+ if !is_redirect_status(status) {
+ return Err(Error::Range("status is not a redirect status".to_string()));
+ }
+
+ // Step 4
+ // see Step 4 continued
+ let r = Response::new(global);
+
+ // Step 5
+ *r.status.borrow_mut() = Some(StatusCode::from_u16(status));
+ *r.raw_status.borrow_mut() = Some((status, b"".to_vec()));
+
+ // Step 6
+ let url_bytestring = ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
+ try!(r.Headers().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().set_guard(Guard::Immutable);
+
+ // Step 7
+ Ok(r)
+ }
+}
+
+// https://fetch.spec.whatwg.org/#redirect-status
+fn is_redirect_status(status: u16) -> bool {
+ status == 301 || status == 302 || status == 303 || status == 307 || status == 308
+}
+
+// https://tools.ietf.org/html/rfc7230#section-3.1.2
+fn is_valid_status_text(status_text: &ByteString) -> bool {
+ // reason-phrase = *( HTAB / SP / VCHAR / obs-text )
+ for byte in status_text.iter() {
+ if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) {
+ return false;
+ }
+ }
+ true
+}
+
+// https://fetch.spec.whatwg.org/#null-body-status
+fn is_null_body_status(status: u16) -> bool {
+ status == 101 || status == 204 || status == 205 || status == 304
+}
+
+impl ResponseMethods for Response {
+ // 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
+ fn Url(&self) -> USVString {
+ USVString(String::from((*self.url.borrow()).as_ref().map(|u| serialize_without_fragment(u)).unwrap_or("")))
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-redirected
+ fn Redirected(&self) -> bool {
+ let url_list_len = self.url_list.borrow().len();
+ url_list_len > 1
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-status
+ fn Status(&self) -> u16 {
+ match *self.raw_status.borrow() {
+ Some((s, _)) => s,
+ None => 0,
+ }
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-ok
+ fn Ok(&self) -> bool {
+ match *self.status.borrow() {
+ Some(s) => {
+ let status_num = s.to_u16();
+ return status_num >= 200 && status_num <= 299;
+ }
+ None => false,
+ }
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-statustext
+ fn StatusText(&self) -> ByteString {
+ match *self.raw_status.borrow() {
+ Some((_, ref st)) => ByteString::new(st.clone()),
+ None => ByteString::new(b"OK".to_vec()),
+ }
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-headers
+ fn Headers(&self) -> Root<Headers> {
+ self.headers_reflector.or_init(|| Headers::for_response(self.global().r()))
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-clone
+ fn Clone(&self) -> Fallible<Root<Response>> {
+ // Step 1
+ // TODO: This step relies on body and stream, which are still unimplemented.
+
+ // Step 2
+ let new_response = Response::new(self.global().r());
+ new_response.Headers().set_guard(self.Headers().get_guard());
+
+ // https://fetch.spec.whatwg.org/#concept-response-clone
+ // Instead of storing a net_traits::Response internally, we
+ // only store the relevant fields, and only clone them here
+ *new_response.response_type.borrow_mut() = self.response_type.borrow().clone();
+ *new_response.status.borrow_mut() = self.status.borrow().clone();
+ *new_response.raw_status.borrow_mut() = self.raw_status.borrow().clone();
+ *new_response.url.borrow_mut() = self.url.borrow().clone();
+ *new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
+
+ if *self.body.borrow() != NetTraitsResponseBody::Empty {
+ *new_response.body.borrow_mut() = self.body.borrow().clone();
+ }
+
+ // Step 3
+ // TODO: This step relies on promises, which are still unimplemented.
+
+ // Step 4
+ Ok(new_response)
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-body-bodyused
+ fn BodyUsed(&self) -> bool {
+ self.body_used.get()
+ }
+}
+
+fn serialize_without_fragment(url: &Url) -> &str {
+ &url[..Position::AfterQuery]
+}
diff --git a/components/script/dom/webidls/Response.webidl b/components/script/dom/webidls/Response.webidl
new file mode 100644
index 00000000000..2052f5c6371
--- /dev/null
+++ b/components/script/dom/webidls/Response.webidl
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// https://fetch.spec.whatwg.org/#response-class
+
+// TODO: pass 'optional ResponseBodyInit? body = null' to constructor in place of USVString
+ [Constructor(optional USVString? body = null, optional ResponseInit init),
+ Exposed=(Window,Worker)]
+interface Response {
+ [NewObject] static Response error();
+ [NewObject, Throws] static Response redirect(USVString url, optional unsigned short status = 302);
+
+ readonly attribute ResponseType type;
+
+ readonly attribute USVString url;
+ readonly attribute boolean redirected;
+ readonly attribute unsigned short status;
+ readonly attribute boolean ok;
+ readonly attribute ByteString statusText;
+ [SameObject] readonly attribute Headers headers;
+ // readonly attribute ReadableStream? body;
+ // [SameObject] readonly attribute Promise<Headers> trailer;
+
+ [NewObject, Throws] Response clone();
+};
+Response implements Body;
+
+dictionary ResponseInit {
+ unsigned short status = 200;
+ ByteString statusText = "OK";
+ HeadersInit headers;
+};
+
+enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
+
+// typedef (BodyInit or ReadableStream) ResponseBodyInit;
diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs
index 382e4c69faa..fc82d63a897 100644
--- a/components/script/dom/xmlhttprequest.rs
+++ b/components/script/dom/xmlhttprequest.rs
@@ -36,7 +36,6 @@ use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef};
use euclid::length::Length;
use hyper::header::Headers;
use hyper::header::{ContentLength, ContentType};
-use hyper::http::RawStatus;
use hyper::method::Method;
use hyper::mime::{self, Mime, Attr as MimeAttr, Value as MimeValue};
use hyper_serde::Serde;
@@ -91,7 +90,7 @@ struct XHRContext {
#[derive(Clone)]
pub enum XHRProgress {
/// Notify that headers have been received
- HeadersReceived(GenerationId, Option<Headers>, Option<RawStatus>),
+ HeadersReceived(GenerationId, Option<Headers>, Option<(u16, Vec<u8>)>),
/// Partial progress (after receiving headers), containing portion of the response
Loading(GenerationId, ByteString),
/// Loading is done
@@ -879,7 +878,7 @@ impl XMLHttpRequest {
self.process_partial_response(XHRProgress::HeadersReceived(
gen_id,
metadata.headers.map(Serde::into_inner),
- metadata.status.map(Serde::into_inner)));
+ metadata.status));
Ok(())
}
@@ -943,9 +942,9 @@ impl XMLHttpRequest {
// Part of step 13, send() (processing response)
// XXXManishearth handle errors, if any (substep 1)
// Substep 2
- status.map(|RawStatus(code, reason)| {
+ status.map(|(code, reason)| {
self.status.set(code);
- *self.status_text.borrow_mut() = ByteString::new(reason.into_owned().into_bytes());
+ *self.status_text.borrow_mut() = ByteString::new(reason);
});
headers.as_ref().map(|h| *self.response_headers.borrow_mut() = h.clone());
diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock
index f402fc54aa3..3d1c5a462e8 100644
--- a/components/servo/Cargo.lock
+++ b/components/servo/Cargo.lock
@@ -493,6 +493,7 @@ name = "devtools"
version = "0.0.1"
dependencies = [
"devtools_traits 0.0.1",
+ "encoding 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",