aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/xmlhttprequest.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/xmlhttprequest.rs')
-rw-r--r--components/script/dom/xmlhttprequest.rs290
1 files changed, 205 insertions, 85 deletions
diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs
index b5f86e6a868..140ad159792 100644
--- a/components/script/dom/xmlhttprequest.rs
+++ b/components/script/dom/xmlhttprequest.rs
@@ -17,11 +17,12 @@ use dom::bindings::conversions::{ToJSValConvertible};
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::global::{GlobalRef, GlobalRoot};
use dom::bindings::inheritance::Castable;
-use dom::bindings::js::{JS, MutNullableHeap};
+use dom::bindings::js::{JS, MutHeapJSVal, MutNullableHeap};
use dom::bindings::js::{Root, RootedReference};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflectable, reflect_dom_object};
use dom::bindings::str::{ByteString, USVString};
+use dom::blob::Blob;
use dom::document::DocumentSource;
use dom::document::{Document, IsHTMLDocument};
use dom::event::{Event, EventBubbles, EventCancelable};
@@ -60,6 +61,7 @@ use string_cache::Atom;
use time;
use timers::{ScheduledCallback, TimerHandle};
use url::Url;
+use url::percent_encoding::{utf8_percent_encode, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET};
use util::str::DOMString;
pub type SendParam = BlobOrStringOrURLSearchParams;
@@ -122,6 +124,9 @@ pub struct XMLHttpRequest {
response: DOMRefCell<ByteString>,
response_type: Cell<XMLHttpRequestResponseType>,
response_xml: MutNullableHeap<JS<Document>>,
+ response_blob: MutNullableHeap<JS<Blob>>,
+ #[ignore_heap_size_of = "Defined in rust-mozjs"]
+ response_json: MutHeapJSVal,
#[ignore_heap_size_of = "Defined in hyper"]
response_headers: DOMRefCell<Headers>,
#[ignore_heap_size_of = "Defined in hyper"]
@@ -161,6 +166,8 @@ impl XMLHttpRequest {
response: DOMRefCell::new(ByteString::new(vec!())),
response_type: Cell::new(XMLHttpRequestResponseType::_empty),
response_xml: Default::default(),
+ response_blob: Default::default(),
+ response_json: MutHeapJSVal::new(),
response_headers: DOMRefCell::new(Headers::new()),
override_mime_type: DOMRefCell::new(None),
override_charset: DOMRefCell::new(None),
@@ -191,6 +198,13 @@ impl XMLHttpRequest {
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>>,
load_data: LoadData,
req: CORSRequest,
@@ -294,6 +308,22 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// 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
+ fn Open_(&self, method: ByteString, url: USVString, async: bool,
+ username: Option<USVString>, password: Option<USVString>) -> ErrorResult {
+ // 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?
let maybe_method = method.as_str().and_then(|s| {
// Note: hyper tests against the uppercase versions
@@ -309,7 +339,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
_ => s.parse().ok()
}
});
- // Step 2
+
match maybe_method {
// Step 4
Some(Method::Connect) | Some(Method::Trace) => Err(Error::Security),
@@ -320,27 +350,44 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
return Err(Error::Syntax)
}
- *self.request_method.borrow_mut() = parsed_method;
-
- // Step 6
+ // Step 2
let base = self.global().r().get_url();
- let parsed_url = match base.join(&url.0) {
+ // Step 6
+ let mut parsed_url = match base.join(&url.0) {
Ok(parsed) => parsed,
- Err(_) => return Err(Error::Syntax) // Step 7
+ // Step 7
+ Err(_) => return Err(Error::Syntax)
};
- // XXXManishearth Do some handling of username/passwords
- if self.sync.get() {
+
+ // Step 9
+ if parsed_url.host().is_some() {
+ if let Some(scheme_data) = parsed_url.relative_scheme_data_mut() {
+ if let Some(user_str) = username {
+ scheme_data.username = utf8_percent_encode(&user_str.0, USERNAME_ENCODE_SET);
+
+ // ensure that the password is mutated when a username is provided
+ scheme_data.password = password.map(|pass_str| {
+ utf8_percent_encode(&pass_str.0, PASSWORD_ENCODE_SET)
+ });
+ }
+ }
+ }
+
+ // 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)
}
}
- // abort existing requests
+ // Step 11 - abort existing requests
self.terminate_ongoing_fetch();
// Step 12
+ *self.request_method.borrow_mut() = parsed_method;
*self.request_url.borrow_mut() = Some(parsed_url);
+ self.sync.set(!async);
*self.request_headers.borrow_mut() = Headers::new();
self.send_flag.set(false);
*self.status_text.borrow_mut() = ByteString::new(vec!());
@@ -352,31 +399,29 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
}
Ok(())
},
+ // Step 3
// This includes cases where as_str() returns None, and when is_token() returns false,
// 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, async: bool,
- _username: Option<USVString>, _password: Option<USVString>) -> ErrorResult {
- self.sync.set(!async);
- self.Open(method, url)
- }
-
// https://xhr.spec.whatwg.org/#the-setrequestheader()-method
fn SetRequestHeader(&self, name: ByteString, mut value: ByteString) -> ErrorResult {
+ // Step 1, 2
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() {
- return Err(Error::Syntax); // Step 3, 4
+ return Err(Error::Syntax);
}
let name_lower = name.to_lower();
let name_str = match name_lower.as_str() {
Some(s) => {
match s {
+ // Step 5
// Disallowed headers
"accept-charset" | "accept-encoding" |
"access-control-request-headers" |
@@ -386,19 +431,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
"expect" | "host" | "keep-alive" | "origin" |
"referer" | "te" | "trailer" | "transfer-encoding" |
"upgrade" | "user-agent" | "via" => {
- return Ok(()); // Step 5
+ return Ok(());
},
_ => s
}
},
- None => return Err(Error::Syntax)
+ None => unreachable!()
};
debug!("SetRequestHeader: name={:?}, value={:?}", name.as_str(), value.as_str());
let mut headers = self.request_headers.borrow_mut();
- // Steps 6,7
+ // Step 6
match headers.get_raw(name_str) {
Some(raw) => {
debug!("SetRequestHeader: old value = {:?}", raw[0]);
@@ -422,26 +467,27 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-timeout-attribute
fn SetTimeout(&self, timeout: u32) -> ErrorResult {
- if self.sync.get() {
- // FIXME: Not valid for a worker environment
- 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);
- }
+ // Step 1
+ if self.sync_in_window() {
+ return Err(Error::InvalidAccess);
+ }
+ // 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(())
}
+ Ok(())
}
// https://xhr.spec.whatwg.org/#the-withcredentials-attribute
@@ -452,17 +498,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-withcredentials
fn SetWithCredentials(&self, with_credentials: bool) -> ErrorResult {
match self.ready_state.get() {
+ // Step 1
XMLHttpRequestState::HeadersReceived |
XMLHttpRequestState::Loading |
XMLHttpRequestState::Done => Err(Error::InvalidState),
+ // Step 2
_ if self.send_flag.get() => Err(Error::InvalidState),
- _ => match self.global() {
- GlobalRoot::Window(_) if self.sync.get() => Err(Error::InvalidAccess),
- _ => {
- self.with_credentials.set(with_credentials);
- Ok(())
- },
- },
+ // Step 3
+ _ if self.sync_in_window() => Err(Error::InvalidAccess),
+ // Step 4
+ _ => {
+ self.with_credentials.set(with_credentials);
+ Ok(())
+ }
}
}
@@ -473,14 +521,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-send()-method
fn Send(&self, data: Option<SendParam>) -> ErrorResult {
+ // Step 1, 2
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() {
- Method::Get | Method::Head => None, // Step 3
+ Method::Get | Method::Head => None,
_ => data
};
+ // Step 4
let extracted = data.as_ref().map(|d| d.extract());
self.request_body_len.set(extracted.as_ref().map_or(0, |e| e.0.len()));
@@ -492,23 +543,25 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
Some (ref e) if e.0.is_empty() => true,
_ => false
});
+ // Step 8
+ self.send_flag.set(true);
+ // Step 9
if !self.sync.get() {
- // Step 8
let event_target = self.upload.upcast::<EventTarget>();
if event_target.has_handlers() {
self.upload_events.set(true);
}
- // Step 9
- self.send_flag.set(true);
// 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.
+ // Substep 1
let gen_id = self.generation_id.get();
self.dispatch_response_progress_event(atom!("loadstart"));
if self.generation_id.get() != gen_id {
return Ok(());
}
+ // Substep 2
if !self.upload_complete.get() {
self.dispatch_upload_progress_event(atom!("loadstart"), Some(0));
if self.generation_id.get() != gen_id {
@@ -518,6 +571,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
}
+ // Step 5
let global = self.global();
let pipeline_id = global.r().pipeline();
let mut load_data =
@@ -593,6 +647,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
self.fetch_time.set(time::now().to_timespec().sec);
let rv = self.fetch(load_data, cors_request, global.r());
+ // Step 10
if self.sync.get() {
return rv;
}
@@ -606,7 +661,9 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-abort()-method
fn Abort(&self) {
+ // Step 1
self.terminate_ongoing_fetch();
+ // Step 2
let state = self.ready_state.get();
if (state == XMLHttpRequestState::Opened && self.send_flag.get()) ||
state == XMLHttpRequestState::HeadersReceived ||
@@ -619,6 +676,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
return
}
}
+ // Step 3
self.ready_state.set(XMLHttpRequestState::Unsent);
}
@@ -653,15 +711,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-overridemimetype()-method
fn OverrideMimeType(&self, mime: DOMString) -> ErrorResult {
+ // Step 1
match self.ready_state.get() {
XMLHttpRequestState::Loading | XMLHttpRequestState::Done => return Err(Error::InvalidState),
_ => {},
}
+ // Step 2
let override_mime = try!(mime.parse::<Mime>().map_err(|_| Error::Syntax));
+ // Step 3
*self.override_mime_type.borrow_mut() = Some(override_mime.clone());
+ // Step 4
let value = override_mime.get_param(mime::Attr::Charset);
*self.override_charset.borrow_mut() = value.and_then(|value| {
- encoding_from_whatwg_label(value)
+ encoding_from_whatwg_label(value)
});
Ok(())
}
@@ -673,16 +735,20 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// https://xhr.spec.whatwg.org/#the-responsetype-attribute
fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult {
+ // Step 1
match self.global() {
GlobalRoot::Worker(_) if response_type == XMLHttpRequestResponseType::Document => return Ok(()),
_ => {}
}
match self.ready_state.get() {
+ // Step 2
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)
} else {
+ // Step 4
self.response_type.set(response_type);
Ok(())
}
@@ -698,34 +764,34 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
match self.response_type.get() {
XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => {
let ready_state = self.ready_state.get();
+ // Step 2
if ready_state == XMLHttpRequestState::Done || ready_state == XMLHttpRequestState::Loading {
self.text_response().to_jsval(cx, rval.handle_mut());
} else {
+ // Step 1
"".to_jsval(cx, rval.handle_mut());
}
},
+ // Step 1
_ if self.ready_state.get() != XMLHttpRequestState::Done => {
- return NullValue()
+ return NullValue();
},
+ // Step 2
XMLHttpRequestResponseType::Document => {
let op_doc = self.GetResponseXML();
if let Ok(Some(doc)) = op_doc {
doc.to_jsval(cx, rval.handle_mut());
} else {
+ // Substep 1
return NullValue();
}
},
XMLHttpRequestResponseType::Json => {
- let decoded = UTF_8.decode(&self.response.borrow(), DecoderTrap::Replace).unwrap().to_owned();
- let decoded: Vec<u16> = decoded.utf16_units().collect();
- if !JS_ParseJSON(cx,
- decoded.as_ptr(),
- decoded.len() as u32,
- rval.handle_mut()) {
- JS_ClearPendingException(cx);
- return NullValue();
- }
- }
+ self.json_response(cx).to_jsval(cx, rval.handle_mut());
+ },
+ XMLHttpRequestResponseType::Blob => {
+ self.blob_response().to_jsval(cx, rval.handle_mut());
+ },
_ => {
// XXXManishearth handle other response types
self.response.borrow().to_jsval(cx, rval.handle_mut());
@@ -740,10 +806,13 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
match self.response_type.get() {
XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => {
Ok(USVString(String::from(match self.ready_state.get() {
+ // Step 3
XMLHttpRequestState::Loading | XMLHttpRequestState::Done => self.text_response(),
+ // Step 2
_ => "".to_owned()
})))
},
+ // Step 1
_ => Err(Error::InvalidState)
}
}
@@ -752,20 +821,19 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
fn GetResponseXML(&self) -> Fallible<Option<Root<Document>>> {
match self.response_type.get() {
XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Document => {
- match self.ready_state.get() {
- XMLHttpRequestState::Done => {
- match self.response_xml.get() {
- Some(response) => Ok(Some(response)),
- None => {
- let response = self.document_response();
- self.response_xml.set(response.r());
- Ok(response)
- }
- }
- },
- _ => Ok(None)
+ // Step 3
+ if let XMLHttpRequestState::Done = self.ready_state.get() {
+ Ok(self.response_xml.get().or_else(|| {
+ let response = self.document_response();
+ self.response_xml.set(response.r());
+ response
+ }))
+ } else {
+ // Step 2
+ Ok(None)
}
- },
+ }
+ // Step 1
_ => { Err(Error::InvalidState) }
}
}
@@ -1015,7 +1083,7 @@ impl XMLHttpRequest {
// This will cancel all previous timeouts
let global = self.global();
let callback = ScheduledXHRTimeout {
- xhr: Trusted::new(self, global.r().networking_thread_source()),
+ xhr: Trusted::new(self, global.r().networking_task_source()),
generation_id: self.generation_id.get(),
};
let duration = Length::new(duration_ms as u64);
@@ -1029,15 +1097,34 @@ 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 {
- 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
// 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
+ fn blob_response(&self) -> Root<Blob> {
+ // Step 1
+ if let Some(response) = self.response_blob.get() {
+ return response;
+ }
+ // Step 2
+ let mime = self.final_mime_type().as_ref().map(Mime::to_string).unwrap_or("".to_owned());
+
+ // Step 3, 4
+ let blob = Blob::new(self.global().r(), self.response.borrow().to_vec(), &mime);
+ self.response_blob.set(Some(blob.r()));
+ blob
}
+ // https://xhr.spec.whatwg.org/#document-response
fn document_response(&self) -> Option<Root<Document>> {
let mime_type = self.final_mime_type();
//TODO: prescan the response to determine encoding if final charset is null
@@ -1071,6 +1158,39 @@ impl XMLHttpRequest {
Some(temp_doc)
}
+ #[allow(unsafe_code)]
+ // https://xhr.spec.whatwg.org/#json-response
+ fn json_response(&self, cx: *mut JSContext) -> JSVal {
+ // Step 1
+ let response_json = self.response_json.get();
+ if !response_json.is_null_or_undefined() {
+ return response_json;
+ }
+ // Step 2
+ let bytes = self.response.borrow();
+ // Step 3
+ if bytes.len() == 0 {
+ return NullValue();
+ }
+ // Step 4
+ let json_text = UTF_8.decode(&bytes, DecoderTrap::Replace).unwrap().to_owned();
+ let json_text: Vec<u16> = json_text.utf16_units().collect();
+ // Step 5
+ let mut rval = RootedValue::new(cx, UndefinedValue());
+ unsafe {
+ if !JS_ParseJSON(cx,
+ json_text.as_ptr(),
+ json_text.len() as u32,
+ rval.handle_mut()) {
+ JS_ClearPendingException(cx);
+ return NullValue();
+ }
+ }
+ // Step 6
+ self.response_json.set(rval.ptr);
+ self.response_json.get()
+ }
+
fn document_text_html(&self) -> Root<Document>{
let charset = self.final_charset().unwrap_or(UTF_8);
let wr = self.global();
@@ -1168,7 +1288,7 @@ impl XMLHttpRequest {
Ok(req) => req,
};
- let xhr = Trusted::new(self, global.networking_thread_source());
+ let xhr = Trusted::new(self, global.networking_task_source());
let context = Arc::new(Mutex::new(XHRContext {
xhr: xhr,
@@ -1182,7 +1302,7 @@ impl XMLHttpRequest {
let (tx, rx) = global.new_script_pair();
(tx, Some(rx))
} else {
- (global.networking_thread_source(), None)
+ (global.networking_task_source(), None)
};
let resource_thread = global.resource_thread();