diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/trace.rs | 5 | ||||
-rw-r--r-- | components/script/dom/headers.rs | 16 | ||||
-rw-r--r-- | components/script/dom/htmllinkelement.rs | 3 | ||||
-rw-r--r-- | components/script/dom/htmlmediaelement.rs | 3 | ||||
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 4 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/response.rs | 290 | ||||
-rw-r--r-- | components/script/dom/webidls/Response.webidl | 37 | ||||
-rw-r--r-- | components/script/dom/xmlhttprequest.rs | 9 |
9 files changed, 351 insertions, 17 deletions
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()); |