Fix race condition in XHR and handle other abort/open scenarios

This fixes issue #3630
A short summary of the changes:
* Use generation id to cancel inflight requests
* Handles nested calls to abort, open, send inside handlers
* Adds XHRReleaseMsg to delay freeing XHR object till all
  inflight events are processed
* Change the ErroredMsg enum to be more symmetric with the returned
  Error enum
This commit is contained in:
Mukilan Thiyagarajan 2014-10-11 23:50:48 +05:30
parent 1a3ff8739c
commit 7435db26ac
3 changed files with 242 additions and 135 deletions

View file

@ -19,7 +19,7 @@ use dom::workerglobalscope::DedicatedGlobalScope;
use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers}; use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers};
use dom::xmlhttprequest::XMLHttpRequest; use dom::xmlhttprequest::XMLHttpRequest;
use script_task::{ScriptTask, ScriptChan}; use script_task::{ScriptTask, ScriptChan};
use script_task::{ScriptMsg, FromWorker, DOMMessage, FireTimerMsg, XHRProgressMsg, WorkerRelease}; use script_task::{ScriptMsg, FromWorker, DOMMessage, FireTimerMsg, XHRProgressMsg, XHRReleaseMsg, WorkerRelease};
use script_task::WorkerPostMessage; use script_task::WorkerPostMessage;
use script_task::StackRootTLS; use script_task::StackRootTLS;
@ -134,7 +134,10 @@ impl DedicatedWorkerGlobalScope {
global.delayed_release_worker(); global.delayed_release_worker();
}, },
Ok(XHRProgressMsg(addr, progress)) => { Ok(XHRProgressMsg(addr, progress)) => {
XMLHttpRequest::handle_xhr_progress(addr, progress) XMLHttpRequest::handle_progress(addr, progress)
},
Ok(XHRReleaseMsg(addr)) => {
XMLHttpRequest::handle_release(addr)
}, },
Ok(WorkerPostMessage(addr, data, nbytes)) => { Ok(WorkerPostMessage(addr, data, nbytes)) => {
Worker::handle_message(addr, data, nbytes); Worker::handle_message(addr, data, nbytes);

View file

@ -46,7 +46,7 @@ use libc::c_void;
use net::resource_task::{ResourceTask, ResourceCORSData, Load, LoadData, Payload, Done}; use net::resource_task::{ResourceTask, ResourceCORSData, Load, LoadData, Payload, Done};
use cors::{allow_cross_origin_request, CORSRequest, CORSMode, ForcedPreflightMode}; use cors::{allow_cross_origin_request, CORSRequest, CORSMode, ForcedPreflightMode};
use script_task::{ScriptChan, XHRProgressMsg}; use script_task::{ScriptChan, XHRProgressMsg, XHRReleaseMsg};
use servo_util::str::DOMString; use servo_util::str::DOMString;
use servo_util::task::spawn_named; use servo_util::task::spawn_named;
@ -83,24 +83,41 @@ enum XMLHttpRequestState {
XHRDone = 4, // So as not to conflict with the ProgressMsg `Done` XHRDone = 4, // So as not to conflict with the ProgressMsg `Done`
} }
#[deriving(PartialEq)]
#[jstraceable]
pub struct GenerationId(uint);
pub enum XHRProgress { pub enum XHRProgress {
/// Notify that headers have been received /// Notify that headers have been received
HeadersReceivedMsg(Option<ResponseHeaderCollection>, Status), HeadersReceivedMsg(GenerationId, Option<ResponseHeaderCollection>, Status),
/// Partial progress (after receiving headers), containing portion of the response /// Partial progress (after receiving headers), containing portion of the response
LoadingMsg(ByteString), LoadingMsg(GenerationId, ByteString),
/// Loading is done /// Loading is done
DoneMsg, DoneMsg(GenerationId),
/// There was an error (Abort or Timeout). For a network or other error, just pass None /// There was an error (only Abort, Timeout or Network is used)
ErroredMsg(Option<Error>), ErroredMsg(GenerationId, Error),
/// Timeout was reached }
TimeoutMsg
impl XHRProgress {
fn generation_id(&self) -> GenerationId {
match *self {
HeadersReceivedMsg(id, _, _) |
LoadingMsg(id, _) |
DoneMsg(id) |
ErroredMsg(id, _) => id
}
}
} }
enum SyncOrAsync<'a> { enum SyncOrAsync<'a> {
Sync(JSRef<'a, XMLHttpRequest>), Sync(JSRef<'a, XMLHttpRequest>),
Async(TrustedXHRAddress, ScriptChan) Async(TrustedXHRAddress, &'a ScriptChan)
} }
enum TerminateReason {
AbortedOrReopened,
TimedOut,
}
#[dom_struct] #[dom_struct]
pub struct XMLHttpRequest { pub struct XMLHttpRequest {
@ -131,8 +148,8 @@ pub struct XMLHttpRequest {
pinned_count: Cell<uint>, pinned_count: Cell<uint>,
timer: DOMRefCell<Timer>, timer: DOMRefCell<Timer>,
fetch_time: Cell<i64>, fetch_time: Cell<i64>,
timeout_pinned: Cell<bool>, terminate_sender: DOMRefCell<Option<Sender<TerminateReason>>>,
terminate_sender: DOMRefCell<Option<Sender<Error>>>, generation_id: Cell<GenerationId>,
} }
impl XMLHttpRequest { impl XMLHttpRequest {
@ -165,8 +182,8 @@ impl XMLHttpRequest {
pinned_count: Cell::new(0), pinned_count: Cell::new(0),
timer: DOMRefCell::new(Timer::new().unwrap()), timer: DOMRefCell::new(Timer::new().unwrap()),
fetch_time: Cell::new(0), fetch_time: Cell::new(0),
timeout_pinned: Cell::new(false),
terminate_sender: DOMRefCell::new(None), terminate_sender: DOMRefCell::new(None),
generation_id: Cell::new(GenerationId(0))
} }
} }
pub fn new(global: &GlobalRef) -> Temporary<XMLHttpRequest> { pub fn new(global: &GlobalRef) -> Temporary<XMLHttpRequest> {
@ -178,86 +195,145 @@ impl XMLHttpRequest {
Ok(XMLHttpRequest::new(global)) Ok(XMLHttpRequest::new(global))
} }
pub fn handle_xhr_progress(addr: TrustedXHRAddress, progress: XHRProgress) { pub fn handle_progress(addr: TrustedXHRAddress, progress: XHRProgress) {
unsafe { unsafe {
let xhr = JS::from_trusted_xhr_address(addr).root(); let xhr = JS::from_trusted_xhr_address(addr).root();
xhr.process_partial_response(progress); xhr.process_partial_response(progress);
} }
} }
pub fn handle_release(addr: TrustedXHRAddress) {
addr.release_once();
}
fn fetch(fetch_type: &SyncOrAsync, resource_task: ResourceTask, fn fetch(fetch_type: &SyncOrAsync, resource_task: ResourceTask,
mut load_data: LoadData, terminate_receiver: Receiver<Error>, mut load_data: LoadData, terminate_receiver: Receiver<TerminateReason>,
cors_request: Result<Option<CORSRequest>,()>) -> ErrorResult { cors_request: Result<Option<CORSRequest>,()>, gen_id: GenerationId) -> ErrorResult {
fn notify_partial_progress(fetch_type: &SyncOrAsync, msg: XHRProgress) { fn notify_partial_progress(fetch_type: &SyncOrAsync, msg: XHRProgress) {
match *fetch_type { match *fetch_type {
Sync(xhr) => { Sync(xhr) => {
xhr.process_partial_response(msg); xhr.process_partial_response(msg);
}, },
Async(addr, ref script_chan) => { Async(addr, script_chan) => {
let ScriptChan(ref chan) = *script_chan; let ScriptChan(ref chan) = *script_chan;
chan.send(XHRProgressMsg(addr, msg)); chan.send(XHRProgressMsg(addr, msg));
} }
} }
} }
match cors_request {
Err(_) => return Err(Network), // Happens in case of cross-origin non-http URIs macro_rules! notify_error_and_return(
Ok(Some(ref req)) => { ($err:expr) => ({
let response = req.http_fetch(); notify_partial_progress(fetch_type, ErroredMsg(gen_id, $err));
if response.network_error { return Err($err)
return Err(Network) });
} else { )
load_data.cors = Some(ResourceCORSData {
preflight: req.preflight_flag, macro_rules! terminate(
origin: req.origin.clone() ($reason:expr) => (
}) match $reason {
AbortedOrReopened => {
return Err(Abort)
}
TimedOut => {
notify_error_and_return!(Timeout);
}
} }
}, );
)
match cors_request {
Err(_) => {
// Happens in case of cross-origin non-http URIs
notify_error_and_return!(Network);
}
Ok(Some(ref req)) => {
let (chan, cors_port) = channel();
let req2 = req.clone();
// TODO: this exists only to make preflight check non-blocking
// perhaps shoud be handled by the resource_loader?
spawn_named("XHR:Cors", proc() {
let response = req2.http_fetch();
chan.send(response);
});
select! (
response = cors_port.recv() => {
if response.network_error {
notify_error_and_return!(Network);
} else {
load_data.cors = Some(ResourceCORSData {
preflight: req.preflight_flag,
origin: req.origin.clone()
});
}
},
reason = terminate_receiver.recv() => terminate!(reason)
)
}
_ => {} _ => {}
} }
// Step 10, 13 // Step 10, 13
let (start_chan, start_port) = channel(); let (start_chan, start_port) = channel();
resource_task.send(Load(load_data, start_chan)); resource_task.send(Load(load_data, start_chan));
let response = start_port.recv();
match terminate_receiver.try_recv() {
Ok(e) => return Err(e),
_ => {}
}
match cors_request {
Ok(Some(ref req)) => {
match response.metadata.headers {
Some(ref h) if allow_cross_origin_request(req, h) => {},
_ => return Err(Network)
}
},
_ => {}
}
// XXXManishearth Clear cache entries in case of a network error
notify_partial_progress(fetch_type, HeadersReceivedMsg(
response.metadata.headers.clone(), response.metadata.status.clone())); let progress_port;
select! (
response = start_port.recv() => {
match cors_request {
Ok(Some(ref req)) => {
match response.metadata.headers {
Some(ref h) if allow_cross_origin_request(req, h) => {},
_ => notify_error_and_return!(Network)
}
},
_ => {}
};
// XXXManishearth Clear cache entries in case of a network error
notify_partial_progress(fetch_type, HeadersReceivedMsg(gen_id,
response.metadata.headers.clone(), response.metadata.status.clone()));
progress_port = response.progress_port;
},
reason = terminate_receiver.recv() => terminate!(reason)
)
let mut buf = vec!(); let mut buf = vec!();
loop { loop {
let progress = response.progress_port.recv(); // Under most circumstances, progress_port will contain lots of Payload
// events. Since select! does not have any fairness or priority, it
// might always remove the progress_port event, even when there is
// a terminate event waiting in the terminate_receiver. If this happens,
// a timeout or abort will take too long to be processed. To avoid this,
// in each iteration, we check for a terminate event before we block.
match terminate_receiver.try_recv() { match terminate_receiver.try_recv() {
Ok(e) => return Err(e), Ok(reason) => terminate!(reason),
_ => {} Err(_) => ()
} };
match progress {
Payload(data) => { select! (
buf.push_all(data.as_slice()); progress = progress_port.recv() => match progress {
notify_partial_progress(fetch_type, LoadingMsg(ByteString::new(buf.clone()))); Payload(data) => {
buf.push_all(data.as_slice());
notify_partial_progress(fetch_type,
LoadingMsg(gen_id, ByteString::new(buf.clone())));
},
Done(Ok(())) => {
notify_partial_progress(fetch_type, DoneMsg(gen_id));
return Ok(());
},
Done(Err(_)) => {
notify_error_and_return!(Network);
}
}, },
Done(Ok(())) => { reason = terminate_receiver.recv() => terminate!(reason)
notify_partial_progress(fetch_type, DoneMsg); )
return Ok(());
},
Done(Err(_)) => {
notify_partial_progress(fetch_type, ErroredMsg(None));
return Err(Network)
}
}
} }
} }
} }
@ -270,8 +346,6 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
} }
fn Open(self, method: ByteString, url: DOMString) -> ErrorResult { fn Open(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 uppercase_method = method.as_str().map(|s| {
let upper = s.to_ascii_upper(); let upper = s.to_ascii_upper();
match upper.as_slice() { match upper.as_slice() {
@ -311,7 +385,9 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
return Err(InvalidAccess) return Err(InvalidAccess)
} }
} }
// XXXManishearth abort existing requests // abort existing requests
self.terminate_ongoing_fetch();
// Step 12 // Step 12
*self.request_url.borrow_mut() = Some(parsed_url); *self.request_url.borrow_mut() = Some(parsed_url);
*self.request_headers.borrow_mut() = RequestHeaderCollection::new(); *self.request_headers.borrow_mut() = RequestHeaderCollection::new();
@ -453,15 +529,8 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
Some (ref v) if v.len() == 0 => true, Some (ref v) if v.len() == 0 => true,
_ => false _ => false
}); });
let mut addr = None;
if !self.sync.get() {
// 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());
}
if !self.sync.get() {
// Step 8 // Step 8
let upload_target = *self.upload.root(); let upload_target = *self.upload.root();
let event_target: JSRef<EventTarget> = EventTargetCast::from_ref(upload_target); let event_target: JSRef<EventTarget> = EventTargetCast::from_ref(upload_target);
@ -471,15 +540,20 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
// Step 9 // Step 9
self.send_flag.set(true); 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.
let gen_id = self.generation_id.get();
self.dispatch_response_progress_event("loadstart".to_string()); self.dispatch_response_progress_event("loadstart".to_string());
if self.generation_id.get() != gen_id {
return Ok(());
}
if !self.upload_complete.get() { if !self.upload_complete.get() {
self.dispatch_upload_progress_event("loadstart".to_string(), Some(0)); self.dispatch_upload_progress_event("loadstart".to_string(), Some(0));
if self.generation_id.get() != gen_id {
return Ok(());
}
} }
}
if self.ready_state.get() == Unsent {
// The progress events above might have run abort(), in which case we terminate the fetch.
return Ok(());
} }
let global = self.global.root(); let global = self.global.root();
@ -544,15 +618,30 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
_ => {} _ => {}
} }
let gen_id = self.generation_id.get();
if self.sync.get() { if self.sync.get() {
return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data, return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data,
terminate_receiver, cors_request); terminate_receiver, cors_request, gen_id);
} else { } else {
self.fetch_time.set(time::now().to_timespec().sec); self.fetch_time.set(time::now().to_timespec().sec);
let script_chan = global.root_ref().script_chan().clone(); let script_chan = global.root_ref().script_chan().clone();
// Pin the object before launching the fetch task.
// The XHRReleaseMsg sent when the fetch task completes will
// unpin it. This is to ensure that the object will stay alive
// as long as there are (possibly cancelled) inflight events queued up
// in the script task's port
let addr = unsafe {
self.to_trusted()
};
spawn_named("XHRTask", proc() { spawn_named("XHRTask", proc() {
let _ = XMLHttpRequest::fetch(&mut Async(addr.unwrap(), script_chan), let _ = XMLHttpRequest::fetch(&mut Async(addr, &script_chan),
resource_task, load_data, terminate_receiver, cors_request); resource_task,
load_data,
terminate_receiver,
cors_request,
gen_id);
let ScriptChan(ref chan) = script_chan;
chan.send(XHRReleaseMsg(addr));
}); });
let timeout = self.timeout.get(); let timeout = self.timeout.get();
if timeout > 0 { if timeout > 0 {
@ -562,12 +651,19 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
Ok(()) Ok(())
} }
fn Abort(self) { fn Abort(self) {
self.terminate_sender.borrow().as_ref().map(|s| s.send_opt(Abort)); self.terminate_ongoing_fetch();
match self.ready_state.get() { let state = self.ready_state.get();
Opened if self.send_flag.get() => self.process_partial_response(ErroredMsg(Some(Abort))), if (state == Opened && self.send_flag.get()) ||
HeadersReceived | Loading => self.process_partial_response(ErroredMsg(Some(Abort))), state == HeadersReceived ||
_ => {} state == Loading {
}; let gen_id = self.generation_id.get();
self.process_partial_response(ErroredMsg(gen_id, Abort));
// If open was called in one of the handlers invoked by the
// above call then we should terminate the abort sequence
if self.generation_id.get() != gen_id {
return
}
}
self.ready_state.set(Unsent); self.ready_state.set(Unsent);
} }
fn ResponseURL(self) -> DOMString { fn ResponseURL(self) -> DOMString {
@ -692,6 +788,7 @@ trait PrivateXMLHttpRequestHelpers {
fn release_once(self); fn release_once(self);
fn change_ready_state(self, XMLHttpRequestState); fn change_ready_state(self, XMLHttpRequestState);
fn process_partial_response(self, progress: XHRProgress); fn process_partial_response(self, progress: XHRProgress);
fn terminate_ongoing_fetch(self);
fn insert_trusted_header(self, name: String, value: String); fn insert_trusted_header(self, name: String, value: String);
fn dispatch_progress_event(self, upload: bool, type_: DOMString, loaded: u64, total: Option<u64>); fn dispatch_progress_event(self, upload: bool, type_: DOMString, loaded: u64, total: Option<u64>);
fn dispatch_upload_progress_event(self, type_: DOMString, partial_load: Option<u64>); fn dispatch_upload_progress_event(self, type_: DOMString, partial_load: Option<u64>);
@ -742,8 +839,24 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
} }
fn process_partial_response(self, progress: XHRProgress) { fn process_partial_response(self, progress: XHRProgress) {
let msg_id = progress.generation_id();
// Aborts processing if abort() or open() was called
// (including from one of the event handlers called below)
macro_rules! return_if_fetch_was_terminated(
() => (
if msg_id != self.generation_id.get() {
return
}
);
)
// Ignore message if it belongs to a terminated fetch
return_if_fetch_was_terminated!();
match progress { match progress {
HeadersReceivedMsg(headers, status) => { HeadersReceivedMsg(_, headers, status) => {
assert!(self.ready_state.get() == Opened);
// For synchronous requests, this should not fire any events, and just store data // 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) // XXXManishearth Find a way to track partial progress of the send (onprogresss for XHRUpload)
@ -753,8 +866,11 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
// Substeps 2-4 // Substeps 2-4
if !self.sync.get() { if !self.sync.get() {
self.dispatch_upload_progress_event("progress".to_string(), None); self.dispatch_upload_progress_event("progress".to_string(), None);
return_if_fetch_was_terminated!();
self.dispatch_upload_progress_event("load".to_string(), None); self.dispatch_upload_progress_event("load".to_string(), None);
return_if_fetch_was_terminated!();
self.dispatch_upload_progress_event("loadend".to_string(), None); self.dispatch_upload_progress_event("loadend".to_string(), None);
return_if_fetch_was_terminated!();
} }
// Part of step 13, send() (processing response) // Part of step 13, send() (processing response)
// XXXManishearth handle errors, if any (substep 1) // XXXManishearth handle errors, if any (substep 1)
@ -768,27 +884,25 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
None => {} None => {}
}; };
// Substep 3 // Substep 3
if self.ready_state.get() == Opened && !self.sync.get() { if !self.sync.get() {
self.change_ready_state(HeadersReceived); self.change_ready_state(HeadersReceived);
} }
}, },
LoadingMsg(partial_response) => { LoadingMsg(_, partial_response) => {
// For synchronous requests, this should not fire any events, and just store data // For synchronous requests, this should not fire any events, and just store data
// Part of step 13, send() (processing response body) // Part of step 13, send() (processing response body)
// XXXManishearth handle errors, if any (substep 1) // XXXManishearth handle errors, if any (substep 1)
// Substep 2
if self.ready_state.get() == HeadersReceived && !self.sync.get() {
self.change_ready_state(Loading);
}
// Substep 3
*self.response.borrow_mut() = partial_response; *self.response.borrow_mut() = partial_response;
// Substep 4
if !self.sync.get() { if !self.sync.get() {
if self.ready_state.get() == HeadersReceived {
self.change_ready_state(Loading);
return_if_fetch_was_terminated!();
}
self.dispatch_response_progress_event("progress".to_string()); self.dispatch_response_progress_event("progress".to_string());
} }
}, },
DoneMsg => { DoneMsg(_) => {
// Part of step 13, send() (processing response end of file) // Part of step 13, send() (processing response end of file)
// XXXManishearth handle errors, if any (substep 1) // XXXManishearth handle errors, if any (substep 1)
@ -797,50 +911,52 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
// Subsubsteps 2-4 // Subsubsteps 2-4
self.send_flag.set(false); self.send_flag.set(false);
self.change_ready_state(XHRDone); self.change_ready_state(XHRDone);
return_if_fetch_was_terminated!();
// Subsubsteps 5-7 // Subsubsteps 5-7
self.dispatch_response_progress_event("progress".to_string()); self.dispatch_response_progress_event("progress".to_string());
return_if_fetch_was_terminated!();
self.dispatch_response_progress_event("load".to_string()); self.dispatch_response_progress_event("load".to_string());
return_if_fetch_was_terminated!();
self.dispatch_response_progress_event("loadend".to_string()); self.dispatch_response_progress_event("loadend".to_string());
} }
self.cancel_timeout();
self.release_once();
}, },
ErroredMsg(e) => { ErroredMsg(_, e) => {
self.send_flag.set(false); self.send_flag.set(false);
// XXXManishearth set response to NetworkError // XXXManishearth set response to NetworkError
self.change_ready_state(XHRDone); self.change_ready_state(XHRDone);
return_if_fetch_was_terminated!();
let errormsg = match e { let errormsg = match e {
Some(Abort) => "abort", Abort => "abort",
Some(Timeout) => "timeout", Timeout => "timeout",
None => "error", _ => "error",
_ => unreachable!()
}; };
let upload_complete: &Cell<bool> = &self.upload_complete; let upload_complete: &Cell<bool> = &self.upload_complete;
if !upload_complete.get() { if !upload_complete.get() {
upload_complete.set(true); upload_complete.set(true);
self.dispatch_upload_progress_event("progress".to_string(), None); self.dispatch_upload_progress_event("progress".to_string(), None);
return_if_fetch_was_terminated!();
self.dispatch_upload_progress_event(errormsg.to_string(), None); self.dispatch_upload_progress_event(errormsg.to_string(), None);
return_if_fetch_was_terminated!();
self.dispatch_upload_progress_event("loadend".to_string(), None); self.dispatch_upload_progress_event("loadend".to_string(), None);
return_if_fetch_was_terminated!();
} }
self.dispatch_response_progress_event("progress".to_string()); self.dispatch_response_progress_event("progress".to_string());
return_if_fetch_was_terminated!();
self.dispatch_response_progress_event(errormsg.to_string()); self.dispatch_response_progress_event(errormsg.to_string());
return_if_fetch_was_terminated!();
self.dispatch_response_progress_event("loadend".to_string()); self.dispatch_response_progress_event("loadend".to_string());
self.cancel_timeout();
self.release_once();
},
TimeoutMsg => {
match self.ready_state.get() {
Opened if self.send_flag.get() => self.process_partial_response(ErroredMsg(Some(Timeout))),
Loading | HeadersReceived => self.process_partial_response(ErroredMsg(Some(Timeout))),
_ => self.release_once()
};
} }
} }
} }
fn terminate_ongoing_fetch(self) {
let GenerationId(prev_id) = self.generation_id.get();
self.generation_id.set(GenerationId(prev_id + 1));
self.terminate_sender.borrow().as_ref().map(|s| s.send_opt(AbortedOrReopened));
}
fn insert_trusted_header(self, name: String, value: String) { fn insert_trusted_header(self, name: String, value: String) {
// Insert a header without checking spec-compliance // Insert a header without checking spec-compliance
// Use for hardcoded headers // Use for hardcoded headers
@ -886,23 +1002,11 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
// This will cancel all previous timeouts // This will cancel all previous timeouts
let oneshot = self.timer.borrow_mut() let oneshot = self.timer.borrow_mut()
.oneshot(Duration::milliseconds(timeout as i64)); .oneshot(Duration::milliseconds(timeout as i64));
let addr = unsafe {
self.to_trusted() // This will increment the pin counter by one
};
if self.timeout_pinned.get() {
// 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.set(true);
let global = self.global.root();
let script_chan = global.root_ref().script_chan().clone();
let terminate_sender = (*self.terminate_sender.borrow()).clone(); let terminate_sender = (*self.terminate_sender.borrow()).clone();
spawn_named("XHR:Timer", proc () { spawn_named("XHR:Timer", proc () {
match oneshot.recv_opt() { match oneshot.recv_opt() {
Ok(_) => { Ok(_) => {
let ScriptChan(ref chan) = script_chan; terminate_sender.map(|s| s.send_opt(TimedOut));
terminate_sender.map(|s| s.send_opt(Timeout));
chan.send(XHRProgressMsg(addr, TimeoutMsg));
}, },
Err(_) => { Err(_) => {
// This occurs if xhr.timeout (the sender) goes out of scope (i.e, xhr went out of scope) // This occurs if xhr.timeout (the sender) goes out of scope (i.e, xhr went out of scope)
@ -913,15 +1017,12 @@ impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
} }
); );
} }
fn cancel_timeout(self) { fn cancel_timeout(self) {
// Cancels timeouts on the object, if any
if self.timeout_pinned.get() {
self.timeout_pinned.set(false);
self.release_once();
}
// oneshot() closes the previous channel, canceling the timeout // oneshot() closes the previous channel, canceling the timeout
self.timer.borrow_mut().oneshot(Zero::zero()); self.timer.borrow_mut().oneshot(Zero::zero());
} }
fn text_response(self) -> DOMString { fn text_response(self) -> DOMString {
let mut encoding = UTF_8 as EncodingRef; let mut encoding = UTF_8 as EncodingRef;
match self.response_headers.borrow().content_type { match self.response_headers.borrow().content_type {

View file

@ -100,6 +100,8 @@ pub enum ScriptMsg {
ExitWindowMsg(PipelineId), ExitWindowMsg(PipelineId),
/// Notifies the script of progress on a fetch (dispatched to all tasks). /// Notifies the script of progress on a fetch (dispatched to all tasks).
XHRProgressMsg(TrustedXHRAddress, XHRProgress), XHRProgressMsg(TrustedXHRAddress, XHRProgress),
/// Releases one reference to the XHR object (dispatched to all tasks).
XHRReleaseMsg(TrustedXHRAddress),
/// Message sent through Worker.postMessage (only dispatched to /// Message sent through Worker.postMessage (only dispatched to
/// DedicatedWorkerGlobalScope). /// DedicatedWorkerGlobalScope).
DOMMessage(*mut u64, size_t), DOMMessage(*mut u64, size_t),
@ -530,7 +532,8 @@ impl ScriptTask {
FromConstellation(ExitPipelineMsg(id)) => if self.handle_exit_pipeline_msg(id) { return false }, FromConstellation(ExitPipelineMsg(id)) => if self.handle_exit_pipeline_msg(id) { return false },
FromScript(ExitWindowMsg(id)) => self.handle_exit_window_msg(id), FromScript(ExitWindowMsg(id)) => self.handle_exit_window_msg(id),
FromConstellation(ResizeMsg(..)) => fail!("should have handled ResizeMsg already"), FromConstellation(ResizeMsg(..)) => fail!("should have handled ResizeMsg already"),
FromScript(XHRProgressMsg(addr, progress)) => XMLHttpRequest::handle_xhr_progress(addr, progress), FromScript(XHRProgressMsg(addr, progress)) => XMLHttpRequest::handle_progress(addr, progress),
FromScript(XHRReleaseMsg(addr)) => XMLHttpRequest::handle_release(addr),
FromScript(DOMMessage(..)) => fail!("unexpected message"), FromScript(DOMMessage(..)) => fail!("unexpected message"),
FromScript(WorkerPostMessage(addr, data, nbytes)) => Worker::handle_message(addr, data, nbytes), FromScript(WorkerPostMessage(addr, data, nbytes)) => Worker::handle_message(addr, data, nbytes),
FromScript(WorkerRelease(addr)) => Worker::handle_release(addr), FromScript(WorkerRelease(addr)) => Worker::handle_release(addr),