mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Add form submission for file type input and related fixings
This commit is contained in:
parent
2553bb7af4
commit
ef091179f5
15 changed files with 243 additions and 113 deletions
|
@ -9,6 +9,7 @@ use dom::bindings::js::{JS, Root};
|
||||||
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||||
use dom::file::File;
|
use dom::file::File;
|
||||||
use dom::window::Window;
|
use dom::window::Window;
|
||||||
|
use std::slice::Iter;
|
||||||
|
|
||||||
// https://w3c.github.io/FileAPI/#dfn-filelist
|
// https://w3c.github.io/FileAPI/#dfn-filelist
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
|
@ -32,6 +33,10 @@ impl FileList {
|
||||||
GlobalRef::Window(window),
|
GlobalRef::Window(window),
|
||||||
FileListBinding::Wrap)
|
FileListBinding::Wrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter_files(&self) -> Iter<JS<File>> {
|
||||||
|
self.list.iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileListMethods for FileList {
|
impl FileListMethods for FileList {
|
||||||
|
|
|
@ -37,11 +37,9 @@ use dom::htmlselectelement::HTMLSelectElement;
|
||||||
use dom::htmltextareaelement::HTMLTextAreaElement;
|
use dom::htmltextareaelement::HTMLTextAreaElement;
|
||||||
use dom::node::{Node, document_from_node, window_from_node};
|
use dom::node::{Node, document_from_node, window_from_node};
|
||||||
use dom::virtualmethods::VirtualMethods;
|
use dom::virtualmethods::VirtualMethods;
|
||||||
use dom::window::Window;
|
|
||||||
use encoding::EncodingRef;
|
use encoding::EncodingRef;
|
||||||
use encoding::all::UTF_8;
|
use encoding::all::UTF_8;
|
||||||
use encoding::label::encoding_from_whatwg_label;
|
use encoding::label::encoding_from_whatwg_label;
|
||||||
use encoding::types::DecoderTrap;
|
|
||||||
use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType};
|
use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType};
|
||||||
use hyper::method::Method;
|
use hyper::method::Method;
|
||||||
use msg::constellation_msg::{LoadData, PipelineId};
|
use msg::constellation_msg::{LoadData, PipelineId};
|
||||||
|
@ -54,7 +52,6 @@ use string_cache::Atom;
|
||||||
use style::attr::AttrValue;
|
use style::attr::AttrValue;
|
||||||
use style::str::split_html_space_chars;
|
use style::str::split_html_space_chars;
|
||||||
use task_source::TaskSource;
|
use task_source::TaskSource;
|
||||||
use url::form_urlencoded;
|
|
||||||
|
|
||||||
#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)]
|
#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)]
|
||||||
pub struct GenerationId(u32);
|
pub struct GenerationId(u32);
|
||||||
|
@ -280,39 +277,38 @@ impl HTMLFormElement {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm
|
// https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm
|
||||||
fn encode_multipart_form_data(&self, form_data: &mut Vec<FormDatum>,
|
fn encode_multipart_form_data(&self, form_data: &mut Vec<FormDatum>,
|
||||||
encoding: Option<EncodingRef>,
|
boundary: String, encoding: EncodingRef) -> Vec<u8> {
|
||||||
boundary: String) -> String {
|
|
||||||
// Step 1
|
// Step 1
|
||||||
let mut result = "".to_owned();
|
let mut result = vec![];
|
||||||
|
|
||||||
// Step 2
|
// Step 2
|
||||||
// (maybe take encoding as input)
|
|
||||||
let encoding = encoding.unwrap_or(self.pick_encoding());
|
|
||||||
|
|
||||||
// Step 3
|
|
||||||
let charset = &*encoding.whatwg_name().unwrap_or("UTF-8");
|
let charset = &*encoding.whatwg_name().unwrap_or("UTF-8");
|
||||||
|
|
||||||
// Step 4
|
// Step 3
|
||||||
for entry in form_data.iter_mut() {
|
for entry in form_data.iter_mut() {
|
||||||
// Substep 1
|
// 3.1
|
||||||
if entry.name == "_charset_" && entry.ty == "hidden" {
|
if entry.name == "_charset_" && entry.ty == "hidden" {
|
||||||
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
||||||
}
|
}
|
||||||
// TODO: Substep 2
|
// TODO: 3.2
|
||||||
|
|
||||||
// Step 5
|
// Step 4
|
||||||
// https://tools.ietf.org/html/rfc7578#section-4
|
// https://tools.ietf.org/html/rfc7578#section-4
|
||||||
result.push_str(&*format!("\r\n--{}\r\n", boundary));
|
// 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 {
|
let mut content_disposition = ContentDisposition {
|
||||||
disposition: DispositionType::Ext("form-data".to_owned()),
|
disposition: DispositionType::Ext("form-data".to_owned()),
|
||||||
parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))]
|
parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))]
|
||||||
};
|
};
|
||||||
|
|
||||||
match entry.value {
|
match entry.value {
|
||||||
FormDatumValue::String(ref s) =>
|
FormDatumValue::String(ref s) => {
|
||||||
result.push_str(&*format!("Content-Disposition: {}\r\n\r\n{}",
|
let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}",
|
||||||
content_disposition,
|
content_disposition, s).into_bytes();
|
||||||
s)),
|
result.append(&mut bytes);
|
||||||
|
}
|
||||||
FormDatumValue::File(ref f) => {
|
FormDatumValue::File(ref f) => {
|
||||||
content_disposition.parameters.push(
|
content_disposition.parameters.push(
|
||||||
DispositionParam::Filename(Charset::Ext(String::from(charset.clone())),
|
DispositionParam::Filename(Charset::Ext(String::from(charset.clone())),
|
||||||
|
@ -321,20 +317,20 @@ impl HTMLFormElement {
|
||||||
// https://tools.ietf.org/html/rfc7578#section-4.4
|
// https://tools.ietf.org/html/rfc7578#section-4.4
|
||||||
let content_type = ContentType(f.upcast::<Blob>().Type()
|
let content_type = ContentType(f.upcast::<Blob>().Type()
|
||||||
.parse().unwrap_or(mime!(Text / Plain)));
|
.parse().unwrap_or(mime!(Text / Plain)));
|
||||||
result.push_str(&*format!("Content-Disposition: {}\r\n{}\r\n\r\n",
|
let mut type_bytes = format!("Content-Disposition: {}\r\n{}\r\n\r\n",
|
||||||
content_disposition,
|
content_disposition,
|
||||||
content_type));
|
content_type).into_bytes();
|
||||||
|
result.append(&mut type_bytes);
|
||||||
|
|
||||||
let bytes = &f.upcast::<Blob>().get_bytes().unwrap_or(vec![])[..];
|
let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
|
||||||
|
|
||||||
let decoded = encoding.decode(bytes, DecoderTrap::Replace)
|
result.append(&mut bytes);
|
||||||
.expect("Invalid encoding in file");
|
|
||||||
result.push_str(&decoded);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push_str(&*format!("\r\n--{}--", boundary));
|
let mut boundary_bytes = format!("\r\n--{}--", boundary).into_bytes();
|
||||||
|
result.append(&mut boundary_bytes);
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -351,18 +347,11 @@ impl HTMLFormElement {
|
||||||
let charset = &*encoding.whatwg_name().unwrap();
|
let charset = &*encoding.whatwg_name().unwrap();
|
||||||
|
|
||||||
for entry in form_data.iter_mut() {
|
for entry in form_data.iter_mut() {
|
||||||
// Step 4
|
// Step 4, 5
|
||||||
if entry.name == "_charset_" && entry.ty == "hidden" {
|
let value = entry.replace_value(charset);
|
||||||
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5
|
|
||||||
if entry.ty == "file" {
|
|
||||||
entry.value = FormDatumValue::String(DOMString::from(entry.value_str()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 6
|
// Step 6
|
||||||
result.push_str(&*format!("{}={}\r\n", entry.name, entry.value_str()));
|
result.push_str(&*format!("{}={}\r\n", entry.name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 7
|
// Step 7
|
||||||
|
@ -374,7 +363,7 @@ impl HTMLFormElement {
|
||||||
// Step 1
|
// Step 1
|
||||||
let doc = document_from_node(self);
|
let doc = document_from_node(self);
|
||||||
let base = doc.url();
|
let base = doc.url();
|
||||||
// TODO: Handle browsing contexts
|
// TODO: Handle browsing contexts (Step 2, 3)
|
||||||
// Step 4
|
// Step 4
|
||||||
if submit_method_flag == SubmittedFrom::NotFromForm &&
|
if submit_method_flag == SubmittedFrom::NotFromForm &&
|
||||||
!submitter.no_validate(self)
|
!submitter.no_validate(self)
|
||||||
|
@ -397,13 +386,18 @@ impl HTMLFormElement {
|
||||||
}
|
}
|
||||||
// Step 6
|
// Step 6
|
||||||
let mut form_data = self.get_form_dataset(Some(submitter));
|
let mut form_data = self.get_form_dataset(Some(submitter));
|
||||||
|
|
||||||
// Step 7
|
// Step 7
|
||||||
let mut action = submitter.action();
|
let encoding = self.pick_encoding();
|
||||||
|
|
||||||
// Step 8
|
// Step 8
|
||||||
|
let mut action = submitter.action();
|
||||||
|
|
||||||
|
// Step 9
|
||||||
if action.is_empty() {
|
if action.is_empty() {
|
||||||
action = DOMString::from(base.as_str());
|
action = DOMString::from(base.as_str());
|
||||||
}
|
}
|
||||||
// Step 9-11
|
// Step 10-11
|
||||||
let action_components = match base.join(&action) {
|
let action_components = match base.join(&action) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(_) => return
|
Err(_) => return
|
||||||
|
@ -417,57 +411,87 @@ impl HTMLFormElement {
|
||||||
|
|
||||||
let mut load_data = LoadData::new(action_components, doc.get_referrer_policy(), Some(doc.url().clone()));
|
let mut load_data = LoadData::new(action_components, doc.get_referrer_policy(), Some(doc.url().clone()));
|
||||||
|
|
||||||
let parsed_data = match enctype {
|
|
||||||
FormEncType::UrlEncoded => {
|
|
||||||
load_data.headers.set(ContentType::form_url_encoded());
|
|
||||||
|
|
||||||
form_urlencoded::Serializer::new(String::new())
|
|
||||||
.encoding_override(Some(self.pick_encoding()))
|
|
||||||
.extend_pairs(form_data.into_iter().map(|field| (field.name.clone(), field.value_str())))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
FormEncType::FormDataEncoded => {
|
|
||||||
let boundary = self.generate_boundary();
|
|
||||||
let mime = mime!(Multipart / FormData; Boundary =(&boundary));
|
|
||||||
load_data.headers.set(ContentType(mime));
|
|
||||||
|
|
||||||
self.encode_multipart_form_data(&mut form_data, None, boundary)
|
|
||||||
}
|
|
||||||
FormEncType::TextPlainEncoded => {
|
|
||||||
load_data.headers.set(ContentType(mime!(Text / Plain)));
|
|
||||||
|
|
||||||
self.encode_plaintext(&mut form_data)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 18
|
// Step 18
|
||||||
let win = window_from_node(self);
|
|
||||||
match (&*scheme, method) {
|
match (&*scheme, method) {
|
||||||
// https://html.spec.whatwg.org/multipage/#submit-dialog
|
(_, FormMethod::FormDialog) => {
|
||||||
(_, FormMethod::FormDialog) => return, // Unimplemented
|
// TODO: Submit dialog
|
||||||
|
// https://html.spec.whatwg.org/multipage/#submit-dialog
|
||||||
|
}
|
||||||
// https://html.spec.whatwg.org/multipage/#submit-mutate-action
|
// https://html.spec.whatwg.org/multipage/#submit-mutate-action
|
||||||
("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) => {
|
("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) | ("data", FormMethod::FormGet) => {
|
||||||
// FIXME(SimonSapin): use url.query_pairs_mut() here.
|
load_data.headers.set(ContentType::form_url_encoded());
|
||||||
load_data.url.set_query(Some(&*parsed_data));
|
self.mutate_action_url(&mut form_data, load_data, encoding);
|
||||||
self.plan_to_navigate(load_data, &win);
|
|
||||||
}
|
}
|
||||||
// https://html.spec.whatwg.org/multipage/#submit-body
|
// https://html.spec.whatwg.org/multipage/#submit-body
|
||||||
("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => {
|
("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => {
|
||||||
load_data.method = Method::Post;
|
load_data.method = Method::Post;
|
||||||
load_data.data = Some(parsed_data.into_bytes());
|
self.submit_entity_body(&mut form_data, load_data, enctype, encoding);
|
||||||
self.plan_to_navigate(load_data, &win);
|
|
||||||
}
|
}
|
||||||
// https://html.spec.whatwg.org/multipage/#submit-get-action
|
// https://html.spec.whatwg.org/multipage/#submit-get-action
|
||||||
("file", _) | ("about", _) | ("data", FormMethod::FormGet) |
|
("file", _) | ("about", _) | ("data", FormMethod::FormPost) |
|
||||||
("ftp", _) | ("javascript", _) => {
|
("ftp", _) | ("javascript", _) => {
|
||||||
self.plan_to_navigate(load_data, &win);
|
self.plan_to_navigate(load_data);
|
||||||
}
|
}
|
||||||
_ => return // Unimplemented (data and mailto)
|
("mailto", FormMethod::FormPost) => {
|
||||||
|
// TODO: Mail as body
|
||||||
|
// https://html.spec.whatwg.org/multipage/#submit-mailto-body
|
||||||
|
}
|
||||||
|
("mailto", FormMethod::FormGet) => {
|
||||||
|
// TODO: Mail with headers
|
||||||
|
// https://html.spec.whatwg.org/multipage/#submit-mailto-headers
|
||||||
|
}
|
||||||
|
_ => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#submit-mutate-action
|
||||||
|
fn mutate_action_url(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, encoding: EncodingRef) {
|
||||||
|
let charset = &*encoding.whatwg_name().unwrap();
|
||||||
|
|
||||||
|
load_data.url.query_pairs_mut().clear()
|
||||||
|
.encoding_override(Some(self.pick_encoding()))
|
||||||
|
.extend_pairs(form_data.into_iter()
|
||||||
|
.map(|field| (field.name.clone(), field.replace_value(charset))));
|
||||||
|
|
||||||
|
self.plan_to_navigate(load_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#submit-body
|
||||||
|
fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData,
|
||||||
|
enctype: FormEncType, encoding: EncodingRef) {
|
||||||
|
let boundary = self.generate_boundary();
|
||||||
|
let bytes = match enctype {
|
||||||
|
FormEncType::UrlEncoded => {
|
||||||
|
let mut url = load_data.url.clone();
|
||||||
|
let charset = &*encoding.whatwg_name().unwrap();
|
||||||
|
load_data.headers.set(ContentType::form_url_encoded());
|
||||||
|
|
||||||
|
url.query_pairs_mut().clear()
|
||||||
|
.encoding_override(Some(self.pick_encoding()))
|
||||||
|
.extend_pairs(form_data.into_iter()
|
||||||
|
.map(|field| (field.name.clone(), field.replace_value(charset))));
|
||||||
|
|
||||||
|
url.query().unwrap_or("").to_string().into_bytes()
|
||||||
|
}
|
||||||
|
FormEncType::FormDataEncoded => {
|
||||||
|
let mime = mime!(Multipart / FormData; Boundary =(&boundary));
|
||||||
|
load_data.headers.set(ContentType(mime));
|
||||||
|
self.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);
|
||||||
|
}
|
||||||
|
|
||||||
/// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
|
/// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation)
|
||||||
fn plan_to_navigate(&self, load_data: LoadData, window: &Window) {
|
fn plan_to_navigate(&self, load_data: LoadData) {
|
||||||
|
let window = window_from_node(self);
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
// Each planned navigation runnable is tagged with a generation ID, and
|
// Each planned navigation runnable is tagged with a generation ID, and
|
||||||
// before the runnable is handled, it first checks whether the HTMLFormElement's
|
// before the runnable is handled, it first checks whether the HTMLFormElement's
|
||||||
|
@ -485,7 +509,7 @@ impl HTMLFormElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
window.dom_manipulation_task_source().queue(nav, GlobalRef::Window(window)).unwrap();
|
window.dom_manipulation_task_source().queue(nav, GlobalRef::Window(&window)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interactively validate the constraints of form elements
|
/// Interactively validate the constraints of form elements
|
||||||
|
@ -558,10 +582,8 @@ impl HTMLFormElement {
|
||||||
match element {
|
match element {
|
||||||
HTMLElementTypeId::HTMLInputElement => {
|
HTMLElementTypeId::HTMLInputElement => {
|
||||||
let input = child.downcast::<HTMLInputElement>().unwrap();
|
let input = child.downcast::<HTMLInputElement>().unwrap();
|
||||||
// Step 3.2-3.7
|
|
||||||
if let Some(datum) = input.form_datum(submitter) {
|
data_set.append(&mut input.form_datums(submitter));
|
||||||
data_set.push(datum);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
HTMLElementTypeId::HTMLButtonElement => {
|
HTMLElementTypeId::HTMLButtonElement => {
|
||||||
let button = child.downcast::<HTMLButtonElement>().unwrap();
|
let button = child.downcast::<HTMLButtonElement>().unwrap();
|
||||||
|
@ -709,10 +731,14 @@ pub struct FormDatum {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormDatum {
|
impl FormDatum {
|
||||||
pub fn value_str(&self) -> String {
|
pub fn replace_value(&self, charset: &str) -> String {
|
||||||
|
if self.name == "_charset_" && self.ty == "hidden" {
|
||||||
|
return charset.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
match self.value {
|
match self.value {
|
||||||
|
FormDatumValue::File(ref f) => String::from(f.name().clone()),
|
||||||
FormDatumValue::String(ref s) => String::from(s.clone()),
|
FormDatumValue::String(ref s) => String::from(s.clone()),
|
||||||
FormDatumValue::File(ref f) => String::from(f.name().clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -648,7 +648,7 @@ impl HTMLInputElement {
|
||||||
|
|
||||||
/// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set
|
/// https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set
|
||||||
/// Steps range from 3.1 to 3.7 (specific to HTMLInputElement)
|
/// Steps range from 3.1 to 3.7 (specific to HTMLInputElement)
|
||||||
pub fn form_datum(&self, submitter: Option<FormSubmitter>) -> Option<FormDatum> {
|
pub fn form_datums(&self, submitter: Option<FormSubmitter>) -> Vec<FormDatum> {
|
||||||
// 3.1: disabled state check is in get_unclean_dataset
|
// 3.1: disabled state check is in get_unclean_dataset
|
||||||
|
|
||||||
// Step 3.2
|
// Step 3.2
|
||||||
|
@ -664,26 +664,55 @@ impl HTMLInputElement {
|
||||||
|
|
||||||
match ty {
|
match ty {
|
||||||
// Step 3.1: it's a button but it is not submitter.
|
// Step 3.1: it's a button but it is not submitter.
|
||||||
atom!("submit") | atom!("button") | atom!("reset") if !is_submitter => return None,
|
atom!("submit") | atom!("button") | atom!("reset") if !is_submitter => return vec![],
|
||||||
// Step 3.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false.
|
// Step 3.1: it's the "Checkbox" or "Radio Button" and whose checkedness is false.
|
||||||
atom!("radio") | atom!("checkbox") => if !self.Checked() || name.is_empty() {
|
atom!("radio") | atom!("checkbox") => if !self.Checked() || name.is_empty() {
|
||||||
return None;
|
return vec![];
|
||||||
},
|
},
|
||||||
|
atom!("file") => {
|
||||||
|
let mut datums = vec![];
|
||||||
|
|
||||||
atom!("image") | atom!("file") => return None, // Unimplemented
|
// Step 3.2-3.7
|
||||||
|
let name = self.Name();
|
||||||
|
let type_ = self.Type();
|
||||||
|
|
||||||
|
match self.GetFiles() {
|
||||||
|
Some(fl) => {
|
||||||
|
for f in fl.iter_files() {
|
||||||
|
datums.push(FormDatum {
|
||||||
|
ty: type_.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
value: FormDatumValue::File(Root::from_ref(&f)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
datums.push(FormDatum {
|
||||||
|
// XXX(izgzhen): Spec says 'application/octet-stream' as the type,
|
||||||
|
// but this is _type_ of element rather than content right?
|
||||||
|
ty: type_.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
value: FormDatumValue::String(DOMString::from("")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return datums;
|
||||||
|
}
|
||||||
|
atom!("image") => return vec![], // Unimplemented
|
||||||
// Step 3.1: it's not the "Image Button" and doesn't have a name attribute.
|
// Step 3.1: it's not the "Image Button" and doesn't have a name attribute.
|
||||||
_ => if name.is_empty() {
|
_ => if name.is_empty() {
|
||||||
return None;
|
return vec![];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3.9
|
// Step 3.9
|
||||||
Some(FormDatum {
|
vec![FormDatum {
|
||||||
ty: DOMString::from(&*ty), // FIXME(ajeffrey): Convert directly from Atoms to DOMStrings
|
ty: DOMString::from(&*ty), // FIXME(ajeffrey): Convert directly from Atoms to DOMStrings
|
||||||
name: name,
|
name: name,
|
||||||
value: FormDatumValue::String(self.Value())
|
value: FormDatumValue::String(self.Value())
|
||||||
})
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#radio-button-group
|
// https://html.spec.whatwg.org/multipage/#radio-button-group
|
||||||
|
|
|
@ -5806,16 +5806,16 @@
|
||||||
"url": "/_mozilla/css/word_break_a.html"
|
"url": "/_mozilla/css/word_break_a.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mozilla/blob_url_upload.html": [
|
"mozilla/FileAPI/blob_url_upload.html": [
|
||||||
{
|
{
|
||||||
"path": "mozilla/blob_url_upload.html",
|
"path": "mozilla/FileAPI/blob_url_upload.html",
|
||||||
"references": [
|
"references": [
|
||||||
[
|
[
|
||||||
"/_mozilla/mozilla/blob_url_upload_ref.html",
|
"/_mozilla/mozilla/FileAPI/blob_url_upload_ref.html",
|
||||||
"=="
|
"=="
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"url": "/_mozilla/mozilla/blob_url_upload.html"
|
"url": "/_mozilla/mozilla/FileAPI/blob_url_upload.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mozilla/canvas/drawimage_html_image_1.html": [
|
"mozilla/canvas/drawimage_html_image_1.html": [
|
||||||
|
@ -6234,6 +6234,24 @@
|
||||||
"url": "/_mozilla/mozilla/Event.html"
|
"url": "/_mozilla/mozilla/Event.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"mozilla/FileAPI/blob.html": [
|
||||||
|
{
|
||||||
|
"path": "mozilla/FileAPI/blob.html",
|
||||||
|
"url": "/_mozilla/mozilla/FileAPI/blob.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mozilla/FileAPI/file-select.html": [
|
||||||
|
{
|
||||||
|
"path": "mozilla/FileAPI/file-select.html",
|
||||||
|
"url": "/_mozilla/mozilla/FileAPI/file-select.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mozilla/FileAPI/file-upload.html": [
|
||||||
|
{
|
||||||
|
"path": "mozilla/FileAPI/file-upload.html",
|
||||||
|
"url": "/_mozilla/mozilla/FileAPI/file-upload.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
"mozilla/FocusEvent.html": [
|
"mozilla/FocusEvent.html": [
|
||||||
{
|
{
|
||||||
"path": "mozilla/FocusEvent.html",
|
"path": "mozilla/FocusEvent.html",
|
||||||
|
@ -6258,12 +6276,6 @@
|
||||||
"url": "/_mozilla/mozilla/binding_keyword.html"
|
"url": "/_mozilla/mozilla/binding_keyword.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mozilla/blob.html": [
|
|
||||||
{
|
|
||||||
"path": "mozilla/blob.html",
|
|
||||||
"url": "/_mozilla/mozilla/blob.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mozilla/body_listener.html": [
|
"mozilla/body_listener.html": [
|
||||||
{
|
{
|
||||||
"path": "mozilla/body_listener.html",
|
"path": "mozilla/body_listener.html",
|
||||||
|
@ -6540,12 +6552,6 @@
|
||||||
"url": "/_mozilla/mozilla/event_listener.html"
|
"url": "/_mozilla/mozilla/event_listener.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mozilla/file_upload.html": [
|
|
||||||
{
|
|
||||||
"path": "mozilla/file_upload.html",
|
|
||||||
"url": "/_mozilla/mozilla/file_upload.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mozilla/focus_blur.html": [
|
"mozilla/focus_blur.html": [
|
||||||
{
|
{
|
||||||
"path": "mozilla/focus_blur.html",
|
"path": "mozilla/focus_blur.html",
|
||||||
|
@ -14936,16 +14942,16 @@
|
||||||
"url": "/_mozilla/css/word_break_a.html"
|
"url": "/_mozilla/css/word_break_a.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mozilla/blob_url_upload.html": [
|
"mozilla/FileAPI/blob_url_upload.html": [
|
||||||
{
|
{
|
||||||
"path": "mozilla/blob_url_upload.html",
|
"path": "mozilla/FileAPI/blob_url_upload.html",
|
||||||
"references": [
|
"references": [
|
||||||
[
|
[
|
||||||
"/_mozilla/mozilla/blob_url_upload_ref.html",
|
"/_mozilla/mozilla/FileAPI/blob_url_upload_ref.html",
|
||||||
"=="
|
"=="
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"url": "/_mozilla/mozilla/blob_url_upload.html"
|
"url": "/_mozilla/mozilla/FileAPI/blob_url_upload.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mozilla/canvas/drawimage_html_image_1.html": [
|
"mozilla/canvas/drawimage_html_image_1.html": [
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[file_upload.html]
|
[file-select.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
prefs: [dom.testing.htmlinputelement.select_files.enabled:true]
|
prefs: [dom.testing.htmlinputelement.select_files.enabled:true]
|
|
@ -0,0 +1,3 @@
|
||||||
|
[file-upload.html]
|
||||||
|
type: testharness
|
||||||
|
prefs: [dom.testing.htmlinputelement.select_files.enabled:true]
|
|
@ -2,6 +2,6 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Reference: Blob URL with File Upload</title>
|
<title>Reference: Blob URL with File Upload</title>
|
||||||
<body>
|
<body>
|
||||||
<img src="test.jpg" id="image">
|
<img src="../test.jpg" id="image">
|
||||||
<input type="file" id="file-input"">
|
<input type="file" id="file-input"">
|
||||||
</body>
|
</body>
|
|
@ -1,6 +1,6 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Test of uploading a file through input element</title>
|
<title>Test of selecting a file through input element</title>
|
||||||
<script src="/resources/testharness.js"></script>
|
<script src="/resources/testharness.js"></script>
|
||||||
<script src="/resources/testharnessreport.js"></script>
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<form id="testform" enctype="multipart/form-data" action="resource/file-submission.py" method="post">
|
||||||
|
<input type="file" name="file-input" id="file-input" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var e = document.getElementById("file-input");
|
||||||
|
e.selectFiles(["./tests/wpt/mozilla/tests/mozilla/FileAPI/resource/upload.txt"]);
|
||||||
|
</script>
|
23
tests/wpt/mozilla/tests/mozilla/FileAPI/file-upload.html
Normal file
23
tests/wpt/mozilla/tests/mozilla/FileAPI/file-upload.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function run_test() {
|
||||||
|
var t = async_test("form submission of uploaded file");
|
||||||
|
|
||||||
|
var testframe = document.getElementById("testframe");
|
||||||
|
var testdocument = testframe.contentWindow.document;
|
||||||
|
|
||||||
|
testframe.onload = function() {
|
||||||
|
t.step(function () {
|
||||||
|
var response = testframe.contentDocument.documentElement.textContent;
|
||||||
|
assert_equals(response, "OK");
|
||||||
|
});
|
||||||
|
t.done();
|
||||||
|
};
|
||||||
|
testdocument.getElementById("testform").submit();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<iframe id=testframe src="file-upload-frame.html" onload="run_test();"></iframe>
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
|
||||||
|
def fail(msg):
|
||||||
|
return ([("Content-Type", "text/plain")], "FAIL: " + msg)
|
||||||
|
|
||||||
|
|
||||||
|
def main(request, response):
|
||||||
|
content_type = request.headers.get('Content-Type').split("; ")
|
||||||
|
|
||||||
|
if len(content_type) != 2:
|
||||||
|
return fail("content type length is incorrect")
|
||||||
|
|
||||||
|
if content_type[0] != 'multipart/form-data':
|
||||||
|
return fail("content type first field is incorrect")
|
||||||
|
|
||||||
|
boundary = content_type[1].strip("boundary=")
|
||||||
|
|
||||||
|
body = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"file-input\"; filename=\"upload.txt\""
|
||||||
|
body += "\r\n" + "text/plain\r\n\r\nHello\r\n--" + boundary + "--"
|
||||||
|
|
||||||
|
if body != request.body:
|
||||||
|
return fail("request body doesn't match: " + body + "+++++++" + request.body)
|
||||||
|
|
||||||
|
return ([("Content-Type", "text/plain")], "OK")
|
|
@ -0,0 +1 @@
|
||||||
|
Hello
|
Loading…
Add table
Add a link
Reference in a new issue