diff --git a/src/components/script/dom/bindings/error.rs b/src/components/script/dom/bindings/error.rs index bbfad388e4f..8793a1c1c6e 100644 --- a/src/components/script/dom/bindings/error.rs +++ b/src/components/script/dom/bindings/error.rs @@ -19,7 +19,10 @@ pub enum Error { InvalidCharacter, NotSupported, InvalidState, - NamespaceError + Syntax, + NamespaceError, + Security, + Network } pub type Fallible = Result; diff --git a/src/components/script/dom/bindings/str.rs b/src/components/script/dom/bindings/str.rs index e44ad6e60d9..e0ac5e57a2e 100644 --- a/src/components/script/dom/bindings/str.rs +++ b/src/components/script/dom/bindings/str.rs @@ -2,16 +2,60 @@ * 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/. */ -#[deriving(Encodable,Clone)] +use std::hash::{Hash, sip}; +use std::str; + +#[deriving(Encodable,Clone,TotalEq,Eq)] pub struct ByteString(Vec); impl ByteString { pub fn new(value: Vec) -> ByteString { ByteString(value) } + pub fn as_str<'a>(&'a self) -> Option<&'a str> { + let ByteString(ref vec) = *self; + str::from_utf8(vec.as_slice()) + } pub fn as_slice<'a>(&'a self) -> &'a [u8] { let ByteString(ref vector) = *self; vector.as_slice() } + + pub fn eq_ignore_case(&self, other: &ByteString) -> bool { + // XXXManishearth make this more efficient + self.to_lower() == other.to_lower() + } + + pub fn to_lower(&self) -> ByteString { + let ByteString(ref vec) = *self; + ByteString::new(vec.iter().map(|&x| { + if x > 'A' as u8 && x < 'Z' as u8 { + x + ('a' as u8) - ('A' as u8) + } else { + x + } + }).collect()) + } + pub fn is_token(&self) -> bool { + let ByteString(ref vec) = *self; + vec.iter().all(|&x| { + // http://tools.ietf.org/html/rfc2616#section-2.2 + match x { + 0..31 | 127 => false, // CTLs + 40 | 41 | 60 | 62 | 64 | + 44 | 59 | 58 | 92 | 34 | + 47 | 91 | 93 | 63 | 61 | + 123 | 125 | 32 => false, // separators + _ => true + } + }) + } } + +impl Hash for ByteString { + fn hash(&self, state: &mut sip::SipState) { + let ByteString(ref vec) = *self; + vec.hash(state); + } +} \ No newline at end of file diff --git a/src/components/script/dom/webidls/XMLHttpRequest.webidl b/src/components/script/dom/webidls/XMLHttpRequest.webidl index 6d98dd37377..4ed726d7e48 100644 --- a/src/components/script/dom/webidls/XMLHttpRequest.webidl +++ b/src/components/script/dom/webidls/XMLHttpRequest.webidl @@ -38,7 +38,8 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { readonly attribute unsigned short readyState; // request - // void open(ByteString method, /* [EnsureUTF16] */ DOMString url); + [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); @@ -46,7 +47,8 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { attribute unsigned long timeout; attribute boolean withCredentials; readonly attribute XMLHttpRequestUpload upload; - // void send(optional /*(ArrayBufferView or Blob or Document or [EnsureUTF16] */ DOMString/* or FormData or URLSearchParams)*/? data = null); + [Throws] + void send(optional /*(ArrayBufferView or Blob or Document or [EnsureUTF16] */ DOMString/* or FormData or URLSearchParams)*/? data = null); // void abort(); // response @@ -57,7 +59,7 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget { // ByteString getAllResponseHeaders(); // void overrideMimeType(DOMString mime); attribute XMLHttpRequestResponseType responseType; - // readonly attribute any response; + readonly attribute any response; readonly attribute DOMString responseText; [Exposed=Window] readonly attribute Document? responseXML; }; diff --git a/src/components/script/dom/xmlhttprequest.rs b/src/components/script/dom/xmlhttprequest.rs index adbd5eac960..b39e84db041 100644 --- a/src/components/script/dom/xmlhttprequest.rs +++ b/src/components/script/dom/xmlhttprequest.rs @@ -2,22 +2,36 @@ * 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 dom::bindings::error::{ErrorResult, InvalidState, Network, Syntax, Security}; use dom::document::Document; use dom::eventtarget::{EventTarget, 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, NullValue}; +use js::jsval::JSVal; 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 servo_util::str::DOMString; +use servo_util::url::{parse_url, try_parse_url}; +use url::Url; + + + +// As send() start accepting more and more parameter types, +// change this to the appropriate type from UnionTypes, eg +// use SendParam = dom::bindings::codegen::UnionTypes::StringOrFormData; +type SendParam = DOMString; #[deriving(Eq,Encodable)] pub enum XMLHttpRequestId { @@ -25,35 +39,70 @@ pub enum XMLHttpRequestId { XMLHttpRequestUploadTypeId } +#[deriving(Eq, Encodable)] +enum XMLHttpRequestState { + Unsent = 0u16, + Opened = 1u16, + HeadersReceived = 2u16, + Loading = 3u16, + Done = 4u16, +} + #[deriving(Encodable)] pub struct XMLHttpRequest { eventtarget: XMLHttpRequestEventTarget, - ready_state: u16, + ready_state: XMLHttpRequestState, timeout: u32, with_credentials: bool, upload: Option>, response_url: DOMString, status: u16, status_text: ByteString, + response: ByteString, response_type: XMLHttpRequestResponseType, response_text: DOMString, - response_xml: Option> + response_xml: Option>, + + // Associated concepts + request_method: ByteString, + request_url: Untraceable, + request_headers: HashMap, + request_body: SendParam, + sync: bool, + upload_complete: bool, + upload_events: bool, + send_flag: bool, + + global: JS } impl XMLHttpRequest { pub fn new_inherited(owner: &JSRef) -> XMLHttpRequest { let mut xhr = XMLHttpRequest { eventtarget: XMLHttpRequestEventTarget::new_inherited(XMLHttpRequestTypeId), - ready_state: 0, + ready_state: Unsent, timeout: 0u32, with_credentials: false, upload: None, response_url: "".to_owned(), status: 0, status_text: ByteString::new(vec!()), + response: ByteString::new(vec!()), response_type: _empty, response_text: "".to_owned(), - response_xml: None + response_xml: None, + + request_method: ByteString::new(vec!()), + request_url: Untraceable::new(parse_url("", None)), + request_headers: HashMap::new(), + request_body: "".to_owned(), + sync: false, + send_flag: false, + + upload_complete: false, + upload_events: false, + + global: owner.unrooted() }; xhr.upload.assign(Some(XMLHttpRequestUpload::new(owner))); xhr @@ -70,16 +119,16 @@ impl XMLHttpRequest { pub trait XMLHttpRequestMethods { fn ReadyState(&self) -> u16; - fn Open(&self, _method: ByteString, _url: DOMString); - fn Open_(&self, _method: ByteString, _url: DOMString, _async: bool, - _username: Option, _password: Option); + fn Open(&mut self, _method: ByteString, _url: DOMString) -> ErrorResult; + fn Open_(&mut self, _method: ByteString, _url: DOMString, _async: bool, + _username: Option, _password: Option) -> ErrorResult; fn SetRequestHeader(&self, _name: ByteString, _value: ByteString); fn Timeout(&self) -> u32; fn SetTimeout(&mut self, timeout: u32); fn WithCredentials(&self) -> bool; fn SetWithCredentials(&mut self, with_credentials: bool); fn Upload(&self) -> Temporary; - fn Send(&self, _data: Option); + fn Send(&mut self, _data: Option) -> ErrorResult; fn Abort(&self); fn ResponseURL(&self) -> DOMString; fn Status(&self) -> u16; @@ -96,14 +145,55 @@ pub trait XMLHttpRequestMethods { impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { fn ReadyState(&self) -> u16 { - self.ready_state + self.ready_state as u16 } - fn Open(&self, _method: ByteString, _url: DOMString) { + 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 = Some(self.global.root().get_url()); + match self.request_method.to_lower().as_str() { + Some("get") => { + + // Step 5 + self.request_method = self.request_method.to_lower(); + + // Step 6 + let parsed_url = match try_parse_url(url, base) { + Ok(parsed) => parsed, + Err(_) => return Err(Syntax) // Step 7 + }; + // XXXManishearth Do some handling of username/passwords, and abort existing requests + + // Step 12 + self.request_url = Untraceable::new(parsed_url); + self.request_headers = HashMap::new(); + self.send_flag = false; + // XXXManishearth Set response to a NetworkError + + // Step 13 + self.ready_state = Opened; + //XXXManishearth fire a progressevent + Ok(()) + }, + // XXXManishearth Handle other standard methods + Some("connect") | Some("trace") | Some("track") => { + Err(Security) // Step 4 + }, + None => Err(Syntax), + _ => { + if self.request_method.is_token() { + Ok(()) // XXXManishearth handle extension methods + } else { + Err(Syntax) // Step 3 + } + } + } } - fn Open_(&self, _method: ByteString, _url: DOMString, _async: bool, - _username: Option, _password: Option) { - + fn Open_(&mut self, _method: ByteString, _url: DOMString, _async: bool, + _username: Option, _password: Option) -> ErrorResult { + Ok(()) } fn SetRequestHeader(&self, _name: ByteString, _value: ByteString) { @@ -123,8 +213,48 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { fn Upload(&self) -> Temporary { Temporary::new(self.upload.get_ref().clone()) } - fn Send(&self, _data: Option) { + fn Send(&mut self, data: Option) -> ErrorResult { + // XXXManishearth handle async requests, POSTdata, and headers + if self.ready_state != Opened || self.send_flag { + return Err(InvalidState); // Step 1, 2 + } + let data = match self.request_method.to_lower().as_str() { + Some("get") | Some("head") => None, // Step 3 + _ => data + }; + + // Step 6 + self.upload_complete = false; + self.upload_events = false; + // XXXManishearth handle upload events + + // 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) + } + }; + Ok(()) } fn Abort(&self) { @@ -153,8 +283,8 @@ impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> { fn SetResponseType(&mut self, response_type: XMLHttpRequestResponseType) { self.response_type = response_type } - fn Response(&self, _cx: *JSContext) -> JSVal { - NullValue() + fn Response(&self, cx: *JSContext) -> JSVal { + self.response.to_jsval(cx) } fn ResponseText(&self) -> DOMString { self.response_text.clone() @@ -176,6 +306,9 @@ impl Reflectable for XMLHttpRequest { impl XMLHttpRequestDerived for EventTarget { fn is_xmlhttprequest(&self) -> bool { - self.type_id == XMLHttpRequestTargetTypeId(XMLHttpRequestTypeId) + match self.type_id { + XMLHttpRequestTargetTypeId(XMLHttpRequestTypeId) => true, + _ => false + } } -} +} \ No newline at end of file diff --git a/src/components/script/script.rs b/src/components/script/script.rs index fcc8ad0a51d..4f90bf8ebd5 100644 --- a/src/components/script/script.rs +++ b/src/components/script/script.rs @@ -23,6 +23,7 @@ extern crate encoding; extern crate js; extern crate libc; extern crate native; +extern crate net; extern crate serialize; extern crate time; #[phase(syntax)] diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 45f1349b3f6..c7021390c95 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -154,7 +154,10 @@ pub struct Page { pub resize_event: Untraceable>>>, /// Pending scroll to fragment event, if any - pub fragment_node: Traceable>>> + pub fragment_node: Traceable>>>, + + /// Associated resource task for use by DOM objects like XMLHttpRequest + pub resource_task: Untraceable, } pub struct PageTree { @@ -167,7 +170,8 @@ pub struct PageTreeIterator<'a> { } impl PageTree { - fn new(id: PipelineId, layout_chan: LayoutChan, window_size: Size2D) -> PageTree { + fn new(id: PipelineId, layout_chan: LayoutChan, + window_size: Size2D, resource_task: ResourceTask) -> PageTree { PageTree { page: Rc::new(Page { id: id, @@ -182,6 +186,7 @@ impl PageTree { resize_event: Untraceable::new(RefCell::new(None)), fragment_node: Traceable::new(RefCell::new(None)), last_reflow_id: Traceable::new(RefCell::new(0)), + resource_task: Untraceable::new(resource_task) }), inner: vec!(), } @@ -593,7 +598,8 @@ impl ScriptTask { -> Rc { let (js_runtime, js_context) = ScriptTask::new_rt_and_cx(); Rc::new(ScriptTask { - page_tree: RefCell::new(PageTree::new(id, layout_chan, window_size)), + page_tree: RefCell::new(PageTree::new(id, layout_chan, + window_size, resource_task.clone())), image_cache_task: img_cache_task, resource_task: resource_task, @@ -775,7 +781,8 @@ impl ScriptTask { task's page tree. This is a bug."); let new_page_tree = { let window_size = parent_page_tree.page().window_size.deref().borrow(); - PageTree::new(new_id, layout_chan, *window_size) + PageTree::new(new_id, layout_chan, *window_size, + parent_page_tree.page().resource_task.deref().clone()) }; parent_page_tree.inner.push(new_page_tree); }