diff options
Diffstat (limited to 'components/script/dom/htmlformelement.rs')
-rw-r--r--[-rwxr-xr-x] | components/script/dom/htmlformelement.rs | 1656 |
1 files changed, 1143 insertions, 513 deletions
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index afcfd9c6690..d06fd85a426 100755..100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -1,96 +1,184 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * 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 dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; -use dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; -use dom::bindings::codegen::Bindings::HTMLFormElementBinding; -use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; -use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; -use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; -use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; -use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::blob::Blob; -use dom::document::Document; -use dom::element::{AttributeMutation, Element}; -use dom::eventtarget::EventTarget; -use dom::file::File; -use dom::globalscope::GlobalScope; -use dom::htmlbuttonelement::HTMLButtonElement; -use dom::htmlcollection::CollectionFilter; -use dom::htmldatalistelement::HTMLDataListElement; -use dom::htmlelement::HTMLElement; -use dom::htmlfieldsetelement::HTMLFieldSetElement; -use dom::htmlformcontrolscollection::HTMLFormControlsCollection; -use dom::htmlimageelement::HTMLImageElement; -use dom::htmlinputelement::HTMLInputElement; -use dom::htmllabelelement::HTMLLabelElement; -use dom::htmllegendelement::HTMLLegendElement; -use dom::htmlobjectelement::HTMLObjectElement; -use dom::htmloutputelement::HTMLOutputElement; -use dom::htmlselectelement::HTMLSelectElement; -use dom::htmltextareaelement::HTMLTextAreaElement; -use dom::node::{Node, PARSER_ASSOCIATED_FORM_OWNER, UnbindContext, VecPreOrderInsertionHelper}; -use dom::node::{document_from_node, window_from_node}; -use dom::validitystate::ValidationFlags; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::body::Extractable; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods; +use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; +use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormControlsCollectionBinding::HTMLFormControlsCollectionMethods; +use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomOnceCell, DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::blob::Blob; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::{AttributeMutation, Element}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::file::File; +use crate::dom::formdata::FormData; +use crate::dom::formdataevent::FormDataEvent; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlanchorelement::{get_element_noopener, get_element_target}; +use crate::dom::htmlbuttonelement::HTMLButtonElement; +use crate::dom::htmlcollection::CollectionFilter; +use crate::dom::htmldatalistelement::HTMLDataListElement; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; +use crate::dom::htmlformcontrolscollection::HTMLFormControlsCollection; +use crate::dom::htmlimageelement::HTMLImageElement; +use crate::dom::htmlinputelement::{HTMLInputElement, InputType}; +use crate::dom::htmllabelelement::HTMLLabelElement; +use crate::dom::htmllegendelement::HTMLLegendElement; +use crate::dom::htmlobjectelement::HTMLObjectElement; +use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlselectelement::HTMLSelectElement; +use crate::dom::htmltextareaelement::HTMLTextAreaElement; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{Node, NodeFlags}; +use crate::dom::node::{UnbindContext, VecPreOrderInsertionHelper}; +use crate::dom::nodelist::{NodeList, RadioListMode}; +use crate::dom::radionodelist::RadioNodeList; +use crate::dom::submitevent::SubmitEvent; +use crate::dom::validitystate::ValidationFlags; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::Window; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use encoding::EncodingRef; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use html5ever_atoms::LocalName; -use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType}; -use hyper::method::Method; -use msg::constellation_msg::PipelineId; -use script_thread::{MainThreadScriptMsg, Runnable}; -use script_traits::LoadData; +use encoding_rs::{Encoding, UTF_8}; +use headers::{ContentType, HeaderMapExt}; +use html5ever::{LocalName, Prefix}; +use hyper::Method; +use mime::{self, Mime}; +use net_traits::http_percent_encode; +use net_traits::request::Referrer; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; +use servo_atoms::Atom; use servo_rand::random; use std::borrow::ToOwned; use std::cell::Cell; -use std::sync::mpsc::Sender; use style::attr::AttrValue; use style::str::split_html_space_chars; -use task_source::TaskSource; -#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] +use crate::dom::bindings::codegen::UnionTypes::RadioNodeListOrElement; +use std::collections::HashMap; +use time::{now, Duration, Tm}; + +use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; + +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] pub struct GenerationId(u32); #[dom_struct] pub struct HTMLFormElement { htmlelement: HTMLElement, marked_for_reset: Cell<bool>, - elements: MutNullableJS<HTMLFormControlsCollection>, + /// https://html.spec.whatwg.org/multipage/#constructing-entry-list + constructing_entry_list: Cell<bool>, + elements: DomOnceCell<HTMLFormControlsCollection>, generation_id: Cell<GenerationId>, - controls: DOMRefCell<Vec<JS<Element>>>, + controls: DomRefCell<Vec<Dom<Element>>>, + past_names_map: DomRefCell<HashMap<Atom, (Dom<Element>, Tm)>>, + firing_submission_events: Cell<bool>, + rel_list: MutNullableDom<DOMTokenList>, } impl HTMLFormElement { - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLFormElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLFormElement { HTMLFormElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), marked_for_reset: Cell::new(false), + constructing_entry_list: Cell::new(false), elements: Default::default(), generation_id: Cell::new(GenerationId(0)), - controls: DOMRefCell::new(Vec::new()), + controls: DomRefCell::new(Vec::new()), + past_names_map: DomRefCell::new(HashMap::new()), + firing_submission_events: Cell::new(false), + rel_list: Default::default(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLFormElement> { - Node::reflect_node(box HTMLFormElement::new_inherited(local_name, prefix, document), - document, - HTMLFormElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLFormElement> { + Node::reflect_node( + Box::new(HTMLFormElement::new_inherited(local_name, prefix, document)), + document, + ) + } + + fn filter_for_radio_list(mode: RadioListMode, child: &Element, name: &Atom) -> bool { + if let Some(child) = child.downcast::<Element>() { + match mode { + RadioListMode::ControlsExceptImageInputs => { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if child.get_id().map_or(false, |i| i == *name) || + child.get_name().map_or(false, |n| n == *name) + { + if let Some(inp) = child.downcast::<HTMLInputElement>() { + // input, only return it if it's not image-button state + return inp.input_type() != InputType::Image; + } else { + // control, but not an input + return true; + } + } + } + return false; + }, + RadioListMode::Images => { + return child.is::<HTMLImageElement>() && + (child.get_id().map_or(false, |i| i == *name) || + child.get_name().map_or(false, |n| n == *name)); + }, + } + } + false + } + + pub fn nth_for_radio_list( + &self, + index: u32, + mode: RadioListMode, + name: &Atom, + ) -> Option<DomRoot<Node>> { + self.controls + .borrow() + .iter() + .filter(|n| HTMLFormElement::filter_for_radio_list(mode, &*n, name)) + .nth(index as usize) + .and_then(|n| Some(DomRoot::from_ref(n.upcast::<Node>()))) + } + + pub fn count_for_radio_list(&self, mode: RadioListMode, name: &Atom) -> u32 { + self.controls + .borrow() + .iter() + .filter(|n| HTMLFormElement::filter_for_radio_list(mode, &**n, name)) + .count() as u32 } } @@ -102,7 +190,7 @@ impl HTMLFormElementMethods for HTMLFormElement { make_setter!(SetAcceptCharset, "accept-charset"); // https://html.spec.whatwg.org/multipage/#dom-fs-action - make_string_or_document_url_getter!(Action, "action"); + make_form_action_getter!(Action, "action"); // https://html.spec.whatwg.org/multipage/#dom-fs-action make_setter!(SetAction, "action"); @@ -114,10 +202,12 @@ impl HTMLFormElementMethods for HTMLFormElement { make_setter!(SetAutocomplete, "autocomplete"); // https://html.spec.whatwg.org/multipage/#dom-fs-enctype - make_enumerated_getter!(Enctype, - "enctype", - "application/x-www-form-urlencoded", - "text/plain" | "multipart/form-data"); + make_enumerated_getter!( + Enctype, + "enctype", + "application/x-www-form-urlencoded", + "text/plain" | "multipart/form-data" + ); // https://html.spec.whatwg.org/multipage/#dom-fs-enctype make_setter!(SetEnctype, "enctype"); @@ -156,63 +246,123 @@ impl HTMLFormElementMethods for HTMLFormElement { // https://html.spec.whatwg.org/multipage/#dom-fs-target make_setter!(SetTarget, "target"); + // https://html.spec.whatwg.org/multipage/#dom-a-rel + make_getter!(Rel, "rel"); + // https://html.spec.whatwg.org/multipage/#the-form-element:concept-form-submit fn Submit(&self) { self.submit(SubmittedFrom::FromForm, FormSubmitter::FormElement(self)); } + // https://html.spec.whatwg.org/multipage/#dom-form-requestsubmit + fn RequestSubmit(&self, submitter: Option<&HTMLElement>) -> Fallible<()> { + let submitter: FormSubmitter = match submitter { + Some(submitter_element) => { + // Step 1.1 + let error_not_a_submit_button = + Err(Error::Type("submitter must be a submit button".to_string())); + + let element = match submitter_element.upcast::<Node>().type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(element)) => element, + _ => { + return error_not_a_submit_button; + }, + }; + + let submit_button = match element { + HTMLElementTypeId::HTMLInputElement => FormSubmitter::InputElement( + &submitter_element + .downcast::<HTMLInputElement>() + .expect("Failed to downcast submitter elem to HTMLInputElement."), + ), + HTMLElementTypeId::HTMLButtonElement => FormSubmitter::ButtonElement( + &submitter_element + .downcast::<HTMLButtonElement>() + .expect("Failed to downcast submitter elem to HTMLButtonElement."), + ), + _ => { + return error_not_a_submit_button; + }, + }; + + if !submit_button.is_submit_button() { + return error_not_a_submit_button; + } + + let submitters_owner = submit_button.form_owner(); + + // Step 1.2 + let owner = match submitters_owner { + Some(owner) => owner, + None => { + return Err(Error::NotFound); + }, + }; + + if *owner != *self { + return Err(Error::NotFound); + } + + submit_button + }, + None => { + // Step 2 + FormSubmitter::FormElement(&self) + }, + }; + // Step 3 + self.submit(SubmittedFrom::NotFromForm, submitter); + Ok(()) + } + // https://html.spec.whatwg.org/multipage/#dom-form-reset fn Reset(&self) { self.reset(ResetFrom::FromForm); } // https://html.spec.whatwg.org/multipage/#dom-form-elements - fn Elements(&self) -> Root<HTMLFormControlsCollection> { - if let Some(elements) = self.elements.get() { - return elements; - } - - #[derive(JSTraceable, HeapSizeOf)] + fn Elements(&self) -> DomRoot<HTMLFormControlsCollection> { + #[derive(JSTraceable, MallocSizeOf)] struct ElementsFilter { - form: Root<HTMLFormElement> + form: DomRoot<HTMLFormElement>, } impl CollectionFilter for ElementsFilter { fn filter<'a>(&self, elem: &'a Element, _root: &'a Node) -> bool { let form_owner = match elem.upcast::<Node>().type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => { - match t { - HTMLElementTypeId::HTMLButtonElement => { - elem.downcast::<HTMLButtonElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLFieldSetElement => { - elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLInputElement => { - let input_elem = elem.downcast::<HTMLInputElement>().unwrap(); - if input_elem.type_() == atom!("image") { - return false; - } - input_elem.form_owner() - } - HTMLElementTypeId::HTMLObjectElement => { - elem.downcast::<HTMLObjectElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLOutputElement => { - elem.downcast::<HTMLOutputElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLSelectElement => { - elem.downcast::<HTMLSelectElement>().unwrap().form_owner() - } - HTMLElementTypeId::HTMLTextAreaElement => { - elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner() - } - _ => { - debug_assert!(!elem.downcast::<HTMLElement>().unwrap().is_listed_element() || - elem.local_name() == &local_name!("keygen")); + NodeTypeId::Element(ElementTypeId::HTMLElement(t)) => match t { + HTMLElementTypeId::HTMLButtonElement => { + elem.downcast::<HTMLButtonElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLFieldSetElement => { + elem.downcast::<HTMLFieldSetElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLInputElement => { + let input_elem = elem.downcast::<HTMLInputElement>().unwrap(); + if input_elem.input_type() == InputType::Image { return false; } - } - } + input_elem.form_owner() + }, + HTMLElementTypeId::HTMLObjectElement => { + elem.downcast::<HTMLObjectElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLOutputElement => { + elem.downcast::<HTMLOutputElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLSelectElement => { + elem.downcast::<HTMLSelectElement>().unwrap().form_owner() + }, + HTMLElementTypeId::HTMLTextAreaElement => { + elem.downcast::<HTMLTextAreaElement>().unwrap().form_owner() + }, + _ => { + debug_assert!( + !elem.downcast::<HTMLElement>().unwrap().is_listed_element() || + elem.local_name() == &local_name!("keygen") + ); + return false; + }, + }, _ => return false, }; @@ -222,11 +372,13 @@ impl HTMLFormElementMethods for HTMLFormElement { } } } - let filter = box ElementsFilter { form: Root::from_ref(self) }; - let window = window_from_node(self); - let elements = HTMLFormControlsCollection::new(&window, self.upcast(), filter); - self.elements.set(Some(&elements)); - elements + DomRoot::from_ref(self.elements.init_once(|| { + let filter = Box::new(ElementsFilter { + form: DomRoot::from_ref(self), + }); + let window = window_from_node(self); + HTMLFormControlsCollection::new(&window, self, filter) + })) } // https://html.spec.whatwg.org/multipage/#dom-form-length @@ -235,35 +387,263 @@ impl HTMLFormElementMethods for HTMLFormElement { } // https://html.spec.whatwg.org/multipage/#dom-form-item - fn IndexedGetter(&self, index: u32) -> Option<Root<Element>> { + fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> { let elements = self.Elements(); elements.IndexedGetter(index) } + + // https://html.spec.whatwg.org/multipage/#the-form-element%3Adetermine-the-value-of-a-named-property + fn NamedGetter(&self, name: DOMString) -> Option<RadioNodeListOrElement> { + let window = window_from_node(self); + + let name = Atom::from(name); + + // Step 1 + let mut candidates = RadioNodeList::new_controls_except_image_inputs(&window, self, &name); + let mut candidates_length = candidates.Length(); + + // Step 2 + if candidates_length == 0 { + candidates = RadioNodeList::new_images(&window, self, &name); + candidates_length = candidates.Length(); + } + + let mut past_names_map = self.past_names_map.borrow_mut(); + + // Step 3 + if candidates_length == 0 { + if past_names_map.contains_key(&name) { + return Some(RadioNodeListOrElement::Element(DomRoot::from_ref( + &*past_names_map.get(&name).unwrap().0, + ))); + } + return None; + } + + // Step 4 + if candidates_length > 1 { + return Some(RadioNodeListOrElement::RadioNodeList(candidates)); + } + + // Step 5 + // candidates_length is 1, so we can unwrap item 0 + let element_node = candidates.upcast::<NodeList>().Item(0).unwrap(); + past_names_map.insert( + name, + ( + Dom::from_ref(&*element_node.downcast::<Element>().unwrap()), + now(), + ), + ); + + // Step 6 + return Some(RadioNodeListOrElement::Element(DomRoot::from_ref( + &*element_node.downcast::<Element>().unwrap(), + ))); + } + + // https://html.spec.whatwg.org/multipage/#dom-a-rel + fn SetRel(&self, rel: DOMString) { + self.upcast::<Element>() + .set_tokenlist_attribute(&local_name!("rel"), rel); + } + + // https://html.spec.whatwg.org/multipage/#dom-a-rellist + fn RelList(&self) -> DomRoot<DOMTokenList> { + self.rel_list.or_init(|| { + DOMTokenList::new( + self.upcast(), + &local_name!("rel"), + Some(vec![ + Atom::from("noopener"), + Atom::from("noreferrer"), + Atom::from("opener"), + ]), + ) + }) + } + + // https://html.spec.whatwg.org/multipage/#the-form-element:supported-property-names + #[allow(non_snake_case)] + fn SupportedPropertyNames(&self) -> Vec<DOMString> { + // Step 1 + #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] + enum SourcedNameSource { + Id, + Name, + Past(Duration), + } + + impl SourcedNameSource { + fn is_past(&self) -> bool { + match self { + SourcedNameSource::Past(..) => true, + _ => false, + } + } + } + + struct SourcedName { + name: Atom, + element: DomRoot<Element>, + source: SourcedNameSource, + } + + let mut sourced_names_vec: Vec<SourcedName> = Vec::new(); + + let controls = self.controls.borrow(); + + // Step 2 + for child in controls.iter() { + if child + .downcast::<HTMLElement>() + .map_or(false, |c| c.is_listed_element()) + { + if let Some(id_atom) = child.get_id() { + let entry = SourcedName { + name: id_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourced_names_vec.push(entry); + } + if let Some(name_atom) = child.get_name() { + let entry = SourcedName { + name: name_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourced_names_vec.push(entry); + } + } + } + + // Step 3 + for child in controls.iter() { + if child.is::<HTMLImageElement>() { + if let Some(id_atom) = child.get_id() { + let entry = SourcedName { + name: id_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Id, + }; + sourced_names_vec.push(entry); + } + if let Some(name_atom) = child.get_name() { + let entry = SourcedName { + name: name_atom, + element: DomRoot::from_ref(&*child), + source: SourcedNameSource::Name, + }; + sourced_names_vec.push(entry); + } + } + } + + // Step 4 + let past_names_map = self.past_names_map.borrow(); + for (key, val) in past_names_map.iter() { + let entry = SourcedName { + name: key.clone(), + element: DomRoot::from_ref(&*val.0), + source: SourcedNameSource::Past(now() - val.1), // calculate difference now()-val.1 to find age + }; + sourced_names_vec.push(entry); + } + + // Step 5 + // TODO need to sort as per spec. + // if a.CompareDocumentPosition(b) returns 0 that means a=b in which case + // the remaining part where sorting is to be done by putting entries whose source is id first, + // then entries whose source is name, and finally entries whose source is past, + // and sorting entries with the same element and source by their age, oldest first. + + // if a.CompareDocumentPosition(b) has set NodeConstants::DOCUMENT_POSITION_FOLLOWING + // (this can be checked by bitwise operations) then b would follow a in tree order and + // Ordering::Less should be returned in the closure else Ordering::Greater + + sourced_names_vec.sort_by(|a, b| { + if a.element + .upcast::<Node>() + .CompareDocumentPosition(b.element.upcast::<Node>()) == + 0 + { + if a.source.is_past() && b.source.is_past() { + b.source.cmp(&a.source) + } else { + a.source.cmp(&b.source) + } + } else { + if a.element + .upcast::<Node>() + .CompareDocumentPosition(b.element.upcast::<Node>()) & + NodeConstants::DOCUMENT_POSITION_FOLLOWING == + NodeConstants::DOCUMENT_POSITION_FOLLOWING + { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } + }); + + // Step 6 + sourced_names_vec.retain(|sn| !sn.name.to_string().is_empty()); + + // Step 7-8 + let mut names_vec: Vec<DOMString> = Vec::new(); + for elem in sourced_names_vec.iter() { + if names_vec + .iter() + .find(|name| &**name == &*elem.name) + .is_none() + { + names_vec.push(DOMString::from(&*elem.name)); + } + } + + return names_vec; + } + + /// https://html.spec.whatwg.org/multipage/#dom-form-checkvalidity + fn CheckValidity(&self) -> bool { + self.static_validation().is_ok() + } + + /// https://html.spec.whatwg.org/multipage/#dom-form-reportvalidity + fn ReportValidity(&self) -> bool { + self.interactive_validation().is_ok() + } } -#[derive(Copy, Clone, HeapSizeOf, PartialEq)] +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum SubmittedFrom { FromForm, - NotFromForm + NotFromForm, } -#[derive(Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum ResetFrom { FromForm, - NotFromForm + NotFromForm, } - impl HTMLFormElement { // https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form - fn pick_encoding(&self) -> EncodingRef { + fn pick_encoding(&self) -> &'static Encoding { // Step 2 - if self.upcast::<Element>().has_attribute(&local_name!("accept-charset")) { + if self + .upcast::<Element>() + .has_attribute(&local_name!("accept-charset")) + { // Substep 1 - let input = self.upcast::<Element>().get_string_attribute(&local_name!("accept-charset")); + let input = self + .upcast::<Element>() + .get_string_attribute(&local_name!("accept-charset")); // Substep 2, 3, 4 - let mut candidate_encodings = split_html_space_chars(&*input).filter_map(encoding_from_whatwg_label); + let mut candidate_encodings = + split_html_space_chars(&*input).filter_map(|c| Encoding::for_label(c.as_bytes())); // Substep 5, 6 return candidate_encodings.next().unwrap_or(UTF_8); @@ -282,7 +662,7 @@ impl HTMLFormElement { let encoding = self.pick_encoding(); // Step 3 - let charset = &*encoding.whatwg_name().unwrap(); + let charset = encoding.name(); for entry in form_data.iter_mut() { // Step 4, 5 @@ -299,251 +679,441 @@ impl HTMLFormElement { /// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit) pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) { // Step 1 + if self.upcast::<Element>().cannot_navigate() { + return; + } + + // Step 2 + if self.constructing_entry_list.get() { + return; + } + // Step 3 let doc = document_from_node(self); let base = doc.base_url(); - // TODO: Handle browsing contexts (Step 2, 3) - // Step 4 - if submit_method_flag == SubmittedFrom::NotFromForm && - !submitter.no_validate(self) - { - if self.interactive_validation().is_err() { - // TODO: Implement event handlers on all form control elements - self.upcast::<EventTarget>().fire_event(atom!("invalid")); + // TODO: Handle browsing contexts (Step 4, 5) + // Step 6 + if submit_method_flag == SubmittedFrom::NotFromForm { + // Step 6.1 + if self.firing_submission_events.get() { return; } - } - // Step 5 - if submit_method_flag == SubmittedFrom::NotFromForm { - let event = self.upcast::<EventTarget>() - .fire_bubbling_cancelable_event(atom!("submit")); + // Step 6.2 + self.firing_submission_events.set(true); + // Step 6.3 + if !submitter.no_validate(self) { + if self.interactive_validation().is_err() { + self.firing_submission_events.set(false); + return; + } + } + // Step 6.4 + // spec calls this "submitterButton" but it doesn't have to be a button, + // just not be the form itself + let submitter_button = match submitter { + FormSubmitter::FormElement(f) => { + if f == self { + None + } else { + Some(f.upcast::<HTMLElement>()) + } + }, + FormSubmitter::InputElement(i) => Some(i.upcast::<HTMLElement>()), + FormSubmitter::ButtonElement(b) => Some(b.upcast::<HTMLElement>()), + }; + + // Step 6.5 + let event = SubmitEvent::new( + &self.global(), + atom!("submit"), + true, + true, + submitter_button.map(|s| DomRoot::from_ref(s)), + ); + let event = event.upcast::<Event>(); + event.fire(self.upcast::<EventTarget>()); + + // Step 6.6 + self.firing_submission_events.set(false); + // Step 6.7 if event.DefaultPrevented() { return; } + // Step 6.8 + if self.upcast::<Element>().cannot_navigate() { + return; + } } - // Step 6 - let mut form_data = self.get_form_dataset(Some(submitter)); // Step 7 let encoding = self.pick_encoding(); // Step 8 - let mut action = submitter.action(); + let mut form_data = match self.get_form_dataset(Some(submitter), Some(encoding)) { + Some(form_data) => form_data, + None => return, + }; // Step 9 + if self.upcast::<Element>().cannot_navigate() { + return; + } + + // Step 10 + let mut action = submitter.action(); + + // Step 11 if action.is_empty() { action = DOMString::from(base.as_str()); } - // Step 10-11 + // Step 12-13 let action_components = match base.join(&action) { Ok(url) => url, - Err(_) => return + Err(_) => return, }; - // Step 12-15 + // Step 14-16 let scheme = action_components.scheme().to_owned(); let enctype = submitter.enctype(); let method = submitter.method(); - let _target = submitter.target(); - // TODO: Handle browsing contexts, partially loaded documents (step 16-17) - let mut load_data = LoadData::new(action_components, doc.get_referrer_policy(), Some(doc.url())); + // Step 17 + let target_attribute_value = + if submitter.is_submit_button() && submitter.target() != DOMString::new() { + Some(submitter.target()) + } else { + let form_owner = submitter.form_owner(); + let form = form_owner.as_ref().map(|form| &**form).unwrap_or(self); + get_element_target(form.upcast::<Element>()) + }; // Step 18 + let noopener = + get_element_noopener(self.upcast::<Element>(), target_attribute_value.clone()); + + // Step 19 + let source = doc.browsing_context().unwrap(); + let (maybe_chosen, _new) = source + .choose_browsing_context(target_attribute_value.unwrap_or(DOMString::new()), noopener); + + // Step 20 + let chosen = match maybe_chosen { + Some(proxy) => proxy, + None => return, + }; + let target_document = match chosen.document() { + Some(doc) => doc, + None => return, + }; + // Step 21 + let target_window = target_document.window(); + let mut load_data = LoadData::new( + LoadOrigin::Script(doc.origin().immutable().clone()), + action_components, + None, + target_window.upcast::<GlobalScope>().get_referrer(), + target_document.get_referrer_policy(), + Some(target_window.upcast::<GlobalScope>().is_secure_context()), + ); + + // Step 22 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); - } + ("http", FormMethod::FormGet) | + ("https", FormMethod::FormGet) | + ("data", FormMethod::FormGet) => { + load_data + .headers + .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED)); + self.mutate_action_url(&mut form_data, load_data, encoding, &target_window); + }, // 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); - } + load_data.method = Method::POST; + self.submit_entity_body( + &mut form_data, + load_data, + enctype, + encoding, + &target_window, + ); + }, // https://html.spec.whatwg.org/multipage/#submit-get-action - ("file", _) | ("about", _) | ("data", FormMethod::FormPost) | - ("ftp", _) | ("javascript", _) => { - self.plan_to_navigate(load_data); - } + ("file", _) | + ("about", _) | + ("data", FormMethod::FormPost) | + ("ftp", _) | + ("javascript", _) => { + self.plan_to_navigate(load_data, &target_window); + }, ("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 - .as_mut_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); + fn mutate_action_url( + &self, + form_data: &mut Vec<FormDatum>, + mut load_data: LoadData, + encoding: &'static Encoding, + target: &Window, + ) { + let charset = encoding.name(); + + self.set_url_query_pairs( + &mut load_data.url, + form_data + .iter() + .map(|field| (&*field.name, field.replace_value(charset))), + ); + + self.plan_to_navigate(load_data, target); } // 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) { + fn submit_entity_body( + &self, + form_data: &mut Vec<FormDatum>, + mut load_data: LoadData, + enctype: FormEncType, + encoding: &'static Encoding, + target: &Window, + ) { let boundary = generate_boundary(); let bytes = match enctype { FormEncType::UrlEncoded => { - let charset = &*encoding.whatwg_name().unwrap(); - load_data.headers.set(ContentType::form_url_encoded()); - - load_data.url - .as_mut_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)))); - - load_data.url.query().unwrap_or("").to_string().into_bytes() - } + let charset = encoding.name(); + load_data + .headers + .typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED)); + + let mut url = load_data.url.clone(); + self.set_url_query_pairs( + &mut url, + form_data + .iter() + .map(|field| (&*field.name, field.replace_value(charset))), + ); + + url.query().unwrap_or("").to_string().into_bytes() + }, FormEncType::FormDataEncoded => { - let mime = mime!(Multipart / FormData; Boundary =(&boundary)); - load_data.headers.set(ContentType(mime)); + let mime: Mime = format!("multipart/form-data; boundary={}", boundary) + .parse() + .unwrap(); + load_data.headers.typed_insert(ContentType::from(mime)); encode_multipart_form_data(form_data, boundary, encoding) - } + }, FormEncType::TextPlainEncoded => { - load_data.headers.set(ContentType(mime!(Text / Plain))); + load_data + .headers + .typed_insert(ContentType::from(mime::TEXT_PLAIN)); self.encode_plaintext(form_data).into_bytes() - } + }, }; - load_data.data = Some(bytes); - self.plan_to_navigate(load_data); + let global = self.global(); + + let request_body = bytes + .extract(&global) + .expect("Couldn't extract body.") + .into_net_request_body() + .0; + load_data.data = Some(request_body); + + self.plan_to_navigate(load_data, target); } - /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) - fn plan_to_navigate(&self, load_data: LoadData) { - let window = window_from_node(self); + fn set_url_query_pairs<'a>( + &self, + url: &mut servo_url::ServoUrl, + pairs: impl Iterator<Item = (&'a str, String)>, + ) { + let encoding = self.pick_encoding(); + url.as_mut_url() + .query_pairs_mut() + .encoding_override(Some(&|s| encoding.encode(s).0)) + .clear() + .extend_pairs(pairs); + } + /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) + fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) { // 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 + // Each planned navigation task is tagged with a generation ID, and + // before the task is handled, it first checks whether the HTMLFormElement's // generation ID is the same as its own generation ID. - let GenerationId(prev_id) = self.generation_id.get(); - self.generation_id.set(GenerationId(prev_id + 1)); + let generation_id = GenerationId(self.generation_id.get().0 + 1); + self.generation_id.set(generation_id); // Step 2 - let nav = box PlannedNavigation { - load_data: load_data, - pipeline_id: window.upcast::<GlobalScope>().pipeline_id(), - script_chan: window.main_thread_script_chan().clone(), - generation_id: self.generation_id.get(), - form: Trusted::new(self) + let elem = self.upcast::<Element>(); + let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) { + Some(ref link_types) if link_types.Value().contains("noreferrer") => { + Referrer::NoReferrer + }, + _ => target.upcast::<GlobalScope>().get_referrer(), }; - // Step 3 - window.dom_manipulation_task_source().queue(nav, window.upcast()).unwrap(); + let referrer_policy = target.Document().get_referrer_policy(); + let pipeline_id = target.upcast::<GlobalScope>().pipeline_id(); + load_data.creator_pipeline_id = Some(pipeline_id); + load_data.referrer = referrer; + load_data.referrer_policy = referrer_policy; + + // Step 4. + let this = Trusted::new(self); + let window = Trusted::new(target); + let task = task!(navigate_to_form_planned_navigation: move || { + if generation_id != this.root().generation_id.get() { + return; + } + window + .root() + .load_url( + HistoryEntryReplacement::Disabled, + false, + load_data, + ); + }); + + // Step 3. + target + .task_manager() + .dom_manipulation_task_source() + .queue(task, target.upcast()) + .unwrap(); } /// Interactively validate the constraints of form elements - /// https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints + /// <https://html.spec.whatwg.org/multipage/#interactively-validate-the-constraints> fn interactive_validation(&self) -> Result<(), ()> { - // Step 1-3 - let _unhandled_invalid_controls = match self.static_validation() { + // Step 1-2 + let unhandled_invalid_controls = match self.static_validation() { Ok(()) => return Ok(()), - Err(err) => err + Err(err) => err, }; - // TODO: Report the problems with the constraints of at least one of - // the elements given in unhandled invalid controls to the user + + // Step 3 + let mut first = true; + + for elem in unhandled_invalid_controls { + if let Some(validatable) = elem.as_maybe_validatable() { + println!("Validation error: {}", validatable.validation_message()); + } + if first { + if let Some(html_elem) = elem.downcast::<HTMLElement>() { + html_elem.Focus(); + first = false; + } + } + } + // Step 4 Err(()) } /// Statitically validate the constraints of form elements - /// https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints - fn static_validation(&self) -> Result<(), Vec<FormSubmittableElement>> { - let node = self.upcast::<Node>(); - // FIXME(#3553): This is an incorrect way of getting controls owned by the - // form, refactor this when html5ever's form owner PR lands + /// <https://html.spec.whatwg.org/multipage/#statically-validate-the-constraints> + fn static_validation(&self) -> Result<(), Vec<DomRoot<Element>>> { + let controls = self.controls.borrow(); // Step 1-3 - let invalid_controls = node.traverse_preorder().filter_map(|field| { - if let Some(el) = field.downcast::<Element>() { - if el.disabled_state() { - None - } else { + let invalid_controls = controls + .iter() + .filter_map(|field| { + if let Some(el) = field.downcast::<Element>() { let validatable = match el.as_maybe_validatable() { Some(v) => v, - None => return None + None => return None, }; if !validatable.is_instance_validatable() { None - } else if validatable.validate(ValidationFlags::empty()) { + } else if validatable.validate(ValidationFlags::all()).is_empty() { None } else { - Some(FormSubmittableElement::from_element(&el)) + Some(DomRoot::from_ref(el)) } + } else { + None } - } else { - None - } - }).collect::<Vec<FormSubmittableElement>>(); + }) + .collect::<Vec<DomRoot<Element>>>(); // Step 4 - if invalid_controls.is_empty() { return Ok(()); } + if invalid_controls.is_empty() { + return Ok(()); + } // Step 5-6 - let unhandled_invalid_controls = invalid_controls.into_iter().filter_map(|field| { - let event = field.as_event_target() - .fire_cancelable_event(atom!("invalid")); - if !event.DefaultPrevented() { return Some(field); } - None - }).collect::<Vec<FormSubmittableElement>>(); + let unhandled_invalid_controls = invalid_controls + .into_iter() + .filter_map(|field| { + let event = field + .upcast::<EventTarget>() + .fire_cancelable_event(atom!("invalid")); + if !event.DefaultPrevented() { + return Some(field); + } + None + }) + .collect::<Vec<DomRoot<Element>>>(); // Step 7 Err(unhandled_invalid_controls) } - /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set - /// Steps range from 1 to 3 - fn get_unclean_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { + /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> + /// terminology note: "form data set" = "entry list" + /// Steps range from 3 to 5 + /// 5.x substeps are mostly handled inside element-specific methods + fn get_unclean_dataset( + &self, + submitter: Option<FormSubmitter>, + encoding: Option<&'static Encoding>, + ) -> Vec<FormDatum> { let controls = self.controls.borrow(); let mut data_set = Vec::new(); for child in controls.iter() { - // Step 3.1: The field element is disabled. + // Step 5.1: The field element is disabled. if child.disabled_state() { continue; } let child = child.upcast::<Node>(); - // Step 3.1: The field element has a datalist element ancestor. - if child.ancestors() - .any(|a| Root::downcast::<HTMLDataListElement>(a).is_some()) { + // Step 5.1: The field element has a datalist element ancestor. + if child + .ancestors() + .any(|a| DomRoot::downcast::<HTMLDataListElement>(a).is_some()) + { continue; } if let NodeTypeId::Element(ElementTypeId::HTMLElement(element)) = child.type_id() { match element { HTMLElementTypeId::HTMLInputElement => { let input = child.downcast::<HTMLInputElement>().unwrap(); - - data_set.append(&mut input.form_datums(submitter)); - } + data_set.append(&mut input.form_datums(submitter, encoding)); + }, HTMLElementTypeId::HTMLButtonElement => { let button = child.downcast::<HTMLButtonElement>().unwrap(); if let Some(datum) = button.form_datum(submitter) { data_set.push(datum); } - } + }, HTMLElementTypeId::HTMLObjectElement => { // Unimplemented () - } + }, HTMLElementTypeId::HTMLSelectElement => { let select = child.downcast::<HTMLSelectElement>().unwrap(); select.push_form_data(&mut data_set); - } + }, HTMLElementTypeId::HTMLTextAreaElement => { let textarea = child.downcast::<HTMLTextAreaElement>().unwrap(); let name = textarea.Name(); @@ -551,21 +1121,45 @@ impl HTMLFormElement { data_set.push(FormDatum { ty: textarea.Type(), name: name, - value: FormDatumValue::String(textarea.Value()) + value: FormDatumValue::String(textarea.Value()), }); } - } - _ => () + }, + _ => (), } } + + // Step: 5.13. Add an entry if element has dirname attribute + // An element can only have a dirname attribute if it is a textarea element + // or an input element whose type attribute is in either the Text state or the Search state + let child_element = child.downcast::<Element>().unwrap(); + let input_matches = + child_element + .downcast::<HTMLInputElement>() + .map_or(false, |input| { + input.input_type() == InputType::Text || + input.input_type() == InputType::Search + }); + let textarea_matches = child_element.is::<HTMLTextAreaElement>(); + let dirname = child_element.get_string_attribute(&local_name!("dirname")); + if (input_matches || textarea_matches) && !dirname.is_empty() { + let dir = DOMString::from(child_element.directionality()); + data_set.push(FormDatum { + ty: DOMString::from("string"), + name: dirname, + value: FormDatumValue::String(dir), + }); + } } data_set - // TODO: Handle `dirnames` (needs directionality support) - // https://html.spec.whatwg.org/multipage/#the-directionality } - /// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set - pub fn get_form_dataset(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> { + /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set> + pub fn get_form_dataset( + &self, + submitter: Option<FormSubmitter>, + encoding: Option<&'static Encoding>, + ) -> Option<Vec<FormDatum>> { fn clean_crlf(s: &str) -> DOMString { // Step 4 let mut buf = "".to_owned(); @@ -586,7 +1180,7 @@ impl HTMLFormElement { buf.push('\n'); buf.push(ch); }, - _ => buf.push(ch) + _ => buf.push(ch), }; prev = ch; } @@ -597,9 +1191,16 @@ impl HTMLFormElement { DOMString::from(buf) } - // Step 1-3 - let mut ret = self.get_unclean_dataset(submitter); - // Step 4 + // Step 1 + if self.constructing_entry_list.get() { + return None; + } + + // Step 2 + self.constructing_entry_list.set(true); + + // Step 3-6 + let mut ret = self.get_unclean_dataset(submitter, encoding); for datum in &mut ret { match &*datum.ty { "file" | "textarea" => (), // TODO @@ -607,13 +1208,33 @@ impl HTMLFormElement { datum.name = clean_crlf(&datum.name); datum.value = FormDatumValue::String(clean_crlf(match datum.value { FormDatumValue::String(ref s) => s, - FormDatumValue::File(_) => unreachable!() + FormDatumValue::File(_) => unreachable!(), })); - } + }, } - }; - // Step 5 - ret + } + + let window = window_from_node(self); + + // Step 6 + let form_data = FormData::new(Some(ret), &window.global()); + + // Step 7 + let event = FormDataEvent::new( + &window.global(), + atom!("formdata"), + EventBubbles::Bubbles, + EventCancelable::NotCancelable, + &form_data, + ); + + event.upcast::<Event>().fire(self.upcast::<EventTarget>()); + + // Step 8 + self.constructing_entry_list.set(false); + + // Step 9 + Some(form_data.datums()) } pub fn reset(&self, _reset_method_flag: ResetFrom) { @@ -624,7 +1245,8 @@ impl HTMLFormElement { self.marked_for_reset.set(true); } - let event = self.upcast::<EventTarget>() + let event = self + .upcast::<EventTarget>() .fire_bubbling_cancelable_event(atom!("reset")); if event.DefaultPrevented() { return; @@ -635,24 +1257,32 @@ impl HTMLFormElement { let child = child.upcast::<Node>(); match child.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) => { child.downcast::<HTMLInputElement>().unwrap().reset(); - } + }, // TODO HTMLKeygenElement unimplemented //NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLKeygenElement)) => { // // Unimplemented // {} //} - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) => { child.downcast::<HTMLSelectElement>().unwrap().reset(); - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => { child.downcast::<HTMLTextAreaElement>().unwrap().reset(); - } - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { - // Unimplemented - } - _ => {} + }, + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLOutputElement, + )) => { + child.downcast::<HTMLOutputElement>().unwrap().reset(); + }, + _ => {}, } } self.marked_for_reset.set(false); @@ -660,7 +1290,7 @@ impl HTMLFormElement { fn add_control<T: ?Sized + FormControl>(&self, control: &T) { let root = self.upcast::<Element>().root_element(); - let root = root.r().upcast::<Node>(); + let root = root.upcast::<Node>(); let mut controls = self.controls.borrow_mut(); controls.insert_pre_order(control.to_element(), root); @@ -669,28 +1299,37 @@ impl HTMLFormElement { fn remove_control<T: ?Sized + FormControl>(&self, control: &T) { let control = control.to_element(); let mut controls = self.controls.borrow_mut(); - controls.iter().position(|c| c.r() == control) - .map(|idx| controls.remove(idx)); + controls + .iter() + .position(|c| &**c == control) + .map(|idx| controls.remove(idx)); + + // https://html.spec.whatwg.org/multipage#forms.html#the-form-element:past-names-map-5 + // "If an element listed in a form element's past names map + // changes form owner, then its entries must be removed + // from that map." + let mut past_names_map = self.past_names_map.borrow_mut(); + past_names_map.retain(|_k, v| v.0 != control); } } -#[derive(JSTraceable, HeapSizeOf, Clone)] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub enum FormDatumValue { #[allow(dead_code)] - File(Root<File>), - String(DOMString) + File(DomRoot<File>), + String(DOMString), } -#[derive(HeapSizeOf, JSTraceable, Clone)] +#[derive(Clone, JSTraceable, MallocSizeOf)] pub struct FormDatum { pub ty: DOMString, pub name: DOMString, - pub value: FormDatumValue + pub value: FormDatumValue, } impl FormDatum { pub fn replace_value(&self, charset: &str) -> String { - if self.name == "_charset_" && self.ty == "hidden" { + if self.name.to_ascii_lowercase() == "_charset_" && self.ty == "hidden" { return charset.to_string(); } @@ -701,168 +1340,148 @@ impl FormDatum { } } -#[derive(Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum FormEncType { TextPlainEncoded, UrlEncoded, - FormDataEncoded + FormDataEncoded, } -#[derive(Copy, Clone, HeapSizeOf)] +#[derive(Clone, Copy, MallocSizeOf)] pub enum FormMethod { FormGet, FormPost, - FormDialog + FormDialog, } -#[derive(HeapSizeOf)] -#[allow(dead_code)] -pub enum FormSubmittableElement { - ButtonElement(Root<HTMLButtonElement>), - InputElement(Root<HTMLInputElement>), - // TODO: HTMLKeygenElement unimplemented - // KeygenElement(&'a HTMLKeygenElement), - ObjectElement(Root<HTMLObjectElement>), - SelectElement(Root<HTMLSelectElement>), - TextAreaElement(Root<HTMLTextAreaElement>), -} - -impl FormSubmittableElement { - fn as_event_target(&self) -> &EventTarget { - match *self { - FormSubmittableElement::ButtonElement(ref button) => button.upcast(), - FormSubmittableElement::InputElement(ref input) => input.upcast(), - FormSubmittableElement::ObjectElement(ref object) => object.upcast(), - FormSubmittableElement::SelectElement(ref select) => select.upcast(), - FormSubmittableElement::TextAreaElement(ref textarea) => textarea.upcast() - } - } - - fn from_element(element: &Element) -> FormSubmittableElement { - if let Some(input) = element.downcast::<HTMLInputElement>() { - FormSubmittableElement::InputElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLButtonElement>() { - FormSubmittableElement::ButtonElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLObjectElement>() { - FormSubmittableElement::ObjectElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLSelectElement>() { - FormSubmittableElement::SelectElement(Root::from_ref(&input)) - } - else if let Some(input) = element.downcast::<HTMLTextAreaElement>() { - FormSubmittableElement::TextAreaElement(Root::from_ref(&input)) - } else { - unreachable!() - } - } -} - -#[derive(Copy, Clone, HeapSizeOf)] +/// <https://html.spec.whatwg.org/multipage/#form-associated-element> +#[derive(Clone, Copy, MallocSizeOf)] pub enum FormSubmitter<'a> { FormElement(&'a HTMLFormElement), InputElement(&'a HTMLInputElement), - ButtonElement(&'a HTMLButtonElement) - // TODO: image submit, etc etc + ButtonElement(&'a HTMLButtonElement), + // TODO: implement other types of form associated elements + // (including custom elements) that can be passed as submitter. } impl<'a> FormSubmitter<'a> { fn action(&self) -> DOMString { match *self { FormSubmitter::FormElement(form) => form.Action(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formaction"), - |i| i.FormAction(), - |f| f.Action()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formaction"), - |i| i.FormAction(), - |f| f.Action()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formaction"), + |i| i.FormAction(), + |f| f.Action(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formaction"), + |i| i.FormAction(), + |f| f.Action(), + ), } } fn enctype(&self) -> FormEncType { let attr = match *self { FormSubmitter::FormElement(form) => form.Enctype(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formenctype"), - |i| i.FormEnctype(), - |f| f.Enctype()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formenctype"), - |i| i.FormEnctype(), - |f| f.Enctype()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formenctype"), + |i| i.FormEnctype(), + |f| f.Enctype(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formenctype"), + |i| i.FormEnctype(), + |f| f.Enctype(), + ), }; match &*attr { "multipart/form-data" => FormEncType::FormDataEncoded, "text/plain" => FormEncType::TextPlainEncoded, // https://html.spec.whatwg.org/multipage/#attr-fs-enctype // urlencoded is the default - _ => FormEncType::UrlEncoded + _ => FormEncType::UrlEncoded, } } fn method(&self) -> FormMethod { let attr = match *self { FormSubmitter::FormElement(form) => form.Method(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formmethod"), - |i| i.FormMethod(), - |f| f.Method()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formmethod"), - |i| i.FormMethod(), - |f| f.Method()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formmethod"), + |i| i.FormMethod(), + |f| f.Method(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formmethod"), + |i| i.FormMethod(), + |f| f.Method(), + ), }; match &*attr { "dialog" => FormMethod::FormDialog, "post" => FormMethod::FormPost, - _ => FormMethod::FormGet + _ => FormMethod::FormGet, } } fn target(&self) -> DOMString { match *self { FormSubmitter::FormElement(form) => form.Target(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_attribute(&local_name!("formtarget"), - |i| i.FormTarget(), - |f| f.Target()) - }, - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_attribute(&local_name!("formtarget"), - |i| i.FormTarget(), - |f| f.Target()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_attribute( + &local_name!("formtarget"), + |i| i.FormTarget(), + |f| f.Target(), + ), + FormSubmitter::ButtonElement(button_element) => button_element.get_form_attribute( + &local_name!("formtarget"), + |i| i.FormTarget(), + |f| f.Target(), + ), } } fn no_validate(&self, _form_owner: &HTMLFormElement) -> bool { match *self { FormSubmitter::FormElement(form) => form.NoValidate(), - FormSubmitter::InputElement(input_element) => { - input_element.get_form_boolean_attribute(&local_name!("formnovalidate"), - |i| i.FormNoValidate(), - |f| f.NoValidate()) - } - FormSubmitter::ButtonElement(button_element) => { - button_element.get_form_boolean_attribute(&local_name!("formnovalidate"), - |i| i.FormNoValidate(), - |f| f.NoValidate()) - } + FormSubmitter::InputElement(input_element) => input_element.get_form_boolean_attribute( + &local_name!("formnovalidate"), + |i| i.FormNoValidate(), + |f| f.NoValidate(), + ), + FormSubmitter::ButtonElement(button_element) => button_element + .get_form_boolean_attribute( + &local_name!("formnovalidate"), + |i| i.FormNoValidate(), + |f| f.NoValidate(), + ), + } + } + + // https://html.spec.whatwg.org/multipage/#concept-submit-button + fn is_submit_button(&self) -> bool { + match *self { + // https://html.spec.whatwg.org/multipage/#image-button-state-(type=image) + // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit) + FormSubmitter::InputElement(input_element) => input_element.is_submit_button(), + // https://html.spec.whatwg.org/multipage/#attr-button-type-submit-state + FormSubmitter::ButtonElement(button_element) => button_element.is_submit_button(), + _ => false, + } + } + + // https://html.spec.whatwg.org/multipage/#form-owner + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> { + match *self { + FormSubmitter::ButtonElement(button_el) => button_el.form_owner(), + FormSubmitter::InputElement(input_el) => input_el.form_owner(), + _ => None, } } } pub trait FormControl: DomObject { - fn form_owner(&self) -> Option<Root<HTMLFormElement>>; + fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>; fn set_form_owner(&self, form: Option<&HTMLFormElement>); @@ -879,7 +1498,7 @@ pub trait FormControl: DomObject { fn set_form_owner_from_parser(&self, form: &HTMLFormElement) { let elem = self.to_element(); let node = elem.upcast::<Node>(); - node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, true); + node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true); form.add_control(self); self.set_form_owner(Some(form)); } @@ -890,9 +1509,10 @@ pub trait FormControl: DomObject { let node = elem.upcast::<Node>(); let old_owner = self.form_owner(); let has_form_id = elem.has_attribute(&local_name!("form")); - let nearest_form_ancestor = node.ancestors() - .filter_map(Root::downcast::<HTMLFormElement>) - .next(); + let nearest_form_ancestor = node + .ancestors() + .filter_map(DomRoot::downcast::<HTMLFormElement>) + .next(); // Step 1 if old_owner.is_some() && !(self.is_listed() && has_form_id) { @@ -905,7 +1525,8 @@ pub trait FormControl: DomObject { // Step 3 let doc = document_from_node(node); let form_id = elem.get_string_attribute(&local_name!("form")); - doc.GetElementById(form_id).and_then(Root::downcast::<HTMLFormElement>) + doc.GetElementById(form_id) + .and_then(DomRoot::downcast::<HTMLFormElement>) } else { // Step 4 nearest_form_ancestor @@ -915,11 +1536,10 @@ pub trait FormControl: DomObject { if let Some(o) = old_owner { o.remove_control(self); } - let new_owner = new_owner.as_ref().map(|o| { - o.add_control(self); - o.r() - }); - self.set_form_owner(new_owner); + if let Some(ref new_owner) = new_owner { + new_owner.add_control(self); + } + self.set_form_owner(new_owner.as_deref()); } } @@ -943,7 +1563,7 @@ pub trait FormControl: DomObject { let form_id = elem.get_string_attribute(&local_name!("form")); let node = elem.upcast::<Node>(); - if self.is_listed() && !form_id.is_empty() && node.is_in_doc() { + if self.is_listed() && !form_id.is_empty() && node.is_connected() { let doc = document_from_node(node); doc.register_form_id_listener(form_id, self); } @@ -968,8 +1588,8 @@ pub trait FormControl: DomObject { // Part of step 12. // '..suppress the running of the reset the form owner algorithm // when the parser subsequently attempts to insert the element..' - let must_skip_reset = node.get_flag(PARSER_ASSOCIATED_FORM_OWNER); - node.set_flag(PARSER_ASSOCIATED_FORM_OWNER, false); + let must_skip_reset = node.get_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER); + node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, false); if !must_skip_reset { self.form_attribute_mutated(AttributeMutation::Set(None)); @@ -980,9 +1600,9 @@ pub trait FormControl: DomObject { fn unbind_form_control_from_tree(&self) { let elem = self.to_element(); let has_form_attr = elem.has_attribute(&local_name!("form")); - let same_subtree = self.form_owner().map_or(true, |form| { - elem.is_in_same_home_subtree(&*form) - }); + let same_subtree = self + .form_owner() + .map_or(true, |form| elem.is_in_same_home_subtree(&*form)); self.unregister_if_necessary(); @@ -996,13 +1616,16 @@ pub trait FormControl: DomObject { } } - fn get_form_attribute<InputFn, OwnerFn>(&self, - attr: &LocalName, - input: InputFn, - owner: OwnerFn) - -> DOMString - where InputFn: Fn(&Self) -> DOMString, - OwnerFn: Fn(&HTMLFormElement) -> DOMString, Self: Sized + fn get_form_attribute<InputFn, OwnerFn>( + &self, + attr: &LocalName, + input: InputFn, + owner: OwnerFn, + ) -> DOMString + where + InputFn: Fn(&Self) -> DOMString, + OwnerFn: Fn(&HTMLFormElement) -> DOMString, + Self: Sized, { if self.to_element().has_attribute(attr) { input(self) @@ -1011,13 +1634,16 @@ pub trait FormControl: DomObject { } } - fn get_form_boolean_attribute<InputFn, OwnerFn>(&self, - attr: &LocalName, - input: InputFn, - owner: OwnerFn) - -> bool - where InputFn: Fn(&Self) -> bool, - OwnerFn: Fn(&HTMLFormElement) -> bool, Self: Sized + fn get_form_boolean_attribute<InputFn, OwnerFn>( + &self, + attr: &LocalName, + input: InputFn, + owner: OwnerFn, + ) -> bool + where + InputFn: Fn(&Self) -> bool, + OwnerFn: Fn(&HTMLFormElement) -> bool, + Self: Sized, { if self.to_element().has_attribute(attr) { input(self) @@ -1032,15 +1658,8 @@ pub trait FormControl: DomObject { } impl VirtualMethods for HTMLFormElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) - } - - fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { - match name { - &local_name!("name") => AttrValue::from_atomic(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), - } + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -1049,97 +1668,93 @@ impl VirtualMethods for HTMLFormElement { // Collect the controls to reset because reset_form_owner // will mutably borrow self.controls rooted_vec!(let mut to_reset); - to_reset.extend(self.controls.borrow().iter() - .filter(|c| !c.is_in_same_home_subtree(self)) - .map(|c| c.clone())); + to_reset.extend( + self.controls + .borrow() + .iter() + .filter(|c| !c.is_in_same_home_subtree(self)) + .map(|c| c.clone()), + ); for control in to_reset.iter() { - control.as_maybe_form_control() - .expect("Element must be a form control") - .reset_form_owner(); + control + .as_maybe_form_control() + .expect("Element must be a form control") + .reset_form_owner(); + } + } + + fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue { + match name { + &local_name!("rel") => AttrValue::from_serialized_tokenlist(value.into()), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } } pub trait FormControlElementHelpers { - fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl>; + fn as_maybe_form_control<'a>(&'a self) -> Option<&'a dyn FormControl>; } impl FormControlElementHelpers for Element { - fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl> { + fn as_maybe_form_control<'a>(&'a self) -> Option<&'a dyn FormControl> { let node = self.upcast::<Node>(); match node.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) => { - Some(self.downcast::<HTMLButtonElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLFieldSetElement)) => { - Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLImageElement)) => { - Some(self.downcast::<HTMLImageElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - Some(self.downcast::<HTMLInputElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLabelElement)) => { - Some(self.downcast::<HTMLLabelElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLegendElement)) => { - Some(self.downcast::<HTMLLegendElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement)) => { - Some(self.downcast::<HTMLObjectElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { - Some(self.downcast::<HTMLOutputElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { - Some(self.downcast::<HTMLSelectElement>().unwrap() as &FormControl) - }, - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &FormControl) - }, - _ => { - None - } - } - } -} - -struct PlannedNavigation { - load_data: LoadData, - pipeline_id: PipelineId, - script_chan: Sender<MainThreadScriptMsg>, - generation_id: GenerationId, - form: Trusted<HTMLFormElement> -} - -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(); - script_chan.send(MainThreadScriptMsg::Navigate(self.pipeline_id, self.load_data, false)).unwrap(); + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLButtonElement, + )) => Some(self.downcast::<HTMLButtonElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLFieldSetElement, + )) => Some(self.downcast::<HTMLFieldSetElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLImageElement, + )) => Some(self.downcast::<HTMLImageElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLInputElement, + )) => Some(self.downcast::<HTMLInputElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLLabelElement, + )) => Some(self.downcast::<HTMLLabelElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLLegendElement, + )) => Some(self.downcast::<HTMLLegendElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLObjectElement, + )) => Some(self.downcast::<HTMLObjectElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLOutputElement, + )) => Some(self.downcast::<HTMLOutputElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLSelectElement, + )) => Some(self.downcast::<HTMLSelectElement>().unwrap() as &dyn FormControl), + NodeTypeId::Element(ElementTypeId::HTMLElement( + HTMLElementTypeId::HTMLTextAreaElement, + )) => Some(self.downcast::<HTMLTextAreaElement>().unwrap() as &dyn FormControl), + _ => None, } } } - // 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> { +pub fn encode_multipart_form_data( + form_data: &mut Vec<FormDatum>, + boundary: String, + encoding: &'static Encoding, +) -> Vec<u8> { // Step 1 let mut result = vec![]; // Step 2 - let charset = &*encoding.whatwg_name().unwrap_or("UTF-8"); + let charset = encoding.name(); // Step 3 for entry in form_data.iter_mut() { // 3.1 - if entry.name == "_charset_" && entry.ty == "hidden" { + if entry.name.to_ascii_lowercase() == "_charset_" && entry.ty == "hidden" { entry.value = FormDatumValue::String(DOMString::from(charset.clone())); } // TODO: 3.2 @@ -1150,38 +1765,53 @@ pub fn encode_multipart_form_data(form_data: &mut Vec<FormDatum>, // 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()))] - }; + // TODO(eijebong): Everthing related to content-disposition it to redo once typed headers + // are capable of it. match entry.value { FormDatumValue::String(ref s) => { - let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}", - content_disposition, s).into_bytes(); + let content_disposition = format!("form-data; name=\"{}\"", entry.name); + 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())); + let extra = if charset.to_lowercase() == "utf-8" { + format!( + "filename=\"{}\"", + String::from_utf8(f.name().as_bytes().into()).unwrap() + ) + } else { + format!( + "filename*=\"{}\"''{}", + charset, + http_percent_encode(f.name().as_bytes()) + ) + }; + + let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra); // 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(); + let content_type: Mime = 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(); + let mut boundary_bytes = format!("\r\n--{}--\r\n", boundary).into_bytes(); result.append(&mut boundary_bytes); result |