From d77fd07ddc70664d8f6bd87efc02c7d7ed0eb109 Mon Sep 17 00:00:00 2001 From: Zhen Zhang Date: Mon, 23 May 2016 01:10:46 -0700 Subject: [PATCH] servo: Merge #11225 - Implement file related functionalities in htmlinputelement and related (from izgzhen:patch-input-element-file); r=Manishearth - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy --faster` does not report any errors - [x] These changes is related to #11131 - [x] These changes do not require tests because it is a partial implementation 1. Improve the `filemanager_thread` by adding type string and create `SelectedFile` 2. Fill several gaps in `htmlinputelement` implementation related to file type 3. Improve the `File` interface to accommodate the above changes 4. Integrate changes introduced by PR #11189 Source-Repo: https://github.com/servo/servo Source-Revision: 7cea4eb01ce3b84ca276ca417d933fb122005b51 --- servo/components/net/filemanager_thread.rs | 48 +++++++++---- servo/components/net/resource_thread.rs | 4 +- .../net_traits/filemanager_thread.rs | 15 +++- servo/components/net_traits/lib.rs | 22 +++++- servo/components/script/dom/bindings/trace.rs | 3 +- servo/components/script/dom/file.rs | 16 ++++- servo/components/script/dom/filelist.rs | 6 +- .../components/script/dom/htmlformelement.rs | 2 +- .../components/script/dom/htmlinputelement.rs | 68 +++++++++++++++++-- servo/components/script/dom/window.rs | 1 - 10 files changed, 155 insertions(+), 30 deletions(-) diff --git a/servo/components/net/filemanager_thread.rs b/servo/components/net/filemanager_thread.rs index 836954343282..f2cdc658b37a 100644 --- a/servo/components/net/filemanager_thread.rs +++ b/servo/components/net/filemanager_thread.rs @@ -3,7 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; -use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult, FileManagerThreadError}; +use mime_guess::guess_mime_type_opt; +use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult}; +use net_traits::filemanager_thread::{SelectedFile, FileManagerThreadError}; use std::cell::RefCell; use std::collections::HashMap; use std::fs::File; @@ -17,15 +19,13 @@ pub struct FileManager { idmap: RefCell>, } -impl FileManager { - fn new(recv: IpcReceiver) -> FileManager { - FileManager { - receiver: recv, - idmap: RefCell::new(HashMap::new()), - } - } +pub trait FileManagerThreadFactory { + fn new() -> Self; +} - pub fn new_thread() -> IpcSender { +impl FileManagerThreadFactory for IpcSender { + /// Create a FileManagerThread + fn new() -> IpcSender { let (chan, recv) = ipc::channel().unwrap(); spawn_named("FileManager".to_owned(), move || { @@ -34,6 +34,16 @@ impl FileManager { chan } +} + + +impl FileManager { + fn new(recv: IpcReceiver) -> FileManager { + FileManager { + receiver: recv, + idmap: RefCell::new(HashMap::new()), + } + } /// Start the file manager event loop fn start(&mut self) { @@ -49,7 +59,7 @@ impl FileManager { } impl FileManager { - fn select_file(&mut self, sender: IpcSender>) { + fn select_file(&mut self, sender: IpcSender>) { // TODO: Pull the dialog UI in and get selected let selected_path = Path::new(""); @@ -63,7 +73,7 @@ impl FileManager { } } - fn select_files(&mut self, sender: IpcSender>>) { + fn select_files(&mut self, sender: IpcSender>>) { let selected_paths = vec![Path::new("")]; let mut replies = vec![]; @@ -81,7 +91,7 @@ impl FileManager { let _ = sender.send(Ok(replies)); } - fn create_entry(&mut self, file_path: &Path) -> Option<(Uuid, PathBuf, u64)> { + fn create_entry(&mut self, file_path: &Path) -> Option { match File::open(file_path) { Ok(handler) => { let id = Uuid::new_v4(); @@ -100,7 +110,19 @@ impl FileManager { let filename = file_path.file_name(); match (epoch, filename) { - (Ok(epoch), Some(filename)) => Some((id, Path::new(filename).to_path_buf(), epoch)), + (Ok(epoch), Some(filename)) => { + let filename_path = Path::new(filename); + let mime = guess_mime_type_opt(filename_path); + Some(SelectedFile { + id: id, + filename: filename_path.to_path_buf(), + modified: epoch, + type_string: match mime { + Some(x) => format!("{}", x), + None => "".to_string(), + }, + }) + } _ => None } }, diff --git a/servo/components/net/resource_thread.rs b/servo/components/net/resource_thread.rs index 7e93901a7407..43919362bfab 100644 --- a/servo/components/net/resource_thread.rs +++ b/servo/components/net/resource_thread.rs @@ -11,6 +11,7 @@ use cookie_storage::CookieStorage; use data_loader; use devtools_traits::{DevtoolsControlMsg}; use file_loader; +use filemanager_thread::FileManagerThreadFactory; use hsts::HstsList; use http_loader::{self, HttpState}; use hyper::client::pool::Pool; @@ -154,7 +155,8 @@ pub fn new_resource_threads(user_agent: String, devtools_chan: Option>, profiler_chan: ProfilerChan) -> ResourceThreads { ResourceThreads::new(new_core_resource_thread(user_agent, devtools_chan, profiler_chan), - StorageThreadFactory::new()) + StorageThreadFactory::new(), + FileManagerThreadFactory::new()) } diff --git a/servo/components/net_traits/filemanager_thread.rs b/servo/components/net_traits/filemanager_thread.rs index dfc2d6a5615e..ee4fdb291cf0 100644 --- a/servo/components/net_traits/filemanager_thread.rs +++ b/servo/components/net_traits/filemanager_thread.rs @@ -6,13 +6,22 @@ use ipc_channel::ipc::IpcSender; use std::path::PathBuf; use uuid::Uuid; +#[derive(Deserialize, Serialize)] +pub struct SelectedFile { + pub id: Uuid, + pub filename: PathBuf, + pub modified: u64, + // https://w3c.github.io/FileAPI/#dfn-type + pub type_string: String, +} + #[derive(Deserialize, Serialize)] pub enum FileManagerThreadMsg { /// Select a single file, return triple (FileID, FileName, lastModified) - SelectFile(IpcSender>), + SelectFile(IpcSender>), /// Select multiple files, return a vector of triples - SelectFiles(IpcSender>>), + SelectFiles(IpcSender>>), /// Read file, return the bytes ReadFile(IpcSender>>, Uuid), @@ -23,7 +32,7 @@ pub enum FileManagerThreadMsg { pub type FileManagerResult = Result; -#[derive(Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] pub enum FileManagerThreadError { /// The selection action is invalid, nothing is selected InvalidSelection, diff --git a/servo/components/net_traits/lib.rs b/servo/components/net_traits/lib.rs index c338002c19a3..dcd85c3af72a 100644 --- a/servo/components/net_traits/lib.rs +++ b/servo/components/net_traits/lib.rs @@ -28,6 +28,7 @@ extern crate util; extern crate uuid; extern crate websocket; +use filemanager_thread::FileManagerThreadMsg; use heapsize::HeapSizeOf; use hyper::header::{ContentType, Headers}; use hyper::http::RawStatus; @@ -217,8 +218,13 @@ pub type CoreResourceThread = IpcSender; pub type IpcSendResult = Result<(), IOError>; +/// Abstraction of the ability to send a particular type of message, +/// used by net_traits::ResourceThreads to ease the use its IpcSender sub-fields +/// XXX: If this trait will be used more in future, some auto derive might be appealing pub trait IpcSend where T: serde::Serialize + serde::Deserialize { + /// send message T fn send(&self, T) -> IpcSendResult; + /// get underlying sender fn sender(&self) -> IpcSender; } @@ -231,13 +237,17 @@ pub trait IpcSend where T: serde::Serialize + serde::Deserialize { pub struct ResourceThreads { core_thread: CoreResourceThread, storage_thread: IpcSender, + filemanager_thread: IpcSender, } impl ResourceThreads { - pub fn new(c: CoreResourceThread, s: IpcSender) -> ResourceThreads { + pub fn new(c: CoreResourceThread, + s: IpcSender, + f: IpcSender) -> ResourceThreads { ResourceThreads { core_thread: c, storage_thread: s, + filemanager_thread: f, } } } @@ -262,6 +272,16 @@ impl IpcSend for ResourceThreads { } } +impl IpcSend for ResourceThreads { + fn send(&self, msg: FileManagerThreadMsg) -> IpcSendResult { + self.filemanager_thread.send(msg) + } + + fn sender(&self) -> IpcSender { + self.filemanager_thread.clone() + } +} + // Ignore the sub-fields impl HeapSizeOf for ResourceThreads { fn heap_size_of_children(&self) -> usize { 0 } diff --git a/servo/components/script/dom/bindings/trace.rs b/servo/components/script/dom/bindings/trace.rs index 1acb5ee9e4ba..4c6941a5f5e5 100644 --- a/servo/components/script/dom/bindings/trace.rs +++ b/servo/components/script/dom/bindings/trace.rs @@ -60,7 +60,7 @@ use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread}; use net_traits::response::HttpsState; use net_traits::storage_thread::StorageType; -use net_traits::{Metadata, NetworkError}; +use net_traits::{Metadata, NetworkError, ResourceThreads}; use offscreen_gl_context::GLLimits; use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; @@ -321,6 +321,7 @@ no_jsmanaged_fields!(HttpsState); no_jsmanaged_fields!(SharedRt); no_jsmanaged_fields!(TouchpadPressurePhase); no_jsmanaged_fields!(ReferrerPolicy); +no_jsmanaged_fields!(ResourceThreads); impl JSTraceable for Box { #[inline] diff --git a/servo/components/script/dom/file.rs b/servo/components/script/dom/file.rs index 9667182671ab..4ece71a5ce38 100644 --- a/servo/components/script/dom/file.rs +++ b/servo/components/script/dom/file.rs @@ -10,6 +10,8 @@ use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; use dom::bindings::reflector::reflect_dom_object; use dom::blob::{Blob, DataSlice, blob_parts_to_bytes}; +use dom::window::Window; +use net_traits::filemanager_thread::SelectedFile; use std::sync::Arc; use time; use util::str::DOMString; @@ -45,6 +47,19 @@ impl File { FileBinding::Wrap) } + // Construct from selected file message from file manager thread + pub fn new_from_selected(window: &Window, selected: SelectedFile) -> Root { + let name = DOMString::from(selected.filename.to_str().expect("File name encoding error")); + + // FIXME: fix this after PR #11221 is landed + let id = selected.id; + let slice = DataSlice::empty(); + + let global = GlobalRef::Window(window); + + File::new(global, slice, name, Some(selected.modified as i64), "") + } + // https://w3c.github.io/FileAPI/#file-constructor pub fn Constructor(global: GlobalRef, fileBits: Vec, @@ -64,7 +79,6 @@ impl File { pub fn name(&self) -> &DOMString { &self.name } - } impl FileMethods for File { diff --git a/servo/components/script/dom/filelist.rs b/servo/components/script/dom/filelist.rs index ef49e88da6d9..bf7243d9e732 100644 --- a/servo/components/script/dom/filelist.rs +++ b/servo/components/script/dom/filelist.rs @@ -27,8 +27,10 @@ impl FileList { } #[allow(unrooted_must_root)] - pub fn new(window: &Window, files: Vec>) -> Root { - reflect_dom_object(box FileList::new_inherited(files), GlobalRef::Window(window), FileListBinding::Wrap) + pub fn new(window: &Window, files: Vec>) -> Root { + reflect_dom_object(box FileList::new_inherited(files.iter().map(|r| JS::from_rooted(&r)).collect()), + GlobalRef::Window(window), + FileListBinding::Wrap) } } diff --git a/servo/components/script/dom/htmlformelement.rs b/servo/components/script/dom/htmlformelement.rs index 92df6695f480..aff02b534b19 100644 --- a/servo/components/script/dom/htmlformelement.rs +++ b/servo/components/script/dom/htmlformelement.rs @@ -615,7 +615,7 @@ impl HTMLFormElement { // Step 4 for datum in &mut ret { match &*datum.ty { - "file" | "textarea" => (), + "file" | "textarea" => (), // TODO _ => { datum.name = clean_crlf(&datum.name); datum.value = FormDatumValue::String(clean_crlf( match datum.value { diff --git a/servo/components/script/dom/htmlinputelement.rs b/servo/components/script/dom/htmlinputelement.rs index c2c76dd16e8d..4eb1d4d9ba05 100644 --- a/servo/components/script/dom/htmlinputelement.rs +++ b/servo/components/script/dom/htmlinputelement.rs @@ -7,16 +7,19 @@ use dom::activation::{Activatable, ActivationSource, synthetic_click_activation} use dom::attr::{Attr, AttrValue}; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::EventBinding::EventMethods; +use dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; use dom::bindings::error::{Error, ErrorResult}; use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, LayoutJS, Root, RootedReference}; +use dom::bindings::js::{JS, LayoutJS, Root, RootedReference, MutNullableHeap}; use dom::document::Document; use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers, LayoutElementHelpers}; use dom::event::{Event, EventBubbles, EventCancelable}; use dom::eventtarget::EventTarget; +use dom::file::File; +use dom::filelist::FileList; use dom::htmlelement::HTMLElement; use dom::htmlfieldsetelement::HTMLFieldSetElement; use dom::htmlformelement::{FormDatumValue, FormControl, FormDatum, FormSubmitter, HTMLFormElement}; @@ -27,7 +30,9 @@ use dom::node::{document_from_node, window_from_node}; use dom::nodelist::NodeList; use dom::validation::Validatable; use dom::virtualmethods::VirtualMethods; -use ipc_channel::ipc::IpcSender; +use ipc_channel::ipc::{self, IpcSender}; +use net_traits::IpcSend; +use net_traits::filemanager_thread::FileManagerThreadMsg; use script_traits::ScriptMsg as ConstellationMsg; use std::borrow::ToOwned; use std::cell::Cell; @@ -81,7 +86,7 @@ pub struct HTMLInputElement { // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag value_dirty: Cell, - // TODO: selected files for file input + filelist: MutNullableHeap>, } #[derive(JSTraceable)] @@ -130,6 +135,7 @@ impl HTMLInputElement { textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan, None, SelectionDirection::None)), activation_state: DOMRefCell::new(InputActivationState::new()), value_dirty: Cell::new(false), + filelist: MutNullableHeap::new(None), } } @@ -354,8 +360,18 @@ impl HTMLInputElementMethods for HTMLInputElement { |a| DOMString::from(a.summarize().value)) } ValueMode::Filename => { - // TODO: return C:\fakepath\ when a file is selected - DOMString::from("") + let mut path = DOMString::from(""); + match self.filelist.get() { + Some(ref fl) => match fl.Item(0) { + Some(ref f) => { + path.push_str("C:\\fakepath\\"); + path.push_str(f.name()); + path + } + None => path, + }, + None => path, + } } } } @@ -373,7 +389,9 @@ impl HTMLInputElementMethods for HTMLInputElement { } ValueMode::Filename => { if value.is_empty() { - // TODO: empty list of selected files + let window = window_from_node(self); + let fl = FileList::new(window.r(), vec![]); + self.filelist.set(Some(&fl)); } else { return Err(Error::InvalidState); } @@ -1096,6 +1114,44 @@ impl Activatable for HTMLInputElement { EventBubbles::Bubbles, EventCancelable::NotCancelable); }, + InputType::InputFile => { + let window = window_from_node(self); + let filemanager = window.resource_threads().sender(); + + let mut files: Vec> = vec![]; + let mut error = None; + + if self.Multiple() { + let (chan, recv) = ipc::channel().expect("Error initializing channel"); + let msg = FileManagerThreadMsg::SelectFiles(chan); + 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)); + } + }, + Err(err) => error = Some(err), + }; + } else { + let (chan, recv) = ipc::channel().expect("Error initializing channel"); + let msg = FileManagerThreadMsg::SelectFile(chan); + let _ = filemanager.send(msg).unwrap(); + + match recv.recv().expect("IpcSender side error") { + Ok(selected) => files.push(File::new_from_selected(window.r(), selected)), + 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)); + } + } _ => () } } diff --git a/servo/components/script/dom/window.rs b/servo/components/script/dom/window.rs index 1fdcd07ba847..8fdb70497158 100644 --- a/servo/components/script/dom/window.rs +++ b/servo/components/script/dom/window.rs @@ -1603,4 +1603,3 @@ fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQue println!("{}", debug_msg); } -no_jsmanaged_fields!(ResourceThreads);