diff --git a/servo/components/net/filemanager_thread.rs b/servo/components/net/filemanager_thread.rs index 3ff8579e0d25..627dcced0e96 100644 --- a/servo/components/net/filemanager_thread.rs +++ b/servo/components/net/filemanager_thread.rs @@ -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 FileManager { 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 FileManagerStore { fn select_file(&self, patterns: Vec, sender: IpcSender>, - origin: FileOrigin) { - match self.ui.open_file_dialog("", patterns) { + origin: FileOrigin, opt_test_path: Option) { + // 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 FileManagerStore { fn select_files(&self, patterns: Vec, sender: IpcSender>>, - origin: FileOrigin) { - match self.ui.open_file_dialog_multi("", patterns) { + origin: FileOrigin, opt_test_paths: Option>) { + // 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 FileManagerStore { } } } + + +fn select_files_pref_enabled() -> bool { + PREFS.get("dom.testing.htmlinputelement.select_files.enabled") + .as_boolean().unwrap_or(false) +} diff --git a/servo/components/net_traits/filemanager_thread.rs b/servo/components/net_traits/filemanager_thread.rs index 78efc142a808..88f1b1473025 100644 --- a/servo/components/net_traits/filemanager_thread.rs +++ b/servo/components/net_traits/filemanager_thread.rs @@ -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, IpcSender>, FileOrigin), + SelectFile(Vec, IpcSender>, FileOrigin, Option), /// Select multiple files, return a vector of triples - SelectFiles(Vec, IpcSender>>, FileOrigin), + SelectFiles(Vec, IpcSender>>, FileOrigin, Option>), /// Read file, return the bytes ReadFile(IpcSender>>, SelectedFileId, FileOrigin), diff --git a/servo/components/script/dom/htmlinputelement.rs b/servo/components/script/dom/htmlinputelement.rs index 0ee2023e44a7..d8acc2dbe756 100644 --- a/servo/components/script/dom/htmlinputelement.rs +++ b/servo/components/script/dom/htmlinputelement.rs @@ -579,6 +579,16 @@ impl HTMLInputElementMethods for HTMLInputElement { EventCancelable::NotCancelable); self.upcast::().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) { + if self.input_type.get() == InputType::InputFile { + self.select_files(Some(paths)); + } + } } @@ -731,6 +741,80 @@ impl HTMLInputElement { let el = self.upcast::(); 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>) { + let window = window_from_node(self); + let origin = window.get_url().origin().unicode_serialization(); + let filemanager = window.resource_threads().sender(); + + let mut files: Vec> = vec![]; + let mut error = None; + + let filter = filter_from_accept(&self.Accept()); + let target = self.upcast::(); + + 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> = vec![]; - let mut error = None; - - let filter = filter_from_accept(&self.Accept()); - let target = self.upcast::(); - - 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), _ => () } } diff --git a/servo/components/script/dom/webidls/HTMLInputElement.webidl b/servo/components/script/dom/webidls/HTMLInputElement.webidl index d145e4986594..1d6160b14cd6 100644 --- a/servo/components/script/dom/webidls/HTMLInputElement.webidl +++ b/servo/components/script/dom/webidls/HTMLInputElement.webidl @@ -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 path); + }; // https://html.spec.whatwg.org/multipage/#HTMLInputElement-partial diff --git a/servo/tests/unit/net/filemanager_thread.rs b/servo/tests/unit/net/filemanager_thread.rs index 27ff56d6a1e8..036fdc969c7d 100644 --- a/servo/tests/unit/net/filemanager_thread.rs +++ b/servo/tests/unit/net/filemanager_thread.rs @@ -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"); }