Bug 1335539 - Get rid of nsIDOMWindowUtils.wrapDOMFile, r=smaug, r=Gijs
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 03 Mar 2017 09:42:54 +0100
changeset 394781 e769531b24333372cf6e28c6c495abb069136060
parent 394780 5ed300efb2fa7afa52c2d60c4c02efa3e642748e
child 394782 c8f9ec4046eeded6871856b50eb7ea8a778d2f64
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, Gijs
bugs1335539
milestone54.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 1335539 - Get rid of nsIDOMWindowUtils.wrapDOMFile, r=smaug, r=Gijs
browser/base/content/test/general/browser_save_link-perwindowpb.js
browser/base/content/test/general/browser_save_link_when_window_navigates.js
browser/base/content/test/general/browser_save_private_link_perwindowpb.js
browser/base/content/test/general/browser_save_video.js
browser/base/content/test/general/browser_save_video_frame.js
browser/base/content/test/webextensions/browser_permissions_local_file.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
devtools/client/aboutdebugging/test/browser_addons_install.js
devtools/client/aboutdebugging/test/head.js
devtools/client/webide/test/test_simulators.html
dom/base/nsDOMWindowUtils.cpp
dom/base/test/mochitest.ini
dom/base/test/test_bug793311.html
dom/file/FileBlobImpl.h
dom/file/FileCreatorHelper.cpp
dom/file/FileCreatorHelper.h
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/tests/test_bug1086684.html
dom/webidl/File.webidl
layout/forms/test/test_bug536567_perwindowpb.html
mobile/android/components/FilePicker.js
testing/specialpowers/content/MockFilePicker.jsm
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
toolkit/components/filepicker/nsFilePicker.js
toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
toolkit/content/tests/browser/browser_save_resend_postdata.js
toolkit/mozapps/extensions/test/browser/browser_bug567127.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings_info.js
--- a/browser/base/content/test/general/browser_save_link-perwindowpb.js
+++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js
@@ -35,17 +35,17 @@ function triggerSave(aWindow, aCallback)
     var destFile = destDir.clone();
 
     MockFilePicker.displayDirectory = destDir;
     MockFilePicker.showCallback = function(fp) {
       info("showCallback");
       fileName = fp.defaultString;
       info("fileName: " + fileName);
       destFile.append(fileName);
-      MockFilePicker.returnFiles = [destFile];
+      MockFilePicker.setFiles([destFile]);
       MockFilePicker.filterIndex = 1; // kSaveAsType_URL
       info("done showCallback");
     };
 
     mockTransferCallback = function(downloadSuccess) {
       info("mockTransferCallback");
       onTransferComplete(aWindow, downloadSuccess, destDir);
       destDir.remove(true);
--- a/browser/base/content/test/general/browser_save_link_when_window_navigates.js
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -40,17 +40,17 @@ function triggerSave(aWindow, aCallback)
   var destFile = destDir.clone();
 
   MockFilePicker.displayDirectory = destDir;
   MockFilePicker.showCallback = function(fp) {
     info("showCallback");
     fileName = fp.defaultString;
     info("fileName: " + fileName);
     destFile.append(fileName);
-    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.setFiles([destFile]);
     MockFilePicker.filterIndex = 1; // kSaveAsType_URL
     info("done showCallback");
   };
 
   mockTransferCallback = function(downloadSuccess) {
     info("mockTransferCallback");
     onTransferComplete(aWindow, downloadSuccess, destDir);
     destDir.remove(true);
--- a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -53,17 +53,17 @@ function promiseImageDownloaded() {
     // Create the folder the image will be saved into.
     var destDir = createTemporarySaveDirectory();
     var destFile = destDir.clone();
 
     MockFilePicker.displayDirectory = destDir;
     MockFilePicker.showCallback = function(fp) {
       fileName = fp.defaultString;
       destFile.append(fileName);
-      MockFilePicker.returnFiles = [destFile];
+      MockFilePicker.setFiles([destFile]);
       MockFilePicker.filterIndex = 1; // kSaveAsType_URL
     };
 
     mockTransferCallback = onTransferComplete;
     mockTransferRegisterer.register();
 
     registerCleanupFunction(function() {
       mockTransferCallback = null;
--- a/browser/base/content/test/general/browser_save_video.js
+++ b/browser/base/content/test/general/browser_save_video.js
@@ -29,17 +29,17 @@ add_task(function* () {
   // Create the folder the video will be saved into.
   var destDir = createTemporarySaveDirectory();
   var destFile = destDir.clone();
 
   MockFilePicker.displayDirectory = destDir;
   MockFilePicker.showCallback = function(fp) {
     fileName = fp.defaultString;
     destFile.append(fileName);
-    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.setFiles([destFile]);
     MockFilePicker.filterIndex = 1; // kSaveAsType_URL
   };
 
   let transferCompletePromise = new Promise((resolve) => {
     function onTransferComplete(downloadSuccess) {
       ok(downloadSuccess, "Video file should have been downloaded successfully");
 
       is(fileName, "web-video1-expectedName.ogv",
--- a/browser/base/content/test/general/browser_save_video_frame.js
+++ b/browser/base/content/test/general/browser_save_video_frame.js
@@ -80,17 +80,17 @@ add_task(function*() {
 
   // Create the folder the video will be saved into.
   let destDir = createTemporarySaveDirectory();
   let destFile = destDir.clone();
 
   MockFilePicker.displayDirectory = destDir;
   MockFilePicker.showCallback = function(fp) {
     destFile.append(fp.defaultString);
-    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.setFiles([destFile]);
     MockFilePicker.filterIndex = 1; // kSaveAsType_URL
   };
 
   mockTransferRegisterer.register();
 
   // Make sure that we clean these things up when we're done.
   registerCleanupFunction(function() {
     mockTransferRegisterer.unregister();
--- a/browser/base/content/test/webextensions/browser_permissions_local_file.js
+++ b/browser/base/content/test/webextensions/browser_permissions_local_file.js
@@ -5,19 +5,19 @@ async function installFile(filename) {
                                      .getService(Ci.nsIChromeRegistry);
   let chromeUrl = Services.io.newURI(gTestPath);
   let fileUrl = ChromeRegistry.convertChromeURL(chromeUrl);
   let file = fileUrl.QueryInterface(Ci.nsIFileURL).file;
   file.leafName = filename;
 
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(window);
-  MockFilePicker.returnFiles = [file];
+  MockFilePicker.setFiles([file]);
+  MockFilePicker.afterOpenCallback = MockFilePicker.cleanup;
 
   await BrowserOpenAddonsMgr("addons://list/extension");
   let contentWin = gBrowser.selectedTab.linkedBrowser.contentWindow;
 
   // Do the install...
   contentWin.gViewController.doCommand("cmd_installFromFile");
-  MockFilePicker.cleanup();
 }
 
 add_task(() => testInstallMethod(installFile));
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
@@ -50,17 +50,17 @@ function test() {
                            aGlobalLastDir, aCallback) {
     // Check lastDir preference.
     is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aDisplayDir.path,
        "LastDir should be the expected display dir");
     // Check gDownloadLastDir value.
     is(gDownloadLastDir.file.path, aDisplayDir.path,
        "gDownloadLastDir should be the expected display dir");
 
-    MockFilePicker.returnFiles = [aFile];
+    MockFilePicker.setFiles([aFile]);
     MockFilePicker.displayDirectory = null;
 
     launcher.saveDestinationAvailable = function(file) {
       ok(!!file, "promptForSaveToFile correctly returned a file");
 
       // File picker should start with expected display dir.
       is(MockFilePicker.displayDirectory.path, aDisplayDir.path,
         "File picker should start with browser.download.lastDir");
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -57,17 +57,17 @@ function test() {
                            aGlobalLastDir, aCallback) {
     // Check lastDir preference.
     is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aDisplayDir.path,
        "LastDir should be the expected display dir");
     // Check gDownloadLastDir value.
     is(gDownloadLastDir.file.path, aDisplayDir.path,
        "gDownloadLastDir should be the expected display dir");
 
-    MockFilePicker.returnFiles = [aFile];
+    MockFilePicker.setFiles([aFile]);
     MockFilePicker.displayDirectory = null;
     aWin.promiseTargetFile(params).then(function() {
       // File picker should start with expected display dir.
       is(MockFilePicker.displayDirectory.path, aDisplayDir.path,
          "File picker should start with browser.download.lastDir");
       // browser.download.lastDir should be modified on not private windows
       is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aLastDir.path,
          "LastDir should be the expected last dir");
--- a/devtools/client/aboutdebugging/test/browser_addons_install.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_install.js
@@ -18,29 +18,29 @@ add_task(function* () {
 
   // Install the add-on, and verify that it disappears in the about:debugging UI
   yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
 
   yield closeAboutDebugging(tab);
 });
 
 add_task(function* () {
-  let { tab, document } = yield openAboutDebugging("addons");
+  let { tab, document, window } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
 
   // Start an observer that looks for the install error before
   // actually doing the install
   let top = document.querySelector(".addons-top");
   let promise = waitForMutation(top, { childList: true });
 
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
-  MockFilePicker.init(null);
+  MockFilePicker.init(window);
   let file = getSupportsFile("addons/bad/manifest.json");
-  MockFilePicker.returnFiles = [file.file];
+  MockFilePicker.setFiles([file.file]);
 
   // Trigger the file picker by clicking on the button
   document.getElementById("load-addon-from-file").click();
 
   // Now wait for the install error to appear.
   yield promise;
 
   // And check that it really is there.
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -30,22 +30,23 @@ function* openAboutDebugging(page, win) 
   let url = "about:debugging";
   if (page) {
     url += "#" + page;
   }
 
   let tab = yield addTab(url, { window: win });
   let browser = tab.linkedBrowser;
   let document = browser.contentDocument;
+  let window = browser.contentWindow;
 
   if (!document.querySelector(".app")) {
     yield waitForMutation(document.body, { childList: true });
   }
 
-  return { tab, document };
+  return { tab, document, window };
 }
 
 /**
  * Change url hash for current about:debugging tab, return a promise after
  * new content is loaded.
  * @param  {DOMDocument}  document   container document from current tab
  * @param  {String}       hash       hash for about:debugging
  * @return {Promise}
@@ -110,17 +111,17 @@ function getTabList(document) {
     document.querySelector("#tabs.targets");
 }
 
 function* installAddon({document, path, name, isWebExtension}) {
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(window);
   let file = getSupportsFile(path);
-  MockFilePicker.returnFiles = [file.file];
+  MockFilePicker.setFiles([file.file]);
 
   let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   let onAddonInstalled;
 
   if (isWebExtension) {
     onAddonInstalled = new Promise(done => {
--- a/devtools/client/webide/test/test_simulators.html
+++ b/devtools/client/webide/test/test_simulators.html
@@ -185,28 +185,28 @@
 
           yield set(form.version, sim20.addonID);
 
           ok(!form.version.classList.contains("custom"), "Version selector is not customized after addon change");
           is(form.name.value, customName + "2.0", "Simulator name was updated to new version");
 
           // Pick custom binary, but act like the user aborted the file picker.
 
-          MockFilePicker.returnFiles = [];
+          MockFilePicker.setFiles([]);
           yield set(form.version, "pick");
 
           is(form.version.value, sim20.addonID, "Version selector reverted to last valid choice after customization abort");
           ok(!form.version.classList.contains("custom"), "Version selector is not customized after customization abort");
 
           // Pick custom binary, and actually follow through. (success, verify value = "custom" and textContent = custom path)
 
-          MockFilePicker.useAnyFile();
+          yield MockFilePicker.useAnyFile();
           yield set(form.version, "pick");
 
-          let fakeBinary = MockFilePicker.returnFiles[0];
+          let fakeBinary = MockFilePicker.file;
 
           ok(form.version.value == "custom", "Version selector was set to a new custom binary");
           ok(form.version.classList.contains("custom"), "Version selector is now customized");
           is(form.version.selectedOptions[0].textContent, fakeBinary.path, "Custom option textContent is correct");
 
           yield set(form.version, sim10.addonID);
 
           ok(form.version.classList.contains("custom"), "Version selector remains customized after change back to addon");
@@ -216,25 +216,25 @@
 
           ok(form.version.value == "custom", "Version selector is back to custom");
 
           // Test `profile`.
 
           is(form.profile.value, "default", "Default simulator profile");
           ok(!form.profile.classList.contains("custom"), "Profile selector is not customized");
 
-          MockFilePicker.returnFiles = [];
+          MockFilePicker.setFiles([]);
           yield set(form.profile, "pick");
 
           is(form.profile.value, "default", "Profile selector reverted to last valid choice after customization abort");
           ok(!form.profile.classList.contains("custom"), "Profile selector is not customized after customization abort");
 
           let fakeProfile = FileUtils.getDir("TmpD", []);
 
-          MockFilePicker.returnFiles = [ fakeProfile ];
+          MockFilePicker.setFiles([ fakeProfile ]);
           yield set(form.profile, "pick");
 
           ok(form.profile.value == "custom", "Profile selector was set to a new custom directory");
           ok(form.profile.classList.contains("custom"), "Profile selector is now customized");
           is(form.profile.selectedOptions[0].textContent, fakeProfile.path, "Custom option textContent is correct");
 
           yield set(form.profile, "default");
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2805,37 +2805,16 @@ nsDOMWindowUtils::GetContainerElement(ns
 
   nsCOMPtr<nsIDOMElement> element =
     do_QueryInterface(window->GetFrameElementInternal());
 
   element.forget(aResult);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsDOMWindowUtils::WrapDOMFile(nsIFile *aFile,
-                              nsISupports **aDOMFile)
-{
-  if (!aFile) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
-  NS_ENSURE_STATE(window);
-
-  nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
-  if (!innerWindow) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsCOMPtr<nsIDOMBlob> blob = File::CreateFromFile(innerWindow, aFile);
-  blob.forget(aDOMFile);
-  return NS_OK;
-}
-
 #ifdef DEBUG
 static bool
 CheckLeafLayers(Layer* aLayer, const nsIntPoint& aOffset, nsIntRegion* aCoveredRegion)
 {
   gfx::Matrix transform;
   if (!aLayer->GetTransform().Is2D(&transform) ||
       transform.HasNonIntegerTranslation())
     return false;
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -527,17 +527,16 @@ skip-if = toolkit == 'android' #bug 6870
 [test_bug750096.html]
 [test_bug753278.html]
 [test_bug761120.html]
 [test_bug769117.html]
 [test_bug782342.html]
 [test_bug787778.html]
 [test_bug789315.html]
 [test_bug789856.html]
-[test_bug793311.html]
 [test_bug804395.html]
 [test_bug809003.html]
 [test_bug810494.html]
 [test_bug811701.html]
 [test_bug811701.xhtml]
 [test_bug813919.html]
 [test_bug814576.html]
 [test_bug819051.html]
deleted file mode 100644
--- a/dom/base/test/test_bug793311.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=793311
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 793311</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript">
-
-  /** Test for Bug {793311} **/
-  SimpleTest.waitForExplicitFinish();
-
-  try {
-    SpecialPowers.DOMWindowUtils.wrapDOMFile(null);
-    ok(false, "wrapDOMFile(null) throws an exception");
-  } catch(e) {
-    ok(true, "wrapDOMFile(null) throws an exception");
-  }
-  SimpleTest.finish();
-
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=793311">Mozilla Bug 793311</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
--- a/dom/file/FileBlobImpl.h
+++ b/dom/file/FileBlobImpl.h
@@ -45,16 +45,26 @@ public:
                                  ErrorResult& aRv) override;
 
   virtual bool IsDirectory() const override;
 
   // We always have size and date for this kind of blob.
   virtual bool IsSizeUnknown() const override { return false; }
   virtual bool IsDateUnknown() const override { return false; }
 
+  void SetName(const nsAString& aName)
+  {
+    mName = aName;
+  }
+
+  void SetType(const nsAString& aType)
+  {
+    mContentType = aType;
+  }
+
 protected:
   virtual ~FileBlobImpl() = default;
 
 private:
   // Create slice
   FileBlobImpl(const FileBlobImpl* aOther, uint64_t aStart,
                uint64_t aLength, const nsAString& aContentType);
 
--- a/dom/file/FileCreatorHelper.cpp
+++ b/dom/file/FileCreatorHelper.cpp
@@ -77,17 +77,18 @@ FileCreatorHelper::CreateFileInternal(ns
   int64_t lastModified = 0;
   if (aBag.mLastModified.WasPassed()) {
     lastModifiedPassed = true;
     lastModified = aBag.mLastModified.Value();
   }
 
   RefPtr<BlobImpl> blobImpl;
   aRv = CreateBlobImpl(aFile, aBag.mType, aBag.mName, lastModifiedPassed,
-                       lastModified, aIsFromNsIFile, getter_AddRefs(blobImpl));
+                       lastModified, aBag.mExistenceCheck, aIsFromNsIFile,
+                       getter_AddRefs(blobImpl));
   if (aRv.Failed()) {
      return nullptr;
   }
 
   RefPtr<File> file = File::Create(aWindow, blobImpl);
   return file.forget();
 }
 
@@ -125,17 +126,18 @@ FileCreatorHelper::SendRequest(nsIFile* 
 
   nsAutoString path;
   aRv = aFile->GetPath(path);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   cc->FileCreationRequest(uuid, this, path, aBag.mType, aBag.mName,
-                          aBag.mLastModified, aIsFromNsIFile);
+                          aBag.mLastModified, aBag.mExistenceCheck,
+                          aIsFromNsIFile);
 }
 
 void
 FileCreatorHelper::ResponseReceived(BlobImpl* aBlobImpl, nsresult aRv)
 {
   if (NS_FAILED(aRv)) {
     mPromise->MaybeReject(aRv);
     return;
@@ -146,38 +148,59 @@ FileCreatorHelper::ResponseReceived(Blob
 }
 
 /* static */ nsresult
 FileCreatorHelper::CreateBlobImplForIPC(const nsAString& aPath,
                                         const nsAString& aType,
                                         const nsAString& aName,
                                         bool aLastModifiedPassed,
                                         int64_t aLastModified,
+                                        bool aExistenceCheck,
                                         bool aIsFromNsIFile,
                                         BlobImpl** aBlobImpl)
 {
   nsCOMPtr<nsIFile> file;
   nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return CreateBlobImpl(file, aType, aName, aLastModifiedPassed, aLastModified,
-                        aIsFromNsIFile, aBlobImpl);
+                        aExistenceCheck, aIsFromNsIFile, aBlobImpl);
 }
 
 /* static */ nsresult
 FileCreatorHelper::CreateBlobImpl(nsIFile* aFile,
                                   const nsAString& aType,
                                   const nsAString& aName,
                                   bool aLastModifiedPassed,
                                   int64_t aLastModified,
+                                  bool aExistenceCheck,
                                   bool aIsFromNsIFile,
                                   BlobImpl** aBlobImpl)
 {
+  if (!aExistenceCheck) {
+    RefPtr<FileBlobImpl> impl = new FileBlobImpl(aFile);
+
+    if (!aName.IsEmpty()) {
+      impl->SetName(aName);
+    }
+
+    if (!aType.IsEmpty()) {
+      impl->SetType(aType);
+    }
+
+    if (aLastModifiedPassed) {
+      impl->SetLastModified(aLastModified);
+    }
+
+    impl.forget(aBlobImpl);
+    return NS_OK;
+  }
+
   RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
   nsresult rv =
     impl->InitializeChromeFile(aFile, aType, aName, aLastModifiedPassed,
                                aLastModified, aIsFromNsIFile);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
--- a/dom/file/FileCreatorHelper.h
+++ b/dom/file/FileCreatorHelper.h
@@ -46,16 +46,17 @@ public:
 
   // For IPC only
   static nsresult
   CreateBlobImplForIPC(const nsAString& aPath,
                        const nsAString& aType,
                        const nsAString& aName,
                        bool aLastModifiedPassed,
                        int64_t aLastModified,
+                       bool aExistenceCheck,
                        bool aIsFromNsIFile,
                        BlobImpl** aBlobImpl);
 
 private:
   static already_AddRefed<File>
   CreateFileInternal(nsPIDOMWindowInner* aWindow,
                      nsIFile* aFile,
                      const ChromeFilePropertyBag& aBag,
@@ -63,16 +64,17 @@ private:
                      ErrorResult& aRv);
 
   static nsresult
   CreateBlobImpl(nsIFile* aFile,
                  const nsAString& aType,
                  const nsAString& aName,
                  bool aLastModifiedPassed,
                  int64_t aLastModified,
+                 bool aExistenceCheck,
                  bool aIsFromNsIFile,
                  BlobImpl** aBlobImpl);
 
   FileCreatorHelper(Promise* aPromise, nsPIDOMWindowInner* aWindow);
   ~FileCreatorHelper();
 
   void
   SendRequest(nsIFile* aFile, const ChromeFilePropertyBag& aBag,
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1559,22 +1559,16 @@ interface nsIDOMWindowUtils : nsISupport
    * property.
    */
   double computeAnimationDistance(in nsIDOMElement element,
                                   in AString property,
                                   in AString value1,
                                   in AString value2);
 
   /**
-   * Wrap an nsIFile in an DOM File
-   * Returns a File object.
-   */
-  nsISupports wrapDOMFile(in nsIFile aFile);
-
-  /**
    * Get the type of the currently focused html input, if any.
    */
   readonly attribute string focusedInputType;
 
   /**
    * Find the view ID for a given element. This is the reverse of
    * findElementWithViewId().
    */
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3170,30 +3170,32 @@ ContentChild::GetConstructedEventTarget(
 }
 
 void
 ContentChild::FileCreationRequest(nsID& aUUID, FileCreatorHelper* aHelper,
                                   const nsAString& aFullPath,
                                   const nsAString& aType,
                                   const nsAString& aName,
                                   const Optional<int64_t>& aLastModified,
+                                  bool aExistenceCheck,
                                   bool aIsFromNsIFile)
 {
   MOZ_ASSERT(aHelper);
 
   bool lastModifiedPassed = false;
   int64_t lastModified = 0;
   if (aLastModified.WasPassed()) {
     lastModifiedPassed = true;
     lastModified = aLastModified.Value();
   }
 
   Unused << SendFileCreationRequest(aUUID, nsString(aFullPath), nsString(aType),
                                     nsString(aName), lastModifiedPassed,
-                                    lastModified, aIsFromNsIFile);
+                                    lastModified, aExistenceCheck,
+                                    aIsFromNsIFile);
   mFileCreationPending.Put(aUUID, aHelper);
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvFileCreationResponse(const nsID& aUUID,
                                        const FileCreationResult& aResult)
 {
   FileCreatorHelper* helper = mFileCreationPending.GetWeak(aUUID);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -628,17 +628,17 @@ public:
                                              base::ProcessId aOtherPid);
 
   // This method is used by FileCreatorHelper for the creation of a BlobImpl.
   void
   FileCreationRequest(nsID& aUUID, FileCreatorHelper* aHelper,
                       const nsAString& aFullPath, const nsAString& aType,
                       const nsAString& aName,
                       const Optional<int64_t>& aLastModified,
-                      bool aIsFromNsIFile);
+                      bool aExistenceCheck, bool aIsFromNsIFile);
 
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
   void StartForceKillTimer();
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   virtual void ProcessingError(Result aCode, const char* aReason) override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5140,23 +5140,25 @@ ContentParent::RecvClassifyLocal(const U
 
 mozilla::ipc::IPCResult
 ContentParent::RecvFileCreationRequest(const nsID& aID,
                                        const nsString& aFullPath,
                                        const nsString& aType,
                                        const nsString& aName,
                                        const bool& aLastModifiedPassed,
                                        const int64_t& aLastModified,
+                                       const bool& aExistenceCheck,
                                        const bool& aIsFromNsIFile)
 {
   RefPtr<BlobImpl> blobImpl;
   nsresult rv =
     FileCreatorHelper::CreateBlobImplForIPC(aFullPath, aType, aName,
                                             aLastModifiedPassed,
-                                            aLastModified, aIsFromNsIFile,
+                                            aLastModified, aExistenceCheck,
+                                            aIsFromNsIFile,
                                             getter_AddRefs(blobImpl));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (!SendFileCreationResponse(aID, FileCreationErrorResult(rv))) {
       return IPC_FAIL_NO_REASON(this);
     }
 
     return IPC_OK();
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1107,16 +1107,17 @@ private:
 
   virtual mozilla::ipc::IPCResult RecvDeleteGetFilesRequest(const nsID& aID) override;
 
   virtual mozilla::ipc::IPCResult
   RecvFileCreationRequest(const nsID& aID, const nsString& aFullPath,
                           const nsString& aType, const nsString& aName,
                           const bool& aLastModifiedPassed,
                           const int64_t& aLastModified,
+                          const bool& aExistenceCheck,
                           const bool& aIsFromNsIFile) override;
 
   virtual mozilla::ipc::IPCResult RecvAccumulateChildHistograms(
     InfallibleTArray<Accumulation>&& aAccumulations) override;
   virtual mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms(
     InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
   virtual mozilla::ipc::IPCResult RecvUpdateChildScalars(
     InfallibleTArray<ScalarAction>&& aScalarActions) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1172,17 +1172,18 @@ parent:
      */
      async NotifyLowMemory();
 
      async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag);
      async DeleteGetFilesRequest(nsID aID);
 
      async FileCreationRequest(nsID aID, nsString aFullPath, nsString aType,
                                nsString aName, bool lastModifiedPassed,
-                               int64_t lastModified, bool aIsFromNsIFile);
+                               int64_t lastModified, bool aExistenceCheck,
+                               bool aIsFromNsIFile);
 
      async StoreAndBroadcastBlobURLRegistration(nsCString url, PBlob blob,
                                                 Principal principal);
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
      async BroadcastLocalStorageChange(nsString documentURI,
                                        nsString key,
--- a/dom/ipc/tests/test_bug1086684.html
+++ b/dom/ipc/tests/test_bug1086684.html
@@ -20,17 +20,17 @@
     function childFrameScript() {
       "use strict";
 
       let { MockFilePicker } =
         Components.utils.import("chrome://specialpowers/content/MockFilePicker.jsm", {});
 
       function parentReady(message) {
         MockFilePicker.init(content);
-        MockFilePicker.returnFiles = [message.data.file];
+        MockFilePicker.setFiles([message.data.file]);
         MockFilePicker.returnValue = MockFilePicker.returnOK;
 
         let input = content.document.getElementById("f");
         input.addEventListener("change", () => {
           MockFilePicker.cleanup();
           let value = input.value;
           message.target.sendAsyncMessage("testBug1086684:childDone", { value });
         });
--- a/dom/webidl/File.webidl
+++ b/dom/webidl/File.webidl
@@ -21,16 +21,17 @@ interface File : Blob {
 
 dictionary FilePropertyBag {
   DOMString type = "";
   long long lastModified;
 };
 
 dictionary ChromeFilePropertyBag : FilePropertyBag {
   DOMString name = "";
+  boolean existenceCheck = true;
 };
 
 // Mozilla extensions
 partial interface File {
   [GetterThrows, Deprecated="FileLastModifiedDate"]
   readonly attribute Date lastModifiedDate;
 
   [BinaryName="relativePath", Func="mozilla::dom::Directory::WebkitBlinkDirectoryPickerEnabled"]
--- a/layout/forms/test/test_bug536567_perwindowpb.html
+++ b/layout/forms/test/test_bug536567_perwindowpb.html
@@ -111,17 +111,17 @@ function runTest() {
     runTest();
   } else if (test == "clear history") {
     Services.obs.notifyObservers(null, "browser:purge-session-history", "");
     testIndex++;
     runTest();
   } else {
     var file = dirs[test[2]].clone();
     file.append("file.file");
-    MockFilePicker.returnFiles = [file];
+    MockFilePicker.setFiles([file]);
     content.setAttribute('src', domains[test[0]] + '/chrome/layout/forms/test/bug536567_subframe.html');
   }
 }
 
 function endTest() {
   for(var i = 0; i < dirs.length - 1; i++) {
     dirs[i].remove(true);
   }
--- a/mobile/android/components/FilePicker.js
+++ b/mobile/android/components/FilePicker.js
@@ -224,25 +224,23 @@ FilePicker.prototype = {
       this._filePath = file || null;
       this._promptActive = false;
 
       if (!file) {
         return;
       }
 
       if (this._domWin) {
-        let utils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-        this._domFile = utils.wrapDOMFile(this.file);
-        return;
+        return this._domWin.File.createFromNsIFile(this.file, { existenceCheck: false });
       }
 
-      return File.createFromNsIFile(this.file).then(domFile => {
-        this._domFile = domFile;
-      });
-    }).catch(() => {
+      return File.createFromNsIFile(this.file, { existenceCheck: false });
+    }).then(domFile => {
+      this._domFile = domFile;
+    }, () => {
     }).then(() => {
       if (this._callback) {
         this._callback.done(this._filePath ?
           Ci.nsIFilePicker.returnOK : Ci.nsIFilePicker.returnCancel);
       }
       delete this._callback;
     });
   },
--- a/testing/specialpowers/content/MockFilePicker.jsm
+++ b/testing/specialpowers/content/MockFilePicker.jsm
@@ -48,16 +48,17 @@ this.MockFilePicker = {
   filterXML: Ci.nsIFilePicker.filterXML,
   filterXUL: Ci.nsIFilePicker.filterXUL,
   filterApps: Ci.nsIFilePicker.filterApps,
   filterAllowURLs: Ci.nsIFilePicker.filterAllowURLs,
   filterAudio: Ci.nsIFilePicker.filterAudio,
   filterVideo: Ci.nsIFilePicker.filterVideo,
 
   window: null,
+  pendingPromises: [],
 
   init: function(window) {
     this.window = window;
 
     this.reset();
     this.factory = newFactory(window);
     if (!registrar.isCIDRegistered(newClassID)) {
       oldClassID = registrar.contractIDToCID(CONTRACT_ID);
@@ -68,17 +69,17 @@ this.MockFilePicker = {
   },
 
   reset: function() {
     this.appendFilterCallback = null;
     this.appendFiltersCallback = null;
     this.displayDirectory = null;
     this.filterIndex = 0;
     this.mode = null;
-    this.returnFiles = [];
+    this.returnData = [];
     this.returnValue = null;
     this.showCallback = null;
     this.afterOpenCallback = null;
     this.shown = false;
     this.showing = false;
   },
 
   cleanup: function() {
@@ -86,42 +87,77 @@ this.MockFilePicker = {
     this.reset();
     this.factory = null;
     if (oldFactory) {
       registrar.unregisterFactory(newClassID, previousFactory);
       registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
     }
   },
 
+  internalFileData(obj) {
+    return {
+      nsIFile: "nsIFile" in obj ? obj.nsIFile : null,
+      domFile: "domFile" in obj ? obj.domFile : null,
+      domDirectory: "domDirectory" in obj ? obj.domDirectory : null,
+    };
+  },
+
   useAnyFile: function() {
     var file = FileUtils.getDir("TmpD", [], false);
     file.append("testfile");
     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
-    this.returnFiles = [file];
+    let promise = this.window.File.createFromNsIFile(file)
+                  .then(domFile => domFile, () => null)
+                  // domFile can be null.
+                  .then(domFile => {
+                    this.returnData = [this.internalFileData({ nsIFile: file, domFile: domFile })];
+                  }).then(() => file);
+
+    this.pendingPromises = [promise];
+
+    // We return a promise in order to support some existing mochitests.
+    return promise;
   },
 
   useBlobFile: function() {
     var blob = new this.window.Blob([]);
     var file = new this.window.File([blob], 'helloworld.txt', { type: 'plain/text' });
-    this.returnFiles = [file];
+    this.returnData = [this.internalFileData({ domFile: file })];
+    this.pendingPromises = [];
   },
 
   useDirectory: function(aPath) {
     var directory = new this.window.Directory(aPath);
-    this.returnFiles = [directory];
+    this.returnData = [this.internalFileData({ domDirectory: directory })];
+    this.pendingPromises = [];
   },
 
-  isNsIFile: function(aFile) {
-    let ret = false;
-    try {
-      if (aFile.QueryInterface(Ci.nsIFile))
-        ret = true;
-    } catch(e) {}
+  setFiles(files) {
+    this.returnData = [];
+    this.pendingPromises = [];
+
+    for (let file of files) {
+      if (file instanceof this.window.File) {
+        this.returnData.push(this.internalFileData({ domFile: file }));
+      } else {
+        let promise = this.window.File.createFromNsIFile(file, { existenceCheck: false });
 
-    return ret;
+        promise.then(domFile => {
+          this.returnData.push(this.internalFileData({ nsIFile: file, domFile: domFile }));
+        });
+        this.pendingPromises.push(promise);
+      }
+    }
+  },
+
+  getNsIFile() {
+    if (this.returnData.length >= 1) {
+      return this.returnData[0].nsIFile;
+    }
+    return null;
   }
 };
 
 function MockFilePickerInstance(window) {
   this.window = window;
 };
 MockFilePickerInstance.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
@@ -139,106 +175,134 @@ MockFilePickerInstance.prototype = {
       MockFilePicker.appendFiltersCallback(this, aFilterMask);
   },
   defaultString: "",
   defaultExtension: "",
   parent: null,
   filterIndex: 0,
   displayDirectory: null,
   get file() {
-    if (MockFilePicker.returnFiles.length >= 1 &&
-        // window.File does not implement nsIFile
-        MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
-      return MockFilePicker.returnFiles[0];
+    if (MockFilePicker.returnData.length >= 1) {
+      return MockFilePicker.returnData[0].nsIFile;
     }
 
     return null;
   },
 
   // We don't support directories here.
   get domFileOrDirectory()  {
-    if (MockFilePicker.returnFiles.length >= 1) {
-      // window.File does not implement nsIFile
-      if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
-        return MockFilePicker.returnFiles[0];
-      }
+    if (MockFilePicker.returnData.length < 1) {
+      return null;
+    }
 
-      let utils = this.parent.QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIDOMWindowUtils);
-      return utils.wrapDOMFile(MockFilePicker.returnFiles[0]);
+    if (MockFilePicker.returnData[0].domFile) {
+      return MockFilePicker.returnData[0].domFile;
     }
+
+    if (MockFilePicker.returnData[0].domDirectory) {
+      return MockFilePicker.returnData[0].domDirectory;
+    }
+
     return null;
   },
   get fileURL() {
-    if (MockFilePicker.returnFiles.length >= 1 &&
-        // window.File does not implement nsIFile
-        MockFilePicker.isNsIFile(MockFilePicker.returnFiles[0])) {
-      return Services.io.newFileURI(MockFilePicker.returnFiles[0]);
+    if (MockFilePicker.returnData.length >= 1 &&
+        MockFilePicker.returnData[0].nsIFile) {
+      return Services.io.newFileURI(MockFilePicker.returnData[0].nsIFile);
     }
 
     return null;
   },
   get files() {
     return {
       index: 0,
       QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
       hasMoreElements: function() {
-        return this.index < MockFilePicker.returnFiles.length;
+        return this.index < MockFilePicker.returnData.length;
       },
       getNext: function() {
-        // window.File does not implement nsIFile
-        if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[this.index])) {
+        if (!MockFilePicker.returnData[this.index].nsIFile) {
           return null;
         }
-        return MockFilePicker.returnFiles[this.index++];
+        return MockFilePicker.returnData[this.index++].nsIFile;
       }
     };
   },
   get domFileOrDirectoryEnumerator()  {
     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;
+        return this.index < MockFilePicker.returnData.length;
       },
       getNext: function() {
         // window.File does not implement nsIFile
-        if (!MockFilePicker.isNsIFile(MockFilePicker.returnFiles[this.index])) {
-          return MockFilePicker.returnFiles[this.index++];
+        if (MockFilePicker.returnData[this.index].domFile) {
+          return MockFilePicker.returnData[this.index++].domFile;
         }
-        return utils.wrapDOMFile(MockFilePicker.returnFiles[this.index++]);
+
+        if (MockFilePicker.returnData[this.index].domDirectory) {
+          return MockFilePicker.returnData[this.index++].domDirectory;
+        }
+
+        return null;
       }
     };
   },
   show: function() {
     throw "This is not implemented";
   },
-  _openInternal: function() {
-    MockFilePicker.displayDirectory = this.displayDirectory;
-    MockFilePicker.shown = true;
-    if (typeof MockFilePicker.showCallback == "function") {
-      var returnValue = MockFilePicker.showCallback(this);
-      if (typeof returnValue != "undefined")
-        return returnValue;
-    }
-    return MockFilePicker.returnValue;
-  },
   open: function(aFilePickerShownCallback) {
     MockFilePicker.showing = true;
-    this.window.setTimeout(function() {
-      let result = Components.interfaces.nsIFilePicker.returnCancel;
-      try {
-        result = this._openInternal();
-      } catch(ex) {
-      }
-      if (aFilePickerShownCallback) {
-        aFilePickerShownCallback.done(result);
-      }
-      if (typeof MockFilePicker.afterOpenCallback == "function") {
-        this.window.setTimeout(() => {
-          MockFilePicker.afterOpenCallback(this);
-        }, 0);
-      }
-    }.bind(this), 0);
+    this.window.setTimeout(() => {
+      // Maybe all the pending promises are already resolved, but we want to be sure.
+      Promise.all(MockFilePicker.pendingPromises).then(() => {
+        return Ci.nsIFilePicker.returnOK;
+      }, () => {
+        return Ci.nsIFilePicker.returnCancel;
+      }).then(result => {
+        // Nothing else has to be done.
+        MockFilePicker.pendingPromises = [];
+
+        if (result == Ci.nsIFilePicker.returnCancel) {
+          return result;
+        }
+
+        MockFilePicker.displayDirectory = this.displayDirectory;
+        MockFilePicker.shown = true;
+        if (typeof MockFilePicker.showCallback == "function") {
+          try {
+            var returnValue = MockFilePicker.showCallback(this);
+            if (typeof returnValue != "undefined") {
+              return returnValue;
+            }
+          } catch(ex) {
+            return Ci.nsIFilePicker.returnCancel;
+          }
+        }
+
+        return MockFilePicker.returnValue;
+      }).then(result => {
+        // Some additional result file can be set by the callback. Let's
+        // resolve the pending promises again.
+        return Promise.all(MockFilePicker.pendingPromises).then(() => {
+          return result;
+        }, () => {
+          return Ci.nsIFilePicker.returnCancel;
+        });
+      }).then(result => {
+        MockFilePicker.pendingPromises = [];
+
+        if (aFilePickerShownCallback) {
+          aFilePickerShownCallback.done(result);
+        }
+
+        if (typeof MockFilePicker.afterOpenCallback == "function") {
+          this.window.setTimeout(() => {
+            MockFilePicker.afterOpenCallback(this);
+          }, 0);
+        }
+      });
+    });
   }
 };
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
@@ -14,42 +14,43 @@
 
 add_task(function* test_downloads_saveAs() {
   function background() {
     const url = URL.createObjectURL(new Blob(["file content"]));
     browser.test.onMessage.addListener(async () => {
       try {
         let id = await browser.downloads.download({url, saveAs: true});
         browser.downloads.onChanged.addListener(delta => {
-          if (delta.state.current === "complete") {
+          if (delta.id == id && delta.state.current === "complete") {
             browser.test.sendMessage("done", {ok: true, id});
           }
         });
       } catch ({message}) {
         browser.test.sendMessage("done", {ok: false, message});
       }
     });
     browser.test.sendMessage("ready");
   }
 
   const {MockFilePicker} = SpecialPowers;
   const manifest = {background, manifest: {permissions: ["downloads"]}};
   const extension = ExtensionTestUtils.loadExtension(manifest);
 
   MockFilePicker.init(window);
-  MockFilePicker.useAnyFile();
-  const [file] = MockFilePicker.returnFiles;
+  const file = yield MockFilePicker.useAnyFile();
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
   extension.sendMessage("download");
   let result = yield extension.awaitMessage("done");
 
   ok(result.ok, "downloads.download() works with saveAs");
+
+  ok(file.exists(), "the file exists.");
   is(file.fileSize, 12, "downloaded file is the correct size");
   file.remove(false);
 
   // Test the user canceling the save dialog.
   MockFilePicker.returnValue = MockFilePicker.returnCancel;
 
   extension.sendMessage("download");
   result = yield extension.awaitMessage("done");
--- a/toolkit/components/filepicker/nsFilePicker.js
+++ b/toolkit/components/filepicker/nsFilePicker.js
@@ -99,45 +99,16 @@ nsFilePicker.prototype = {
     return enumerator ? enumerator.mFiles[0] : null;
   },
 
   /* readonly attribute nsISimpleEnumerator domFileOrDirectoryEnumerator; */
   get domFileOrDirectoryEnumerator() {
     if (!this.mFilesEnumerator) {
       return null;
     }
-
-    if (!this.mDOMFilesEnumerator) {
-      this.mDOMFilesEnumerator = {
-        QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleEnumerator]),
-
-        mFiles: [],
-        mIndex: 0,
-
-        hasMoreElements() {
-          return (this.mIndex < this.mFiles.length);
-        },
-
-        getNext() {
-          if (this.mIndex >= this.mFiles.length) {
-            throw Components.results.NS_ERROR_FAILURE;
-          }
-          return this.mFiles[this.mIndex++];
-        }
-      };
-
-      var utils = this.mParentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                                    .getInterface(Components.interfaces.nsIDOMWindowUtils);
-
-      for (var i = 0; i < this.mFilesEnumerator.mFiles.length; ++i) {
-        var file = utils.wrapDOMFile(this.mFilesEnumerator.mFiles[i]);
-        this.mDOMFilesEnumerator.mFiles.push(file);
-      }
-    }
-
     return this.mDOMFilesEnumerator;
   },
 
   /* readonly attribute nsIURI fileURL; */
   get fileURL() {
     if (this.mFileURL)
       return this.mFileURL;
 
@@ -225,26 +196,64 @@ nsFilePicker.prototype = {
   appendFilter(title, extensions) {
     this.mFilterTitles.push(title);
     this.mFilters.push(extensions);
   },
 
   open(aFilePickerShownCallback) {
     var tm = Components.classes["@mozilla.org/thread-manager;1"]
                        .getService(Components.interfaces.nsIThreadManager);
-    tm.mainThread.dispatch(function() {
+    tm.mainThread.dispatch(() => {
       let result = Components.interfaces.nsIFilePicker.returnCancel;
       try {
         result = this.show();
       } catch (ex) {
       }
-      if (aFilePickerShownCallback) {
-        aFilePickerShownCallback.done(result);
+
+      let promises = [];
+
+      // Let's create the DOMFileEnumerator right now because it requires some
+      // async operation.
+      if (this.mFilesEnumerator) {
+        this.mDOMFilesEnumerator = {
+          QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleEnumerator]),
+
+          mFiles: [],
+          mIndex: 0,
+
+          hasMoreElements() {
+            return (this.mIndex < this.mFiles.length);
+          },
+
+          getNext() {
+            if (this.mIndex >= this.mFiles.length) {
+              throw Components.results.NS_ERROR_FAILURE;
+            }
+            return this.mFiles[this.mIndex++];
+          }
+        };
+
+        for (let i = 0; i < this.mFilesEnumerator.mFiles.length; ++i) {
+          if (this.mFilesEnumerator.mFiles[i].exists()) {
+            let promise =
+              this.mParentWindow.File.createFromNsIFile(
+                this.mFilesEnumerator.mFiles[i]).then(file => {
+                  this.mDOMFilesEnumerator.mFiles.push(file);
+                });
+            promises.push(promise);
+          }
+        }
       }
-    }.bind(this), Components.interfaces.nsIThread.DISPATCH_NORMAL);
+
+      Promise.all(promises).then(() => {
+        if (aFilePickerShownCallback) {
+          aFilePickerShownCallback.done(result);
+        }
+      });
+    }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
   },
 
   show() {
     var o = {};
     o.title = this.mTitle;
     o.mode = this.mMode;
     o.displayDirectory = this.mDisplayDirectory;
     o.defaultString = this.mDefaultString;
--- a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
+++ b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
@@ -58,32 +58,31 @@ function* do_test(test) {
   });
 
   if (test.value) {
     info("Creating mock filepicker to select files");
     let MockFilePicker = SpecialPowers.MockFilePicker;
     MockFilePicker.init(window);
     MockFilePicker.returnValue = MockFilePicker.returnOK;
     MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", [], false);
-    MockFilePicker.returnFiles = [tempFile];
+    MockFilePicker.setFiles([tempFile]);
+    MockFilePicker.afterOpenCallback = MockFilePicker.cleanup;
 
     try {
       // Open the File Picker dialog (MockFilePicker) to select
       // the files for the test.
       yield BrowserTestUtils.synthesizeMouseAtCenter("#test_input", {}, tab.linkedBrowser);
       info("Waiting for the input to have the requisite files");
       yield ContentTask.spawn(tab.linkedBrowser, {}, function*() {
         let input = content.document.querySelector("#test_input");
         yield ContentTaskUtils.waitForCondition(() => input.files.length,
           "The input should have at least one file selected");
         info(`The input has ${input.files.length} file(s) selected.`);
       });
-    } finally {
-      MockFilePicker.cleanup();
-    }
+    } catch (e) {}
   } else {
     info("No real file selection required.");
   }
 
   let awaitTooltipOpen = new Promise(resolve => {
     let tooltipId = Services.appinfo.browserTabsRemoteAutostart ?
                       "remoteBrowserTooltip" :
                       "aHTMLTooltip";
--- a/toolkit/content/tests/browser/browser_save_resend_postdata.js
+++ b/toolkit/content/tests/browser/browser_save_resend_postdata.js
@@ -48,17 +48,17 @@ function test() {
 
   function handleInnerSubmit() {
     gBrowser.removeEventListener("DOMContentLoaded", handleInnerSubmit);
 
     // Create the folder the page will be saved into.
     var destDir = createTemporarySaveDirectory();
     var file = destDir.clone();
     file.append("no_default_file_name");
-    MockFilePicker.returnFiles = [file];
+    MockFilePicker.setFiles([file]);
     MockFilePicker.showCallback = function(fp) {
       MockFilePicker.filterIndex = 1; // kSaveAsType_URL
     };
 
     mockTransferCallback = onTransferComplete;
     mockTransferRegisterer.register();
 
     registerCleanupFunction(function() {
@@ -75,17 +75,17 @@ function test() {
                  docToSave.referrer ? makeURI(docToSave.referrer) : null,
                  docToSave, false, null);
   }
 
   function onTransferComplete(downloadSuccess) {
     ok(downloadSuccess, "The inner frame should have been downloaded successfully");
 
     // Read the entire saved file.
-    var file = MockFilePicker.returnFiles[0];
+    var file = MockFilePicker.getNsIFile();
     var fileContents = readShortFile(file);
 
     // Check if outer POST data is found (bug 471962).
     is(fileContents.indexOf("inputfield=outer"), -1,
        "The saved inner frame does not contain outer POST data");
 
     // Check if inner POST data is found (bug 485196).
     isnot(fileContents.indexOf("inputfield=inner"), -1,
--- a/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
@@ -86,17 +86,17 @@ function checkInstallConfirmation(...url
 
 add_task(function* test_install_from_file() {
   gManagerWindow = yield open_manager("addons://list/extension");
 
   var filePaths = [
                    get_addon_file_url("browser_bug567127_1.xpi"),
                    get_addon_file_url("browser_bug567127_2.xpi")
                   ];
-  MockFilePicker.returnFiles = filePaths.map(aPath => aPath.file);
+  MockFilePicker.setFiles(filePaths.map(aPath => aPath.file));
 
   // Set handler that executes the core test after the window opens,
   // and resolves the promise when the window closes
   let pInstallURIClosed = checkInstallConfirmation(...filePaths.map(path => path.spec));
 
   gManagerWindow.gViewController.doCommand("cmd_installFromFile");
 
   yield pInstallURIClosed;
--- a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings.js
@@ -282,31 +282,31 @@ add_test(function() {
     input = gManagerWindow.document.getAnonymousElementByAttribute(settings[6], "anonid", "input");
     is(input.value, "", "Label value should be empty");
     is(input.tooltipText, "", "Label tooltip should be empty");
 
     var testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
     testFile.append("\u2622");
     var curProcD = Services.dirsvc.get("CurProcD", Ci.nsIFile);
 
-    MockFilePicker.returnFiles = [testFile];
+    MockFilePicker.setFiles([testFile]);
     MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
 
     let promise = new Promise(resolve => {
       MockFilePicker.afterOpenCallback = resolve;
       EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
     });
 
     promise.then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeOpen, "File picker mode should be open file");
       is(input.value, testFile.path, "Label value should match file chosen");
       is(input.tooltipText, testFile.path, "Label tooltip should match file chosen");
       is(Preferences.get("extensions.inlinesettings1.file", "wrong"), testFile.path, "File pref should match file chosen");
 
-      MockFilePicker.returnFiles = [curProcD];
+      MockFilePicker.setFiles([curProcD]);
       MockFilePicker.returnValue = Ci.nsIFilePicker.returnCancel;
 
       return new Promise(resolve => {
         MockFilePicker.afterOpenCallback = resolve;
         EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
       });
     }).then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeOpen, "File picker mode should be open file");
@@ -315,30 +315,30 @@ add_test(function() {
       is(Preferences.get("extensions.inlinesettings1.file", "wrong"), testFile.path, "File pref should not have changed");
 
       ok(!settings[7].hasAttribute("first-row"), "Not the first row");
       button = gManagerWindow.document.getAnonymousElementByAttribute(settings[7], "anonid", "button");
       input = gManagerWindow.document.getAnonymousElementByAttribute(settings[7], "anonid", "input");
       is(input.value, "", "Label value should be empty");
       is(input.tooltipText, "", "Label tooltip should be empty");
 
-      MockFilePicker.returnFiles = [testFile];
+      MockFilePicker.setFiles([testFile]);
       MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
 
       return new Promise(resolve => {
         MockFilePicker.afterOpenCallback = resolve;
         EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
       });
     }).then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeGetFolder, "File picker mode should be directory");
       is(input.value, testFile.path, "Label value should match file chosen");
       is(input.tooltipText, testFile.path, "Label tooltip should match file chosen");
       is(Preferences.get("extensions.inlinesettings1.directory", "wrong"), testFile.path, "Directory pref should match file chosen");
 
-      MockFilePicker.returnFiles = [curProcD];
+      MockFilePicker.setFiles([curProcD]);
       MockFilePicker.returnValue = Ci.nsIFilePicker.returnCancel;
 
       return new Promise(resolve => {
         MockFilePicker.afterOpenCallback = resolve;
         EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
       });
     }).then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeGetFolder, "File picker mode should be directory");
--- a/toolkit/mozapps/extensions/test/browser/browser_inlinesettings_info.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_inlinesettings_info.js
@@ -277,31 +277,31 @@ add_test(function() {
 
     input = gManagerWindow.document.getAnonymousElementByAttribute(settings[6], "anonid", "input");
     is(input.value, "", "Label value should be empty");
     is(input.tooltipText, "", "Label tooltip should be empty");
 
     var profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
     var curProcD = Services.dirsvc.get("CurProcD", Ci.nsIFile);
 
-    MockFilePicker.returnFiles = [profD];
+    MockFilePicker.setFiles([profD]);
     MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
 
     let promise = new Promise(resolve => {
       MockFilePicker.afterOpenCallback = resolve;
       EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
     });
 
     promise.then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeOpen, "File picker mode should be open file");
       is(input.value, profD.path, "Label value should match file chosen");
       is(input.tooltipText, profD.path, "Label tooltip should match file chosen");
       is(Services.prefs.getCharPref("extensions.inlinesettings1.file"), profD.path, "File pref should match file chosen");
 
-      MockFilePicker.returnFiles = [curProcD];
+      MockFilePicker.setFiles([curProcD]);
       MockFilePicker.returnValue = Ci.nsIFilePicker.returnCancel;
 
       return promise = new Promise(resolve => {
         MockFilePicker.afterOpenCallback = resolve;
         EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
       });
     }).then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeOpen, "File picker mode should be open file");
@@ -310,30 +310,30 @@ add_test(function() {
       is(Services.prefs.getCharPref("extensions.inlinesettings1.file"), profD.path, "File pref should not have changed");
 
       ok(!settings[7].hasAttribute("first-row"), "Not the first row");
       button = gManagerWindow.document.getAnonymousElementByAttribute(settings[7], "anonid", "button");
       input = gManagerWindow.document.getAnonymousElementByAttribute(settings[7], "anonid", "input");
       is(input.value, "", "Label value should be empty");
       is(input.tooltipText, "", "Label tooltip should be empty");
 
-      MockFilePicker.returnFiles = [profD];
+      MockFilePicker.setFiles([profD]);
       MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
 
       return new Promise(resolve => {
         MockFilePicker.afterOpenCallback = resolve;
         EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
       });
     }).then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeGetFolder, "File picker mode should be directory");
       is(input.value, profD.path, "Label value should match file chosen");
       is(input.tooltipText, profD.path, "Label tooltip should match file chosen");
       is(Services.prefs.getCharPref("extensions.inlinesettings1.directory"), profD.path, "Directory pref should match file chosen");
 
-      MockFilePicker.returnFiles = [curProcD];
+      MockFilePicker.setFiles([curProcD]);
       MockFilePicker.returnValue = Ci.nsIFilePicker.returnCancel;
 
       return new Promise(resolve => {
         MockFilePicker.afterOpenCallback = resolve;
         EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
       });
     }).then(() => {
       is(MockFilePicker.mode, Ci.nsIFilePicker.modeGetFolder, "File picker mode should be directory");