aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/htmlformelement.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/htmlformelement.rs')
-rw-r--r--[-rwxr-xr-x]components/script/dom/htmlformelement.rs1656
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