mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Add XHR support for FormData
This commit is contained in:
parent
c420a870c1
commit
184b522230
13 changed files with 173 additions and 174 deletions
|
@ -5,40 +5,41 @@
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::FormDataBinding;
|
use dom::bindings::codegen::Bindings::FormDataBinding;
|
||||||
use dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
|
use dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
|
||||||
use dom::bindings::codegen::UnionTypes::BlobOrUSVString;
|
use dom::bindings::codegen::UnionTypes::FileOrUSVString;
|
||||||
use dom::bindings::error::Fallible;
|
use dom::bindings::error::Fallible;
|
||||||
use dom::bindings::global::GlobalRef;
|
use dom::bindings::global::GlobalRef;
|
||||||
use dom::bindings::js::{JS, Root};
|
use dom::bindings::js::Root;
|
||||||
use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
|
use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
|
||||||
use dom::bindings::str::{DOMString, USVString};
|
use dom::bindings::str::{DOMString, USVString};
|
||||||
use dom::blob::{Blob, BlobImpl};
|
use dom::blob::{Blob, BlobImpl};
|
||||||
use dom::file::File;
|
use dom::file::File;
|
||||||
use dom::htmlformelement::HTMLFormElement;
|
use dom::htmlformelement::{HTMLFormElement, FormDatumValue, FormDatum};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
|
|
||||||
#[derive(JSTraceable, Clone)]
|
|
||||||
#[must_root]
|
|
||||||
#[derive(HeapSizeOf)]
|
|
||||||
pub enum FormDatum {
|
|
||||||
StringData(String),
|
|
||||||
BlobData(JS<Blob>)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct FormData {
|
pub struct FormData {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
data: DOMRefCell<HashMap<Atom, Vec<FormDatum>>>,
|
data: DOMRefCell<HashMap<Atom, Vec<FormDatum>>>,
|
||||||
form: Option<JS<HTMLFormElement>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormData {
|
impl FormData {
|
||||||
fn new_inherited(form: Option<&HTMLFormElement>) -> FormData {
|
fn new_inherited(opt_form: Option<&HTMLFormElement>) -> FormData {
|
||||||
|
let mut hashmap: HashMap<Atom, Vec<FormDatum>> = HashMap::new();
|
||||||
|
|
||||||
|
if let Some(form) = opt_form {
|
||||||
|
for datum in form.get_form_dataset(None) {
|
||||||
|
match hashmap.entry(Atom::from(datum.name.as_ref())) {
|
||||||
|
Occupied(entry) => entry.into_mut().push(datum),
|
||||||
|
Vacant(entry) => { entry.insert(vec!(datum)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FormData {
|
FormData {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
data: DOMRefCell::new(HashMap::new()),
|
data: DOMRefCell::new(hashmap),
|
||||||
form: form.map(|f| JS::from_ref(f)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,24 +56,34 @@ impl FormData {
|
||||||
|
|
||||||
impl FormDataMethods for FormData {
|
impl FormDataMethods for FormData {
|
||||||
// https://xhr.spec.whatwg.org/#dom-formdata-append
|
// https://xhr.spec.whatwg.org/#dom-formdata-append
|
||||||
fn Append(&self, name: USVString, value: USVString) {
|
fn Append(&self, name: USVString, str_value: USVString) {
|
||||||
|
let datum = FormDatum {
|
||||||
|
ty: DOMString::from("string"),
|
||||||
|
name: DOMString::from(name.0.clone()),
|
||||||
|
value: FormDatumValue::String(DOMString::from(str_value.0)),
|
||||||
|
};
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
match data.entry(Atom::from(name.0)) {
|
match data.entry(Atom::from(name.0)) {
|
||||||
Occupied(entry) => entry.into_mut().push(FormDatum::StringData(value.0)),
|
Occupied(entry) => entry.into_mut().push(datum),
|
||||||
Vacant (entry) => { entry.insert(vec!(FormDatum::StringData(value.0))); }
|
Vacant(entry) => { entry.insert(vec!(datum)); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
// https://xhr.spec.whatwg.org/#dom-formdata-append
|
// https://xhr.spec.whatwg.org/#dom-formdata-append
|
||||||
fn Append_(&self, name: USVString, value: &Blob, filename: Option<USVString>) {
|
fn Append_(&self, name: USVString, blob: &Blob, filename: Option<USVString>) {
|
||||||
let blob = FormDatum::BlobData(JS::from_ref(&*self.get_file_or_blob(value, filename)));
|
let datum = FormDatum {
|
||||||
|
ty: DOMString::from("file"),
|
||||||
|
name: DOMString::from(name.0.clone()),
|
||||||
|
value: FormDatumValue::File(Root::from_ref(&*self.get_file(blob, filename))),
|
||||||
|
};
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
|
|
||||||
match data.entry(Atom::from(name.0)) {
|
match data.entry(Atom::from(name.0)) {
|
||||||
Occupied(entry) => entry.into_mut().push(blob),
|
Occupied(entry) => entry.into_mut().push(datum),
|
||||||
Vacant(entry) => {
|
Vacant(entry) => { entry.insert(vec!(datum)); },
|
||||||
entry.insert(vec!(blob));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,23 +93,23 @@ impl FormDataMethods for FormData {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://xhr.spec.whatwg.org/#dom-formdata-get
|
// https://xhr.spec.whatwg.org/#dom-formdata-get
|
||||||
fn Get(&self, name: USVString) -> Option<BlobOrUSVString> {
|
fn Get(&self, name: USVString) -> Option<FileOrUSVString> {
|
||||||
self.data.borrow()
|
self.data.borrow()
|
||||||
.get(&Atom::from(name.0))
|
.get(&Atom::from(name.0))
|
||||||
.map(|entry| match entry[0] {
|
.map(|entry| match entry[0].value {
|
||||||
FormDatum::StringData(ref s) => BlobOrUSVString::USVString(USVString(s.clone())),
|
FormDatumValue::String(ref s) => FileOrUSVString::USVString(USVString(s.to_string())),
|
||||||
FormDatum::BlobData(ref b) => BlobOrUSVString::Blob(Root::from_ref(&*b)),
|
FormDatumValue::File(ref b) => FileOrUSVString::File(Root::from_ref(&*b)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://xhr.spec.whatwg.org/#dom-formdata-getall
|
// https://xhr.spec.whatwg.org/#dom-formdata-getall
|
||||||
fn GetAll(&self, name: USVString) -> Vec<BlobOrUSVString> {
|
fn GetAll(&self, name: USVString) -> Vec<FileOrUSVString> {
|
||||||
self.data.borrow()
|
self.data.borrow()
|
||||||
.get(&Atom::from(name.0))
|
.get(&Atom::from(name.0))
|
||||||
.map_or(vec![], |data|
|
.map_or(vec![], |data|
|
||||||
data.iter().map(|item| match *item {
|
data.iter().map(|item| match item.value {
|
||||||
FormDatum::StringData(ref s) => BlobOrUSVString::USVString(USVString(s.clone())),
|
FormDatumValue::String(ref s) => FileOrUSVString::USVString(USVString(s.to_string())),
|
||||||
FormDatum::BlobData(ref b) => BlobOrUSVString::Blob(Root::from_ref(&*b)),
|
FormDatumValue::File(ref b) => FileOrUSVString::File(Root::from_ref(&*b)),
|
||||||
}).collect()
|
}).collect()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -108,29 +119,48 @@ impl FormDataMethods for FormData {
|
||||||
self.data.borrow().contains_key(&Atom::from(name.0))
|
self.data.borrow().contains_key(&Atom::from(name.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://xhr.spec.whatwg.org/#dom-formdata-set
|
||||||
|
fn Set(&self, name: USVString, str_value: USVString) {
|
||||||
|
self.data.borrow_mut().insert(Atom::from(name.0.clone()), vec![FormDatum {
|
||||||
|
ty: DOMString::from("string"),
|
||||||
|
name: DOMString::from(name.0),
|
||||||
|
value: FormDatumValue::String(DOMString::from(str_value.0)),
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
// https://xhr.spec.whatwg.org/#dom-formdata-set
|
// https://xhr.spec.whatwg.org/#dom-formdata-set
|
||||||
fn Set(&self, name: USVString, value: BlobOrUSVString) {
|
fn Set_(&self, name: USVString, blob: &Blob, filename: Option<USVString>) {
|
||||||
let val = match value {
|
self.data.borrow_mut().insert(Atom::from(name.0.clone()), vec![FormDatum {
|
||||||
BlobOrUSVString::USVString(s) => FormDatum::StringData(s.0),
|
ty: DOMString::from("file"),
|
||||||
BlobOrUSVString::Blob(b) => FormDatum::BlobData(JS::from_ref(&*b))
|
name: DOMString::from(name.0),
|
||||||
};
|
value: FormDatumValue::File(Root::from_ref(&*self.get_file(blob, filename))),
|
||||||
self.data.borrow_mut().insert(Atom::from(name.0), vec!(val));
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl FormData {
|
impl FormData {
|
||||||
fn get_file_or_blob(&self, blob: &Blob, filename: Option<USVString>) -> Root<Blob> {
|
fn get_file(&self, blob: &Blob, opt_filename: Option<USVString>) -> Root<File> {
|
||||||
match filename {
|
|
||||||
Some(fname) => {
|
|
||||||
let global = self.global();
|
let global = self.global();
|
||||||
let name = DOMString::from(fname.0);
|
|
||||||
|
let name = match opt_filename {
|
||||||
|
Some(filename) => DOMString::from(filename.0),
|
||||||
|
None => DOMString::from(""),
|
||||||
|
};
|
||||||
|
|
||||||
let bytes = blob.get_bytes().unwrap_or(vec![]);
|
let bytes = blob.get_bytes().unwrap_or(vec![]);
|
||||||
|
|
||||||
Root::upcast(File::new(global.r(), BlobImpl::new_from_bytes(bytes), name, None, ""))
|
File::new(global.r(), BlobImpl::new_from_bytes(bytes), name, None, "")
|
||||||
}
|
}
|
||||||
None => Root::from_ref(blob)
|
|
||||||
|
pub fn datums(&self) -> Vec<FormDatum> {
|
||||||
|
let mut ret = vec![];
|
||||||
|
for values in self.data.borrow().values() {
|
||||||
|
ret.append(&mut values.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,13 +250,6 @@ pub enum ResetFrom {
|
||||||
|
|
||||||
|
|
||||||
impl HTMLFormElement {
|
impl HTMLFormElement {
|
||||||
fn generate_boundary(&self) -> String {
|
|
||||||
let i1 = random::<u32>();
|
|
||||||
let i2 = random::<u32>();
|
|
||||||
|
|
||||||
format!("---------------------------{0}{1}", i1, i2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form
|
// https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form
|
||||||
fn pick_encoding(&self) -> EncodingRef {
|
fn pick_encoding(&self) -> EncodingRef {
|
||||||
// Step 2
|
// Step 2
|
||||||
|
@ -275,66 +268,6 @@ impl HTMLFormElement {
|
||||||
document_from_node(self).encoding()
|
document_from_node(self).encoding()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm
|
|
||||||
fn encode_multipart_form_data(&self, form_data: &mut Vec<FormDatum>,
|
|
||||||
boundary: String, encoding: EncodingRef) -> Vec<u8> {
|
|
||||||
// Step 1
|
|
||||||
let mut result = vec![];
|
|
||||||
|
|
||||||
// Step 2
|
|
||||||
let charset = &*encoding.whatwg_name().unwrap_or("UTF-8");
|
|
||||||
|
|
||||||
// Step 3
|
|
||||||
for entry in form_data.iter_mut() {
|
|
||||||
// 3.1
|
|
||||||
if entry.name == "_charset_" && entry.ty == "hidden" {
|
|
||||||
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
|
||||||
}
|
|
||||||
// TODO: 3.2
|
|
||||||
|
|
||||||
// Step 4
|
|
||||||
// https://tools.ietf.org/html/rfc7578#section-4
|
|
||||||
// NOTE(izgzhen): The encoding here expected by most servers seems different from
|
|
||||||
// what spec says (that it should start with a '\r\n').
|
|
||||||
let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
|
|
||||||
result.append(&mut boundary_bytes);
|
|
||||||
let mut content_disposition = ContentDisposition {
|
|
||||||
disposition: DispositionType::Ext("form-data".to_owned()),
|
|
||||||
parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))]
|
|
||||||
};
|
|
||||||
|
|
||||||
match entry.value {
|
|
||||||
FormDatumValue::String(ref s) => {
|
|
||||||
let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}",
|
|
||||||
content_disposition, s).into_bytes();
|
|
||||||
result.append(&mut bytes);
|
|
||||||
}
|
|
||||||
FormDatumValue::File(ref f) => {
|
|
||||||
content_disposition.parameters.push(
|
|
||||||
DispositionParam::Filename(Charset::Ext(String::from(charset.clone())),
|
|
||||||
None,
|
|
||||||
f.name().clone().into()));
|
|
||||||
// https://tools.ietf.org/html/rfc7578#section-4.4
|
|
||||||
let content_type = ContentType(f.upcast::<Blob>().Type()
|
|
||||||
.parse().unwrap_or(mime!(Text / Plain)));
|
|
||||||
let mut type_bytes = format!("Content-Disposition: {}\r\n{}\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://html.spec.whatwg.org/multipage/#text/plain-encoding-algorithm
|
// https://html.spec.whatwg.org/multipage/#text/plain-encoding-algorithm
|
||||||
fn encode_plaintext(&self, form_data: &mut Vec<FormDatum>) -> String {
|
fn encode_plaintext(&self, form_data: &mut Vec<FormDatum>) -> String {
|
||||||
// Step 1
|
// Step 1
|
||||||
|
@ -459,7 +392,7 @@ impl HTMLFormElement {
|
||||||
// https://html.spec.whatwg.org/multipage/#submit-body
|
// https://html.spec.whatwg.org/multipage/#submit-body
|
||||||
fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData,
|
fn submit_entity_body(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData,
|
||||||
enctype: FormEncType, encoding: EncodingRef) {
|
enctype: FormEncType, encoding: EncodingRef) {
|
||||||
let boundary = self.generate_boundary();
|
let boundary = generate_boundary();
|
||||||
let bytes = match enctype {
|
let bytes = match enctype {
|
||||||
FormEncType::UrlEncoded => {
|
FormEncType::UrlEncoded => {
|
||||||
let mut url = load_data.url.clone();
|
let mut url = load_data.url.clone();
|
||||||
|
@ -476,7 +409,7 @@ impl HTMLFormElement {
|
||||||
FormEncType::FormDataEncoded => {
|
FormEncType::FormDataEncoded => {
|
||||||
let mime = mime!(Multipart / FormData; Boundary =(&boundary));
|
let mime = mime!(Multipart / FormData; Boundary =(&boundary));
|
||||||
load_data.headers.set(ContentType(mime));
|
load_data.headers.set(ContentType(mime));
|
||||||
self.encode_multipart_form_data(form_data, boundary, encoding)
|
encode_multipart_form_data(form_data, boundary, encoding)
|
||||||
}
|
}
|
||||||
FormEncType::TextPlainEncoded => {
|
FormEncType::TextPlainEncoded => {
|
||||||
load_data.headers.set(ContentType(mime!(Text / Plain)));
|
load_data.headers.set(ContentType(mime!(Text / Plain)));
|
||||||
|
@ -718,12 +651,13 @@ impl HTMLFormElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(JSTraceable, HeapSizeOf, Clone)]
|
||||||
pub enum FormDatumValue {
|
pub enum FormDatumValue {
|
||||||
File(Root<File>),
|
File(Root<File>),
|
||||||
String(DOMString)
|
String(DOMString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(HeapSizeOf)]
|
#[derive(HeapSizeOf, JSTraceable, Clone)]
|
||||||
pub struct FormDatum {
|
pub struct FormDatum {
|
||||||
pub ty: DOMString,
|
pub ty: DOMString,
|
||||||
pub name: DOMString,
|
pub name: DOMString,
|
||||||
|
@ -972,3 +906,72 @@ impl Runnable for PlannedNavigation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm
|
||||||
|
pub fn encode_multipart_form_data(form_data: &mut Vec<FormDatum>,
|
||||||
|
boundary: String, encoding: EncodingRef) -> Vec<u8> {
|
||||||
|
// Step 1
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
|
// Step 2
|
||||||
|
let charset = &*encoding.whatwg_name().unwrap_or("UTF-8");
|
||||||
|
|
||||||
|
// Step 3
|
||||||
|
for entry in form_data.iter_mut() {
|
||||||
|
// 3.1
|
||||||
|
if entry.name == "_charset_" && entry.ty == "hidden" {
|
||||||
|
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
||||||
|
}
|
||||||
|
// TODO: 3.2
|
||||||
|
|
||||||
|
// Step 4
|
||||||
|
// https://tools.ietf.org/html/rfc7578#section-4
|
||||||
|
// NOTE(izgzhen): The encoding here expected by most servers seems different from
|
||||||
|
// what spec says (that it should start with a '\r\n').
|
||||||
|
let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes();
|
||||||
|
result.append(&mut boundary_bytes);
|
||||||
|
let mut content_disposition = ContentDisposition {
|
||||||
|
disposition: DispositionType::Ext("form-data".to_owned()),
|
||||||
|
parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))]
|
||||||
|
};
|
||||||
|
|
||||||
|
match entry.value {
|
||||||
|
FormDatumValue::String(ref s) => {
|
||||||
|
let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}",
|
||||||
|
content_disposition, s).into_bytes();
|
||||||
|
result.append(&mut bytes);
|
||||||
|
}
|
||||||
|
FormDatumValue::File(ref f) => {
|
||||||
|
content_disposition.parameters.push(
|
||||||
|
DispositionParam::Filename(Charset::Ext(String::from(charset.clone())),
|
||||||
|
None,
|
||||||
|
f.name().clone().into()));
|
||||||
|
// https://tools.ietf.org/html/rfc7578#section-4.4
|
||||||
|
let content_type = ContentType(f.upcast::<Blob>().Type()
|
||||||
|
.parse().unwrap_or(mime!(Text / Plain)));
|
||||||
|
let mut type_bytes = format!("Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n",
|
||||||
|
content_disposition,
|
||||||
|
content_type).into_bytes();
|
||||||
|
result.append(&mut type_bytes);
|
||||||
|
|
||||||
|
let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]);
|
||||||
|
|
||||||
|
result.append(&mut bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut boundary_bytes = format!("\r\n--{}--", boundary).into_bytes();
|
||||||
|
result.append(&mut boundary_bytes);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc7578#section-4.1
|
||||||
|
pub fn generate_boundary() -> String {
|
||||||
|
let i1 = random::<u32>();
|
||||||
|
let i2 = random::<u32>();
|
||||||
|
|
||||||
|
format!("---------------------------{0}{1}", i1, i2)
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* https://xhr.spec.whatwg.org/#interface-formdata
|
* https://xhr.spec.whatwg.org/#interface-formdata
|
||||||
*/
|
*/
|
||||||
|
|
||||||
typedef (Blob or USVString) FormDataEntryValue;
|
typedef (File or USVString) FormDataEntryValue;
|
||||||
|
|
||||||
[Constructor(optional HTMLFormElement form),
|
[Constructor(optional HTMLFormElement form),
|
||||||
Exposed=(Window,Worker)]
|
Exposed=(Window,Worker)]
|
||||||
|
@ -17,6 +17,7 @@ interface FormData {
|
||||||
FormDataEntryValue? get(USVString name);
|
FormDataEntryValue? get(USVString name);
|
||||||
sequence<FormDataEntryValue> getAll(USVString name);
|
sequence<FormDataEntryValue> getAll(USVString name);
|
||||||
boolean has(USVString name);
|
boolean has(USVString name);
|
||||||
void set(USVString name, FormDataEntryValue value);
|
void set(USVString name, USVString value);
|
||||||
|
void set(USVString name, Blob value, optional USVString filename);
|
||||||
// iterable<USVString, FormDataEntryValue>;
|
// iterable<USVString, FormDataEntryValue>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#bodyinit
|
// https://fetch.spec.whatwg.org/#bodyinit
|
||||||
typedef (Blob or /*BufferSource or FormData or */DOMString or URLSearchParams) BodyInit;
|
typedef (Blob or /*BufferSource or */ FormData or DOMString or URLSearchParams) BodyInit;
|
||||||
|
|
||||||
enum XMLHttpRequestResponseType {
|
enum XMLHttpRequestResponseType {
|
||||||
"",
|
"",
|
||||||
|
|
|
@ -26,6 +26,7 @@ use dom::document::{Document, IsHTMLDocument};
|
||||||
use dom::event::{Event, EventBubbles, EventCancelable};
|
use dom::event::{Event, EventBubbles, EventCancelable};
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::EventTarget;
|
||||||
use dom::headers::is_forbidden_header_name;
|
use dom::headers::is_forbidden_header_name;
|
||||||
|
use dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
|
||||||
use dom::progressevent::ProgressEvent;
|
use dom::progressevent::ProgressEvent;
|
||||||
use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
|
use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
|
||||||
use dom::xmlhttprequestupload::XMLHttpRequestUpload;
|
use dom::xmlhttprequestupload::XMLHttpRequestUpload;
|
||||||
|
@ -1355,12 +1356,12 @@ impl Extractable for BodyInit {
|
||||||
let encoding = UTF_8 as EncodingRef;
|
let encoding = UTF_8 as EncodingRef;
|
||||||
(encoding.encode(s, EncoderTrap::Replace).unwrap(),
|
(encoding.encode(s, EncoderTrap::Replace).unwrap(),
|
||||||
Some(DOMString::from("text/plain;charset=UTF-8")))
|
Some(DOMString::from("text/plain;charset=UTF-8")))
|
||||||
},
|
}
|
||||||
BodyInit::URLSearchParams(ref usp) => {
|
BodyInit::URLSearchParams(ref usp) => {
|
||||||
// Default encoding is UTF-8.
|
// Default encoding is UTF-8.
|
||||||
(usp.serialize(None).into_bytes(),
|
(usp.serialize(None).into_bytes(),
|
||||||
Some(DOMString::from("application/x-www-form-urlencoded;charset=UTF-8")))
|
Some(DOMString::from("application/x-www-form-urlencoded;charset=UTF-8")))
|
||||||
},
|
}
|
||||||
BodyInit::Blob(ref b) => {
|
BodyInit::Blob(ref b) => {
|
||||||
let content_type = if b.Type().as_ref().is_empty() {
|
let content_type = if b.Type().as_ref().is_empty() {
|
||||||
None
|
None
|
||||||
|
@ -1369,7 +1370,13 @@ impl Extractable for BodyInit {
|
||||||
};
|
};
|
||||||
let bytes = b.get_bytes().unwrap_or(vec![]);
|
let bytes = b.get_bytes().unwrap_or(vec![]);
|
||||||
(bytes, content_type)
|
(bytes, content_type)
|
||||||
},
|
}
|
||||||
|
BodyInit::FormData(ref formdata) => {
|
||||||
|
let boundary = generate_boundary();
|
||||||
|
let bytes = encode_multipart_form_data(&mut formdata.datums(), boundary.clone(),
|
||||||
|
UTF_8 as EncodingRef);
|
||||||
|
(bytes, Some(DOMString::from(format!("multipart/form-data;boundary={}", boundary))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
[FormData-append.html]
|
|
||||||
type: testharness
|
|
||||||
[Passing a String object to FormData.append should work.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[testFormDataAppendEmptyBlob]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
[formdata-delete.htm]
|
|
||||||
type: testharness
|
|
||||||
[testFormDataDeleteFromFormNonExistentKey]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[testFormDataDeleteFromFormOtherKey]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
[formdata-get.htm]
|
|
||||||
type: testharness
|
|
||||||
[testFormDataGetFromForm]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[testFormDataGetAllFromForm]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[formdata-has.htm]
|
|
||||||
type: testharness
|
|
||||||
[testFormDataHasFromForm]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
[formdata-set.htm]
|
|
||||||
type: testharness
|
|
||||||
[Passing a String object to FormData.set should work]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[testFormDataSetEmptyBlob]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
[formdata.htm]
|
|
||||||
type: testharness
|
|
||||||
[formdata with string]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[formdata with named string]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[formdata from form]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ def main(request, response):
|
||||||
boundary = content_type[1].strip("boundary=")
|
boundary = content_type[1].strip("boundary=")
|
||||||
|
|
||||||
body = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"file-input\"; filename=\"upload.txt\""
|
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 + "--"
|
body += "\r\n" + "content-type: text/plain\r\n\r\nHello\r\n--" + boundary + "--"
|
||||||
|
|
||||||
if body != request.body:
|
if body != request.body:
|
||||||
return fail("request body doesn't match: " + body + "+++++++" + request.body)
|
return fail("request body doesn't match: " + body + "+++++++" + request.body)
|
||||||
|
|
|
@ -80,8 +80,14 @@
|
||||||
assert_equals(fd.get('key'), "null");
|
assert_equals(fd.get('key'), "null");
|
||||||
}, 'testFormDataAppendToFormNull2');
|
}, 'testFormDataAppendToFormNull2');
|
||||||
test(function() {
|
test(function() {
|
||||||
assert_object_equals(create_formdata(['key', new Blob(), 'blank.txt']).get('key'),
|
var f1 = create_formdata(['key', new Blob(), 'blank.txt']).get('key');
|
||||||
new File(new Blob(), 'blank.txt'));
|
var f2 = new File([new Blob()], 'blank.txt');
|
||||||
|
|
||||||
|
var fileAttrs = ['name', 'size', 'type'];
|
||||||
|
|
||||||
|
fileAttrs.forEach(function(attr) {
|
||||||
|
assert_equals(f1[attr], f2[attr], attr + " should be equal");
|
||||||
|
});
|
||||||
}, 'testFormDataAppendEmptyBlob');
|
}, 'testFormDataAppendEmptyBlob');
|
||||||
|
|
||||||
function create_formdata() {
|
function create_formdata() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue