Auto merge of #9383 - KiChjang:xhr-cleanup, r=Ms2ger

Clean up XHR API

I've also added annotations about the steps that we're performing within each method.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/9383)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-02-19 02:25:18 +05:30
commit 96d185359d
2 changed files with 140 additions and 86 deletions

View file

@ -55,7 +55,6 @@ use std::ascii::AsciiExt;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::default::Default; use std::default::Default;
use std::string::ToString;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use string_cache::Atom; use string_cache::Atom;
@ -199,6 +198,13 @@ impl XMLHttpRequest {
Ok(XMLHttpRequest::new(global)) Ok(XMLHttpRequest::new(global))
} }
fn sync_in_window(&self) -> bool {
match self.global() {
GlobalRoot::Window(_) if self.sync.get() => true,
_ => false
}
}
fn check_cors(context: Arc<Mutex<XHRContext>>, fn check_cors(context: Arc<Mutex<XHRContext>>,
load_data: LoadData, load_data: LoadData,
req: CORSRequest, req: CORSRequest,
@ -300,10 +306,24 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
self.ready_state.get() as u16 self.ready_state.get() as u16
} }
// https://xhr.spec.whatwg.org/#the-open()-method
fn Open(&self, method: ByteString, url: USVString) -> ErrorResult {
// Step 8
self.Open_(method, url, true, None, None)
}
// https://xhr.spec.whatwg.org/#the-open()-method // https://xhr.spec.whatwg.org/#the-open()-method
fn Open_(&self, method: ByteString, url: USVString, async: bool, fn Open_(&self, method: ByteString, url: USVString, async: bool,
username: Option<USVString>, password: Option<USVString>) -> ErrorResult { username: Option<USVString>, password: Option<USVString>) -> ErrorResult {
self.sync.set(!async); // Step 1
match self.global() {
GlobalRoot::Window(ref window) => {
if !window.Document().r().is_fully_active() { return Err(Error::InvalidState); }
}
_ => {}
}
// Step 5
//FIXME(seanmonstar): use a Trie instead? //FIXME(seanmonstar): use a Trie instead?
let maybe_method = method.as_str().and_then(|s| { let maybe_method = method.as_str().and_then(|s| {
// Note: hyper tests against the uppercase versions // Note: hyper tests against the uppercase versions
@ -319,7 +339,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
_ => s.parse().ok() _ => s.parse().ok()
} }
}); });
// Step 2
match maybe_method { match maybe_method {
// Step 4 // Step 4
Some(Method::Connect) | Some(Method::Trace) => Err(Error::Security), Some(Method::Connect) | Some(Method::Trace) => Err(Error::Security),
@ -330,43 +350,44 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
return Err(Error::Syntax) return Err(Error::Syntax)
} }
*self.request_method.borrow_mut() = parsed_method; // Step 2
// Step 6
let base = self.global().r().get_url(); let base = self.global().r().get_url();
// Step 6
let mut parsed_url = match base.join(&url.0) { let mut parsed_url = match base.join(&url.0) {
Ok(parsed) => parsed, Ok(parsed) => parsed,
Err(_) => return Err(Error::Syntax) // Step 7 // Step 7
Err(_) => return Err(Error::Syntax)
}; };
// XXXManishearth Do some handling of username/passwords // Step 9
if self.sync.get() {
// FIXME: This should only happen if the global environment is a document environment
if self.timeout.get() != 0 || self.with_credentials.get() ||
self.response_type.get() != XMLHttpRequestResponseType::_empty {
return Err(Error::InvalidAccess)
}
}
if parsed_url.host().is_some() { if parsed_url.host().is_some() {
if let Some(scheme_data) = parsed_url.relative_scheme_data_mut() { if let Some(scheme_data) = parsed_url.relative_scheme_data_mut() {
if let Some(user_str) = username { if let Some(user_str) = username {
scheme_data.username = utf8_percent_encode(&user_str.0, USERNAME_ENCODE_SET); scheme_data.username = utf8_percent_encode(&user_str.0, USERNAME_ENCODE_SET);
// ensure that the password is mutated when a username is provided // ensure that the password is mutated when a username is provided
scheme_data.password = match password { scheme_data.password = password.map(|pass_str| {
Some(pass_str) => Some(utf8_percent_encode(&pass_str.0, PASSWORD_ENCODE_SET)), utf8_percent_encode(&pass_str.0, PASSWORD_ENCODE_SET)
None => None });
}
} }
} }
} }
// abort existing requests // Step 10
if !async {
// FIXME: This should only happen if the global environment is a document environment
if self.timeout.get() != 0 || self.with_credentials.get() ||
self.response_type.get() != XMLHttpRequestResponseType::_empty {
return Err(Error::InvalidAccess)
}
}
// Step 11 - abort existing requests
self.terminate_ongoing_fetch(); self.terminate_ongoing_fetch();
// Step 12 // Step 12
*self.request_method.borrow_mut() = parsed_method;
*self.request_url.borrow_mut() = Some(parsed_url); *self.request_url.borrow_mut() = Some(parsed_url);
self.sync.set(!async);
*self.request_headers.borrow_mut() = Headers::new(); *self.request_headers.borrow_mut() = Headers::new();
self.send_flag.set(false); self.send_flag.set(false);
*self.status_text.borrow_mut() = ByteString::new(vec!()); *self.status_text.borrow_mut() = ByteString::new(vec!());
@ -378,29 +399,29 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
} }
Ok(()) Ok(())
}, },
// Step 3
// This includes cases where as_str() returns None, and when is_token() returns false, // This includes cases where as_str() returns None, and when is_token() returns false,
// both of which indicate invalid extension method names // both of which indicate invalid extension method names
_ => Err(Error::Syntax), // Step 3 _ => Err(Error::Syntax)
} }
} }
// https://xhr.spec.whatwg.org/#the-open()-method
fn Open(&self, method: ByteString, url: USVString) -> ErrorResult {
self.Open_(method, url, true, None, None)
}
// https://xhr.spec.whatwg.org/#the-setrequestheader()-method // https://xhr.spec.whatwg.org/#the-setrequestheader()-method
fn SetRequestHeader(&self, name: ByteString, mut value: ByteString) -> ErrorResult { fn SetRequestHeader(&self, name: ByteString, mut value: ByteString) -> ErrorResult {
// Step 1, 2
if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() { if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() {
return Err(Error::InvalidState); // Step 1, 2 return Err(Error::InvalidState);
} }
// FIXME(#9548): Step 3. Normalize value
// Step 4
if !name.is_token() || !value.is_field_value() { if !name.is_token() || !value.is_field_value() {
return Err(Error::Syntax); // Step 3, 4 return Err(Error::Syntax);
} }
let name_lower = name.to_lower(); let name_lower = name.to_lower();
let name_str = match name_lower.as_str() { let name_str = match name_lower.as_str() {
Some(s) => { Some(s) => {
match s { match s {
// Step 5
// Disallowed headers // Disallowed headers
"accept-charset" | "accept-encoding" | "accept-charset" | "accept-encoding" |
"access-control-request-headers" | "access-control-request-headers" |
@ -410,19 +431,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
"expect" | "host" | "keep-alive" | "origin" | "expect" | "host" | "keep-alive" | "origin" |
"referer" | "te" | "trailer" | "transfer-encoding" | "referer" | "te" | "trailer" | "transfer-encoding" |
"upgrade" | "user-agent" | "via" => { "upgrade" | "user-agent" | "via" => {
return Ok(()); // Step 5 return Ok(());
}, },
_ => s _ => s
} }
}, },
None => return Err(Error::Syntax) None => unreachable!()
}; };
debug!("SetRequestHeader: name={:?}, value={:?}", name.as_str(), value.as_str()); debug!("SetRequestHeader: name={:?}, value={:?}", name.as_str(), value.as_str());
let mut headers = self.request_headers.borrow_mut(); let mut headers = self.request_headers.borrow_mut();
// Steps 6,7 // Step 6
match headers.get_raw(name_str) { match headers.get_raw(name_str) {
Some(raw) => { Some(raw) => {
debug!("SetRequestHeader: old value = {:?}", raw[0]); debug!("SetRequestHeader: old value = {:?}", raw[0]);
@ -446,26 +467,27 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-timeout-attribute // https://xhr.spec.whatwg.org/#the-timeout-attribute
fn SetTimeout(&self, timeout: u32) -> ErrorResult { fn SetTimeout(&self, timeout: u32) -> ErrorResult {
if self.sync.get() { // Step 1
// FIXME: Not valid for a worker environment if self.sync_in_window() {
Err(Error::InvalidAccess) return Err(Error::InvalidAccess);
} else {
self.timeout.set(timeout);
if self.send_flag.get() {
if timeout == 0 {
self.cancel_timeout();
return Ok(());
}
let progress = time::now().to_timespec().sec - self.fetch_time.get();
if timeout > (progress * 1000) as u32 {
self.set_timeout(timeout - (progress * 1000) as u32);
} else {
// Immediately execute the timeout steps
self.set_timeout(0);
}
}
Ok(())
} }
// Step 2
self.timeout.set(timeout);
if self.send_flag.get() {
if timeout == 0 {
self.cancel_timeout();
return Ok(());
}
let progress = time::now().to_timespec().sec - self.fetch_time.get();
if timeout > (progress * 1000) as u32 {
self.set_timeout(timeout - (progress * 1000) as u32);
} else {
// Immediately execute the timeout steps
self.set_timeout(0);
}
}
Ok(())
} }
// https://xhr.spec.whatwg.org/#the-withcredentials-attribute // https://xhr.spec.whatwg.org/#the-withcredentials-attribute
@ -476,17 +498,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-withcredentials // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-withcredentials
fn SetWithCredentials(&self, with_credentials: bool) -> ErrorResult { fn SetWithCredentials(&self, with_credentials: bool) -> ErrorResult {
match self.ready_state.get() { match self.ready_state.get() {
// Step 1
XMLHttpRequestState::HeadersReceived | XMLHttpRequestState::HeadersReceived |
XMLHttpRequestState::Loading | XMLHttpRequestState::Loading |
XMLHttpRequestState::Done => Err(Error::InvalidState), XMLHttpRequestState::Done => Err(Error::InvalidState),
// Step 2
_ if self.send_flag.get() => Err(Error::InvalidState), _ if self.send_flag.get() => Err(Error::InvalidState),
_ => match self.global() { // Step 3
GlobalRoot::Window(_) if self.sync.get() => Err(Error::InvalidAccess), _ if self.sync_in_window() => Err(Error::InvalidAccess),
_ => { // Step 4
self.with_credentials.set(with_credentials); _ => {
Ok(()) self.with_credentials.set(with_credentials);
}, Ok(())
}, }
} }
} }
@ -497,14 +521,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-send()-method // https://xhr.spec.whatwg.org/#the-send()-method
fn Send(&self, data: Option<SendParam>) -> ErrorResult { fn Send(&self, data: Option<SendParam>) -> ErrorResult {
// Step 1, 2
if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() { if self.ready_state.get() != XMLHttpRequestState::Opened || self.send_flag.get() {
return Err(Error::InvalidState); // Step 1, 2 return Err(Error::InvalidState);
} }
// Step 3
let data = match *self.request_method.borrow() { let data = match *self.request_method.borrow() {
Method::Get | Method::Head => None, // Step 3 Method::Get | Method::Head => None,
_ => data _ => data
}; };
// Step 4
let extracted = data.as_ref().map(|d| d.extract()); let extracted = data.as_ref().map(|d| d.extract());
self.request_body_len.set(extracted.as_ref().map_or(0, |e| e.0.len())); self.request_body_len.set(extracted.as_ref().map_or(0, |e| e.0.len()));
@ -516,23 +543,25 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
Some (ref e) if e.0.is_empty() => true, Some (ref e) if e.0.is_empty() => true,
_ => false _ => false
}); });
// Step 8
self.send_flag.set(true);
// Step 9
if !self.sync.get() { if !self.sync.get() {
// Step 8
let event_target = self.upload.upcast::<EventTarget>(); let event_target = self.upload.upcast::<EventTarget>();
if event_target.has_handlers() { if event_target.has_handlers() {
self.upload_events.set(true); self.upload_events.set(true);
} }
// Step 9
self.send_flag.set(true);
// If one of the event handlers below aborts the fetch by calling // If one of the event handlers below aborts the fetch by calling
// abort or open we will need the current generation id to detect it. // abort or open we will need the current generation id to detect it.
// Substep 1
let gen_id = self.generation_id.get(); let gen_id = self.generation_id.get();
self.dispatch_response_progress_event(atom!("loadstart")); self.dispatch_response_progress_event(atom!("loadstart"));
if self.generation_id.get() != gen_id { if self.generation_id.get() != gen_id {
return Ok(()); return Ok(());
} }
// Substep 2
if !self.upload_complete.get() { if !self.upload_complete.get() {
self.dispatch_upload_progress_event(atom!("loadstart"), Some(0)); self.dispatch_upload_progress_event(atom!("loadstart"), Some(0));
if self.generation_id.get() != gen_id { if self.generation_id.get() != gen_id {
@ -542,6 +571,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
} }
// Step 5
let global = self.global(); let global = self.global();
let pipeline_id = global.r().pipeline(); let pipeline_id = global.r().pipeline();
let mut load_data = let mut load_data =
@ -617,6 +647,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
self.fetch_time.set(time::now().to_timespec().sec); self.fetch_time.set(time::now().to_timespec().sec);
let rv = self.fetch(load_data, cors_request, global.r()); let rv = self.fetch(load_data, cors_request, global.r());
// Step 10
if self.sync.get() { if self.sync.get() {
return rv; return rv;
} }
@ -630,7 +661,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-abort()-method // https://xhr.spec.whatwg.org/#the-abort()-method
fn Abort(&self) { fn Abort(&self) {
// Step 1
self.terminate_ongoing_fetch(); self.terminate_ongoing_fetch();
// Step 2
let state = self.ready_state.get(); let state = self.ready_state.get();
if (state == XMLHttpRequestState::Opened && self.send_flag.get()) || if (state == XMLHttpRequestState::Opened && self.send_flag.get()) ||
state == XMLHttpRequestState::HeadersReceived || state == XMLHttpRequestState::HeadersReceived ||
@ -643,6 +676,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
return return
} }
} }
// Step 3
self.ready_state.set(XMLHttpRequestState::Unsent); self.ready_state.set(XMLHttpRequestState::Unsent);
} }
@ -677,15 +711,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-overridemimetype()-method // https://xhr.spec.whatwg.org/#the-overridemimetype()-method
fn OverrideMimeType(&self, mime: DOMString) -> ErrorResult { fn OverrideMimeType(&self, mime: DOMString) -> ErrorResult {
// Step 1
match self.ready_state.get() { match self.ready_state.get() {
XMLHttpRequestState::Loading | XMLHttpRequestState::Done => return Err(Error::InvalidState), XMLHttpRequestState::Loading | XMLHttpRequestState::Done => return Err(Error::InvalidState),
_ => {}, _ => {},
} }
// Step 2
let override_mime = try!(mime.parse::<Mime>().map_err(|_| Error::Syntax)); let override_mime = try!(mime.parse::<Mime>().map_err(|_| Error::Syntax));
// Step 3
*self.override_mime_type.borrow_mut() = Some(override_mime.clone()); *self.override_mime_type.borrow_mut() = Some(override_mime.clone());
// Step 4
let value = override_mime.get_param(mime::Attr::Charset); let value = override_mime.get_param(mime::Attr::Charset);
*self.override_charset.borrow_mut() = value.and_then(|value| { *self.override_charset.borrow_mut() = value.and_then(|value| {
encoding_from_whatwg_label(value) encoding_from_whatwg_label(value)
}); });
Ok(()) Ok(())
} }
@ -697,16 +735,20 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-responsetype-attribute // https://xhr.spec.whatwg.org/#the-responsetype-attribute
fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult { fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult {
// Step 1
match self.global() { match self.global() {
GlobalRoot::Worker(_) if response_type == XMLHttpRequestResponseType::Document => return Ok(()), GlobalRoot::Worker(_) if response_type == XMLHttpRequestResponseType::Document => return Ok(()),
_ => {} _ => {}
} }
match self.ready_state.get() { match self.ready_state.get() {
// Step 2
XMLHttpRequestState::Loading | XMLHttpRequestState::Done => Err(Error::InvalidState), XMLHttpRequestState::Loading | XMLHttpRequestState::Done => Err(Error::InvalidState),
_ => { _ => {
if let (GlobalRoot::Window(_), true) = (self.global(), self.sync.get()) { if self.sync_in_window() {
// Step 3
Err(Error::InvalidAccess) Err(Error::InvalidAccess)
} else { } else {
// Step 4
self.response_type.set(response_type); self.response_type.set(response_type);
Ok(()) Ok(())
} }
@ -722,20 +764,25 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
match self.response_type.get() { match self.response_type.get() {
XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => {
let ready_state = self.ready_state.get(); let ready_state = self.ready_state.get();
// Step 2
if ready_state == XMLHttpRequestState::Done || ready_state == XMLHttpRequestState::Loading { if ready_state == XMLHttpRequestState::Done || ready_state == XMLHttpRequestState::Loading {
self.text_response().to_jsval(cx, rval.handle_mut()); self.text_response().to_jsval(cx, rval.handle_mut());
} else { } else {
// Step 1
"".to_jsval(cx, rval.handle_mut()); "".to_jsval(cx, rval.handle_mut());
} }
}, },
// Step 1
_ if self.ready_state.get() != XMLHttpRequestState::Done => { _ if self.ready_state.get() != XMLHttpRequestState::Done => {
return NullValue() return NullValue();
}, },
// Step 2
XMLHttpRequestResponseType::Document => { XMLHttpRequestResponseType::Document => {
let op_doc = self.GetResponseXML(); let op_doc = self.GetResponseXML();
if let Ok(Some(doc)) = op_doc { if let Ok(Some(doc)) = op_doc {
doc.to_jsval(cx, rval.handle_mut()); doc.to_jsval(cx, rval.handle_mut());
} else { } else {
// Substep 1
return NullValue(); return NullValue();
} }
}, },
@ -759,10 +806,13 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
match self.response_type.get() { match self.response_type.get() {
XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => {
Ok(USVString(String::from(match self.ready_state.get() { Ok(USVString(String::from(match self.ready_state.get() {
// Step 3
XMLHttpRequestState::Loading | XMLHttpRequestState::Done => self.text_response(), XMLHttpRequestState::Loading | XMLHttpRequestState::Done => self.text_response(),
// Step 2
_ => "".to_owned() _ => "".to_owned()
}))) })))
}, },
// Step 1
_ => Err(Error::InvalidState) _ => Err(Error::InvalidState)
} }
} }
@ -771,20 +821,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
fn GetResponseXML(&self) -> Fallible<Option<Root<Document>>> { fn GetResponseXML(&self) -> Fallible<Option<Root<Document>>> {
match self.response_type.get() { match self.response_type.get() {
XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Document => { XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Document => {
match self.ready_state.get() { // Step 3
XMLHttpRequestState::Done => { if let XMLHttpRequestState::Done = self.ready_state.get() {
match self.response_xml.get() { Ok(self.response_xml.get().or_else(|| {
Some(response) => Ok(Some(response)), let response = self.document_response();
None => { self.response_xml.set(response.r());
let response = self.document_response(); response
self.response_xml.set(response.r()); }))
Ok(response) } else {
} // Step 2
} Ok(None)
},
_ => Ok(None)
} }
}, }
// Step 1
_ => { Err(Error::InvalidState) } _ => { Err(Error::InvalidState) }
} }
} }
@ -1048,13 +1097,16 @@ impl XMLHttpRequest {
} }
} }
//FIXME: add support for XML encoding guess stuff using XML spec // https://xhr.spec.whatwg.org/#text-response
fn text_response(&self) -> String { fn text_response(&self) -> String {
let encoding = self.final_charset().unwrap_or(UTF_8); // Step 3, 5
let charset = self.final_charset().unwrap_or(UTF_8);
// TODO: Step 4 - add support for XML encoding guess stuff using XML spec
// According to Simon, decode() should never return an error, so unwrap()ing // According to Simon, decode() should never return an error, so unwrap()ing
// the result should be fine. XXXManishearth have a closer look at this later // the result should be fine. XXXManishearth have a closer look at this later
encoding.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap().to_owned() // Step 1, 2, 6
charset.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap().to_owned()
} }
// https://xhr.spec.whatwg.org/#blob-response // https://xhr.spec.whatwg.org/#blob-response
@ -1064,14 +1116,15 @@ impl XMLHttpRequest {
return response; return response;
} }
// Step 2 // Step 2
let mime = self.final_mime_type().as_ref().map(ToString::to_string).unwrap_or("".to_owned()); let mime = self.final_mime_type().as_ref().map(Mime::to_string).unwrap_or("".to_owned());
// Steps 3 && 4 // Step 3, 4
let blob = Blob::new(self.global().r(), self.response.borrow().to_vec(), &mime); let blob = Blob::new(self.global().r(), self.response.borrow().to_vec(), &mime);
self.response_blob.set(Some(blob.r())); self.response_blob.set(Some(blob.r()));
blob blob
} }
// https://xhr.spec.whatwg.org/#document-response
fn document_response(&self) -> Option<Root<Document>> { fn document_response(&self) -> Option<Root<Document>> {
let mime_type = self.final_mime_type(); let mime_type = self.final_mime_type();
//TODO: prescan the response to determine encoding if final charset is null //TODO: prescan the response to determine encoding if final charset is null

View file

@ -1,3 +1,4 @@
[xmlhttprequest-timeout-worker-synconworker.html] [xmlhttprequest-timeout-worker-synconworker.html]
type: testharness type: testharness
expected: TIMEOUT [Timeout test: timeout hit before load, timeout scheduled at 2000]
expected: FAIL