Bug 832923 - Implement <input type='file'> on B2G, r=mounir, r=fabrice
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 19 Mar 2013 18:49:16 +0100
changeset 136680 18ccc35f888040ea1fc26b12e9b4448d7508a92a
parent 136679 e19e707704ce50cbe2a7713dfe2746b606c29610
child 136681 015f4e8a16a8e1886fe2773697aa8b28d07a2fc9
push id336
push userakeybl@mozilla.com
push dateMon, 17 Jun 2013 22:53:19 +0000
treeherdermozilla-release@574a39cdf657 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, fabrice
bugs832923
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 832923 - Implement <input type='file'> on B2G, r=mounir, r=fabrice
b2g/chrome/content/shell.js
b2g/components/B2GComponents.manifest
b2g/components/FilePicker.js
b2g/components/Makefile.in
b2g/installer/package-manifest.in
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
testing/specialpowers/content/MockFilePicker.jsm
toolkit/components/filepicker/nsFilePicker.js
widget/nsIFilePicker.idl
widget/xpwidgets/nsBaseFilePicker.cpp
widget/xpwidgets/nsBaseFilePicker.h
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -303,16 +303,17 @@ var shell = {
     this.contentBrowser.src = homeURL;
     this.isHomeLoaded = false;
 
     ppmm.addMessageListener("content-handler", this);
     ppmm.addMessageListener("dial-handler", this);
     ppmm.addMessageListener("sms-handler", this);
     ppmm.addMessageListener("mail-handler", this);
     ppmm.addMessageListener("app-notification-send", AlertsHelper);
+    ppmm.addMessageListener("file-picker", this);
   },
 
   stop: function shell_stop() {
     window.removeEventListener('keydown', this, true);
     window.removeEventListener('keypress', this, true);
     window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('mozfullscreenchange', this);
@@ -527,29 +528,44 @@ var shell = {
       manifestURL: msg.manifest,
       isActivity: (msg.type == 'activity'),
       target: msg.target,
       expectingSystemMessage: true
     });
   },
 
   receiveMessage: function shell_receiveMessage(message) {
-    var names = { 'content-handler': 'view',
-                  'dial-handler'   : 'dial',
-                  'mail-handler'   : 'new',
-                  'sms-handler'    : 'new' }
+    var activities = { 'content-handler': { name: 'view', response: null },
+                       'dial-handler':    { name: 'dial', response: null },
+                       'mail-handler':    { name: 'new',  response: null },
+                       'sms-handler':     { name: 'new',  response: null },
+                       'file-picker':     { name: 'pick', response: 'file-picked' } };
 
-    if (!(message.name in names))
+    if (!(message.name in activities))
       return;
 
     let data = message.data;
-    new MozActivity({
-      name: names[message.name],
+    let activity = activities[message.name];
+
+    let a = new MozActivity({
+      name: activity.name,
       data: data
     });
+
+    if (activity.response) {
+      a.onsuccess = function() {
+        let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+        sender.sendAsyncMessage(activity.response, { success: true,
+                                                     result:  a.result });
+      }
+      a.onerror = function() {
+        let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+        sender.sendAsyncMessage(activity.response, { success: false });
+      }
+    }
   }
 };
 
 function nsBrowserAccess() {
 }
 
 nsBrowserAccess.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -60,8 +60,12 @@ contract @mozilla.org/network/protocol;1
 
 # RecoveryService.js
 component {b3caca5d-0bb0-48c6-912b-6be6cbf08832} RecoveryService.js
 contract @mozilla.org/recovery-service;1 {b3caca5d-0bb0-48c6-912b-6be6cbf08832}
 
 # B2GAboutRedirector
 component {920400b1-cf8f-4760-a9c4-441417b15134} B2GAboutRedirector.js
 contract @mozilla.org/network/protocol/about;1?what=certerror {920400b1-cf8f-4760-a9c4-441417b15134}
+
+# FilePicker.js
+component {436ff8f9-0acc-4b11-8ec7-e293efba3141} FilePicker.js
+contract @mozilla.org/filepicker;1 {436ff8f9-0acc-4b11-8ec7-e293efba3141}
new file mode 100644
--- /dev/null
+++ b/b2g/components/FilePicker.js
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/*
+ * No magic constructor behaviour, as is de rigeur for XPCOM.
+ * If you must perform some initialization, and it could possibly fail (even
+ * due to an out-of-memory condition), you should use an Init method, which
+ * can convey failure appropriately (thrown exception in JS,
+ * NS_FAILED(nsresult) return in C++).
+ *
+ * In JS, you can actually cheat, because a thrown exception will cause the
+ * CreateInstance call to fail in turn, but not all languages are so lucky.
+ * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code
+ * for portability reasons -- and even when you're building completely
+ * platform-specific code, you can't throw across an XPCOM method boundary.)
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+// FIXME: improve this list of filters.
+const IMAGE_FILTERS = ['image/gif', 'image/jpeg', 'image/pjpeg',
+                       'image/png', 'image/svg+xml', 'image/tiff',
+                       'image/vnd.microsoft.icon'];
+const VIDEO_FILTERS = ['video/mpeg', 'video/mp4', 'video/ogg',
+                       'video/quicktime', 'video/webm', 'video/x-matroska',
+                       'video/x-ms-wmv', 'video/x-flv'];
+const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4',
+                       'audio/mpeg', 'audio/ogg', 'audio/vorbis',
+                       'audio/vnd.rn-realaudio', 'audio/vnd.wave',
+                       'audio/webm'];
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyServiceGetter(this, 'cpmm',
+                                   '@mozilla.org/childprocessmessagemanager;1',
+                                   'nsIMessageSender');
+
+function FilePicker() {
+}
+
+FilePicker.prototype = {
+  classID: Components.ID('{436ff8f9-0acc-4b11-8ec7-e293efba3141}'),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
+
+  /* members */
+
+  mParent: undefined,
+  mFilterTypes: [],
+  mFileEnumerator: undefined,
+  mFilePickerShownCallback: undefined,
+
+  /* methods */
+
+  init: function(parent, title, mode) {
+    this.mParent = parent;
+
+    if (mode != Ci.nsIFilePicker.modeOpen &&
+        mode != Ci.nsIFilePicker.modeOpenMultiple) {
+      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    }
+  },
+
+  /* readonly attribute nsILocalFile file - not implemented; */
+  /* readonly attribute nsISimpleEnumerator files - not implemented; */
+  /* readonly attribute nsIURI fileURL - not implemented; */
+
+  get domfiles() {
+    return this.mFilesEnumerator;
+  },
+
+  get domfile() {
+    return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null;
+  },
+
+  appendFilters: function(filterMask) {
+    this.mFilterTypes = null;
+
+    // Ci.nsIFilePicker.filterHTML is not supported
+    // Ci.nsIFilePicker.filterText is not supported
+
+    if (filterMask & Ci.nsIFilePicker.filterImages) {
+      this.mFilterTypes = IMAGE_FILTERS;
+    }
+
+    // Ci.nsIFilePicker.filterXML is not supported
+    // Ci.nsIFilePicker.filterXUL is not supported
+    // Ci.nsIFilePicker.filterApps is not supported
+    // Ci.nsIFilePicker.filterAllowURLs is not supported
+
+    if (filterMask & Ci.nsIFilePicker.filterVideo) {
+      this.mFilterTypes = VIDEO_FILTERS;
+    }
+
+    if (filterMask & Ci.nsIFilePicker.filterAudio) {
+      this.mFilterTypes = AUDIO_FILTERS;
+    }
+
+    // Ci.nsIFilePicker.filterAll is by default
+  },
+
+  appendFilter: function(title, extensions) {
+    // pick activity doesn't support extensions
+  },
+
+  open: function(aFilePickerShownCallback) {
+    this.mFilePickerShownCallback = aFilePickerShownCallback;
+
+    cpmm.addMessageListener('file-picked', this);
+
+    let detail = {};
+    if (this.mFilterTypes) {
+       detail.type = this.mFilterTypes;
+    }
+
+    cpmm.sendAsyncMessage('file-picker', detail);
+  },
+
+  fireSuccess: function(file) {
+    this.mFilesEnumerator = {
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+
+      mFiles: [file],
+      mIndex: 0,
+
+      hasMoreElements: function() {
+        return (this.mIndex < this.mFiles.length);
+      },
+
+      getNext: function() {
+        if (this.mIndex >= this.mFiles.length) {
+          throw Components.results.NS_ERROR_FAILURE;
+        }
+        return this.mFiles[this.mIndex++];
+      }
+    };
+
+    if (this.mFilePickerShownCallback) {
+      this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK);
+      this.mFilePickerShownCallback = null;
+    }
+  },
+
+  fireError: function() {
+    if (this.mFilePickerShownCallback) {
+      this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel);
+      this.mFilePickerShownCallback = null;
+    }
+  },
+
+  receiveMessage: function(message) {
+    if (message.name !== 'file-picked') {
+      return;
+    }
+
+    cpmm.removeMessageListener('file-picked', this);
+
+    let data = message.data;
+    if (!data.success || !data.result.blob) {
+      this.fireError();
+      return;
+    }
+
+    var mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+    var mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, '');
+
+    var name = 'blob';
+    if (mimeInfo) {
+      name += '.' + mimeInfo.primaryExtension;
+    }
+
+    let file = new this.mParent.File(data.result.blob,
+                                     { name: name,
+                                       type: data.result.blob.type });
+    if (file) {
+      this.fireSuccess(file);
+    } else {
+      this.fireError();
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FilePicker]);
--- a/b2g/components/Makefile.in
+++ b/b2g/components/Makefile.in
@@ -14,16 +14,17 @@ MODULE = B2GComponents
 EXTRA_PP_COMPONENTS = \
         ActivitiesGlue.js \
         AlertsService.js \
         B2GAboutRedirector.js \
         B2GComponents.manifest \
         ContentHandler.js \
         ContentPermissionPrompt.js \
         DirectoryProvider.js \
+        FilePicker.js \
         MailtoProtocolHandler.js \
         MozKeyboard.js \
         ProcessGlobal.js \
         PaymentGlue.js \
         SmsProtocolHandler.js \
         TelProtocolHandler.js \
         YoutubeProtocolHandler.js \
         RecoveryService.js \
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -374,20 +374,16 @@
 @BINPATH@/components/nsLoginManagerPrompter.js
 @BINPATH@/components/SiteSpecificUserAgent.js
 @BINPATH@/components/SiteSpecificUserAgent.manifest
 @BINPATH@/components/storage-Legacy.js
 @BINPATH@/components/storage-mozStorage.js
 @BINPATH@/components/crypto-SDR.js
 @BINPATH@/components/jsconsole-clhandler.manifest
 @BINPATH@/components/jsconsole-clhandler.js
-#ifdef MOZ_GTK2
-@BINPATH@/components/nsFilePicker.manifest
-@BINPATH@/components/nsFilePicker.js
-#endif
 @BINPATH@/components/nsHelperAppDlg.manifest
 @BINPATH@/components/nsHelperAppDlg.js
 @BINPATH@/components/nsDownloadManagerUI.manifest
 @BINPATH@/components/nsDownloadManagerUI.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
 #ifndef MOZ_WIDGET_GONK
 @BINPATH@/components/extensions.manifest
@@ -734,16 +730,17 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 @BINPATH@/components/ContentHandler.js
 @BINPATH@/components/PaymentGlue.js
 @BINPATH@/components/YoutubeProtocolHandler.js
 @BINPATH@/components/RecoveryService.js
 @BINPATH@/components/MailtoProtocolHandler.js
 @BINPATH@/components/SmsProtocolHandler.js
 @BINPATH@/components/TelProtocolHandler.js
 @BINPATH@/components/B2GAboutRedirector.js
+@BINPATH@/components/FilePicker.js
 
 #ifdef XP_MACOSX
 @BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@
 #endif
 
 #ifdef PACKAGE_GAIA
 [gaia]
 @BINPATH@/gaia/*
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -262,59 +262,48 @@ nsHTMLInputElement::nsFilePickerShownCal
   if (aResult == nsIFilePicker::returnCancel) {
     return NS_OK;
   }
 
   // Collect new selected filenames
   nsCOMArray<nsIDOMFile> newFiles;
   if (mMulti) {
     nsCOMPtr<nsISimpleEnumerator> iter;
-    nsresult rv = mFilePicker->GetFiles(getter_AddRefs(iter));
+    nsresult rv = mFilePicker->GetDomfiles(getter_AddRefs(iter));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsISupports> tmp;
     bool prefSaved = false;
     bool loop = true;
+
     while (NS_SUCCEEDED(iter->HasMoreElements(&loop)) && loop) {
       iter->GetNext(getter_AddRefs(tmp));
-      nsCOMPtr<nsIFile> localFile = do_QueryInterface(tmp);
-      if (!localFile) {
-        continue;
-      }
-      nsString path;
-      localFile->GetPath(path);
-      if (path.IsEmpty()) {
-        continue;
-      }
-      nsCOMPtr<nsIDOMFile> domFile =
-        do_QueryObject(new nsDOMFileFile(localFile));
+      nsCOMPtr<nsIDOMFile> domFile = do_QueryInterface(tmp);
+      MOZ_ASSERT(domFile);
+
       newFiles.AppendObject(domFile);
+
       if (!prefSaved) {
         // Store the last used directory using the content pref service
         nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
-          mInput->OwnerDoc(), localFile);
+          mInput->OwnerDoc(), domFile);
         prefSaved = true;
       }
     }
   }
   else {
-    nsCOMPtr<nsIFile> localFile;
-    nsresult rv = mFilePicker->GetFile(getter_AddRefs(localFile));
+    nsCOMPtr<nsIDOMFile> domFile;
+    nsresult rv = mFilePicker->GetDomfile(getter_AddRefs(domFile));
     NS_ENSURE_SUCCESS(rv, rv);
-    if (localFile) {
-      nsString path;
-      rv = localFile->GetPath(path);
-      if (!path.IsEmpty()) {
-        nsCOMPtr<nsIDOMFile> domFile=
-          do_QueryObject(new nsDOMFileFile(localFile));
-        newFiles.AppendObject(domFile);
-        // Store the last used directory using the content pref service
-        nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
-          mInput->OwnerDoc(), localFile);
-      }
+    if (domFile) {
+      newFiles.AppendObject(domFile);
+
+      // Store the last used directory using the content pref service
+      nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
+        mInput->OwnerDoc(), domFile);
     }
   }
 
   if (!newFiles.Count()) {
     return NS_OK;
   }
 
   // The text control frame (if there is one) isn't going to send a change
@@ -497,26 +486,37 @@ UploadLastDir::FetchLastUsedDirectory(ns
       return NS_ERROR_OUT_OF_MEMORY;
     localFile->InitWithPath(prefStr);
     localFile.forget(aFile);
   }
   return NS_OK;
 }
 
 nsresult
-UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aFile)
+UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIDOMFile* aDomFile)
 {
   NS_PRECONDITION(aDoc, "aDoc is null");
-  NS_PRECONDITION(aFile, "aFile is null");
+  NS_PRECONDITION(aDomFile, "aDomFile is null");
+
+  nsString path;
+  nsresult rv = aDomFile->GetMozFullPathInternal(path);
+  if (NS_FAILED(rv) || path.IsEmpty()) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIFile> localFile;
+  rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
+                             getter_AddRefs(localFile));
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   NS_PRECONDITION(docURI, "docURI is null");
 
   nsCOMPtr<nsIFile> parentFile;
-  aFile->GetParent(getter_AddRefs(parentFile));
+  localFile->GetParent(getter_AddRefs(parentFile));
   if (!parentFile) {
     return NS_OK;
   }
 
   // Attempt to get the CPS, if it's not present we'll just return
   nsCOMPtr<nsIContentPrefService> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   if (!contentPrefService)
@@ -1877,16 +1877,19 @@ nsHTMLInputElement::GetDisplayFileName(n
     aValue = mStaticDocFileList;
     return;
   }
 
   aValue.Truncate();
   for (int32_t i = 0; i < mFiles.Count(); ++i) {
     nsString str;
     mFiles[i]->GetMozFullPathInternal(str);
+    if (str.IsEmpty()) {
+      mFiles[i]->GetName(str);
+    }
     if (i == 0) {
       aValue.Append(str);
     }
     else {
       aValue.Append(NS_LITERAL_STRING(", ") + str);
     }
   }
 }
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -39,20 +39,20 @@ public:
    * @param aFile path to the last used directory
    */
   nsresult FetchLastUsedDirectory(nsIDocument* aDoc, nsIFile** aFile);
 
   /**
    * Store the last used directory for this location using the
    * content pref service, if it is available
    * @param aURI URI of the current page
-   * @param aFile file chosen by the user - the path to the parent of this
+   * @param aDomFile file chosen by the user - the path to the parent of this
    *        file will be stored
    */
-  nsresult StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aFile);
+  nsresult StoreLastUsedDirectory(nsIDocument* aDoc, nsIDOMFile* aDomFile);
 };
 
 class nsHTMLInputElement : public nsGenericHTMLFormElement,
                            public nsImageLoadingContent,
                            public nsIDOMHTMLInputElement,
                            public nsITextControlElement,
                            public nsIPhonetic,
                            public nsIDOMNSEditableElement,
--- a/testing/specialpowers/content/MockFilePicker.jsm
+++ b/testing/specialpowers/content/MockFilePicker.jsm
@@ -92,34 +92,44 @@ this.MockFilePicker = {
 function MockFilePickerInstance(window) {
   this.window = window;
 };
 MockFilePickerInstance.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
   init: function(aParent, aTitle, aMode) {
     MockFilePicker.mode = aMode;
     this.filterIndex = MockFilePicker.filterIndex;
+    this.parent = aParent;
   },
   appendFilter: function(aTitle, aFilter) {
     if (typeof MockFilePicker.appendFilterCallback == "function")
       MockFilePicker.appendFilterCallback(this, aTitle, aFilter);
   },
   appendFilters: function(aFilterMask) {
     if (typeof MockFilePicker.appendFiltersCallback == "function")
       MockFilePicker.appendFiltersCallback(this, aFilterMask);
   },
   defaultString: "",
   defaultExtension: "",
+  parent: null,
   filterIndex: 0,
   displayDirectory: null,
   get file() {
     if (MockFilePicker.returnFiles.length >= 1)
       return MockFilePicker.returnFiles[0];
     return null;
   },
+  get domfile()  {
+    if (MockFilePicker.returnFiles.length >= 1) {
+      let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils);
+      return utils.wrapDOMFile(MockFilePicker.returnFiles[0]);
+    }
+    return null;
+  },
   get fileURL() {
     if (MockFilePicker.returnFiles.length >= 1)
       return Services.io.newFileURI(MockFilePicker.returnFiles[0]);
     return null;
   },
   get files() {
     return {
       index: 0,
@@ -127,16 +137,30 @@ MockFilePickerInstance.prototype = {
       hasMoreElements: function() {
         return this.index < MockFilePicker.returnFiles.length;
       },
       getNext: function() {
         return MockFilePicker.returnFiles[this.index++];
       }
     };
   },
+  get domfiles()  {
+    let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDOMWindowUtils);
+    return {
+      index: 0,
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+      hasMoreElements: function() {
+        return this.index < MockFilePicker.returnFiles.length;
+      },
+      getNext: function() {
+        return utils.wrapDOMFile(MockFilePicker.returnFiles[this.index++]);
+      }
+    };
+  },
   show: function() {
     MockFilePicker.displayDirectory = this.displayDirectory;
     MockFilePicker.shown = true;
     if (typeof MockFilePicker.showCallback == "function") {
       var returnValue = MockFilePicker.showCallback(this);
       if (typeof returnValue != "undefined")
         return returnValue;
     }
--- a/toolkit/components/filepicker/nsFilePicker.js
+++ b/toolkit/components/filepicker/nsFilePicker.js
@@ -83,25 +83,63 @@ nsFilePicker.prototype = {
   },
   get displayDirectory()  {
     return this.mDisplayDirectory &&
            this.mDisplayDirectory.clone()
                .QueryInterface(nsILocalFile);
   },
 
   /* readonly attribute nsILocalFile file; */
-  set file(a) { throw "readonly property"; },
   get file()  { return this.mFilesEnumerator.mFiles[0]; },
 
   /* readonly attribute nsISimpleEnumerator files; */
-  set files(a) { throw "readonly property"; },
   get files()  { return this.mFilesEnumerator; },
 
+  /* readonly attribute nsIDOMFile domfile; */
+  get domfile()  {
+    let enumerator = this.domfiles;
+    return enumerator ? enumerator.mFiles[0] : null;
+  },
+
+  /* readonly attribute nsISimpleEnumerator domfiles; */
+  get domfiles()  {
+    if (!this.mFilesEnumerator) {
+      return null;
+    }
+
+    if (!this.mDOMFilesEnumerator) {
+      this.mDOMFilesEnumerator {
+        QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleEnumerator]),
+
+        mFiles: [],
+        mIndex: 0,
+
+        hasMoreElements: function() {
+          return (this.mIndex < this.mFiles.length);
+        },
+
+        getNext: function() {
+          if (this.mIndex >= this.mFiles.length) {
+            throw Components.results.NS_ERROR_FAILURE;
+          }
+          return this.mFiles[this.mIndex++];
+        }
+      };
+
+      for (var i = 0, i < this.mFilesEnumerator.mFiles.length; ++i) {
+        var file = this.mParentWindow.wrapDOMFile(
+                     this.mFilesEnumerator.mFiles[i]);
+        this.mDOMFilesEnumerator.mFiles.push(file);
+      }
+    }
+
+    return this.mDOMFilesEnumerator;
+  },
+
   /* readonly attribute nsIURI fileURL; */
-  set fileURL(a) { throw "readonly property"; },
   get fileURL()  {
     if (this.mFileURL)
       return this.mFileURL;
 
     if (!this.mFilesEnumerator)
       return null;
 
       var ioService = Components.classes["@mozilla.org/network/io-service;1"]
@@ -123,16 +161,17 @@ nsFilePicker.prototype = {
   get filterIndex() { return this.mFilterIndex; },
 
   /* attribute boolean addToRecentDocs; */
   set addToRecentDocs(a) {},
   get addToRecentDocs()  { return false; },
 
   /* members */
   mFilesEnumerator: undefined,
+  mDOMFilesEnumerator: undefined,
   mParentWindow: null,
 
   /* methods */
   init: function(parent, title, mode) {
     this.mParentWindow = parent;
     this.mTitle = title;
     this.mMode = mode;
   },
--- a/widget/nsIFilePicker.idl
+++ b/widget/nsIFilePicker.idl
@@ -3,32 +3,33 @@
  * 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIFile;
 interface nsIURI;
+interface nsIDOMFile;
 interface nsIDOMWindow;
 interface nsISimpleEnumerator;
 
 [scriptable, function, uuid(0d79adad-b244-49A5-9997-2a8cad93fc44)]
 interface nsIFilePickerShownCallback : nsISupports
 {
  /**
   * Callback which is called when a filepicker is shown and a result
   * is returned.
   *
   * @param aResult One of returnOK, returnCancel, or returnReplace
   */
   void done(in short aResult);
 };
 
-[scriptable, uuid(60e2dfb6-3fc7-4A2C-8137-16bef44536fc)]
+[scriptable, uuid(a6a24df3-d20a-4b6a-96d4-4736b10a51b7)]
 interface nsIFilePicker : nsISupports
 {
   const short modeOpen        = 0;              // Load a file or directory
   const short modeSave        = 1;              // Save a file or directory
   const short modeGetFolder   = 2;              // Select a folder/directory
   const short modeOpenMultiple= 3;              // Load multiple files
 
   const short returnOK        = 0;              // User hit Ok, process selection
@@ -137,16 +138,31 @@ interface nsIFilePicker : nsISupports
   * Get the enumerator for the selected files
   * only works in the modeOpenMultiple mode
   *
   * @return Returns the files currently selected
   */
   readonly attribute nsISimpleEnumerator files;
 
  /**
+  * Get the nsIDOMFile for the file.
+  *
+  * @return Returns the file currently selected as DOMFile
+  */
+  readonly attribute nsIDOMFile domfile;
+
+ /**
+  * Get the enumerator for the selected files
+  * only works in the modeOpenMultiple mode
+  *
+  * @return Returns the files currently selected as DOMFiles
+  */
+  readonly attribute nsISimpleEnumerator domfiles;
+
+ /**
   * Controls whether the chosen file(s) should be added to the system's recent
   * documents list. This attribute will be ignored if the system has no "Recent
   * Docs" concept, or if the application is in private browsing mode (in which
   * case the file will not be added). Defaults to true.
   */
   attribute boolean addToRecentDocs;
 
  /**
--- a/widget/xpwidgets/nsBaseFilePicker.cpp
+++ b/widget/xpwidgets/nsBaseFilePicker.cpp
@@ -11,16 +11,17 @@
 #include "nsIBaseWindow.h"
 #include "nsIWidget.h"
 
 #include "nsIStringBundle.h"
 #include "nsXPIDLString.h"
 #include "nsIServiceManager.h"
 #include "nsCOMArray.h"
 #include "nsIFile.h"
+#include "nsDOMFile.h"
 #include "nsEnumeratorUtils.h"
 #include "mozilla/Services.h"
 #include "WidgetUtils.h"
 #include "nsThreadUtils.h"
 
 #include "nsBaseFilePicker.h"
 
 using namespace mozilla::widget;
@@ -62,16 +63,61 @@ public:
     return NS_OK;
   }
 
 private:
   nsRefPtr<nsIFilePicker> mFilePicker;
   nsRefPtr<nsIFilePickerShownCallback> mCallback;
 };
 
+class nsBaseFilePickerEnumerator : public nsISimpleEnumerator
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  nsBaseFilePickerEnumerator(nsISimpleEnumerator* iterator)
+    : mIterator(iterator)
+  {}
+
+  virtual ~nsBaseFilePickerEnumerator()
+  {}
+
+  NS_IMETHOD
+  GetNext(nsISupports** aResult)
+  {
+    nsCOMPtr<nsISupports> tmp;
+    nsresult rv = mIterator->GetNext(getter_AddRefs(tmp));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!tmp) {
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsIFile> localFile = do_QueryInterface(tmp);
+    if (!localFile) {
+      return NS_ERROR_FAILURE;
+    }
+
+    nsCOMPtr<nsIDOMFile> domFile = new nsDOMFileFile(localFile);
+    domFile.forget(aResult);
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  HasMoreElements(bool* aResult)
+  {
+    return mIterator->HasMoreElements(aResult);
+  }
+
+private:
+  nsCOMPtr<nsISimpleEnumerator> mIterator;
+};
+
+NS_IMPL_ISUPPORTS1(nsBaseFilePickerEnumerator, nsISimpleEnumerator)
+
 nsBaseFilePicker::nsBaseFilePicker() :
   mAddToRecentDocs(true)
 {
 
 }
 
 nsBaseFilePicker::~nsBaseFilePicker()
 {
@@ -196,18 +242,16 @@ NS_IMETHODIMP nsBaseFilePicker::GetFiles
   rv = GetFile(getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv,rv);
 
   files.AppendObject(file);
 
   return NS_NewArrayEnumerator(aFiles, files);
 }
 
-#ifdef BASEFILEPICKER_HAS_DISPLAYDIRECTORY
-
 // Set the display directory
 NS_IMETHODIMP nsBaseFilePicker::SetDisplayDirectory(nsIFile *aDirectory)
 {
   if (!aDirectory) {
     mDisplayDirectory = nullptr;
     return NS_OK;
   }
   nsCOMPtr<nsIFile> directory;
@@ -225,23 +269,54 @@ NS_IMETHODIMP nsBaseFilePicker::GetDispl
   if (!mDisplayDirectory)
     return NS_OK;
   nsCOMPtr<nsIFile> directory;
   nsresult rv = mDisplayDirectory->Clone(getter_AddRefs(directory));
   if (NS_FAILED(rv))
     return rv;
   return CallQueryInterface(directory, aDirectory);
 }
-#endif
 
 NS_IMETHODIMP
 nsBaseFilePicker::GetAddToRecentDocs(bool *aFlag)
 {
   *aFlag = mAddToRecentDocs;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBaseFilePicker::SetAddToRecentDocs(bool aFlag)
 {
   mAddToRecentDocs = aFlag;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetDomfile(nsIDOMFile** aDomfile)
+{
+  nsCOMPtr<nsIFile> localFile;
+  nsresult rv = GetFile(getter_AddRefs(localFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!localFile) {
+    *aDomfile = nullptr;
+    return NS_OK;
+  }
+
+  nsRefPtr<nsDOMFileFile> domFile = new nsDOMFileFile(localFile);
+  domFile.forget(aDomfile);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetDomfiles(nsISimpleEnumerator** aDomfiles)
+{
+  nsCOMPtr<nsISimpleEnumerator> iter;
+  nsresult rv = GetFiles(getter_AddRefs(iter));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<nsBaseFilePickerEnumerator> retIter =
+    new nsBaseFilePickerEnumerator(iter);
+
+  retIter.forget(aDomfiles);
+  return NS_OK;
+}
+
--- a/widget/xpwidgets/nsBaseFilePicker.h
+++ b/widget/xpwidgets/nsBaseFilePicker.h
@@ -10,44 +10,41 @@
 #include "nsISupports.h"
 #include "nsIFilePicker.h"
 #include "nsISimpleEnumerator.h"
 #include "nsArrayEnumerator.h"
 #include "nsCOMPtr.h"
 
 class nsIWidget;
 
-#define BASEFILEPICKER_HAS_DISPLAYDIRECTORY 1
-
 class nsBaseFilePicker : public nsIFilePicker
 {
 public:
   nsBaseFilePicker(); 
   virtual ~nsBaseFilePicker();
 
   NS_IMETHOD Init(nsIDOMWindow *aParent,
                   const nsAString& aTitle,
                   int16_t aMode);
 
   NS_IMETHOD Open(nsIFilePickerShownCallback *aCallback);
   NS_IMETHOD AppendFilters(int32_t filterMask);
   NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex);
   NS_IMETHOD SetFilterIndex(int32_t aFilterIndex);
   NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles);
-#ifdef BASEFILEPICKER_HAS_DISPLAYDIRECTORY 
   NS_IMETHOD GetDisplayDirectory(nsIFile * *aDisplayDirectory);
   NS_IMETHOD SetDisplayDirectory(nsIFile * aDisplayDirectory);
-#endif
   NS_IMETHOD GetAddToRecentDocs(bool *aFlag);
   NS_IMETHOD SetAddToRecentDocs(bool aFlag);
 
+  NS_IMETHOD GetDomfile(nsIDOMFile** aDomfile);
+  NS_IMETHOD GetDomfiles(nsISimpleEnumerator** aDomfiles);
+
 protected:
 
   virtual void InitNative(nsIWidget *aParent, const nsAString& aTitle,
                           int16_t aMode) = 0;
 
   bool mAddToRecentDocs;
-#ifdef BASEFILEPICKER_HAS_DISPLAYDIRECTORY 
   nsCOMPtr<nsIFile> mDisplayDirectory;
-#endif
 };
 
 #endif // nsBaseFilePicker_h__