aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/script/dom/xmlhttprequest.rs
diff options
context:
space:
mode:
authorManish Goregaokar <manishsmail@gmail.com>2014-06-14 01:49:08 +0530
committerManish Goregaokar <manishsmail@gmail.com>2014-06-19 22:37:33 +0530
commitf558f9aad076172fad72dfe975176ceccdd74e98 (patch)
tree98fe9e2dad167f2481efb160a45b6d1534e88819 /src/components/script/dom/xmlhttprequest.rs
parent54f01aa4f4b58eba480c3c5c120c16a62372fc6c (diff)
downloadservo-f558f9aad076172fad72dfe975176ceccdd74e98.tar.gz
servo-f558f9aad076172fad72dfe975176ceccdd74e98.zip
Request termination for XHR
Diffstat (limited to 'src/components/script/dom/xmlhttprequest.rs')
-rw-r--r--src/components/script/dom/xmlhttprequest.rs280
1 files changed, 203 insertions, 77 deletions
diff --git a/src/components/script/dom/xmlhttprequest.rs b/src/components/script/dom/xmlhttprequest.rs
index b0e313a0f31..20e4efb8534 100644
--- a/src/components/script/dom/xmlhttprequest.rs
+++ b/src/components/script/dom/xmlhttprequest.rs
@@ -8,7 +8,8 @@ use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestRespo
use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseTypeValues::{_empty, Json, Text};
use dom::bindings::codegen::InheritTypes::{EventCast, EventTargetCast, XMLHttpRequestDerived};
use dom::bindings::conversions::ToJSValConvertible;
-use dom::bindings::error::{ErrorResult, Fallible, InvalidState, InvalidAccess, Network, Syntax, Security};
+use dom::bindings::error::{Error, ErrorResult, Fallible, InvalidState, InvalidAccess};
+use dom::bindings::error::{Network, Syntax, Security, Abort, Timeout};
use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootedRootable};
use dom::bindings::str::ByteString;
use dom::bindings::trace::Untraceable;
@@ -42,16 +43,17 @@ use libc::c_void;
use net::resource_task::{ResourceTask, Load, LoadData, Payload, Done};
use script_task::{ScriptChan, XHRProgressMsg};
use servo_util::str::DOMString;
+use servo_util::task::spawn_named;
use servo_util::url::{parse_url, try_parse_url};
use std::ascii::StrAsciiExt;
use std::cell::Cell;
-use std::comm::channel;
-use std::io::{BufReader, MemWriter};
+use std::comm::{Sender, Receiver, channel};
+use std::io::{BufReader, MemWriter, Timer};
use std::from_str::FromStr;
use std::path::BytesContainer;
use std::task::TaskBuilder;
-
+use time;
use url::Url;
// As send() start accepting more and more parameter types,
@@ -81,10 +83,10 @@ pub enum XHRProgress {
LoadingMsg(ByteString),
/// Loading is done
DoneMsg,
- /// There was an error
- ErroredMsg,
- /// Release the pinned XHR object.
- ReleaseMsg,
+ /// There was an error (Abort or Timeout). For a network or other error, just pass None
+ ErroredMsg(Option<Error>),
+ /// Timeout was reached
+ TimeoutMsg
}
enum SyncOrAsync<'a, 'b> {
@@ -92,14 +94,7 @@ enum SyncOrAsync<'a, 'b> {
Async(TrustedXHRAddress, ScriptChan)
}
-impl<'a,'b> SyncOrAsync<'a,'b> {
- fn is_async(&self) -> bool {
- match *self {
- Async(_,_) => true,
- _ => false
- }
- }
-}
+
#[deriving(Encodable)]
pub struct XMLHttpRequest {
eventtarget: XMLHttpRequestEventTarget,
@@ -126,7 +121,11 @@ pub struct XMLHttpRequest {
send_flag: bool,
global: JS<Window>,
- pinned: bool,
+ pinned_count: uint,
+ timer: Untraceable<Timer>,
+ fetch_time: i64,
+ timeout_pinned: bool,
+ terminate_sender: Untraceable<Option<Sender<Error>>>,
}
impl XMLHttpRequest {
@@ -156,7 +155,11 @@ impl XMLHttpRequest {
upload_events: false,
global: JS::from_rooted(owner),
- pinned: false,
+ pinned_count: 0,
+ timer: Untraceable::new(Timer::new().unwrap()),
+ fetch_time: 0,
+ timeout_pinned: false,
+ terminate_sender: Untraceable::new(None),
};
xhr
}
@@ -176,7 +179,8 @@ impl XMLHttpRequest {
}
}
- fn fetch(fetch_type: &mut SyncOrAsync, resource_task: ResourceTask, load_data: LoadData) -> ErrorResult {
+ fn fetch(fetch_type: &mut SyncOrAsync, resource_task: ResourceTask,
+ load_data: LoadData, terminate_receiver: Receiver<Error>) -> ErrorResult {
fn notify_partial_progress(fetch_type: &mut SyncOrAsync, msg: XHRProgress) {
match *fetch_type {
@@ -194,27 +198,30 @@ impl XMLHttpRequest {
let (start_chan, start_port) = channel();
resource_task.send(Load(load_data, start_chan));
let response = start_port.recv();
+ match terminate_receiver.try_recv() {
+ Ok(e) => return Err(e),
+ _ => {}
+ }
notify_partial_progress(fetch_type, HeadersReceivedMsg(
response.metadata.headers.clone(), response.metadata.status.clone()));
let mut buf = vec!();
loop {
- match response.progress_port.recv() {
+ let progress = response.progress_port.recv();
+ match terminate_receiver.try_recv() {
+ Ok(e) => return Err(e),
+ _ => {}
+ }
+ match progress {
Payload(data) => {
buf.push_all(data.as_slice());
notify_partial_progress(fetch_type, LoadingMsg(ByteString::new(buf.clone())));
},
Done(Ok(())) => {
notify_partial_progress(fetch_type, DoneMsg);
- if fetch_type.is_async() {
- notify_partial_progress(fetch_type, ReleaseMsg)
- }
return Ok(());
},
Done(Err(_)) => {
- notify_partial_progress(fetch_type, ErroredMsg);
- if fetch_type.is_async() {
- notify_partial_progress(fetch_type, ReleaseMsg)
- }
+ notify_partial_progress(fetch_type, ErroredMsg(None));
return Err(Network)
}
}
@@ -231,12 +238,12 @@ pub trait XMLHttpRequestMethods<'a> {
_username: Option<DOMString>, _password: Option<DOMString>) -> ErrorResult;
fn SetRequestHeader(&mut self, name: ByteString, mut value: ByteString) -> ErrorResult;
fn Timeout(&self) -> u32;
- fn SetTimeout(&mut self, timeout: u32);
+ fn SetTimeout(&mut self, timeout: u32) -> ErrorResult;
fn WithCredentials(&self) -> bool;
fn SetWithCredentials(&mut self, with_credentials: bool);
fn Upload(&self) -> Temporary<XMLHttpRequestUpload>;
fn Send(&mut self, _data: Option<SendParam>) -> ErrorResult;
- fn Abort(&self);
+ fn Abort(&mut self);
fn ResponseURL(&self) -> DOMString;
fn Status(&self) -> u16;
fn StatusText(&self) -> ByteString;
@@ -266,6 +273,9 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
}
fn Open(&mut self, method: ByteString, url: DOMString) -> ErrorResult {
+ // Clean up from previous requests, if any:
+ self.cancel_timeout();
+
let uppercase_method = method.as_str().map(|s| {
let upper = s.to_ascii_upper();
match upper.as_slice() {
@@ -396,8 +406,27 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
fn Timeout(&self) -> u32 {
self.timeout
}
- fn SetTimeout(&mut self, timeout: u32) {
- self.timeout = timeout
+ fn SetTimeout(&mut self, timeout: u32) -> ErrorResult {
+ if self.sync {
+ // FIXME: Not valid for a worker environment
+ Err(InvalidState)
+ } else {
+ self.timeout = timeout;
+ if self.send_flag {
+ if timeout == 0 {
+ self.cancel_timeout();
+ return Ok(());
+ }
+ let progress = time::now().to_timespec().sec - self.fetch_time;
+ 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(())
+ }
}
fn WithCredentials(&self) -> bool {
self.with_credentials
@@ -426,7 +455,15 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
Some (ref s) if s.len() == 0 => true,
_ => false
};
+ let mut addr = None;
if !self.sync {
+ // If one of the event handlers below aborts the fetch,
+ // the assertion in release_once() will fail since we haven't pinned it yet.
+ // Pin early to avoid dealing with this
+ unsafe {
+ addr = Some(self.to_trusted());
+ }
+
// Step 8
let upload_target = &*self.upload.get().root();
let event_target: &JSRef<EventTarget> = EventTargetCast::from_ref(upload_target);
@@ -442,8 +479,13 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
}
}
- let mut global = self.global.root();
- let resource_task = global.page().resource_task.deref().clone();
+ if self.ready_state == Unsent {
+ // The progress events above might have run abort(), in which case we terminate the fetch.
+ return Ok(());
+ }
+
+ let global = self.global.root();
+ let resource_task = global.deref().page().resource_task.deref().clone();
let mut load_data = LoadData::new((*self.request_url).clone());
load_data.data = data;
@@ -476,22 +518,32 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
load_data.headers = (*self.request_headers).clone();
load_data.method = (*self.request_method).clone();
+ let (terminate_sender, terminate_receiver) = channel();
+ *self.terminate_sender = Some(terminate_sender);
if self.sync {
- return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data);
+ return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data, terminate_receiver);
} else {
let builder = TaskBuilder::new().named("XHRTask");
- unsafe {
- let addr = self.to_trusted();
- let script_chan = global.script_chan.clone();
- builder.spawn(proc() {
- let _ = XMLHttpRequest::fetch(&mut Async(addr, script_chan), resource_task, load_data);
- })
+ self.fetch_time = time::now().to_timespec().sec;
+ let script_chan = global.deref().script_chan.clone();
+ builder.spawn(proc() {
+ let _ = XMLHttpRequest::fetch(&mut Async(addr.unwrap(), script_chan), resource_task, load_data, terminate_receiver);
+ });
+ let timeout = self.timeout;
+ if timeout > 0 {
+ self.set_timeout(timeout);
}
}
Ok(())
}
- fn Abort(&self) {
-
+ fn Abort(&mut self) {
+ self.terminate_sender.as_ref().map(|s| s.send_opt(Abort));
+ match self.ready_state {
+ Opened if self.send_flag => self.process_partial_response(ErroredMsg(Some(Abort))),
+ HeadersReceived | Loading => self.process_partial_response(ErroredMsg(Some(Abort))),
+ _ => {}
+ };
+ self.ready_state = Unsent;
}
fn ResponseURL(&self) -> DOMString {
self.response_url.clone()
@@ -512,7 +564,13 @@ impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> {
fn GetAllResponseHeaders(&self) -> ByteString {
let mut writer = MemWriter::new();
self.response_headers.deref().write_all(&mut writer).ok().expect("Writing response headers failed");
- ByteString::new(writer.unwrap())
+ let mut vec = writer.unwrap();
+
+ // rust-http appends an extra "\r\n" when using write_all
+ vec.pop();
+ vec.pop();
+
+ ByteString::new(vec)
}
fn OverrideMimeType(&self, _mime: DOMString) {
@@ -592,9 +650,9 @@ impl XMLHttpRequestDerived for EventTarget {
pub struct TrustedXHRAddress(pub *c_void);
impl TrustedXHRAddress {
- pub fn release(self) {
+ pub fn release_once(self) {
unsafe {
- JS::from_trusted_xhr_address(self).root().release();
+ JS::from_trusted_xhr_address(self).root().release_once();
}
}
}
@@ -602,7 +660,7 @@ impl TrustedXHRAddress {
trait PrivateXMLHttpRequestHelpers {
unsafe fn to_trusted(&mut self) -> TrustedXHRAddress;
- fn release(&mut self);
+ fn release_once(&mut self);
fn change_ready_state(&mut self, XMLHttpRequestState);
fn process_partial_response(&mut self, progress: XHRProgress);
fn insert_trusted_header(&mut self, name: String, value: String);
@@ -610,23 +668,34 @@ trait PrivateXMLHttpRequestHelpers {
fn dispatch_upload_progress_event(&self, type_: DOMString, partial_load: Option<u64>);
fn dispatch_response_progress_event(&self, type_: DOMString);
fn text_response(&self) -> DOMString;
+ fn set_timeout(&mut self, timeout:u32);
+ fn cancel_timeout(&mut self);
}
impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
// Creates a trusted address to the object, and roots it. Always pair this with a release()
unsafe fn to_trusted(&mut self) -> TrustedXHRAddress {
- assert!(self.pinned == false);
- self.pinned = true;
- JS_AddObjectRoot(self.global.root().get_cx(), self.reflector().rootable());
+ if self.pinned_count == 0 {
+ JS_AddObjectRoot(self.global.root().get_cx(), self.reflector().rootable());
+ }
+ self.pinned_count += 1;
TrustedXHRAddress(self.deref() as *XMLHttpRequest as *libc::c_void)
}
- fn release(&mut self) {
- assert!(self.pinned);
- unsafe {
- JS_RemoveObjectRoot(self.global.root().get_cx(), self.reflector().rootable());
+ fn release_once(&mut self) {
+ if self.sync {
+ // Lets us call this at various termination cases without having to
+ // check self.sync every time, since the pinning mechanism only is
+ // meaningful during an async fetch
+ return;
+ }
+ assert!(self.pinned_count > 0)
+ self.pinned_count -= 1;
+ if self.pinned_count == 0 {
+ unsafe {
+ JS_RemoveObjectRoot(self.global.root().get_cx(), self.reflector().rootable());
+ }
}
- self.pinned = false;
}
fn change_ready_state(&mut self, rs: XMLHttpRequestState) {
@@ -642,16 +711,18 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
fn process_partial_response(&mut self, progress: XHRProgress) {
match progress {
HeadersReceivedMsg(headers, status) => {
+ // For synchronous requests, this should not fire any events, and just store data
// XXXManishearth Find a way to track partial progress of the send (onprogresss for XHRUpload)
// Part of step 13, send() (processing request end of file)
// Substep 1
self.upload_complete = true;
// Substeps 2-4
- self.dispatch_upload_progress_event("progress".to_string(), None);
- self.dispatch_upload_progress_event("load".to_string(), None);
- self.dispatch_upload_progress_event("loadend".to_string(), None);
-
+ if !self.sync {
+ self.dispatch_upload_progress_event("progress".to_string(), None);
+ self.dispatch_upload_progress_event("load".to_string(), None);
+ self.dispatch_upload_progress_event("loadend".to_string(), None);
+ }
// Part of step 13, send() (processing response)
// XXXManishearth handle errors, if any (substep 1)
// Substep 2
@@ -662,29 +733,32 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
None => {}
};
// Substep 3
- if self.ready_state == Opened {
+ if self.ready_state == Opened && !self.sync {
self.change_ready_state(HeadersReceived);
}
},
LoadingMsg(partial_response) => {
+ // For synchronous requests, this should not fire any events, and just store data
// Part of step 13, send() (processing response body)
// XXXManishearth handle errors, if any (substep 1)
// Substep 2
- if self.ready_state == HeadersReceived {
+ if self.ready_state == HeadersReceived && !self.sync {
self.change_ready_state(Loading);
}
// Substep 3
self.response = partial_response;
// Substep 4
- self.dispatch_response_progress_event("progress".to_string());
+ if !self.sync {
+ self.dispatch_response_progress_event("progress".to_string());
+ }
},
DoneMsg => {
// Part of step 13, send() (processing response end of file)
// XXXManishearth handle errors, if any (substep 1)
// Substep 3
- if self.ready_state == Loading {
+ if self.ready_state == Loading || self.sync {
// Subsubsteps 2-4
self.send_flag = false;
self.change_ready_state(XHRDone);
@@ -694,27 +768,40 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
self.dispatch_response_progress_event("load".to_string());
self.dispatch_response_progress_event("loadend".to_string());
}
+ self.cancel_timeout();
+ self.release_once();
},
- ErroredMsg => {
+ ErroredMsg(e) => {
self.send_flag = false;
+
// XXXManishearth set response to NetworkError
- // XXXManishearth also handle terminated requests (timeout/abort/fatal)
self.change_ready_state(XHRDone);
- if !self.sync {
- if !self.upload_complete {
- self.upload_complete = true;
- self.dispatch_upload_progress_event("progress".to_string(), None);
- self.dispatch_upload_progress_event("load".to_string(), None);
- self.dispatch_upload_progress_event("loadend".to_string(), None);
- }
- self.dispatch_response_progress_event("progress".to_string());
- self.dispatch_response_progress_event("load".to_string());
- self.dispatch_response_progress_event("loadend".to_string());
+ let errormsg = match e {
+ Some(Abort) => "abort",
+ Some(Timeout) => "timeout",
+ None => "error",
+ _ => unreachable!()
+ };
+
+ if !self.upload_complete {
+ self.upload_complete = true;
+ self.dispatch_upload_progress_event("progress".to_string(), None);
+ self.dispatch_upload_progress_event(errormsg.to_string(), None);
+ self.dispatch_upload_progress_event("loadend".to_string(), None);
}
+ self.dispatch_response_progress_event("progress".to_string());
+ self.dispatch_response_progress_event(errormsg.to_string());
+ self.dispatch_response_progress_event("loadend".to_string());
+ self.cancel_timeout();
+ self.release_once();
},
- ReleaseMsg => {
- self.release();
+ TimeoutMsg => {
+ match self.ready_state {
+ Opened if self.send_flag => self.process_partial_response(ErroredMsg(Some(Timeout))),
+ Loading | HeadersReceived => self.process_partial_response(ErroredMsg(Some(Timeout))),
+ _ => self.release_once()
+ };
}
}
}
@@ -758,7 +845,46 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
let total = self.response_headers.deref().content_length.map(|x| {x as u64});
self.dispatch_progress_event(false, type_, len, total);
}
-
+ fn set_timeout(&mut self, timeout: u32) {
+ // Sets up the object to timeout in a given number of milliseconds
+ // This will cancel all previous timeouts
+ let oneshot = self.timer.oneshot(timeout as u64);
+ let addr = unsafe {
+ self.to_trusted() // This will increment the pin counter by one
+ };
+ if self.timeout_pinned {
+ // Already pinned due to a timeout, no need to pin it again since the old timeout was cancelled above
+ self.release_once();
+ }
+ self.timeout_pinned = true;
+ let global = self.global.root();
+ let script_chan = global.deref().script_chan.clone();
+ let terminate_sender = (*self.terminate_sender).clone();
+ spawn_named("XHR:Timer", proc () {
+ match oneshot.recv_opt() {
+ Ok(_) => {
+ let ScriptChan(ref chan) = script_chan;
+ terminate_sender.map(|s| s.send_opt(Timeout));
+ chan.send(XHRProgressMsg(addr, TimeoutMsg));
+ },
+ Err(_) => {
+ // This occurs if xhr.timeout (the sender) goes out of scope (i.e, xhr went out of scope)
+ // or if the oneshot timer was overwritten. The former case should not happen due to pinning.
+ debug!("XHR timeout was overwritten or canceled")
+ }
+ }
+ }
+ );
+ }
+ fn cancel_timeout(&mut self) {
+ // Cancels timeouts on the object, if any
+ if self.timeout_pinned {
+ self.timeout_pinned = false;
+ self.release_once();
+ }
+ // oneshot() closes the previous channel, canceling the timeout
+ self.timer.oneshot(0);
+ }
fn text_response(&self) -> DOMString {
let mut encoding = UTF_8 as &Encoding:Send;
match self.response_headers.content_type {