mirror of
https://github.com/servo/servo.git
synced 2025-08-04 21:20:23 +01:00
Add ability to WPT-test file uploads and fetches, fixes #12322
This commit is contained in:
parent
c2a22bd05e
commit
5e051c08f6
9 changed files with 168 additions and 71 deletions
|
@ -21,6 +21,7 @@ use std::sync::{Arc, RwLock};
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
use tinyfiledialogs;
|
use tinyfiledialogs;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use util::prefs::PREFS;
|
||||||
use util::thread::spawn_named;
|
use util::thread::spawn_named;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -128,14 +129,14 @@ impl<UI: 'static + UIProvider> FileManager<UI> {
|
||||||
loop {
|
loop {
|
||||||
let store = self.store.clone();
|
let store = self.store.clone();
|
||||||
match self.receiver.recv().unwrap() {
|
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 || {
|
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 || {
|
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) => {
|
FileManagerThreadMsg::ReadFile(sender, id, origin) => {
|
||||||
|
@ -309,8 +310,17 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
|
||||||
|
|
||||||
fn select_file(&self, patterns: Vec<FilterPattern>,
|
fn select_file(&self, patterns: Vec<FilterPattern>,
|
||||||
sender: IpcSender<FileManagerResult<SelectedFile>>,
|
sender: IpcSender<FileManagerResult<SelectedFile>>,
|
||||||
origin: FileOrigin) {
|
origin: FileOrigin, opt_test_path: Option<String>) {
|
||||||
match self.ui.open_file_dialog("", patterns) {
|
// 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) => {
|
Some(s) => {
|
||||||
let selected_path = Path::new(&s);
|
let selected_path = Path::new(&s);
|
||||||
|
|
||||||
|
@ -328,8 +338,17 @@ impl <UI: 'static + UIProvider> FileManagerStore<UI> {
|
||||||
|
|
||||||
fn select_files(&self, patterns: Vec<FilterPattern>,
|
fn select_files(&self, patterns: Vec<FilterPattern>,
|
||||||
sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>,
|
sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>,
|
||||||
origin: FileOrigin) {
|
origin: FileOrigin, opt_test_paths: Option<Vec<String>>) {
|
||||||
match self.ui.open_file_dialog_multi("", patterns) {
|
// 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) => {
|
Some(v) => {
|
||||||
let mut selected_paths = vec![];
|
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)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum FileManagerThreadMsg {
|
pub enum FileManagerThreadMsg {
|
||||||
/// Select a single file, return triple (FileID, FileName, lastModified)
|
/// 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
|
/// 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
|
/// Read file, return the bytes
|
||||||
ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId, FileOrigin),
|
ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, SelectedFileId, FileOrigin),
|
||||||
|
|
|
@ -579,6 +579,16 @@ impl HTMLInputElementMethods for HTMLInputElement {
|
||||||
EventCancelable::NotCancelable);
|
EventCancelable::NotCancelable);
|
||||||
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
|
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>();
|
let el = self.upcast::<Element>();
|
||||||
el.set_placeholder_shown_state(has_placeholder && !has_value);
|
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 {
|
impl VirtualMethods for HTMLInputElement {
|
||||||
|
@ -1149,65 +1233,7 @@ impl Activatable for HTMLInputElement {
|
||||||
EventBubbles::Bubbles,
|
EventBubbles::Bubbles,
|
||||||
EventCancelable::NotCancelable);
|
EventCancelable::NotCancelable);
|
||||||
},
|
},
|
||||||
InputType::InputFile => {
|
InputType::InputFile => self.select_files(None),
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,11 @@ interface HTMLInputElement : HTMLElement {
|
||||||
void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
|
void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
|
||||||
|
|
||||||
// also has obsolete members
|
// 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
|
// 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"
|
// Try to select a dummy file "tests/unit/net/test.txt"
|
||||||
let (tx, rx) = ipc::channel().unwrap();
|
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")
|
let selected = rx.recv().expect("Broken channel")
|
||||||
.expect("The file manager failed to find test.txt");
|
.expect("The file manager failed to find test.txt");
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ fn test_filemanager() {
|
||||||
|
|
||||||
{
|
{
|
||||||
let (tx, rx) = ipc::channel().unwrap();
|
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");
|
assert!(rx.try_recv().is_err(), "The thread should not respond normally after exited");
|
||||||
}
|
}
|
||||||
|
|
|
@ -6456,6 +6456,12 @@
|
||||||
"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",
|
||||||
|
|
3
tests/wpt/mozilla/meta/mozilla/file_upload.html.ini
Normal file
3
tests/wpt/mozilla/meta/mozilla/file_upload.html.ini
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[file_upload.html]
|
||||||
|
type: testharness
|
||||||
|
prefs: [dom.testing.htmlinputelement.select_files.enabled:true]
|
31
tests/wpt/mozilla/tests/mozilla/file_upload.html
Normal file
31
tests/wpt/mozilla/tests/mozilla/file_upload.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test of uploading a file through input element</title>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
|
||||||
|
<body><input id="file-input" type="file"></body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
async_test(function() {
|
||||||
|
var e = document.getElementById("file-input");
|
||||||
|
|
||||||
|
assert_true(e.selectFiles != undefined, "selectFiles is not defined")
|
||||||
|
|
||||||
|
e.selectFiles(["./tests/wpt/mozilla/tests/mozilla/test.txt"]);
|
||||||
|
|
||||||
|
assert_true(e.files.length > 0, "test.txt is not selected");
|
||||||
|
|
||||||
|
var reader = new FileReader;
|
||||||
|
|
||||||
|
reader.onloadend = this.step_func(function(evt) {
|
||||||
|
assert_equals(evt.target.result, "hello, servo\n");
|
||||||
|
|
||||||
|
this.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.readAsText(e.files[0]);
|
||||||
|
|
||||||
|
}, "Select a file");
|
||||||
|
</script>
|
1
tests/wpt/mozilla/tests/mozilla/test.txt
Normal file
1
tests/wpt/mozilla/tests/mozilla/test.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hello, servo
|
Loading…
Add table
Add a link
Reference in a new issue