servo: Merge #12344 - Add ability to WPT-test file uploads and fetches, fixes #12322 (from izgzhen:set-file-js); r=Manishearth

Using `inputElem.selectFiles(["path1", "path2"])` in JavaScript with pref `dom.htmlinputelement.select_files.enabled` = `true` will simulate the effect of `inputElem.click()`.

See #12322  for more, also related to #12343.

Source-Repo: https://github.com/servo/servo
Source-Revision: 8cb05a36bc1a20ec6373ddb23ce127c7801ba5d6
This commit is contained in:
Zhen Zhang 2016-07-09 02:02:34 -07:00
Родитель eb3875e623
Коммит 01fe9016e4
5 изменённых файлов: 127 добавлений и 71 удалений

Просмотреть файл

@ -21,6 +21,7 @@ use std::sync::{Arc, RwLock};
#[cfg(any(target_os = "macos", target_os = "linux"))]
use tinyfiledialogs;
use url::Url;
use util::prefs::PREFS;
use util::thread::spawn_named;
use uuid::Uuid;
@ -128,14 +129,14 @@ impl<UI: 'static + UIProvider> FileManager<UI> {
loop {
let store = self.store.clone();
match self.receiver.recv().unwrap() {
FileManagerThreadMsg::SelectFile(filter, sender, origin) => {
FileManagerThreadMsg::SelectFile(filter, sender, origin, opt_test_path) => {
spawn_named("select file".to_owned(), move || {
store.select_file(filter, sender, origin);
store.select_file(filter, sender, origin, opt_test_path);
});
}
FileManagerThreadMsg::SelectFiles(filter, sender, origin) => {
FileManagerThreadMsg::SelectFiles(filter, sender, origin, opt_test_paths) => {
spawn_named("select files".to_owned(), move || {
store.select_files(filter, sender, origin);
store.select_files(filter, sender, origin, opt_test_paths);
})
}
FileManagerThreadMsg::ReadFile(sender, id, origin) => {
@ -309,8 +310,17 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
fn select_file(&self, patterns: Vec<FilterPattern>,
sender: IpcSender<FileManagerResult<SelectedFile>>,
origin: FileOrigin) {
match self.ui.open_file_dialog("", patterns) {
origin: FileOrigin, opt_test_path: Option<String>) {
// Check if the select_files preference is enabled
// to ensure process-level security against compromised script;
// Then try applying opt_test_path directly for testing convenience
let opt_s = if select_files_pref_enabled() {
opt_test_path
} else {
self.ui.open_file_dialog("", patterns)
};
match opt_s {
Some(s) => {
let selected_path = Path::new(&s);
@ -328,8 +338,17 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
fn select_files(&self, patterns: Vec<FilterPattern>,
sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>,
origin: FileOrigin) {
match self.ui.open_file_dialog_multi("", patterns) {
origin: FileOrigin, opt_test_paths: Option<Vec<String>>) {
// Check if the select_files preference is enabled
// to ensure process-level security against compromised script;
// Then try applying opt_test_paths directly for testing convenience
let opt_v = if select_files_pref_enabled() {
opt_test_paths
} else {
self.ui.open_file_dialog_multi("", patterns)
};
match opt_v {
Some(v) => {
let mut selected_paths = vec![];
@ -481,3 +500,9 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
}
}
}
fn select_files_pref_enabled() -> bool {
PREFS.get("dom.testing.htmlinputelement.select_files.enabled")
.as_boolean().unwrap_or(false)
}

Просмотреть файл

@ -118,10 +118,10 @@ pub struct FilterPattern(pub String);
#[derive(Deserialize, Serialize)]
pub enum FileManagerThreadMsg {
/// Select a single file, return triple (FileID, FileName, lastModified)
SelectFile(Vec<FilterPattern>, IpcSender<FileManagerResult<SelectedFile>>, FileOrigin),
SelectFile(Vec<FilterPattern>, IpcSender<FileManagerResult<SelectedFile>>, FileOrigin, Option<String>),
/// Select multiple files, return a vector of triples
SelectFiles(Vec<FilterPattern>, IpcSender<FileManagerResult<Vec<SelectedFile>>>, FileOrigin),
SelectFiles(Vec<FilterPattern>, IpcSender<FileManagerResult<Vec<SelectedFile>>>, FileOrigin, Option<Vec<String>>),
/// Read file, return the bytes
ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId, FileOrigin),

Просмотреть файл

@ -579,6 +579,16 @@ impl HTMLInputElementMethods for HTMLInputElement {
EventCancelable::NotCancelable);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
// Select the files based on filepaths passed in,
// enabled by dom.htmlinputelement.select_files.enabled,
// used for test purpose.
// check-tidy: no specs after this line
fn SelectFiles(&self, paths: Vec<DOMString>) {
if self.input_type.get() == InputType::InputFile {
self.select_files(Some(paths));
}
}
}
@ -731,6 +741,80 @@ impl HTMLInputElement {
let el = self.upcast::<Element>();
el.set_placeholder_shown_state(has_placeholder && !has_value);
}
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)
// Select files by invoking UI or by passed in argument
fn select_files(&self, opt_test_paths: Option<Vec<DOMString>>) {
let window = window_from_node(self);
let origin = window.get_url().origin().unicode_serialization();
let filemanager = window.resource_threads().sender();
let mut files: Vec<Root<File>> = vec![];
let mut error = None;
let filter = filter_from_accept(&self.Accept());
let target = self.upcast::<EventTarget>();
if self.Multiple() {
let opt_test_paths = opt_test_paths.map(|paths| paths.iter().map(|p| p.to_string()).collect());
let (chan, recv) = ipc::channel().expect("Error initializing channel");
let msg = FileManagerThreadMsg::SelectFiles(filter, chan, origin, opt_test_paths);
let _ = filemanager.send(msg).unwrap();
match recv.recv().expect("IpcSender side error") {
Ok(selected_files) => {
for selected in selected_files {
files.push(File::new_from_selected(window.r(), selected));
}
target.fire_event("input",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
target.fire_event("change",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
},
Err(err) => error = Some(err),
};
} else {
let opt_test_path = match opt_test_paths {
Some(paths) => {
if paths.len() == 0 {
return;
} else {
Some(paths[0].to_string()) // neglect other paths
}
}
None => None,
};
let (chan, recv) = ipc::channel().expect("Error initializing channel");
let msg = FileManagerThreadMsg::SelectFile(filter, chan, origin, opt_test_path);
let _ = filemanager.send(msg).unwrap();
match recv.recv().expect("IpcSender side error") {
Ok(selected) => {
files.push(File::new_from_selected(window.r(), selected));
target.fire_event("input",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
target.fire_event("change",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
},
Err(err) => error = Some(err),
};
}
if let Some(err) = error {
debug!("Input file select error: {:?}", err);
} else {
let filelist = FileList::new(window.r(), files);
self.filelist.set(Some(&filelist));
}
}
}
impl VirtualMethods for HTMLInputElement {
@ -1149,65 +1233,7 @@ impl Activatable for HTMLInputElement {
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
},
InputType::InputFile => {
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)
let window = window_from_node(self);
let origin = window.get_url().origin().unicode_serialization();
let filemanager = window.resource_threads().sender();
let mut files: Vec<Root<File>> = vec![];
let mut error = None;
let filter = filter_from_accept(&self.Accept());
let target = self.upcast::<EventTarget>();
if self.Multiple() {
let (chan, recv) = ipc::channel().expect("Error initializing channel");
let msg = FileManagerThreadMsg::SelectFiles(filter, chan, origin);
let _ = filemanager.send(msg).unwrap();
match recv.recv().expect("IpcSender side error") {
Ok(selected_files) => {
for selected in selected_files {
files.push(File::new_from_selected(window.r(), selected));
}
target.fire_event("input",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
target.fire_event("change",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
},
Err(err) => error = Some(err),
};
} else {
let (chan, recv) = ipc::channel().expect("Error initializing channel");
let msg = FileManagerThreadMsg::SelectFile(filter, chan, origin);
let _ = filemanager.send(msg).unwrap();
match recv.recv().expect("IpcSender side error") {
Ok(selected) => {
files.push(File::new_from_selected(window.r(), selected));
target.fire_event("input",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
target.fire_event("change",
EventBubbles::Bubbles,
EventCancelable::NotCancelable);
},
Err(err) => error = Some(err),
};
}
if let Some(err) = error {
debug!("Input file select error: {:?}", err);
} else {
let filelist = FileList::new(window.r(), files);
self.filelist.set(Some(&filelist));
}
}
InputType::InputFile => self.select_files(None),
_ => ()
}
}

Просмотреть файл

@ -70,6 +70,11 @@ interface HTMLInputElement : HTMLElement {
void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
// also has obsolete members
// Select with file-system paths for testing purpose
[Pref="dom.testing.htmlinputelement.select_files.enabled"]
void selectFiles(sequence<DOMString> path);
};
// https://html.spec.whatwg.org/multipage/#HTMLInputElement-partial

Просмотреть файл

@ -40,7 +40,7 @@ fn test_filemanager() {
{
// Try to select a dummy file "tests/unit/net/test.txt"
let (tx, rx) = ipc::channel().unwrap();
chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone())).unwrap();
chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone(), None)).unwrap();
let selected = rx.recv().expect("Broken channel")
.expect("The file manager failed to find test.txt");
@ -88,7 +88,7 @@ fn test_filemanager() {
{
let (tx, rx) = ipc::channel().unwrap();
let _ = chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone()));
let _ = chan.send(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone(), None));
assert!(rx.try_recv().is_err(), "The thread should not respond normally after exited");
}