servo: Merge #11225 - Implement file related functionalities in htmlinputelement and related (from izgzhen:patch-input-element-file); r=Manishearth
authorZhen Zhang <izgzhen@gmail.com>
Mon, 23 May 2016 01:10:46 -0700
changeset 338874 59cfdf62fdbc6984b2849555f2d10f76c5e088d2
parent 338873 b5e22f4ac5c02abb3bd3ea949dde2234aa8fd8c5
child 338875 3fcb1f86fd765ab8a56d45b44916c7a7334f250a
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
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
servo/components/net/resource_thread.rs
servo/components/net_traits/filemanager_thread.rs
servo/components/net_traits/lib.rs
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/file.rs
servo/components/script/dom/filelist.rs
servo/components/script/dom/htmlformelement.rs
servo/components/script/dom/htmlinputelement.rs
servo/components/script/dom/window.rs
--- a/servo/components/net/filemanager_thread.rs
+++ b/servo/components/net/filemanager_thread.rs
@@ -1,74 +1,84 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * 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;
 use std::io::Read;
 use std::path::{Path, PathBuf};
 use util::thread::spawn_named;
 use uuid::Uuid;
 
 pub struct FileManager {
     receiver: IpcReceiver<FileManagerThreadMsg>,
     idmap: RefCell<HashMap<Uuid, PathBuf>>,
 }
 
-impl FileManager {
-    fn new(recv: IpcReceiver<FileManagerThreadMsg>) -> FileManager {
-        FileManager {
-            receiver: recv,
-            idmap: RefCell::new(HashMap::new()),
-        }
-    }
+pub trait FileManagerThreadFactory {
+    fn new() -> Self;
+}
 
-    pub fn new_thread() -> IpcSender<FileManagerThreadMsg> {
+impl FileManagerThreadFactory for IpcSender<FileManagerThreadMsg> {
+    /// Create a FileManagerThread
+    fn new() -> IpcSender<FileManagerThreadMsg> {
         let (chan, recv) = ipc::channel().unwrap();
 
         spawn_named("FileManager".to_owned(), move || {
             FileManager::new(recv).start();
         });
 
         chan
     }
+}
+
+
+impl FileManager {
+    fn new(recv: IpcReceiver<FileManagerThreadMsg>) -> FileManager {
+        FileManager {
+            receiver: recv,
+            idmap: RefCell::new(HashMap::new()),
+        }
+    }
 
     /// Start the file manager event loop
     fn start(&mut self) {
         loop {
             match self.receiver.recv().unwrap() {
                 FileManagerThreadMsg::SelectFile(sender) => self.select_file(sender),
                 FileManagerThreadMsg::SelectFiles(sender) => self.select_files(sender),
                 FileManagerThreadMsg::ReadFile(sender, id) => self.read_file(sender, id),
                 FileManagerThreadMsg::DeleteFileID(id) => self.delete_fileid(id),
             }
         }
     }
 }
 
 impl FileManager {
-    fn select_file(&mut self, sender: IpcSender<FileManagerResult<(Uuid, PathBuf, u64)>>) {
+    fn select_file(&mut self, sender: IpcSender<FileManagerResult<SelectedFile>>) {
         // TODO: Pull the dialog UI in and get selected
         let selected_path = Path::new("");
 
         match self.create_entry(selected_path) {
             Some(triple) => {
                 let _ = sender.send(Ok(triple));
             },
             None => {
                 let _ = sender.send(Err(FileManagerThreadError::InvalidSelection));
             }
         }
     }
 
-    fn select_files(&mut self, sender: IpcSender<FileManagerResult<Vec<(Uuid, PathBuf, u64)>>>) {
+    fn select_files(&mut self, sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>) {
         let selected_paths = vec![Path::new("")];
 
         let mut replies = vec![];
 
         for path in selected_paths {
             match self.create_entry(path) {
                 Some(triple) => replies.push(triple),
                 None => {
@@ -76,17 +86,17 @@ impl FileManager {
                     return;
                 }
             }
         }
 
         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<SelectedFile> {
         match File::open(file_path) {
             Ok(handler) => {
                 let id = Uuid::new_v4();
                 self.idmap.borrow_mut().insert(id, file_path.to_path_buf());
 
                 // Unix Epoch: https://doc.servo.org/std/time/constant.UNIX_EPOCH.html
                 let epoch = handler.metadata().and_then(|metadata| metadata.modified()).map_err(|_| ())
                                               .and_then(|systime| systime.elapsed().map_err(|_| ()))
@@ -95,17 +105,29 @@ impl FileManager {
                                                     let nsecs = elapsed.subsec_nanos();
                                                     let msecs = secs * 1000 + nsecs as u64 / 1000000;
                                                     Ok(msecs)
                                                 });
 
                 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
                 }
             },
             Err(_) => None
         }
     }
 
     fn read_file(&mut self, sender: IpcSender<FileManagerResult<Vec<u8>>>, id: Uuid) {
--- a/servo/components/net/resource_thread.rs
+++ b/servo/components/net/resource_thread.rs
@@ -6,16 +6,17 @@
 use about_loader;
 use chrome_loader;
 use connector::{Connector, create_http_connector};
 use cookie;
 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;
 use hyper::header::{ContentType, Header, SetCookie};
 use hyper::mime::{Mime, SubLevel, TopLevel};
 use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
 use mime_classifier::{ApacheBugFlag, MIMEClassifier, NoSniffFlag};
 use net_traits::LoadContext;
@@ -149,17 +150,18 @@ fn start_sending_opt(start_chan: LoadCon
         }
     }
 }
 
 pub fn new_resource_threads(user_agent: String,
                             devtools_chan: Option<Sender<DevtoolsControlMsg>>,
                             profiler_chan: ProfilerChan) -> ResourceThreads {
     ResourceThreads::new(new_core_resource_thread(user_agent, devtools_chan, profiler_chan),
-                         StorageThreadFactory::new())
+                         StorageThreadFactory::new(),
+                         FileManagerThreadFactory::new())
 }
 
 
 /// Create a CoreResourceThread
 pub fn new_core_resource_thread(user_agent: String,
                                 devtools_chan: Option<Sender<DevtoolsControlMsg>>,
                                 profiler_chan: ProfilerChan) -> CoreResourceThread {
     let hsts_preload = HstsList::from_servo_preload();
--- a/servo/components/net_traits/filemanager_thread.rs
+++ b/servo/components/net_traits/filemanager_thread.rs
@@ -2,33 +2,42 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 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<FileManagerResult<(Uuid, PathBuf, u64)>>),
+    SelectFile(IpcSender<FileManagerResult<SelectedFile>>),
 
     /// Select multiple files, return a vector of triples
-    SelectFiles(IpcSender<FileManagerResult<Vec<(Uuid, PathBuf, u64)>>>),
+    SelectFiles(IpcSender<FileManagerResult<Vec<SelectedFile>>>),
 
     /// Read file, return the bytes
     ReadFile(IpcSender<FileManagerResult<Vec<u8>>>, Uuid),
 
     /// Delete the FileID entry
     DeleteFileID(Uuid),
 }
 
 pub type FileManagerResult<T> = Result<T, FileManagerThreadError>;
 
-#[derive(Deserialize, Serialize)]
+#[derive(Debug, Deserialize, Serialize)]
 pub enum FileManagerThreadError {
     /// The selection action is invalid, nothing is selected
     InvalidSelection,
     /// Failure to process file information such as file name, modified time etc.
     FileInfoProcessingError,
     /// Failure to read the file content
     ReadFileError,
 }
--- a/servo/components/net_traits/lib.rs
+++ b/servo/components/net_traits/lib.rs
@@ -23,16 +23,17 @@ extern crate lazy_static;
 extern crate log;
 extern crate msg;
 extern crate serde;
 extern crate url;
 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;
 use hyper::method::Method;
 use hyper::mime::{Attr, Mime};
 use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
 use msg::constellation_msg::{PipelineId, ReferrerPolicy};
 use std::io::Error as IOError;
@@ -212,37 +213,46 @@ pub enum LoadConsumer {
     Listener(AsyncResponseTarget),
 }
 
 /// Handle to a resource thread
 pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
 
 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<T> where T: serde::Serialize + serde::Deserialize {
+    /// send message T
     fn send(&self, T) -> IpcSendResult;
+    /// get underlying sender
     fn sender(&self) -> IpcSender<T>;
 }
 
 // FIXME: Originally we will construct an Arc<ResourceThread> from ResourceThread
 // in script_thread to avoid some performance pitfall. Now we decide to deal with
 // the "Arc" hack implicitly in future.
 // See discussion: http://logs.glob.uno/?c=mozilla%23servo&s=16+May+2016&e=16+May+2016#c430412
 // See also: https://github.com/servo/servo/blob/735480/components/script/script_thread.rs#L313
 #[derive(Clone, Serialize, Deserialize)]
 pub struct ResourceThreads {
     core_thread: CoreResourceThread,
     storage_thread: IpcSender<StorageThreadMsg>,
+    filemanager_thread: IpcSender<FileManagerThreadMsg>,
 }
 
 impl ResourceThreads {
-    pub fn new(c: CoreResourceThread, s: IpcSender<StorageThreadMsg>) -> ResourceThreads {
+    pub fn new(c: CoreResourceThread,
+               s: IpcSender<StorageThreadMsg>,
+               f: IpcSender<FileManagerThreadMsg>) -> ResourceThreads {
         ResourceThreads {
             core_thread: c,
             storage_thread: s,
+            filemanager_thread: f,
         }
     }
 }
 
 impl IpcSend<CoreResourceMsg> for ResourceThreads {
     fn send(&self, msg: CoreResourceMsg) -> IpcSendResult {
         self.core_thread.send(msg)
     }
@@ -257,16 +267,26 @@ impl IpcSend<StorageThreadMsg> for Resou
         self.storage_thread.send(msg)
     }
 
     fn sender(&self) -> IpcSender<StorageThreadMsg> {
         self.storage_thread.clone()
     }
 }
 
+impl IpcSend<FileManagerThreadMsg> for ResourceThreads {
+    fn send(&self, msg: FileManagerThreadMsg) -> IpcSendResult {
+        self.filemanager_thread.send(msg)
+    }
+
+    fn sender(&self) -> IpcSender<FileManagerThreadMsg> {
+        self.filemanager_thread.clone()
+    }
+}
+
 // Ignore the sub-fields
 impl HeapSizeOf for ResourceThreads {
     fn heap_size_of_children(&self) -> usize { 0 }
 }
 
 #[derive(PartialEq, Copy, Clone, Deserialize, Serialize)]
 pub enum IncludeSubdomains {
     Included,
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -55,17 +55,17 @@ use js::jsval::JSVal;
 use js::rust::Runtime;
 use layout_interface::{LayoutChan, LayoutRPC};
 use libc;
 use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData, WindowSizeType, ReferrerPolicy};
 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;
 use script_runtime::ScriptChan;
 use script_traits::{TimerEventId, TimerSource, TouchpadPressurePhase, UntrustedNodeAddress};
 use serde::{Deserialize, Serialize};
 use smallvec::SmallVec;
 use std::boxed::FnBox;
@@ -316,16 +316,17 @@ no_jsmanaged_fields!(DOMString);
 no_jsmanaged_fields!(Mime);
 no_jsmanaged_fields!(AttrIdentifier);
 no_jsmanaged_fields!(AttrValue);
 no_jsmanaged_fields!(ElementSnapshot);
 no_jsmanaged_fields!(HttpsState);
 no_jsmanaged_fields!(SharedRt);
 no_jsmanaged_fields!(TouchpadPressurePhase);
 no_jsmanaged_fields!(ReferrerPolicy);
+no_jsmanaged_fields!(ResourceThreads);
 
 impl JSTraceable for Box<ScriptChan + Send> {
     #[inline]
     fn trace(&self, _trc: *mut JSTracer) {
         // Do nothing
     }
 }
 
--- a/servo/components/script/dom/file.rs
+++ b/servo/components/script/dom/file.rs
@@ -5,16 +5,18 @@
 use dom::bindings::codegen::Bindings::FileBinding;
 use dom::bindings::codegen::Bindings::FileBinding::FileMethods;
 use dom::bindings::codegen::UnionTypes::BlobOrString;
 use dom::bindings::error::Fallible;
 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;
 
 #[dom_struct]
 pub struct File {
     blob: Blob,
     name: DOMString,
@@ -40,16 +42,29 @@ impl File {
 
     pub fn new(global: GlobalRef, slice: DataSlice,
                name: DOMString, modified: Option<i64>, typeString: &str) -> Root<File> {
         reflect_dom_object(box File::new_inherited(slice, name, modified, typeString),
                            global,
                            FileBinding::Wrap)
     }
 
+    // Construct from selected file message from file manager thread
+    pub fn new_from_selected(window: &Window, selected: SelectedFile) -> Root<File> {
+        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<BlobOrString>,
                        filename: DOMString,
                        filePropertyBag: &FileBinding::FilePropertyBag)
                        -> Fallible<Root<File>> {
         let bytes: Vec<u8> = blob_parts_to_bytes(fileBits);
 
@@ -59,17 +74,16 @@ impl File {
         let slice = DataSlice::new(Arc::new(bytes), None, None);
         let modified = filePropertyBag.lastModified;
         Ok(File::new(global, slice, filename, modified, &typeString))
     }
 
     pub fn name(&self) -> &DOMString {
         &self.name
     }
-
 }
 
 impl FileMethods for File {
 
     // https://w3c.github.io/FileAPI/#dfn-name
     fn Name(&self) -> DOMString {
         self.name.clone()
     }
--- a/servo/components/script/dom/filelist.rs
+++ b/servo/components/script/dom/filelist.rs
@@ -22,18 +22,20 @@ impl FileList {
     fn new_inherited(files: Vec<JS<File>>) -> FileList {
         FileList {
             reflector_: Reflector::new(),
             list: files
         }
     }
 
     #[allow(unrooted_must_root)]
-    pub fn new(window: &Window, files: Vec<JS<File>>) -> Root<FileList> {
-        reflect_dom_object(box FileList::new_inherited(files), GlobalRef::Window(window), FileListBinding::Wrap)
+    pub fn new(window: &Window, files: Vec<Root<File>>) -> Root<FileList> {
+        reflect_dom_object(box FileList::new_inherited(files.iter().map(|r| JS::from_rooted(&r)).collect()),
+                           GlobalRef::Window(window),
+                           FileListBinding::Wrap)
     }
 }
 
 impl FileListMethods for FileList {
     // https://w3c.github.io/FileAPI/#dfn-length
     fn Length(&self) -> u32 {
         self.list.len() as u32
     }
--- a/servo/components/script/dom/htmlformelement.rs
+++ b/servo/components/script/dom/htmlformelement.rs
@@ -610,17 +610,17 @@ impl HTMLFormElement {
             DOMString::from(buf)
         }
 
         // Step 1-3
         let mut ret = self.get_unclean_dataset(submitter);
         // 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 {
                         FormDatumValue::String(ref s) => s,
                         FormDatumValue::File(_) => unreachable!()
                     }));
                 }
             }
--- a/servo/components/script/dom/htmlinputelement.rs
+++ b/servo/components/script/dom/htmlinputelement.rs
@@ -2,37 +2,42 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use caseless::compatibility_caseless_match_str;
 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};
 use dom::htmlformelement::{ResetFrom, SubmittedFrom};
 use dom::keyboardevent::KeyboardEvent;
 use dom::node::{Node, NodeDamage, UnbindContext};
 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;
 use std::ops::Range;
 use string_cache::Atom;
 use style::element_state::*;
 use textinput::KeyReaction::{DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction};
 use textinput::Lines::Single;
@@ -76,17 +81,17 @@ pub struct HTMLInputElement {
     size: Cell<u32>,
     maxlength: Cell<i32>,
     #[ignore_heap_size_of = "#7193"]
     textinput: DOMRefCell<TextInput<IpcSender<ConstellationMsg>>>,
     activation_state: DOMRefCell<InputActivationState>,
     // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag
     value_dirty: Cell<bool>,
 
-    // TODO: selected files for file input
+    filelist: MutNullableHeap<JS<FileList>>,
 }
 
 #[derive(JSTraceable)]
 #[must_root]
 #[derive(HeapSizeOf)]
 struct InputActivationState {
     indeterminate: bool,
     checked: bool,
@@ -125,16 +130,17 @@ impl HTMLInputElement {
             placeholder: DOMRefCell::new(DOMString::new()),
             checked_changed: Cell::new(false),
             value_changed: Cell::new(false),
             maxlength: Cell::new(DEFAULT_MAX_LENGTH),
             size: Cell::new(DEFAULT_INPUT_SIZE),
             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),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(localName: Atom,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLInputElement> {
         let element = HTMLInputElement::new_inherited(localName, prefix, document);
@@ -349,18 +355,28 @@ impl HTMLInputElementMethods for HTMLInp
             }
             ValueMode::DefaultOn => {
                 self.upcast::<Element>()
                     .get_attribute(&ns!(), &atom!("value"))
                     .map_or(DOMString::from("on"),
                             |a| DOMString::from(a.summarize().value))
             }
             ValueMode::Filename => {
-                // TODO: return C:\fakepath\<first of selected files> 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,
+                }
             }
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-input-value
     fn SetValue(&self, value: DOMString) -> ErrorResult {
         match self.value_mode() {
             ValueMode::Value => {
@@ -368,17 +384,19 @@ impl HTMLInputElementMethods for HTMLInp
                 self.value_dirty.set(true);
             }
             ValueMode::Default |
             ValueMode::DefaultOn => {
                 self.upcast::<Element>().set_string_attribute(&atom!("value"), value);
             }
             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);
                 }
             }
         }
 
         self.value_changed.set(true);
         self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@@ -1091,16 +1109,54 @@ impl Activatable for HTMLInputElement {
                 let target = self.upcast::<EventTarget>();
                 target.fire_event("input",
                                   EventBubbles::Bubbles,
                                   EventCancelable::NotCancelable);
                 target.fire_event("change",
                                   EventBubbles::Bubbles,
                                   EventCancelable::NotCancelable);
             },
+            InputType::InputFile => {
+                let window = window_from_node(self);
+                let filemanager = window.resource_threads().sender();
+
+                let mut files: Vec<Root<File>> = 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));
+                }
+            }
             _ => ()
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#implicit-submission
     #[allow(unsafe_code)]
     fn implicit_submission(&self, ctrlKey: bool, shiftKey: bool, altKey: bool, metaKey: bool) {
         let doc = document_from_node(self);
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -1598,9 +1598,8 @@ fn debug_reflow_events(id: PipelineId, g
         ReflowReason::FramedContentChanged => "\tFramedContentChanged",
         ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent",
         ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow",
     });
 
     println!("{}", debug_msg);
 }
 
-no_jsmanaged_fields!(ResourceThreads);