diff options
Diffstat (limited to 'components/script/dom/htmlformelement.rs')
-rw-r--r-- | components/script/dom/htmlformelement.rs | 307 |
1 files changed, 169 insertions, 138 deletions
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 515d49c0f7c..d6a794621e5 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -12,6 +12,7 @@ use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMet use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; use dom::bindings::conversions::DerivedFrom; +use dom::bindings::global::GlobalRef; use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use dom::bindings::js::{JS, MutNullableHeap, Root}; use dom::bindings::refcounted::Trusted; @@ -36,11 +37,9 @@ use dom::htmlselectelement::HTMLSelectElement; use dom::htmltextareaelement::HTMLTextAreaElement; use dom::node::{Node, document_from_node, window_from_node}; use dom::virtualmethods::VirtualMethods; -use dom::window::Window; use encoding::EncodingRef; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; -use encoding::types::DecoderTrap; use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType}; use hyper::method::Method; use msg::constellation_msg::{LoadData, PipelineId}; @@ -51,10 +50,8 @@ use std::cell::Cell; use std::sync::mpsc::Sender; use string_cache::Atom; use style::attr::AttrValue; +use style::str::split_html_space_chars; use task_source::TaskSource; -use task_source::dom_manipulation::DOMManipulationTask; -use url::form_urlencoded; -use util::str::split_html_space_chars; #[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] pub struct GenerationId(u32); @@ -68,11 +65,11 @@ pub struct HTMLFormElement { } impl HTMLFormElement { - fn new_inherited(localName: Atom, + fn new_inherited(local_name: Atom, prefix: Option<DOMString>, document: &Document) -> HTMLFormElement { HTMLFormElement { - htmlelement: HTMLElement::new_inherited(localName, prefix, document), + htmlelement: HTMLElement::new_inherited(local_name, prefix, document), marked_for_reset: Cell::new(false), elements: Default::default(), generation_id: Cell::new(GenerationId(0)) @@ -80,11 +77,12 @@ impl HTMLFormElement { } #[allow(unrooted_must_root)] - pub fn new(localName: Atom, + pub fn new(local_name: Atom, prefix: Option<DOMString>, document: &Document) -> Root<HTMLFormElement> { - let element = HTMLFormElement::new_inherited(localName, prefix, document); - Node::reflect_node(box element, document, HTMLFormElementBinding::Wrap) + Node::reflect_node(box HTMLFormElement::new_inherited(local_name, prefix, document), + document, + HTMLFormElementBinding::Wrap) } pub fn generation_id(&self) -> GenerationId { @@ -232,9 +230,9 @@ impl HTMLFormElementMethods for HTMLFormElement { } // https://html.spec.whatwg.org/multipage/#dom-form-item - fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { let elements = self.Elements(); - elements.IndexedGetter(index, found) + elements.IndexedGetter(index) } } @@ -252,13 +250,6 @@ pub enum ResetFrom { impl HTMLFormElement { - fn generate_boundary(&self) -> String { - let i1 = random::<u32>(); - let i2 = random::<u32>(); - - format!("---------------------------{0}{1}", i1, i2) - } - // https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form fn pick_encoding(&self) -> EncodingRef { // Step 2 @@ -277,67 +268,6 @@ impl HTMLFormElement { document_from_node(self).encoding() } - // https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm - fn encode_multipart_form_data(&self, form_data: &mut Vec<FormDatum>, - encoding: Option<EncodingRef>, - boundary: String) -> String { - // Step 1 - let mut result = "".to_owned(); - - // Step 2 - // (maybe take encoding as input) - let encoding = encoding.unwrap_or(self.pick_encoding()); - - // Step 3 - let charset = &*encoding.whatwg_name().unwrap_or("UTF-8"); - - // Step 4 - for entry in form_data.iter_mut() { - // Substep 1 - if entry.name == "_charset_" && entry.ty == "hidden" { - entry.value = FormDatumValue::String(DOMString::from(charset.clone())); - } - // TODO: Substep 2 - - // Step 5 - // https://tools.ietf.org/html/rfc7578#section-4 - result.push_str(&*format!("\r\n--{}\r\n", boundary)); - let mut content_disposition = ContentDisposition { - disposition: DispositionType::Ext("form-data".to_owned()), - parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))] - }; - - match entry.value { - FormDatumValue::String(ref s) => - result.push_str(&*format!("Content-Disposition: {}\r\n\r\n{}", - content_disposition, - s)), - FormDatumValue::File(ref f) => { - content_disposition.parameters.push( - DispositionParam::Filename(Charset::Ext(String::from(charset.clone())), - None, - f.name().clone().into())); - // https://tools.ietf.org/html/rfc7578#section-4.4 - let content_type = ContentType(f.upcast::<Blob>().Type() - .parse().unwrap_or(mime!(Text / Plain))); - result.push_str(&*format!("Content-Disposition: {}\r\n{}\r\n\r\n", - content_disposition, - content_type)); - - let slice = f.upcast::<Blob>().get_slice_or_empty(); - - let decoded = encoding.decode(&slice.get_bytes(), DecoderTrap::Replace) - .expect("Invalid encoding in file"); - result.push_str(&decoded); - } - } - } - - result.push_str(&*format!("\r\n--{}--", boundary)); - - result - } - // https://html.spec.whatwg.org/multipage/#text/plain-encoding-algorithm fn encode_plaintext(&self, form_data: &mut Vec<FormDatum>) -> String { // Step 1 @@ -350,18 +280,11 @@ impl HTMLFormElement { let charset = &*encoding.whatwg_name().unwrap(); for entry in form_data.iter_mut() { - // Step 4 - if entry.name == "_charset_" && entry.ty == "hidden" { - entry.value = FormDatumValue::String(DOMString::from(charset.clone())); - } - - // Step 5 - if entry.ty == "file" { - entry.value = FormDatumValue::String(DOMString::from(entry.value_str())); - } + // Step 4, 5 + let value = entry.replace_value(charset); // Step 6 - result.push_str(&*format!("{}={}\r\n", entry.name, entry.value_str())); + result.push_str(&*format!("{}={}\r\n", entry.name, value)); } // Step 7 @@ -373,7 +296,7 @@ impl HTMLFormElement { // Step 1 let doc = document_from_node(self); let base = doc.url(); - // TODO: Handle browsing contexts + // TODO: Handle browsing contexts (Step 2, 3) // Step 4 if submit_method_flag == SubmittedFrom::NotFromForm && !submitter.no_validate(self) @@ -396,13 +319,18 @@ impl HTMLFormElement { } // Step 6 let mut form_data = self.get_form_dataset(Some(submitter)); + // Step 7 - let mut action = submitter.action(); + let encoding = self.pick_encoding(); + // Step 8 + let mut action = submitter.action(); + + // Step 9 if action.is_empty() { action = DOMString::from(base.as_str()); } - // Step 9-11 + // Step 10-11 let action_components = match base.join(&action) { Ok(url) => url, Err(_) => return @@ -416,57 +344,87 @@ impl HTMLFormElement { let mut load_data = LoadData::new(action_components, doc.get_referrer_policy(), Some(doc.url().clone())); - let parsed_data = match enctype { + // Step 18 + match (&*scheme, method) { + (_, FormMethod::FormDialog) => { + // TODO: Submit dialog + // https://html.spec.whatwg.org/multipage/#submit-dialog + } + // https://html.spec.whatwg.org/multipage/#submit-mutate-action + ("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) | ("data", FormMethod::FormGet) => { + load_data.headers.set(ContentType::form_url_encoded()); + self.mutate_action_url(&mut form_data, load_data, encoding); + } + // https://html.spec.whatwg.org/multipage/#submit-body + ("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => { + load_data.method = Method::Post; + self.submit_entity_body(&mut form_data, load_data, enctype, encoding); + } + // https://html.spec.whatwg.org/multipage/#submit-get-action + ("file", _) | ("about", _) | ("data", FormMethod::FormPost) | + ("ftp", _) | ("javascript", _) => { + self.plan_to_navigate(load_data); + } + ("mailto", FormMethod::FormPost) => { + // TODO: Mail as body + // https://html.spec.whatwg.org/multipage/#submit-mailto-body + } + ("mailto", FormMethod::FormGet) => { + // TODO: Mail with headers + // https://html.spec.whatwg.org/multipage/#submit-mailto-headers + } + _ => return, + } + } + + // https://html.spec.whatwg.org/multipage/#submit-mutate-action + fn mutate_action_url(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, encoding: EncodingRef) { + let charset = &*encoding.whatwg_name().unwrap(); + + load_data.url.query_pairs_mut().clear() + .encoding_override(Some(self.pick_encoding())) + .extend_pairs(form_data.into_iter() + .map(|field| (field.name.clone(), field.replace_value(charset)))); + + self.plan_to_navigate(load_data); + } + + // https://html.spec.whatwg.org/multipage/#submit-body + fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, + enctype: FormEncType, encoding: EncodingRef) { + let boundary = generate_boundary(); + let bytes = match enctype { FormEncType::UrlEncoded => { + let mut url = load_data.url.clone(); + let charset = &*encoding.whatwg_name().unwrap(); load_data.headers.set(ContentType::form_url_encoded()); - form_urlencoded::Serializer::new(String::new()) - .encoding_override(Some(self.pick_encoding())) - .extend_pairs(form_data.into_iter().map(|field| (field.name.clone(), field.value_str()))) - .finish() + url.query_pairs_mut().clear() + .encoding_override(Some(self.pick_encoding())) + .extend_pairs(form_data.into_iter() + .map(|field| (field.name.clone(), field.replace_value(charset)))); + + url.query().unwrap_or("").to_string().into_bytes() } FormEncType::FormDataEncoded => { - let boundary = self.generate_boundary(); let mime = mime!(Multipart / FormData; Boundary =(&boundary)); load_data.headers.set(ContentType(mime)); - - self.encode_multipart_form_data(&mut form_data, None, boundary) + encode_multipart_form_data(form_data, boundary, encoding) } FormEncType::TextPlainEncoded => { load_data.headers.set(ContentType(mime!(Text / Plain))); - - self.encode_plaintext(&mut form_data) + self.encode_plaintext(form_data).into_bytes() } }; - // Step 18 - let win = window_from_node(self); - match (&*scheme, method) { - // https://html.spec.whatwg.org/multipage/#submit-dialog - (_, FormMethod::FormDialog) => return, // Unimplemented - // https://html.spec.whatwg.org/multipage/#submit-mutate-action - ("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) => { - // FIXME(SimonSapin): use url.query_pairs_mut() here. - load_data.url.set_query(Some(&*parsed_data)); - self.plan_to_navigate(load_data, &win); - } - // https://html.spec.whatwg.org/multipage/#submit-body - ("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => { - load_data.method = Method::Post; - load_data.data = Some(parsed_data.into_bytes()); - self.plan_to_navigate(load_data, &win); - } - // https://html.spec.whatwg.org/multipage/#submit-get-action - ("file", _) | ("about", _) | ("data", FormMethod::FormGet) | - ("ftp", _) | ("javascript", _) => { - self.plan_to_navigate(load_data, &win); - } - _ => return // Unimplemented (data and mailto) - } + load_data.data = Some(bytes); + self.plan_to_navigate(load_data); } /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) - fn plan_to_navigate(&self, load_data: LoadData, window: &Window) { + fn plan_to_navigate(&self, load_data: LoadData) { + let window = window_from_node(self); + // Step 1 // Each planned navigation runnable is tagged with a generation ID, and // before the runnable is handled, it first checks whether the HTMLFormElement's @@ -477,15 +435,14 @@ impl HTMLFormElement { // Step 2 let nav = box PlannedNavigation { load_data: load_data, - pipeline_id: window.pipeline(), + pipeline_id: window.pipeline_id(), script_chan: window.main_thread_script_chan().clone(), generation_id: self.generation_id.get(), form: Trusted::new(self) }; // Step 3 - window.dom_manipulation_task_source().queue( - DOMManipulationTask::PlannedNavigation(nav)).unwrap(); + window.dom_manipulation_task_source().queue(nav, GlobalRef::Window(&window)).unwrap(); } /// Interactively validate the constraints of form elements @@ -558,10 +515,8 @@ impl HTMLFormElement { match element { HTMLElementTypeId::HTMLInputElement => { let input = child.downcast::<HTMLInputElement>().unwrap(); - // Step 3.2-3.7 - if let Some(datum) = input.form_datum(submitter) { - data_set.push(datum); - } + + data_set.append(&mut input.form_datums(submitter)); } HTMLElementTypeId::HTMLButtonElement => { let button = child.downcast::<HTMLButtonElement>().unwrap(); @@ -696,12 +651,13 @@ impl HTMLFormElement { } +#[derive(JSTraceable, HeapSizeOf, Clone)] pub enum FormDatumValue { File(Root<File>), String(DOMString) } -// #[derive(HeapSizeOf)] +#[derive(HeapSizeOf, JSTraceable, Clone)] pub struct FormDatum { pub ty: DOMString, pub name: DOMString, @@ -709,10 +665,14 @@ pub struct FormDatum { } impl FormDatum { - pub fn value_str(&self) -> String { + pub fn replace_value(&self, charset: &str) -> String { + if self.name == "_charset_" && self.ty == "hidden" { + return charset.to_string(); + } + match self.value { + FormDatumValue::File(ref f) => String::from(f.name().clone()), FormDatumValue::String(ref s) => String::from(s.clone()), - FormDatumValue::File(ref f) => String::from(f.name().clone()) } } } @@ -937,6 +897,8 @@ struct PlannedNavigation { } impl Runnable for PlannedNavigation { + fn name(&self) -> &'static str { "PlannedNavigation" } + fn handler(self: Box<PlannedNavigation>) { if self.generation_id == self.form.root().generation_id.get() { let script_chan = self.script_chan.clone(); @@ -944,3 +906,72 @@ impl Runnable for PlannedNavigation { } } } + + +// https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm +pub fn encode_multipart_form_data(form_data: &mut Vec<FormDatum>, + boundary: String, encoding: EncodingRef) -> Vec<u8> { + // Step 1 + let mut result = vec![]; + + // Step 2 + let charset = &*encoding.whatwg_name().unwrap_or("UTF-8"); + + // Step 3 + for entry in form_data.iter_mut() { + // 3.1 + if entry.name == "_charset_" && entry.ty == "hidden" { + entry.value = FormDatumValue::String(DOMString::from(charset.clone())); + } + // TODO: 3.2 + + // Step 4 + // https://tools.ietf.org/html/rfc7578#section-4 + // NOTE(izgzhen): The encoding here expected by most servers seems different from + // what spec says (that it should start with a '\r\n'). + let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes(); + result.append(&mut boundary_bytes); + let mut content_disposition = ContentDisposition { + disposition: DispositionType::Ext("form-data".to_owned()), + parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))] + }; + + match entry.value { + FormDatumValue::String(ref s) => { + let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}", + content_disposition, s).into_bytes(); + result.append(&mut bytes); + } + FormDatumValue::File(ref f) => { + content_disposition.parameters.push( + DispositionParam::Filename(Charset::Ext(String::from(charset.clone())), + None, + f.name().clone().into())); + // https://tools.ietf.org/html/rfc7578#section-4.4 + let content_type = ContentType(f.upcast::<Blob>().Type() + .parse().unwrap_or(mime!(Text / Plain))); + let mut type_bytes = format!("Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n", + content_disposition, + content_type).into_bytes(); + result.append(&mut type_bytes); + + let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]); + + result.append(&mut bytes); + } + } + } + + let mut boundary_bytes = format!("\r\n--{}--", boundary).into_bytes(); + result.append(&mut boundary_bytes); + + result +} + +// https://tools.ietf.org/html/rfc7578#section-4.1 +pub fn generate_boundary() -> String { + let i1 = random::<u32>(); + let i2 = random::<u32>(); + + format!("---------------------------{0}{1}", i1, i2) +} |