mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
1180 lines
46 KiB
Rust
Executable file
1180 lines
46 KiB
Rust
Executable file
/* 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::refcounted::Trusted;
|
|
use dom::bindings::reflector::DomObject;
|
|
use dom::bindings::root::{Dom, DomOnceCell, DomRoot, RootedReference};
|
|
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, NodeFlags, UnbindContext, VecPreOrderInsertionHelper};
|
|
use dom::node::{document_from_node, window_from_node};
|
|
use dom::validitystate::ValidationFlags;
|
|
use dom::virtualmethods::VirtualMethods;
|
|
use dom_struct::dom_struct;
|
|
use encoding_rs::{Encoding, UTF_8};
|
|
use html5ever::{LocalName, Prefix};
|
|
use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType};
|
|
use hyper::method::Method;
|
|
use script_thread::MainThreadScriptMsg;
|
|
use script_traits::LoadData;
|
|
use servo_rand::random;
|
|
use std::borrow::ToOwned;
|
|
use std::cell::Cell;
|
|
use style::attr::AttrValue;
|
|
use style::str::split_html_space_chars;
|
|
use task_source::TaskSource;
|
|
use url::UrlQuery;
|
|
use url::form_urlencoded::Serializer;
|
|
|
|
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
|
|
pub struct GenerationId(u32);
|
|
|
|
#[dom_struct]
|
|
pub struct HTMLFormElement {
|
|
htmlelement: HTMLElement,
|
|
marked_for_reset: Cell<bool>,
|
|
elements: DomOnceCell<HTMLFormControlsCollection>,
|
|
generation_id: Cell<GenerationId>,
|
|
controls: DomRefCell<Vec<Dom<Element>>>,
|
|
}
|
|
|
|
impl 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),
|
|
elements: Default::default(),
|
|
generation_id: Cell::new(GenerationId(0)),
|
|
controls: DomRefCell::new(Vec::new()),
|
|
}
|
|
}
|
|
|
|
#[allow(unrooted_must_root)]
|
|
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,
|
|
HTMLFormElementBinding::Wrap)
|
|
}
|
|
}
|
|
|
|
impl HTMLFormElementMethods for HTMLFormElement {
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-acceptcharset
|
|
make_getter!(AcceptCharset, "accept-charset");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-acceptcharset
|
|
make_setter!(SetAcceptCharset, "accept-charset");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-action
|
|
make_form_action_getter!(Action, "action");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-action
|
|
make_setter!(SetAction, "action");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-autocomplete
|
|
make_enumerated_getter!(Autocomplete, "autocomplete", "on", "off");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-autocomplete
|
|
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");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-enctype
|
|
make_setter!(SetEnctype, "enctype");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-encoding
|
|
fn Encoding(&self) -> DOMString {
|
|
self.Enctype()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-encoding
|
|
fn SetEncoding(&self, value: DOMString) {
|
|
self.SetEnctype(value)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-method
|
|
make_enumerated_getter!(Method, "method", "get", "post" | "dialog");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-method
|
|
make_setter!(SetMethod, "method");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-name
|
|
make_getter!(Name, "name");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-name
|
|
make_atomic_setter!(SetName, "name");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-novalidate
|
|
make_bool_getter!(NoValidate, "novalidate");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-novalidate
|
|
make_bool_setter!(SetNoValidate, "novalidate");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-target
|
|
make_getter!(Target, "target");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fs-target
|
|
make_setter!(SetTarget, "target");
|
|
|
|
// 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-reset
|
|
fn Reset(&self) {
|
|
self.reset(ResetFrom::FromForm);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-elements
|
|
fn Elements(&self) -> DomRoot<HTMLFormControlsCollection> {
|
|
#[derive(JSTraceable, MallocSizeOf)]
|
|
struct ElementsFilter {
|
|
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"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
_ => return false,
|
|
};
|
|
|
|
match form_owner {
|
|
Some(form_owner) => form_owner == self.form,
|
|
None => false,
|
|
}
|
|
}
|
|
}
|
|
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.upcast(), filter)
|
|
}))
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-length
|
|
fn Length(&self) -> u32 {
|
|
self.Elements().Length() as u32
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-form-item
|
|
fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Element>> {
|
|
let elements = self.Elements();
|
|
elements.IndexedGetter(index)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
|
|
pub enum SubmittedFrom {
|
|
FromForm,
|
|
NotFromForm
|
|
}
|
|
|
|
#[derive(Clone, Copy, MallocSizeOf)]
|
|
pub enum ResetFrom {
|
|
FromForm,
|
|
NotFromForm
|
|
}
|
|
|
|
|
|
impl HTMLFormElement {
|
|
// https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form
|
|
fn pick_encoding(&self) -> &'static Encoding {
|
|
// Step 2
|
|
if self.upcast::<Element>().has_attribute(&local_name!("accept-charset")) {
|
|
// Substep 1
|
|
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(|c| Encoding::for_label(c.as_bytes()));
|
|
|
|
// Substep 5, 6
|
|
return candidate_encodings.next().unwrap_or(UTF_8);
|
|
}
|
|
|
|
// Step 1, 3
|
|
document_from_node(self).encoding()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#text/plain-encoding-algorithm
|
|
fn encode_plaintext(&self, form_data: &mut Vec<FormDatum>) -> String {
|
|
// Step 1
|
|
let mut result = String::new();
|
|
|
|
// Step 2
|
|
let encoding = self.pick_encoding();
|
|
|
|
// Step 3
|
|
let charset = encoding.name();
|
|
|
|
for entry in form_data.iter_mut() {
|
|
// Step 4, 5
|
|
let value = entry.replace_value(charset);
|
|
|
|
// Step 6
|
|
result.push_str(&*format!("{}={}\r\n", entry.name, value));
|
|
}
|
|
|
|
// Step 7
|
|
result
|
|
}
|
|
|
|
/// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
|
|
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
|
|
// Step 1
|
|
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"));
|
|
return;
|
|
}
|
|
}
|
|
// Step 5
|
|
if submit_method_flag == SubmittedFrom::NotFromForm {
|
|
let event = self.upcast::<EventTarget>()
|
|
.fire_bubbling_cancelable_event(atom!("submit"));
|
|
if event.DefaultPrevented() {
|
|
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();
|
|
|
|
// Step 9
|
|
if action.is_empty() {
|
|
action = DOMString::from(base.as_str());
|
|
}
|
|
// Step 10-11
|
|
let action_components = match base.join(&action) {
|
|
Ok(url) => url,
|
|
Err(_) => return
|
|
};
|
|
// Step 12-15
|
|
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, None, doc.get_referrer_policy(), Some(doc.url()));
|
|
|
|
// Step 18
|
|
match (&*scheme, method) {
|
|
(_, FormMethod::FormDialog) => {
|
|
// TODO: Submit dialog
|
|
// https://html.spec.whatwg.org/multipage/#submit-dialog
|
|
}
|
|
// https://html.spec.whatwg.org/multipage/#submit-mutate-action
|
|
("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) | ("data", FormMethod::FormGet) => {
|
|
load_data.headers.set(ContentType::form_url_encoded());
|
|
self.mutate_action_url(&mut form_data, load_data, encoding);
|
|
}
|
|
// https://html.spec.whatwg.org/multipage/#submit-body
|
|
("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => {
|
|
load_data.method = Method::Post;
|
|
self.submit_entity_body(&mut form_data, load_data, enctype, encoding);
|
|
}
|
|
// https://html.spec.whatwg.org/multipage/#submit-get-action
|
|
("file", _) | ("about", _) | ("data", FormMethod::FormPost) |
|
|
("ftp", _) | ("javascript", _) => {
|
|
self.plan_to_navigate(load_data);
|
|
}
|
|
("mailto", FormMethod::FormPost) => {
|
|
// TODO: Mail as body
|
|
// https://html.spec.whatwg.org/multipage/#submit-mailto-body
|
|
}
|
|
("mailto", FormMethod::FormGet) => {
|
|
// TODO: Mail with headers
|
|
// https://html.spec.whatwg.org/multipage/#submit-mailto-headers
|
|
}
|
|
_ => return,
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#submit-mutate-action
|
|
fn mutate_action_url(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, encoding: &'static Encoding) {
|
|
let charset = encoding.name();
|
|
|
|
self.set_encoding_override(load_data.url.as_mut_url().query_pairs_mut())
|
|
.clear()
|
|
.extend_pairs(form_data.into_iter()
|
|
.map(|field| (field.name.clone(), field.replace_value(charset))));
|
|
|
|
self.plan_to_navigate(load_data);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#submit-body
|
|
fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData,
|
|
enctype: FormEncType, encoding: &'static Encoding) {
|
|
let boundary = generate_boundary();
|
|
let bytes = match enctype {
|
|
FormEncType::UrlEncoded => {
|
|
let charset = encoding.name();
|
|
load_data.headers.set(ContentType::form_url_encoded());
|
|
|
|
self.set_encoding_override(load_data.url.as_mut_url().query_pairs_mut())
|
|
.clear()
|
|
.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()
|
|
}
|
|
FormEncType::FormDataEncoded => {
|
|
let mime = mime!(Multipart / FormData; Boundary =(&boundary));
|
|
load_data.headers.set(ContentType(mime));
|
|
encode_multipart_form_data(form_data, boundary, encoding)
|
|
}
|
|
FormEncType::TextPlainEncoded => {
|
|
load_data.headers.set(ContentType(mime!(Text / Plain)));
|
|
self.encode_plaintext(form_data).into_bytes()
|
|
}
|
|
};
|
|
|
|
load_data.data = Some(bytes);
|
|
self.plan_to_navigate(load_data);
|
|
}
|
|
|
|
fn set_encoding_override<'a>(&self, mut serializer: Serializer<UrlQuery<'a>>)
|
|
-> Serializer<UrlQuery<'a>> {
|
|
let encoding = self.pick_encoding();
|
|
serializer.custom_encoding_override(move |s| encoding.encode(s).0);
|
|
serializer
|
|
}
|
|
|
|
/// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
|
|
fn plan_to_navigate(&self, load_data: LoadData) {
|
|
let window = window_from_node(self);
|
|
|
|
// Step 1
|
|
// 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 generation_id = GenerationId(self.generation_id.get().0 + 1);
|
|
self.generation_id.set(generation_id);
|
|
|
|
// Step 2.
|
|
let pipeline_id = window.upcast::<GlobalScope>().pipeline_id();
|
|
let script_chan = window.main_thread_script_chan().clone();
|
|
let this = Trusted::new(self);
|
|
let task = task!(navigate_to_form_planned_navigation: move || {
|
|
if generation_id != this.root().generation_id.get() {
|
|
return;
|
|
}
|
|
script_chan.send(MainThreadScriptMsg::Navigate(
|
|
pipeline_id,
|
|
load_data,
|
|
false,
|
|
)).unwrap();
|
|
});
|
|
|
|
// Step 3.
|
|
window.dom_manipulation_task_source().queue(task, window.upcast()).unwrap();
|
|
}
|
|
|
|
/// Interactively validate the constraints of form elements
|
|
/// <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() {
|
|
Ok(()) => return Ok(()),
|
|
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 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
|
|
// 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 validatable = match el.as_maybe_validatable() {
|
|
Some(v) => v,
|
|
None => return None
|
|
};
|
|
if !validatable.is_instance_validatable() {
|
|
None
|
|
} else if validatable.validate(ValidationFlags::empty()) {
|
|
None
|
|
} else {
|
|
Some(FormSubmittableElement::from_element(&el))
|
|
}
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}).collect::<Vec<FormSubmittableElement>>();
|
|
// Step 4
|
|
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>>();
|
|
// 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> {
|
|
let controls = self.controls.borrow();
|
|
let mut data_set = Vec::new();
|
|
for child in controls.iter() {
|
|
// Step 3.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| 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));
|
|
}
|
|
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();
|
|
if !name.is_empty() {
|
|
data_set.push(FormDatum {
|
|
ty: textarea.Type(),
|
|
name: name,
|
|
value: FormDatumValue::String(textarea.Value())
|
|
});
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
}
|
|
}
|
|
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> {
|
|
fn clean_crlf(s: &str) -> DOMString {
|
|
// Step 4
|
|
let mut buf = "".to_owned();
|
|
let mut prev = ' ';
|
|
for ch in s.chars() {
|
|
match ch {
|
|
'\n' if prev != '\r' => {
|
|
buf.push('\r');
|
|
buf.push('\n');
|
|
},
|
|
'\n' => {
|
|
buf.push('\n');
|
|
},
|
|
// This character isn't LF but is
|
|
// preceded by CR
|
|
_ if prev == '\r' => {
|
|
buf.push('\r');
|
|
buf.push('\n');
|
|
buf.push(ch);
|
|
},
|
|
_ => buf.push(ch)
|
|
};
|
|
prev = ch;
|
|
}
|
|
// In case the last character was CR
|
|
if prev == '\r' {
|
|
buf.push('\n');
|
|
}
|
|
DOMString::from(buf)
|
|
}
|
|
|
|
// Step 1-3
|
|
let mut ret = self.get_unclean_dataset(submitter);
|
|
// Step 4
|
|
for datum in &mut ret {
|
|
match &*datum.ty {
|
|
"file" | "textarea" => (), // TODO
|
|
_ => {
|
|
datum.name = clean_crlf(&datum.name);
|
|
datum.value = FormDatumValue::String(clean_crlf(match datum.value {
|
|
FormDatumValue::String(ref s) => s,
|
|
FormDatumValue::File(_) => unreachable!()
|
|
}));
|
|
}
|
|
}
|
|
};
|
|
// Step 5
|
|
ret
|
|
}
|
|
|
|
pub fn reset(&self, _reset_method_flag: ResetFrom) {
|
|
// https://html.spec.whatwg.org/multipage/#locked-for-reset
|
|
if self.marked_for_reset.get() {
|
|
return;
|
|
} else {
|
|
self.marked_for_reset.set(true);
|
|
}
|
|
|
|
let event = self.upcast::<EventTarget>()
|
|
.fire_bubbling_cancelable_event(atom!("reset"));
|
|
if event.DefaultPrevented() {
|
|
return;
|
|
}
|
|
|
|
let controls = self.controls.borrow();
|
|
for child in controls.iter() {
|
|
let child = child.upcast::<Node>();
|
|
|
|
match child.type_id() {
|
|
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)) => {
|
|
child.downcast::<HTMLSelectElement>().unwrap().reset();
|
|
}
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => {
|
|
child.downcast::<HTMLTextAreaElement>().unwrap().reset();
|
|
}
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => {
|
|
// Unimplemented
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
self.marked_for_reset.set(false);
|
|
}
|
|
|
|
fn add_control<T: ?Sized + FormControl>(&self, control: &T) {
|
|
let root = self.upcast::<Element>().root_element();
|
|
let root = root.r().upcast::<Node>();
|
|
|
|
let mut controls = self.controls.borrow_mut();
|
|
controls.insert_pre_order(control.to_element(), root);
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
|
pub enum FormDatumValue {
|
|
#[allow(dead_code)]
|
|
File(DomRoot<File>),
|
|
String(DOMString)
|
|
}
|
|
|
|
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
|
pub struct FormDatum {
|
|
pub ty: DOMString,
|
|
pub name: DOMString,
|
|
pub value: FormDatumValue
|
|
}
|
|
|
|
impl FormDatum {
|
|
pub fn replace_value(&self, charset: &str) -> String {
|
|
if self.name == "_charset_" && self.ty == "hidden" {
|
|
return charset.to_string();
|
|
}
|
|
|
|
match self.value {
|
|
FormDatumValue::File(ref f) => String::from(f.name().clone()),
|
|
FormDatumValue::String(ref s) => String::from(s.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, MallocSizeOf)]
|
|
pub enum FormEncType {
|
|
TextPlainEncoded,
|
|
UrlEncoded,
|
|
FormDataEncoded
|
|
}
|
|
|
|
#[derive(Clone, Copy, MallocSizeOf)]
|
|
pub enum FormMethod {
|
|
FormGet,
|
|
FormPost,
|
|
FormDialog
|
|
}
|
|
|
|
#[derive(MallocSizeOf)]
|
|
#[allow(dead_code)]
|
|
pub enum FormSubmittableElement {
|
|
ButtonElement(DomRoot<HTMLButtonElement>),
|
|
InputElement(DomRoot<HTMLInputElement>),
|
|
// TODO: HTMLKeygenElement unimplemented
|
|
// KeygenElement(&'a HTMLKeygenElement),
|
|
ObjectElement(DomRoot<HTMLObjectElement>),
|
|
SelectElement(DomRoot<HTMLSelectElement>),
|
|
TextAreaElement(DomRoot<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(DomRoot::from_ref(&input))
|
|
}
|
|
else if let Some(input) = element.downcast::<HTMLButtonElement>() {
|
|
FormSubmittableElement::ButtonElement(DomRoot::from_ref(&input))
|
|
}
|
|
else if let Some(input) = element.downcast::<HTMLObjectElement>() {
|
|
FormSubmittableElement::ObjectElement(DomRoot::from_ref(&input))
|
|
}
|
|
else if let Some(input) = element.downcast::<HTMLSelectElement>() {
|
|
FormSubmittableElement::SelectElement(DomRoot::from_ref(&input))
|
|
}
|
|
else if let Some(input) = element.downcast::<HTMLTextAreaElement>() {
|
|
FormSubmittableElement::TextAreaElement(DomRoot::from_ref(&input))
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, MallocSizeOf)]
|
|
pub enum FormSubmitter<'a> {
|
|
FormElement(&'a HTMLFormElement),
|
|
InputElement(&'a HTMLInputElement),
|
|
ButtonElement(&'a HTMLButtonElement)
|
|
// TODO: image submit, etc etc
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
};
|
|
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
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
};
|
|
match &*attr {
|
|
"dialog" => FormMethod::FormDialog,
|
|
"post" => FormMethod::FormPost,
|
|
_ => 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())
|
|
}
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait FormControl: DomObject {
|
|
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>>;
|
|
|
|
fn set_form_owner(&self, form: Option<&HTMLFormElement>);
|
|
|
|
fn to_element<'a>(&'a self) -> &'a Element;
|
|
|
|
fn is_listed(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
|
|
// Part of step 12.
|
|
// '..suppress the running of the reset the form owner algorithm
|
|
// when the parser subsequently attempts to insert the element..'
|
|
fn set_form_owner_from_parser(&self, form: &HTMLFormElement) {
|
|
let elem = self.to_element();
|
|
let node = elem.upcast::<Node>();
|
|
node.set_flag(NodeFlags::PARSER_ASSOCIATED_FORM_OWNER, true);
|
|
form.add_control(self);
|
|
self.set_form_owner(Some(form));
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#reset-the-form-owner
|
|
fn reset_form_owner(&self) {
|
|
let elem = self.to_element();
|
|
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(DomRoot::downcast::<HTMLFormElement>)
|
|
.next();
|
|
|
|
// Step 1
|
|
if old_owner.is_some() && !(self.is_listed() && has_form_id) {
|
|
if nearest_form_ancestor == old_owner {
|
|
return;
|
|
}
|
|
}
|
|
|
|
let new_owner = if self.is_listed() && has_form_id && elem.is_connected() {
|
|
// Step 3
|
|
let doc = document_from_node(node);
|
|
let form_id = elem.get_string_attribute(&local_name!("form"));
|
|
doc.GetElementById(form_id).and_then(DomRoot::downcast::<HTMLFormElement>)
|
|
} else {
|
|
// Step 4
|
|
nearest_form_ancestor
|
|
};
|
|
|
|
if old_owner != new_owner {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
|
fn form_attribute_mutated(&self, mutation: AttributeMutation) {
|
|
match mutation {
|
|
AttributeMutation::Set(_) => {
|
|
self.register_if_necessary();
|
|
},
|
|
AttributeMutation::Removed => {
|
|
self.unregister_if_necessary();
|
|
},
|
|
}
|
|
|
|
self.reset_form_owner();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
|
fn register_if_necessary(&self) {
|
|
let elem = self.to_element();
|
|
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() {
|
|
let doc = document_from_node(node);
|
|
doc.register_form_id_listener(form_id, self);
|
|
}
|
|
}
|
|
|
|
fn unregister_if_necessary(&self) {
|
|
let elem = self.to_element();
|
|
let form_id = elem.get_string_attribute(&local_name!("form"));
|
|
|
|
if self.is_listed() && !form_id.is_empty() {
|
|
let doc = document_from_node(elem.upcast::<Node>());
|
|
doc.unregister_form_id_listener(form_id, self);
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
|
fn bind_form_control_to_tree(&self) {
|
|
let elem = self.to_element();
|
|
let node = elem.upcast::<Node>();
|
|
|
|
// https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token
|
|
// 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(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));
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#association-of-controls-and-forms
|
|
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)
|
|
});
|
|
|
|
self.unregister_if_necessary();
|
|
|
|
// Since this control has been unregistered from the id->listener map
|
|
// in the previous step, reset_form_owner will not be invoked on it
|
|
// when the form owner element is unbound (i.e it is in the same
|
|
// subtree) if it appears later in the tree order. Hence invoke
|
|
// reset from here if this control has the form attribute set.
|
|
if !same_subtree || (self.is_listed() && has_form_attr) {
|
|
self.reset_form_owner();
|
|
}
|
|
}
|
|
|
|
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)
|
|
} else {
|
|
self.form_owner().map_or(DOMString::new(), |t| owner(&t))
|
|
}
|
|
}
|
|
|
|
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)
|
|
} else {
|
|
self.form_owner().map_or(false, |t| owner(&t))
|
|
}
|
|
}
|
|
|
|
// XXXKiChjang: Implement these on inheritors
|
|
// fn candidate_for_validation(&self) -> bool;
|
|
// fn satisfies_constraints(&self) -> bool;
|
|
}
|
|
|
|
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 unbind_from_tree(&self, context: &UnbindContext) {
|
|
self.super_type().unwrap().unbind_from_tree(context);
|
|
|
|
// 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()));
|
|
|
|
for control in to_reset.iter() {
|
|
control.as_maybe_form_control()
|
|
.expect("Element must be a form control")
|
|
.reset_form_owner();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait FormControlElementHelpers {
|
|
fn as_maybe_form_control<'a>(&'a self) -> Option<&'a FormControl>;
|
|
}
|
|
|
|
impl FormControlElementHelpers for Element {
|
|
fn as_maybe_form_control<'a>(&'a self) -> Option<&'a 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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: &'static Encoding) -> Vec<u8> {
|
|
// Step 1
|
|
let mut result = vec![];
|
|
|
|
// Step 2
|
|
let charset = encoding.name();
|
|
|
|
// Step 3
|
|
for entry in form_data.iter_mut() {
|
|
// 3.1
|
|
if entry.name == "_charset_" && entry.ty == "hidden" {
|
|
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
|
}
|
|
// TODO: 3.2
|
|
|
|
// Step 4
|
|
// https://tools.ietf.org/html/rfc7578#section-4
|
|
// NOTE(izgzhen): The encoding here expected by most servers seems different from
|
|
// what spec says (that it should start with a '\r\n').
|
|
let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
|
|
result.append(&mut boundary_bytes);
|
|
let mut content_disposition = ContentDisposition {
|
|
disposition: DispositionType::Ext("form-data".to_owned()),
|
|
parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))]
|
|
};
|
|
|
|
match entry.value {
|
|
FormDatumValue::String(ref s) => {
|
|
let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}",
|
|
content_disposition, s).into_bytes();
|
|
result.append(&mut bytes);
|
|
}
|
|
FormDatumValue::File(ref f) => {
|
|
content_disposition.parameters.push(
|
|
DispositionParam::Filename(Charset::Ext(String::from(charset.clone())),
|
|
None,
|
|
f.name().clone().into()));
|
|
// https://tools.ietf.org/html/rfc7578#section-4.4
|
|
let content_type = ContentType(f.upcast::<Blob>().Type()
|
|
.parse().unwrap_or(mime!(Text / Plain)));
|
|
let mut type_bytes = format!("Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n",
|
|
content_disposition,
|
|
content_type).into_bytes();
|
|
result.append(&mut type_bytes);
|
|
|
|
let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
|
|
|
|
result.append(&mut bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut boundary_bytes = format!("\r\n--{}--", boundary).into_bytes();
|
|
result.append(&mut boundary_bytes);
|
|
|
|
result
|
|
}
|
|
|
|
// https://tools.ietf.org/html/rfc7578#section-4.1
|
|
pub fn generate_boundary() -> String {
|
|
let i1 = random::<u32>();
|
|
let i2 = random::<u32>();
|
|
|
|
format!("---------------------------{0}{1}", i1, i2)
|
|
}
|