diff options
author | bors-servo <release+servo@mozilla.com> | 2014-05-20 12:16:35 -0400 |
---|---|---|
committer | bors-servo <release+servo@mozilla.com> | 2014-05-20 12:16:35 -0400 |
commit | 28e3c1734048217cd2194ea37c887911ad03e894 (patch) | |
tree | 8a3c5cc1a7dbb4ef751a9e96487dfdf7e9db3309 /src | |
parent | ca9396ff9c7ebeea48d881b421854168b9afb825 (diff) | |
parent | 533fab46f95ec64da3385851e0a09cae15211e90 (diff) | |
download | servo-28e3c1734048217cd2194ea37c887911ad03e894.tar.gz servo-28e3c1734048217cd2194ea37c887911ad03e894.zip |
auto merge of #2442 : Manishearth/servo/xhr-async, r=jdm
(Note that only `getAllResponseHeaders()` is supported at the moment, I'll be adding the other header methods later.)
Diffstat (limited to 'src')
-rw-r--r-- | src/components/net/http_loader.rs | 1 | ||||
-rw-r--r-- | src/components/net/resource_task.rs | 8 | ||||
-rw-r--r-- | src/components/script/dom/bindings/js.rs | 10 | ||||
-rw-r--r-- | src/components/script/dom/webidls/XMLHttpRequest.webidl | 6 | ||||
-rw-r--r-- | src/components/script/dom/xmlhttprequest.rs | 272 | ||||
-rw-r--r-- | src/components/script/html/cssparse.rs | 2 | ||||
-rw-r--r-- | src/components/script/script.rs | 1 | ||||
-rw-r--r-- | src/components/script/script_task.rs | 4 |
8 files changed, 253 insertions, 51 deletions
diff --git a/src/components/net/http_loader.rs b/src/components/net/http_loader.rs index df768a098be..26d6e0c0a72 100644 --- a/src/components/net/http_loader.rs +++ b/src/components/net/http_loader.rs @@ -92,6 +92,7 @@ fn load(mut url: Url, start_chan: Sender<LoadResponse>) { let mut metadata = Metadata::default(url); metadata.set_content_type(&response.headers.content_type); + metadata.headers = Some(*response.headers.clone()); let progress_chan = start_sending(start_chan, metadata); loop { diff --git a/src/components/net/resource_task.rs b/src/components/net/resource_task.rs index 62732d3e7bb..9231ad0f86b 100644 --- a/src/components/net/resource_task.rs +++ b/src/components/net/resource_task.rs @@ -11,6 +11,7 @@ use data_loader; use std::comm::{channel, Receiver, Sender}; use std::task; use http::headers::content_type::MediaType; +use http::headers::response::HeaderCollection; use url::Url; #[cfg(test)] @@ -32,6 +33,9 @@ pub struct Metadata { /// Character set. pub charset: Option<~str>, + + /// Headers + pub headers: Option<HeaderCollection>, } impl Metadata { @@ -41,6 +45,7 @@ impl Metadata { final_url: url, content_type: None, charset: None, + headers: None } } @@ -84,8 +89,7 @@ pub enum ProgressMsg { } /// For use by loaders in responding to a Load message. -pub fn start_sending(start_chan: Sender<LoadResponse>, - metadata: Metadata) -> Sender<ProgressMsg> { +pub fn start_sending(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Sender<ProgressMsg> { let (progress_chan, progress_port) = channel(); start_chan.send(LoadResponse { metadata: metadata, diff --git a/src/components/script/dom/bindings/js.rs b/src/components/script/dom/bindings/js.rs index 8ed125f74ee..8c33333e3cf 100644 --- a/src/components/script/dom/bindings/js.rs +++ b/src/components/script/dom/bindings/js.rs @@ -41,6 +41,7 @@ use dom::bindings::utils::{Reflector, Reflectable, cx_for_dom_object}; use dom::node::Node; +use dom::xmlhttprequest::{XMLHttpRequest, TrustedXHRAddress}; use js::jsapi::{JSObject, JS_AddObjectRoot, JS_RemoveObjectRoot}; use layout_interface::TrustedNodeAddress; use script_task::StackRoots; @@ -141,6 +142,15 @@ impl JS<Node> { } } +impl JS<XMLHttpRequest> { + pub unsafe fn from_trusted_xhr_address(inner: TrustedXHRAddress) -> JS<XMLHttpRequest> { + let TrustedXHRAddress(addr) = inner; + JS { + ptr: RefCell::new(addr as *mut XMLHttpRequest) + } + } +} + impl<T: Reflectable> JS<T> { /// Create a new JS-owned value wrapped from a raw Rust pointer. pub unsafe fn from_raw(raw: *mut T) -> JS<T> { diff --git a/src/components/script/dom/webidls/XMLHttpRequest.webidl b/src/components/script/dom/webidls/XMLHttpRequest.webidl index 4ed726d7e48..da849b7903d 100644 --- a/src/components/script/dom/webidls/XMLHttpRequest.webidl +++ b/src/components/script/dom/webidls/XMLHttpRequest.webidl @@ -40,8 +40,8 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { // request [Throws] void open(ByteString method, /* [EnsureUTF16] */ DOMString url); - - // void open(ByteString method, /* [EnsureUTF16] */ DOMString url, boolean async, optional /* [EnsureUTF16] */ DOMString? username = null, optional /* [EnsureUTF16] */ DOMString? password = null); + [Throws] + void open(ByteString method, /* [EnsureUTF16] */ DOMString url, boolean async, optional /* [EnsureUTF16] */ DOMString? username = null, optional /* [EnsureUTF16] */ DOMString? password = null); // void setRequestHeader(ByteString name, ByteString value); attribute unsigned long timeout; @@ -56,7 +56,7 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { readonly attribute unsigned short status; readonly attribute ByteString statusText; // ByteString? getResponseHeader(ByteString name); - // ByteString getAllResponseHeaders(); + ByteString getAllResponseHeaders(); // void overrideMimeType(DOMString mime); attribute XMLHttpRequestResponseType responseType; readonly attribute any response; diff --git a/src/components/script/dom/xmlhttprequest.rs b/src/components/script/dom/xmlhttprequest.rs index b39e84db041..6e5fb3facd2 100644 --- a/src/components/script/dom/xmlhttprequest.rs +++ b/src/components/script/dom/xmlhttprequest.rs @@ -2,31 +2,41 @@ * 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 collections::hashmap::HashMap; use dom::bindings::codegen::BindingDeclarations::XMLHttpRequestBinding; use dom::bindings::str::ByteString; use self::XMLHttpRequestBinding::XMLHttpRequestResponseType; -use self::XMLHttpRequestBinding::XMLHttpRequestResponseTypeValues::_empty; -use dom::bindings::codegen::InheritTypes::XMLHttpRequestDerived; +use self::XMLHttpRequestBinding::XMLHttpRequestResponseTypeValues::{_empty, Text}; +use dom::bindings::codegen::InheritTypes::{EventTargetCast, XMLHttpRequestDerived}; use dom::bindings::error::{ErrorResult, InvalidState, Network, Syntax, Security}; use dom::document::Document; -use dom::eventtarget::{EventTarget, XMLHttpRequestTargetTypeId}; +use dom::event::{Event, EventMethods}; +use dom::eventtarget::{EventTarget, EventTargetHelpers, XMLHttpRequestTargetTypeId}; use dom::bindings::conversions::ToJSValConvertible; use dom::bindings::error::Fallible; use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable}; use dom::bindings::trace::Untraceable; -use js::jsapi::JSContext; -use js::jsval::JSVal; +use js::jsapi::{JS_AddObjectRoot, JS_RemoveObjectRoot, JSContext}; +use js::jsval::{JSVal, NullValue}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::window::Window; use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget; use dom::xmlhttprequestupload::XMLHttpRequestUpload; -use net::resource_task::{load_whole_resource}; +use net::resource_task::{ResourceTask, Load, Payload, Done}; +use script_task::{ScriptChan, XHRProgressMsg}; use servo_util::str::DOMString; use servo_util::url::{parse_url, try_parse_url}; use url::Url; +use libc; +use libc::c_void; +use std::comm::channel; +use std::io::MemWriter; + +use std::task; + +use ResponseHeaderCollection = http::headers::response::HeaderCollection; +use RequestHeaderCollection = http::headers::request::HeaderCollection; // As send() start accepting more and more parameter types, // change this to the appropriate type from UnionTypes, eg @@ -45,9 +55,35 @@ enum XMLHttpRequestState { Opened = 1u16, HeadersReceived = 2u16, Loading = 3u16, - Done = 4u16, + XHRDone = 4u16, // So as not to conflict with the ProgressMsg `Done` +} + +pub enum XHRProgress { + /// Notify that headers have been received + HeadersReceivedMsg(Option<ResponseHeaderCollection>), + /// Partial progress (after receiving headers), containing portion of the response + LoadingMsg(ByteString), + /// Loading is done + DoneMsg, + /// There was an error + ErroredMsg, + /// Release the pinned XHR object. + ReleaseMsg, +} + +enum SyncOrAsync<'a, 'b> { + Sync(&'b mut JSRef<'a, XMLHttpRequest>), + Async(TrustedXHRAddress, ScriptChan) } +impl<'a,'b> SyncOrAsync<'a,'b> { + fn is_async(&self) -> bool { + match *self { + Sync(_) => true, + _ => false + } + } +} #[deriving(Encodable)] pub struct XMLHttpRequest { eventtarget: XMLHttpRequestEventTarget, @@ -62,18 +98,20 @@ pub struct XMLHttpRequest { response_type: XMLHttpRequestResponseType, response_text: DOMString, response_xml: Option<JS<Document>>, + response_headers: Untraceable<ResponseHeaderCollection>, // Associated concepts request_method: ByteString, request_url: Untraceable<Url>, - request_headers: HashMap<ByteString, ByteString>, + request_headers: Untraceable<RequestHeaderCollection>, request_body: SendParam, sync: bool, upload_complete: bool, upload_events: bool, send_flag: bool, - global: JS<Window> + global: JS<Window>, + pinned: bool, } impl XMLHttpRequest { @@ -91,10 +129,11 @@ impl XMLHttpRequest { response_type: _empty, response_text: "".to_owned(), response_xml: None, + response_headers: Untraceable::new(ResponseHeaderCollection::new()), request_method: ByteString::new(vec!()), request_url: Untraceable::new(parse_url("", None)), - request_headers: HashMap::new(), + request_headers: Untraceable::new(RequestHeaderCollection::new()), request_body: "".to_owned(), sync: false, send_flag: false, @@ -102,7 +141,8 @@ impl XMLHttpRequest { upload_complete: false, upload_events: false, - global: owner.unrooted() + global: owner.unrooted(), + pinned: false, }; xhr.upload.assign(Some(XMLHttpRequestUpload::new(owner))); xhr @@ -115,9 +155,60 @@ impl XMLHttpRequest { pub fn Constructor(owner: &JSRef<Window>) -> Fallible<Temporary<XMLHttpRequest>> { Ok(XMLHttpRequest::new(owner)) } + + pub fn handle_xhr_progress(addr: TrustedXHRAddress, progress: XHRProgress) { + unsafe { + let mut xhr = JS::from_trusted_xhr_address(addr).root(); + xhr.process_partial_response(progress); + } + } + + fn fetch(fetch_type: &mut SyncOrAsync, resource_task: ResourceTask, url: Url) -> ErrorResult { + + fn notify_partial_progress(fetch_type: &mut SyncOrAsync, msg: XHRProgress) { + match *fetch_type { + Sync(ref mut xhr) => { + xhr.process_partial_response(msg); + }, + Async(addr, ref script_chan) => { + let ScriptChan(ref chan) = *script_chan; + chan.send(XHRProgressMsg(addr, msg)); + } + } + } + + // Step 10, 13 + let (start_chan, start_port) = channel(); + resource_task.send(Load(url, start_chan)); + let response = start_port.recv(); + notify_partial_progress(fetch_type, HeadersReceivedMsg(response.metadata.headers.clone())); + let mut buf = vec!(); + loop { + match response.progress_port.recv() { + 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) + } + return Err(Network) + } + } + } + } } -pub trait XMLHttpRequestMethods { +pub trait XMLHttpRequestMethods<'a> { fn ReadyState(&self) -> u16; fn Open(&mut self, _method: ByteString, _url: DOMString) -> ErrorResult; fn Open_(&mut self, _method: ByteString, _url: DOMString, _async: bool, @@ -143,13 +234,12 @@ pub trait XMLHttpRequestMethods { fn GetResponseXML(&self) -> Option<Temporary<Document>>; } -impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { +impl<'a> XMLHttpRequestMethods<'a> for JSRef<'a, XMLHttpRequest> { fn ReadyState(&self) -> u16 { self.ready_state as u16 } fn Open(&mut self, method: ByteString, url: DOMString) -> ErrorResult { self.request_method = method; - self.sync = true; //XXXManishearth the default should be changed later // Step 2 let base: Option<Url> = Some(self.global.root().get_url()); @@ -168,12 +258,12 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { // Step 12 self.request_url = Untraceable::new(parsed_url); - self.request_headers = HashMap::new(); + self.request_headers = Untraceable::new(RequestHeaderCollection::new()); self.send_flag = false; // XXXManishearth Set response to a NetworkError // Step 13 - self.ready_state = Opened; + self.change_ready_state(Opened); //XXXManishearth fire a progressevent Ok(()) }, @@ -191,9 +281,10 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { } } } - fn Open_(&mut self, _method: ByteString, _url: DOMString, _async: bool, + fn Open_(&mut self, method: ByteString, url: DOMString, async: bool, _username: Option<DOMString>, _password: Option<DOMString>) -> ErrorResult { - Ok(()) + self.sync = !async; + self.Open(method, url) } fn SetRequestHeader(&self, _name: ByteString, _value: ByteString) { @@ -214,7 +305,7 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { Temporary::new(self.upload.get_ref().clone()) } fn Send(&mut self, data: Option<DOMString>) -> ErrorResult { - // XXXManishearth handle async requests, POSTdata, and headers + // XXXManishearth handle POSTdata, and headers if self.ready_state != Opened || self.send_flag { return Err(InvalidState); // Step 1, 2 } @@ -231,29 +322,21 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { // Step 9 self.send_flag = true; - - let resource_task = self.global.root().page().resource_task.deref().clone(); - - // Step 10, 13 - let fetched = load_whole_resource(&resource_task, self.request_url.clone()); - self.ready_state = Done; - - // Error and result handling - match fetched { - Ok((_, text)) => { - self.response = ByteString::new(text) - }, - Err(_) => { - self.send_flag = false; - // XXXManishearth set response to NetworkError - if !self.upload_complete { - self.upload_complete = true; - // XXXManishearth handle upload progress - } - // XXXManishearth fire some progress events - return Err(Network) + let mut global = self.global.root(); + let resource_task = global.page().resource_task.deref().clone(); + let url = self.request_url.clone(); + if self.sync { + return XMLHttpRequest::fetch(&mut Sync(self), resource_task, url); + } else { + let mut builder = task::task().named("XHRTask"); + unsafe { + let addr = self.to_trusted(); + let script_chan = global.script_chan.clone(); + builder.spawn(proc() { + XMLHttpRequest::fetch(&mut Async(addr, script_chan), resource_task, url); + }) } - }; + } Ok(()) } fn Abort(&self) { @@ -268,11 +351,13 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { fn StatusText(&self) -> ByteString { self.status_text.clone() } - fn GetResponseHeader(&self, _name: ByteString) -> Option<ByteString> { + fn GetResponseHeader(&self, name: ByteString) -> Option<ByteString> { None } fn GetAllResponseHeaders(&self) -> ByteString { - ByteString::new(vec!()) + let mut writer = MemWriter::new(); + self.response_headers.deref().write_all(&mut writer); + ByteString::new(writer.unwrap()) } fn OverrideMimeType(&self, _mime: DOMString) { @@ -284,7 +369,24 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { self.response_type = response_type } fn Response(&self, cx: *JSContext) -> JSVal { - self.response.to_jsval(cx) + match self.response_type { + _empty | Text => { + if self.ready_state == XHRDone || self.ready_state == Loading { + self.response.to_jsval(cx) + } else { + "".to_owned().to_jsval(cx) + } + }, + _ => { + if self.ready_state == XHRDone { + // XXXManishearth we may not be able to store + // other response types as DOMStrings + self.response.to_jsval(cx) + } else { + NullValue() + } + } + } } fn ResponseText(&self) -> DOMString { self.response_text.clone() @@ -311,4 +413,84 @@ impl XMLHttpRequestDerived for EventTarget { _ => false } } +} + +pub struct TrustedXHRAddress(pub *c_void); + +impl TrustedXHRAddress { + pub fn release(self) { + unsafe { + JS::from_trusted_xhr_address(self).root().release(); + } + } +} + + +trait PrivateXMLHttpRequestHelpers { + unsafe fn to_trusted(&mut self) -> TrustedXHRAddress; + fn release(&mut self); + fn change_ready_state(&mut self, XMLHttpRequestState); + fn process_partial_response(&mut self, progress: XHRProgress); +} + +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()); + 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()); + } + self.pinned = false; + } + + fn change_ready_state(&mut self, rs: XMLHttpRequestState) { + self.ready_state = rs; + let win = &*self.global.root(); + let mut event = Event::new(win).root(); + event.InitEvent("readystatechange".to_owned(), false, true); + let target: &JSRef<EventTarget> = EventTargetCast::from_ref(self); + target.dispatch_event_with_target(None, &mut *event).ok(); + } + + fn process_partial_response(&mut self, progress: XHRProgress) { + match progress { + HeadersReceivedMsg(headers) => { + match headers { + Some(ref h) => *self.response_headers = h.clone(), + None => {} + }; + self.change_ready_state(HeadersReceived); + }, + LoadingMsg(partial_response) => { + self.response = partial_response; + if self.ready_state == HeadersReceived { + self.change_ready_state(Loading); + } + }, + DoneMsg => { + self.send_flag = false; + self.change_ready_state(XHRDone); + }, + ErroredMsg => { + self.send_flag = false; + // XXXManishearth set response to NetworkError + if !self.upload_complete { + self.upload_complete = true; + // XXXManishearth handle upload progress + } + // XXXManishearth fire some progress events + self.change_ready_state(XHRDone); + }, + ReleaseMsg => { + self.release(); + } + } + } }
\ No newline at end of file diff --git a/src/components/script/html/cssparse.rs b/src/components/script/html/cssparse.rs index 51a05a7656e..156ae2f6d0e 100644 --- a/src/components/script/html/cssparse.rs +++ b/src/components/script/html/cssparse.rs @@ -32,7 +32,7 @@ fn parse_css(provenance: StylesheetProvenance) -> Stylesheet { debug!("cssparse: loading style sheet at {:s}", url.to_str()); let (input_chan, input_port) = channel(); resource_task.send(Load(url, input_chan)); - let LoadResponse { metadata: metadata, progress_port: progress_port } + let LoadResponse { metadata: metadata, progress_port: progress_port , ..} = input_port.recv(); let final_url = &metadata.final_url; let protocol_encoding_label = metadata.charset.as_ref().map(|s| s.as_slice()); diff --git a/src/components/script/script.rs b/src/components/script/script.rs index 841bf7f8449..a3dcae637ec 100644 --- a/src/components/script/script.rs +++ b/src/components/script/script.rs @@ -20,6 +20,7 @@ extern crate collections; extern crate geom; extern crate hubbub; extern crate encoding; +extern crate http; extern crate js; extern crate libc; extern crate native; diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 0ce993d5e19..9c50b2f5b5d 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -22,6 +22,7 @@ use dom::eventtarget::{EventTarget, EventTargetHelpers}; use dom::node; use dom::node::{Node, NodeHelpers}; use dom::window::{TimerId, Window, WindowHelpers}; +use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress}; use html::hubbub_html_parser::HtmlParserResult; use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredScript}; use html::hubbub_html_parser; @@ -89,6 +90,8 @@ pub enum ScriptMsg { ExitPipelineMsg(PipelineId), /// Notifies the script that a window associated with a particular pipeline should be closed. ExitWindowMsg(PipelineId), + /// Notifies the script of progress on a fetch + XHRProgressMsg(TrustedXHRAddress, XHRProgress) } pub struct NewLayoutInfo { @@ -780,6 +783,7 @@ impl ScriptTask { ExitPipelineMsg(id) => if self.handle_exit_pipeline_msg(id) { return false }, ExitWindowMsg(id) => self.handle_exit_window_msg(id), ResizeMsg(..) => fail!("should have handled ResizeMsg already"), + XHRProgressMsg(addr, progress) => XMLHttpRequest::handle_xhr_progress(addr, progress), } } |