Bug 1335536 - File.createFromNsIFile and File.createFromFileName should be async - part 1 - tests, r=smaug draft
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 08 Feb 2017 10:18:32 +0100
changeset 480537 1e8f56d4e7d70ef91f20c4d35cea6877afd0d19c
parent 480508 3a95aa4246653a7863914ffec032897d13359fb0
child 480538 35902d02315744816e9cf5ce7e035742b5fa6733
push id44574
push userbmo:jbeich@FreeBSD.org
push dateWed, 08 Feb 2017 13:34:21 +0000
reviewerssmaug
bugs1335536
milestone54.0a1
Bug 1335536 - File.createFromNsIFile and File.createFromFileName should be async - part 1 - tests, r=smaug
browser/components/migration/MSMigrationUtils.jsm
dom/base/test/bug403852_fileOpener.js
dom/base/test/bug578096LoadChromeScript.js
dom/base/test/chrome/test_bug914381.html
dom/base/test/chrome/test_fileconstructor.xul
dom/base/test/file_bug1198095.js
dom/base/test/script_bug1238440.js
dom/base/test/script_postmessages_fileList.js
dom/file/File.cpp
dom/file/File.h
dom/file/tests/create_file_objects.js
dom/file/tests/fileapi_chromeScript.js
dom/filesystem/compat/tests/script_entries.js
dom/filesystem/tests/script_fileList.js
dom/html/test/formSubmission_chrome.js
dom/html/test/script_fakepath.js
dom/html/test/simpleFileOpener.js
dom/indexedDB/test/unit/test_wasm_recompile.js
dom/indexedDB/test/unit/xpcshell-head-parent-process.js
dom/webidl/File.webidl
dom/workers/test/fileapi_chromeScript.js
dom/workers/test/script_createFile.js
js/xpconnect/tests/unit/component-file.js
js/xpconnect/tests/unit/test_file.js
js/xpconnect/tests/unit/test_file2.js
mobile/android/components/FilePicker.js
testing/marionette/interaction.js
testing/specialpowers/content/SpecialPowersObserver.jsm
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/crashreporter/CrashSubmit.jsm
toolkit/modules/PropertyListUtils.jsm
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -536,38 +536,42 @@ Cookies.prototype = {
       this.ctypesKernelHelpers.finalize();
 
       aCallback(success);
     }).apply(this);
     cookiesGenerator.next();
   },
 
   _readCookieFile(aFile, aCallback) {
-    let fileReader = new FileReader();
-    let onLoadEnd = () => {
-      fileReader.removeEventListener("loadend", onLoadEnd);
+    File.createFromNsIFile(aFile).then(aFile => {
+      let fileReader = new FileReader();
+      let onLoadEnd = () => {
+        fileReader.removeEventListener("loadend", onLoadEnd);
 
-      if (fileReader.readyState != fileReader.DONE) {
-        Cu.reportError("Could not read cookie contents: " + fileReader.error);
-        aCallback(false);
-        return;
-      }
+        if (fileReader.readyState != fileReader.DONE) {
+          Cu.reportError("Could not read cookie contents: " + fileReader.error);
+          aCallback(false);
+          return;
+        }
 
-      let success = true;
-      try {
-        this._parseCookieBuffer(fileReader.result);
-      } catch (ex) {
-        Components.utils.reportError("Unable to migrate cookie: " + ex);
-        success = false;
-      } finally {
-        aCallback(success);
-      }
-    };
-    fileReader.addEventListener("loadend", onLoadEnd);
-    fileReader.readAsText(File.createFromNsIFile(aFile));
+        let success = true;
+        try {
+          this._parseCookieBuffer(fileReader.result);
+        } catch (ex) {
+          Components.utils.reportError("Unable to migrate cookie: " + ex);
+          success = false;
+        } finally {
+          aCallback(success);
+        }
+      };
+      fileReader.addEventListener("loadend", onLoadEnd);
+      fileReader.readAsText(aFile);
+    }, () => {
+      aCallback(false);
+    });
   },
 
   /**
    * Parses a cookie file buffer and returns an array of the contained cookies.
    *
    * The cookie file format is a newline-separated-values with a "*" used as
    * delimeter between multiple records.
    * Each cookie has the following fields:
--- a/dom/base/test/bug403852_fileOpener.js
+++ b/dom/base/test/bug403852_fileOpener.js
@@ -3,15 +3,19 @@ Cu.importGlobalProperties(["File"]);
 
 var testFile = Cc["@mozilla.org/file/directory_service;1"]
                  .getService(Ci.nsIDirectoryService)
                  .QueryInterface(Ci.nsIProperties)
                  .get("ProfD", Ci.nsIFile);
 testFile.append("prefs.js");
 
 addMessageListener("file.open", function () {
-  sendAsyncMessage("file.opened", {
-    file: File.createFromNsIFile(testFile),
-    mtime: testFile.lastModifiedTime,
-    fileWithDate: File.createFromNsIFile(testFile, { lastModified: 123 }),
-    fileDate: 123,
+  File.createFromNsIFile(testFile).then(function(file) {
+    File.createFromNsIFile(testFile, { lastModified: 123 }).then(function(fileWithDate) {
+      sendAsyncMessage("file.opened", {
+        file,
+        mtime: testFile.lastModifiedTime,
+        fileWithDate,
+        fileDate: 123,
+      });
+    });
   });
 });
--- a/dom/base/test/bug578096LoadChromeScript.js
+++ b/dom/base/test/bug578096LoadChromeScript.js
@@ -2,15 +2,17 @@ var file;
 Components.utils.importGlobalProperties(["File"]);
 
 addMessageListener("file.create", function (message) {
   file = Components.classes["@mozilla.org/file/directory_service;1"]
              .getService(Components.interfaces.nsIProperties)
              .get("TmpD", Components.interfaces.nsIFile);
   file.append("foo.txt");
   file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
-  sendAsyncMessage("file.created", File.createFromNsIFile(file));
+  File.createFromNsIFile(file).then(function(domFile) {
+    sendAsyncMessage("file.created", domFile);
+  });
 });
 
 addMessageListener("file.remove", function (message) {
   file.remove(false);
   sendAsyncMessage("file.removed", {});
 });
--- a/dom/base/test/chrome/test_bug914381.html
+++ b/dom/base/test/chrome/test_bug914381.html
@@ -30,19 +30,28 @@ function createFileWithData(fileData) {
   outStream.write(fileData, fileData.length);
   outStream.close();
 
   return testFile;
 }
 
 /** Test for Bug 914381. File's created in JS using an nsIFile should allow mozGetFullPathInternal calls to succeed **/
 var file = createFileWithData("Test bug 914381");
-var f = File.createFromNsIFile(file);
-is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
-is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile");
+File.createFromNsIFile(file).then(f => {
+  is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+  is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile");
+})
+.then(() => {
+  return File.createFromFileName(file.path);
+})
+.then(f => {
+  is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+  is(f.mozFullPath, "", "mozFullPath returns blank if created with a string");
+})
+.then(() => {
+  SimpleTest.finish();
+});
 
-f = File.createFromFileName(file.path);
-is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
-is(f.mozFullPath, "", "mozFullPath returns blank if created with a string");
+SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/base/test/chrome/test_fileconstructor.xul
+++ b/dom/base/test/chrome/test_fileconstructor.xul
@@ -37,36 +37,46 @@ var file = Components.classes["@mozilla.
 // man I wish this were simpler ...
 file.append("chrome");
 file.append("dom");
 file.append("base");
 file.append("test");
 file.append("chrome");
 file.append("fileconstructor_file.png");
 
-doTest(File.createFromFileName(file.path));
-doTest(File.createFromNsIFile(file));
-function doTest(domfile) {
+File.createFromFileName(file.path).then(function(domFile) {
+  ok(domfile instanceof File, "File() should return a File");
+  is(domfile.type, "image/png", "File should be a PNG");
+  is(domfile.size, 95, "File has size 95 (and more importantly we can read it)");
+})
+.then(() => {
+  return File.createFromNsIFile(file);
+})
+.then(function(domFile) {
   ok(domfile instanceof File, "File() should return a File");
   is(domfile.type, "image/png", "File should be a PNG");
   is(domfile.size, 95, "File has size 95 (and more importantly we can read it)");
-}
-
-try {
-  var nonexistentfile = File.createFromFileName("i/sure/hope/this/does/not/exist/anywhere.txt");
+})
+.then(function() {
+  return File.createFromFileName("i/sure/hope/this/does/not/exist/anywhere.txt");
+})
+.then(function() {
   ok(false, "This should never be reached!");
-} catch (e) {
+}, function() {
   ok(true, "Attempt to construct a non-existent file should fail.");
-}
-
-try {
+}).then(function() {
   var dir = Components.classes["@mozilla.org/file/directory_service;1"]
                       .getService(Components.interfaces.nsIProperties)
                       .get("CurWorkD", Components.interfaces.nsIFile);
-  var dirfile = File.createFromNsIFile(dir);
+  return File.createFromNsIFile(dir);
+}).then(function() {
   ok(false, "This should never be reached!");
-} catch (e) {
+}, function() {
   ok(true, "Attempt to construct a file from a directory should fail.");
-}
+}).then(function() {
+  SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
 ]]>
 </script>
 
 </window>
--- a/dom/base/test/file_bug1198095.js
+++ b/dom/base/test/file_bug1198095.js
@@ -8,19 +8,22 @@ function createFileWithData(message) {
 
   var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
   outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
                  0o666, 0);
 
   outStream.write(message, message.length);
   outStream.close();
 
-  var domFile = File.createFromNsIFile(testFile);
-  return domFile;
+  return File.createFromNsIFile(testFile);
 }
 
 addMessageListener("file.open", function (message) {
-  sendAsyncMessage("file.opened", createFileWithData(message));
+  createFileWithData(message).then(function(file) {
+    sendAsyncMessage("file.opened", file);
+  });
 });
 
 addMessageListener("file.modify", function (message) {
-  sendAsyncMessage("file.modified", createFileWithData(message));
+  createFileWithData(message).then(function(file) {
+    sendAsyncMessage("file.modified", file);
+  });
 });
--- a/dom/base/test/script_bug1238440.js
+++ b/dom/base/test/script_bug1238440.js
@@ -5,18 +5,18 @@ var tmpFile;
 
 function writeFile(text, answer) {
   var stream = Cc["@mozilla.org/network/file-output-stream;1"]
                  .createInstance(Ci.nsIFileOutputStream);
   stream.init(tmpFile, 0x02 | 0x08 | 0x10, 0o600, 0);
   stream.write(text, text.length);
   stream.close();
 
-  sendAsyncMessage(answer, {
-    file: File.createFromNsIFile(tmpFile)
+  File.createFromNsIFile(tmpFile).then(function(file) {
+    sendAsyncMessage(answer, { file });
   });
 }
 
 addMessageListener("file.open", function (e) {
   tmpFile = Cc["@mozilla.org/file/directory_service;1"]
               .getService(Ci.nsIDirectoryService)
               .QueryInterface(Ci.nsIProperties)
               .get('TmpD', Ci.nsIFile)
--- a/dom/base/test/script_postmessages_fileList.js
+++ b/dom/base/test/script_postmessages_fileList.js
@@ -3,18 +3,18 @@ Cu.importGlobalProperties(["File"]);
 
 addMessageListener("file.open", function () {
   var testFile = Cc["@mozilla.org/file/directory_service;1"]
                    .getService(Ci.nsIDirectoryService)
                    .QueryInterface(Ci.nsIProperties)
                    .get("ProfD", Ci.nsIFile);
   testFile.append("prefs.js");
 
-  sendAsyncMessage("file.opened", {
-    file: File.createFromNsIFile(testFile)
+  File.createFromNsIFile(testFile).then(function(file) {
+    sendAsyncMessage("file.opened", { file });
   });
 });
 
 addMessageListener("dir.open", function () {
   var testFile = Cc["@mozilla.org/file/directory_service;1"]
                    .getService(Ci.nsIDirectoryService)
                    .QueryInterface(Ci.nsIProperties)
                    .get("ProfD", Ci.nsIFile);
--- a/dom/file/File.cpp
+++ b/dom/file/File.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/SHA1.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/FileBinding.h"
 #include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "nsThreadUtils.h"
 #include "nsStreamUtils.h"
 #include "SlicedInputStream.h"
 
 namespace mozilla {
 namespace dom {
@@ -576,17 +577,17 @@ File::Constructor(const GlobalObject& aG
   if (aBag.mLastModified.WasPassed()) {
     impl->SetLastModified(aBag.mLastModified.Value());
   }
 
   RefPtr<File> file = new File(aGlobal.GetAsSupports(), impl);
   return file.forget();
 }
 
-/* static */ already_AddRefed<File>
+/* static */ already_AddRefed<Promise>
 File::CreateFromNsIFile(const GlobalObject& aGlobal,
                         nsIFile* aData,
                         const ChromeFilePropertyBag& aBag,
                         SystemCallerGuarantee aGuarantee,
                         ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -598,21 +599,29 @@ File::CreateFromNsIFile(const GlobalObje
     return nullptr;
   }
   MOZ_ASSERT(impl->IsFile());
 
   if (aBag.mLastModified.WasPassed()) {
     impl->SetLastModified(aBag.mLastModified.Value());
   }
 
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   RefPtr<File> domFile = new File(aGlobal.GetAsSupports(), impl);
-  return domFile.forget();
+  promise->MaybeResolve(domFile);
+
+  return promise.forget();
 }
 
-/* static */ already_AddRefed<File>
+/* static */ already_AddRefed<Promise>
 File::CreateFromFileName(const GlobalObject& aGlobal,
                          const nsAString& aData,
                          const ChromeFilePropertyBag& aBag,
                          SystemCallerGuarantee aGuarantee,
                          ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
 
@@ -622,18 +631,26 @@ File::CreateFromFileName(const GlobalObj
     return nullptr;
   }
   MOZ_ASSERT(impl->IsFile());
 
   if (aBag.mLastModified.WasPassed()) {
     impl->SetLastModified(aBag.mLastModified.Value());
   }
 
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
   RefPtr<File> domFile = new File(aGlobal.GetAsSupports(), impl);
-  return domFile.forget();
+  promise->MaybeResolve(domFile);
+
+  return promise.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////
 // mozilla::dom::BlobImpl implementation
 
 already_AddRefed<BlobImpl>
 BlobImpl::Slice(const Optional<int64_t>& aStart,
                 const Optional<int64_t>& aEnd,
--- a/dom/file/File.h
+++ b/dom/file/File.h
@@ -38,16 +38,17 @@ namespace mozilla {
 namespace dom {
 
 struct BlobPropertyBag;
 struct ChromeFilePropertyBag;
 struct FilePropertyBag;
 class BlobImpl;
 class File;
 class OwningArrayBufferOrArrayBufferViewOrBlobOrUSVString;
+class Promise;
 
 class Blob : public nsIDOMBlob
            , public nsIXHRSendable
            , public nsIMutable
            , public nsSupportsWeakReference
            , public nsWrapperCache
 {
 public:
@@ -199,25 +200,25 @@ public:
   static already_AddRefed<File>
   Constructor(const GlobalObject& aGlobal,
               const Sequence<BlobPart>& aData,
               const nsAString& aName,
               const FilePropertyBag& aBag,
               ErrorResult& aRv);
 
   // ChromeOnly
-  static already_AddRefed<File>
+  static already_AddRefed<Promise>
   CreateFromFileName(const GlobalObject& aGlobal,
                      const nsAString& aData,
                      const ChromeFilePropertyBag& aBag,
                      SystemCallerGuarantee aGuarantee,
                      ErrorResult& aRv);
 
   // ChromeOnly
-  static already_AddRefed<File>
+  static already_AddRefed<Promise>
   CreateFromNsIFile(const GlobalObject& aGlobal,
                     nsIFile* aData,
                     const ChromeFilePropertyBag& aBag,
                     SystemCallerGuarantee aGuarantee,
                     ErrorResult& aRv);
 
   void GetName(nsAString& aName) const;
 
--- a/dom/file/tests/create_file_objects.js
+++ b/dom/file/tests/create_file_objects.js
@@ -1,10 +1,15 @@
 Components.utils.importGlobalProperties(['File']);
 
 addMessageListener("create-file-objects", function(message) {
   let files = []
+  let promises = [];
   for (fileName of message.fileNames) {
-    files.push(File.createFromFileName(fileName));
+    promises.push(File.createFromFileName(fileName).then(function(file) {
+      files.push(file);
+    }));
   }
 
-  sendAsyncMessage("created-file-objects", files);
+  Promise.all(promises).then(function() {
+    sendAsyncMessage("created-file-objects", files);
+  });
 });
--- a/dom/file/tests/fileapi_chromeScript.js
+++ b/dom/file/tests/fileapi_chromeScript.js
@@ -12,18 +12,31 @@ function createFileWithData(fileData) {
   var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
   outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
                  0o666, 0);
   if (willDelete) {
     fileData = "some irrelevant test data\n";
   }
   outStream.write(fileData, fileData.length);
   outStream.close();
-  var domFile = File.createFromNsIFile(testFile);
-  if (willDelete) {
-    testFile.remove(/* recursive: */ false);
-  }
-  return domFile;
+  return File.createFromNsIFile(testFile).then(domFile => {
+    if (willDelete) {
+      testFile.remove(/* recursive: */ false);
+    }
+
+    return domFile;
+  });
 }
 
 addMessageListener("files.open", function (message) {
-  sendAsyncMessage("files.opened", message.map(createFileWithData));
+  let promises = [];
+  let list = [];
+
+  for (let fileData of message) {
+    promises.push(createFileWithData(fileData).then(domFile => {
+      list.push(domFile);
+    }));
+  }
+
+  Promise.all(promises).then(() => {
+    sendAsyncMessage("files.opened", list);
+  });
 });
--- a/dom/filesystem/compat/tests/script_entries.js
+++ b/dom/filesystem/compat/tests/script_entries.js
@@ -30,18 +30,20 @@ addMessageListener("entries.open", funct
   var file2 = dir1.clone();
   file2.append('bar.txt');
   file2.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
   var dir2 = dir1.clone();
   dir2.append('subsubdir');
   dir2.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o700);
 
-  sendAsyncMessage("entries.opened", {
-    data: [ new Directory(tmpDir.path), File.createFromNsIFile(tmpFile) ]
+  File.createFromNsIFile(tmpFile).then(function(file) {
+    sendAsyncMessage("entries.opened", {
+      data: [ new Directory(tmpDir.path), file ]
+    });
   });
 });
 
 addMessageListener("entries.delete", function(e) {
   tmpFile.remove(true);
   tmpDir.remove(true);
   sendAsyncMessage("entries.deleted");
 });
--- a/dom/filesystem/tests/script_fileList.js
+++ b/dom/filesystem/tests/script_fileList.js
@@ -118,12 +118,12 @@ addMessageListener("dir.open", function 
 
 addMessageListener("file.open", function (e) {
   var testFile = Cc["@mozilla.org/file/directory_service;1"]
                    .getService(Ci.nsIDirectoryService)
                    .QueryInterface(Ci.nsIProperties)
                    .get("ProfD", Ci.nsIFile);
   testFile.append("prefs.js");
 
-  sendAsyncMessage("file.opened", {
-    file: File.createFromNsIFile(testFile)
+  File.createFromNsIFile(testFile).then(function(file) {
+    sendAsyncMessage("file.opened", { file });
   });
 });
--- a/dom/html/test/formSubmission_chrome.js
+++ b/dom/html/test/formSubmission_chrome.js
@@ -1,6 +1,16 @@
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 Cu.importGlobalProperties(["File"]);
 
 addMessageListener("files.open", function (message) {
-  sendAsyncMessage("files.opened", message.map(path => File.createFromFileName(path)));
+  let list = [];
+  let promises = [];
+  for (let path of message) {
+    promises.push(File.createFromFileName(path).then(file => {
+      list.push(file);
+    }));
+  }
+
+  Promise.all(promises).then(() => {
+    sendAsyncMessage("files.opened", list);
+  });
 });
--- a/dom/html/test/script_fakepath.js
+++ b/dom/html/test/script_fakepath.js
@@ -2,12 +2,13 @@ var { classes: Cc, interfaces: Ci, utils
 Cu.importGlobalProperties(["File"]);
 
 addMessageListener("file.open", function (e) {
   var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIDirectoryService)
                   .QueryInterface(Ci.nsIProperties)
                   .get('ProfD', Ci.nsIFile);
   tmpFile.append('prefs.js');
-  sendAsyncMessage("file.opened", {
-    data: [ File.createFromNsIFile(tmpFile) ]
+
+  File.createFromNsIFile(tmpFile).then(file => {
+    sendAsyncMessage("file.opened", { data: [ file ] });
   });
 });
--- a/dom/html/test/simpleFileOpener.js
+++ b/dom/html/test/simpleFileOpener.js
@@ -6,20 +6,23 @@ var file;
 addMessageListener("file.open", function (stem) {
   try {
     if (!file) {
       file = Cc["@mozilla.org/file/directory_service;1"]
                .getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
       file.append(stem);
       file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
     }
-    sendAsyncMessage("file.opened", {
-      fullPath: file.path,
-      leafName: file.leafName,
-      domFile: File.createFromNsIFile(file),
+
+    File.createFromNsIFile(file).then(function(domFile) {
+      sendAsyncMessage("file.opened", {
+        fullPath: file.path,
+        leafName: file.leafName,
+        domFile,
+      });
     });
   } catch(e) {
     sendAsyncMessage("fail", e.toString());
   }
 });
 
 addMessageListener("file.remove", function () {
   try {
--- a/dom/indexedDB/test/unit/test_wasm_recompile.js
+++ b/dom/indexedDB/test/unit/test_wasm_recompile.js
@@ -43,17 +43,22 @@ function testSteps()
 
   let file = filesDir.clone();
   file.append("2");
 
   info("Reading out contents of compiled blob");
 
   let fileReader = new FileReader();
   fileReader.onload = continueToNextStepSync;
-  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  let domFile;
+  File.createFromNsIFile(file).then(file => { domFile = file; }).then(continueToNextStepSync);
+  yield undefined;
+
+  fileReader.readAsArrayBuffer(domFile);
 
   yield undefined;
 
   let compiledBuffer = fileReader.result;
 
   info("Opening database");
 
   let request = indexedDB.open(name);
@@ -77,17 +82,21 @@ function testSteps()
 
   verifyWasmModule(request.result, wasmData.wasm);
   yield undefined;
 
   info("Reading out contents of new compiled blob");
 
   fileReader = new FileReader();
   fileReader.onload = continueToNextStepSync;
-  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  File.createFromNsIFile(file).then(file => { domFile = file; }).then(continueToNextStepSync);
+  yield undefined;
+
+  fileReader.readAsArrayBuffer(domFile);
 
   yield undefined;
 
   let newCompiledBuffer = fileReader.result;
 
   info("Verifying blobs differ");
 
   ok(!compareBuffers(newCompiledBuffer, compiledBuffer), "Blobs differ");
@@ -103,17 +112,21 @@ function testSteps()
 
   verifyWasmModule(request.result, wasmData.wasm);
   yield undefined;
 
   info("Reading contents of new compiled blob again");
 
   fileReader = new FileReader();
   fileReader.onload = continueToNextStepSync;
-  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  File.createFromNsIFile(file).then(file => { domFile = file; }).then(continueToNextStepSync);
+  yield undefined;
+
+  fileReader.readAsArrayBuffer(domFile);
 
   yield undefined;
 
   let newCompiledBuffer2 = fileReader.result;
 
   info("Verifying blob didn't change");
 
   ok(compareBuffers(newCompiledBuffer2, newCompiledBuffer),
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -641,38 +641,43 @@ var SpecialPowers = {
   // Based on SpecialPowersObserver.prototype.receiveMessage
   createFiles: function(requests, callback) {
     let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
     let filePaths = new Array;
     if (!this._createdFiles) {
       this._createdFiles = new Array;
     }
     let createdFiles = this._createdFiles;
+    let promises = [];
     requests.forEach(function(request) {
       const filePerms = 0o666;
       let testFile = dirSvc.get("ProfD", Ci.nsIFile);
       if (request.name) {
         testFile.append(request.name);
       } else {
         testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, filePerms);
       }
-        let outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
-        outStream.init(testFile, 0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
-                       filePerms, 0);
-        if (request.data) {
-          outStream.write(request.data, request.data.length);
-          outStream.close();
-        }
-        filePaths.push(File.createFromFileName(testFile.path, request.options));
-        createdFiles.push(testFile);
+      let outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+      outStream.init(testFile, 0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
+                     filePerms, 0);
+      if (request.data) {
+        outStream.write(request.data, request.data.length);
+        outStream.close();
+      }
+      promises.push(File.createFromFileName(testFile.path, request.options).then(function(file) {
+        filePaths.push(file);
+      }));
+      createdFiles.push(testFile);
     });
 
-    setTimeout(function () {
-      callback(filePaths);
-    }, 0);
+    Promise.all(promises).then(function() {
+      setTimeout(function () {
+        callback(filePaths);
+      }, 0);
+    });
   },
 
   removeFiles: function() {
     if (this._createdFiles) {
       this._createdFiles.forEach(function (testFile) {
         try {
           testFile.remove(false);
         } catch (e) {}
--- a/dom/webidl/File.webidl
+++ b/dom/webidl/File.webidl
@@ -35,15 +35,15 @@ partial interface File {
 
   [BinaryName="relativePath", Func="mozilla::dom::Directory::WebkitBlinkDirectoryPickerEnabled"]
   readonly attribute USVString webkitRelativePath;
 
   [GetterThrows, ChromeOnly, NeedsCallerType]
   readonly attribute DOMString mozFullPath;
 
   [ChromeOnly, Throws, NeedsCallerType]
-  static File createFromNsIFile(nsIFile file,
-                                optional ChromeFilePropertyBag options);
+  static Promise<File> createFromNsIFile(nsIFile file,
+                                         optional ChromeFilePropertyBag options);
 
   [ChromeOnly, Throws, NeedsCallerType]
-  static File createFromFileName(USVString fileName,
-                                 optional ChromeFilePropertyBag options);
+  static Promise<File> createFromFileName(USVString fileName,
+                                          optional ChromeFilePropertyBag options);
 };
--- a/dom/workers/test/fileapi_chromeScript.js
+++ b/dom/workers/test/fileapi_chromeScript.js
@@ -12,18 +12,31 @@ function createFileWithData(fileData) {
   var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
   outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
                  0o666, 0);
   if (willDelete) {
     fileData = "some irrelevant test data\n";
   }
   outStream.write(fileData, fileData.length);
   outStream.close();
-  var domFile = File.createFromNsIFile(testFile);
-  if (willDelete) {
-    testFile.remove(/* recursive: */ false);
-  }
-  return domFile;
+
+  return File.createFromNsIFile(testFile).then(function(domFile) {
+    if (willDelete) {
+      testFile.remove(/* recursive: */ false);
+    }
+    return domFile;
+  });
 }
 
 addMessageListener("files.open", function (message) {
-  sendAsyncMessage("files.opened", message.map(createFileWithData));
+  let promises = [];
+  let list = [];
+
+  for (let fileData of message) {
+    promises.push(createFileWithData(fileData).then(domFile => {
+      list.push(domFile);
+    }));
+  }
+
+  Promise.all(promises).then(() => {
+    sendAsyncMessage("files.opened", list);
+  });
 });
--- a/dom/workers/test/script_createFile.js
+++ b/dom/workers/test/script_createFile.js
@@ -4,12 +4,12 @@ Cu.importGlobalProperties(["File", "Dire
 addMessageListener("file.open", function (e) {
   var tmpFile = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIDirectoryService)
                   .QueryInterface(Ci.nsIProperties)
                   .get('TmpD', Ci.nsIFile)
   tmpFile.append('file.txt');
   tmpFile.createUnique(Components.interfaces.nsIFile.FILE_TYPE, 0o600);
 
-  sendAsyncMessage("file.opened", {
-    data: File.createFromNsIFile(tmpFile)
+  File.createFromNsIFile(tmpFile).then(function(file) {
+    sendAsyncMessage("file.opened", { data: file });
   });
 });
--- a/js/xpconnect/tests/unit/component-file.js
+++ b/js/xpconnect/tests/unit/component-file.js
@@ -16,71 +16,75 @@ function do_check_true(cond, text) {
     throw "Failed check: " + text;
 }
 
 function FileComponent() {
   this.wrappedJSObject = this;
 }
 FileComponent.prototype =
 {
-  doTest: function() {
+  doTest: function(cb) {
     // throw if anything goes wrong
 
     // find the current directory path
     var file = Components.classes["@mozilla.org/file/directory_service;1"]
                .getService(Ci.nsIProperties)
                .get("CurWorkD", Ci.nsIFile);
     file.append("xpcshell.ini");
 
     // should be able to construct a file
-    var f1 = File.createFromFileName(file.path);
-    // and with nsIFiles
-    var f2 = File.createFromNsIFile(file);
+    var f1, f2;
+    Promise.all([
+      File.createFromFileName(file.path).then(f => { f1 = f; }),
+      File.createFromNsIFile(file).then(f => { f2 = f; }),
+    ])
+    .then(() => {
+      // do some tests
+      do_check_true(f1 instanceof File, "Should be a DOM File");
+      do_check_true(f2 instanceof File, "Should be a DOM File");
 
-    // do some tests
-    do_check_true(f1 instanceof File, "Should be a DOM File");
-    do_check_true(f2 instanceof File, "Should be a DOM File");
-
-    do_check_true(f1.name == "xpcshell.ini", "Should be the right file");
-    do_check_true(f2.name == "xpcshell.ini", "Should be the right file");
-
-    do_check_true(f1.type == "", "Should be the right type");
-    do_check_true(f2.type == "", "Should be the right type");
+      do_check_true(f1.name == "xpcshell.ini", "Should be the right file");
+      do_check_true(f2.name == "xpcshell.ini", "Should be the right file");
 
-    var threw = false;
-    try {
-      // Needs a ctor argument
-      var f7 = new File();
-    } catch (e) {
-      threw = true;
-    }
-    do_check_true(threw, "No ctor arguments should throw");
+      do_check_true(f1.type == "", "Should be the right type");
+      do_check_true(f2.type == "", "Should be the right type");
+    })
+    .then(() => {
+      var threw = false;
+      try {
+        // Needs a ctor argument
+        var f7 = new File();
+      } catch (e) {
+        threw = true;
+      }
+      do_check_true(threw, "No ctor arguments should throw");
 
-    var threw = false;
-    try {
-      // Needs a valid ctor argument
-      var f7 = new File(Date(132131532));
-    } catch (e) {
-      threw = true;
-    }
-    do_check_true(threw, "Passing a random object should fail");
+      var threw = false;
+      try {
+        // Needs a valid ctor argument
+        var f7 = new File(Date(132131532));
+      } catch (e) {
+        threw = true;
+      }
+      do_check_true(threw, "Passing a random object should fail");
 
-    var threw = false
-    try {
       // Directories fail
       var dir = Components.classes["@mozilla.org/file/directory_service;1"]
                           .getService(Ci.nsIProperties)
                           .get("CurWorkD", Ci.nsIFile);
-      var f7 = File.createFromNsIFile(dir)
-    } catch (e) {
-      threw = true;
-    }
-    do_check_true(threw, "Can't create a File object for a directory");
-
-    return true;
+      return File.createFromNsIFile(dir)
+    })
+    .then(() => {
+      do_check_true(false, "Can't create a File object for a directory");
+    }, () => {
+      do_check_true(true, "Can't create a File object for a directory");
+    })
+    .then(() => {
+      cb(true);
+    });
   },
 
   // nsIClassInfo + information for XPCOM registration code in XPCOMUtils.jsm
   classDescription: "File in components scope code",
   classID: Components.ID("{da332370-91d4-464f-a730-018e14769cab}"),
   contractID: "@mozilla.org/tests/component-file;1",
 
   // nsIClassInfo
--- a/js/xpconnect/tests/unit/test_file.js
+++ b/js/xpconnect/tests/unit/test_file.js
@@ -1,15 +1,19 @@
 /* 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/. */
  
-function run_test() {   
+add_task(function() {
   do_load_manifest("component-file.manifest");
   const contractID = "@mozilla.org/tests/component-file;1";
   do_check_true(contractID in Components.classes);
   var foo = Components.classes[contractID]
                       .createInstance(Components.interfaces.nsIClassInfo);
   do_check_true(Boolean(foo));
   do_check_true(foo.contractID == contractID);
   do_check_true(!!foo.wrappedJSObject);
-  do_check_true(foo.wrappedJSObject.doTest());
-}
+
+  foo.wrappedJSObject.doTest(result => {
+    do_check_true(result);
+    run_next_test();
+  });
+});
--- a/js/xpconnect/tests/unit/test_file2.js
+++ b/js/xpconnect/tests/unit/test_file2.js
@@ -1,29 +1,29 @@
 /* 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/. */
 
 Components.utils.importGlobalProperties(['File']);
 
 const Ci = Components.interfaces;
 
-function run_test() {
+add_task(function*() {
   // throw if anything goes wrong
 
   // find the current directory path
   var file = Components.classes["@mozilla.org/file/directory_service;1"]
              .getService(Ci.nsIProperties)
              .get("CurWorkD", Ci.nsIFile);
   file.append("xpcshell.ini");
 
   // should be able to construct a file
-  var f1 = File.createFromFileName(file.path);
+  var f1 = yield File.createFromFileName(file.path);
   // and with nsIFiles
-  var f2 = File.createFromNsIFile(file);
+  var f2 = yield File.createFromNsIFile(file);
 
   // do some tests
   do_check_true(f1 instanceof File, "Should be a DOM File");
   do_check_true(f2 instanceof File, "Should be a DOM File");
 
   do_check_true(f1.name == "xpcshell.ini", "Should be the right file");
   do_check_true(f2.name == "xpcshell.ini", "Should be the right file");
 
@@ -49,14 +49,14 @@ function run_test() {
   do_check_true(threw, "Passing a random object should fail");
 
   var threw = false
   try {
     // Directories fail
     var dir = Components.classes["@mozilla.org/file/directory_service;1"]
                         .getService(Ci.nsIProperties)
                         .get("CurWorkD", Ci.nsIFile);
-    var f7 = File.createFromNsIFile(dir)
+    var f7 = yield File.createFromNsIFile(dir)
   } catch (e) {
     threw = true;
   }
   do_check_true(threw, "Can't create a File object for a directory");
-}
+});
--- a/mobile/android/components/FilePicker.js
+++ b/mobile/android/components/FilePicker.js
@@ -16,16 +16,17 @@ Cu.importGlobalProperties(['File']);
 function FilePicker() {
 }
 
 FilePicker.prototype = {
   _mimeTypeFilter: 0,
   _extensionsFilter: "",
   _defaultString: "",
   _domWin: null,
+  _domFile: null,
   _defaultExtension: null,
   _displayDirectory: null,
   _filePath: null,
   _promptActive: false,
   _filterIndex: 0,
   _addToRecentDocs: false,
   _title: "",
 
@@ -130,47 +131,26 @@ FilePicker.prototype = {
   },
 
   get fileURL() {
     let file = this.getFile();
     return Services.io.newFileURI(file);
   },
 
   get files() {
-    return this.getEnumerator([this.file], function(file) {
-      return file;
-    });
+    return this.getEnumerator([this.file]);
   },
 
   // We don't support directory selection yet.
   get domFileOrDirectory() {
-    let f = this.file;
-    if (!f) {
-        return null;
-    }
-
-    let win = this._domWin;
-    if (win) {
-      let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-      return utils.wrapDOMFile(f);
-    }
-
-    return File.createFromNsIFile(f);
+    return this._domFile;
   },
 
   get domFileOrDirectoryEnumerator() {
-    let win = this._domWin;
-    return this.getEnumerator([this.file], function(file) {
-      if (win) {
-        let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-        return utils.wrapDOMFile(file);
-      }
-
-      return File.createFromNsIFile(file);
-    });
+    return this.getEnumerator([this._domFile]);
   },
 
   get addToRecentDocs() {
     return this._addToRecentDocs;
   },
 
   set addToRecentDocs(val) {
     this._addToRecentDocs = val;
@@ -239,37 +219,52 @@ FilePicker.prototype = {
       msg.mode = "mimeType";
       msg.mimeType = this._mimeTypeFilter;
     }
 
     EventDispatcher.instance.sendRequestForResult(msg).then(file => {
       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 File.createFromNsIFile(this.file).then(domFile => {
+        this._domFile = domFile;
+      });
+    }).catch(() => {
+    }).then(() => {
       if (this._callback) {
         this._callback.done(this._filePath ?
-            Ci.nsIFilePicker.returnOK : Ci.nsIFilePicker.returnCancel);
+          Ci.nsIFilePicker.returnOK : Ci.nsIFilePicker.returnCancel);
       }
       delete this._callback;
     });
   },
 
-  getEnumerator: function(files, mapFunction) {
+  getEnumerator: function(files) {
     return {
       QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
       mFiles: files,
       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 mapFunction(this.mFiles[this.mIndex++]);
+        return this.mFiles[this.mIndex++];
       }
     };
   },
 
   fireDialogEvent: function(aDomWin, aEventName) {
     // accessing the document object can throw if this window no longer exists. See bug 789888.
     try {
       if (!aDomWin.document)
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -227,20 +227,23 @@ interaction.selectOption = function (el)
  * Appends |path| to an <input type=file>'s file list.
  *
  * @param {HTMLInputElement} el
  *     An <input type=file> element.
  * @param {string} path
  *     Full path to file.
  */
 interaction.uploadFile = function (el, path) {
-  let file;
-  try {
-    file = File.createFromFileName(path);
-  } catch (e) {
+  let file = yield File.createFromFileName(path).then(file => {
+    return file;
+  }, () => {
+    return null;
+  });
+
+  if (!file) {
     throw new InvalidArgumentError("File not found: " + path);
   }
 
   let fs = Array.prototype.slice.call(el.files);
   fs.push(file);
 
   // <input type=file> opens OS widget dialogue
   // which means the mousedown/focus/mouseup/click events
--- a/testing/specialpowers/content/SpecialPowersObserver.jsm
+++ b/testing/specialpowers/content/SpecialPowersObserver.jsm
@@ -248,39 +248,51 @@ SpecialPowersObserver.prototype.receiveM
       break;
     case "SpecialPowers.CreateFiles":
       let filePaths = new Array;
       if (!this._createdFiles) {
         this._createdFiles = new Array;
       }
       let createdFiles = this._createdFiles;
       try {
+        let promises = [];
         aMessage.data.forEach(function(request) {
           const filePerms = 0666;
           let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
           if (request.name) {
             testFile.appendRelativePath(request.name);
           } else {
             testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, filePerms);
           }
           let outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
           outStream.init(testFile, 0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
                          filePerms, 0);
           if (request.data) {
             outStream.write(request.data, request.data.length);
           }
           outStream.close();
-          filePaths.push(File.createFromFileName(testFile.path, request.options));
+          promises.push(File.createFromFileName(testFile.path, request.options).then(function(file) {
+            filePaths.push(file);
+          }));
           createdFiles.push(testFile);
         });
-        aMessage.target
-                .QueryInterface(Ci.nsIFrameLoaderOwner)
-                .frameLoader
-                .messageManager
-                .sendAsyncMessage("SpecialPowers.FilesCreated", filePaths);
+
+        Promise.all(promises).then(function() {
+          aMessage.target
+                  .QueryInterface(Ci.nsIFrameLoaderOwner)
+                  .frameLoader
+                  .messageManager
+                  .sendAsyncMessage("SpecialPowers.FilesCreated", filePaths);
+        }, function(e) {
+          aMessage.target
+                  .QueryInterface(Ci.nsIFrameLoaderOwner)
+                  .frameLoader
+                  .messageManager
+                  .sendAsyncMessage("SpecialPowers.FilesError", e.toString());
+        });
       } catch (e) {
           aMessage.target
                   .QueryInterface(Ci.nsIFrameLoaderOwner)
                   .frameLoader
                   .messageManager
                   .sendAsyncMessage("SpecialPowers.FilesError", e.toString());
       }
 
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -635,17 +635,19 @@ function loadMemoryReportsFromFile(aFile
     reader.onload = (aEvent) => {
       // Clear "Loading..." from above.
       updateTitleMainAndFooter(aTitleNote, "", NO_TIMESTAMP, SHOW_FOOTER);
       aFn(aEvent.target.result);
     };
 
     // If it doesn't have a .gz suffix, read it as a (legacy) ungzipped file.
     if (!aFilename.endsWith(".gz")) {
-      reader.readAsText(File.createFromFileName(aFilename));
+      File.createFromFileName(aFilename).then(file => {
+        reader.readAsText(file);
+      });
       return;
     }
 
     // Read compressed gzip file.
     let converter = new nsGzipConverter();
     converter.asyncConvertData("gzip", "uncompressed", {
       data: [],
       onStartRequest(aR, aC) {},
--- a/toolkit/crashreporter/CrashSubmit.jsm
+++ b/toolkit/crashreporter/CrashSubmit.jsm
@@ -279,26 +279,35 @@ Submitter.prototype = {
         formData.append(name, value);
       }
     }
     if (this.noThrottle) {
       // tell the server not to throttle this, since it was manually submitted
       formData.append("Throttleable", "0");
     }
     // add the minidumps
-    formData.append("upload_file_minidump", File.createFromFileName(this.dump.path));
+    let promises = [
+      File.createFromFileName(this.dump.path).then(file => {
+        formData.append("upload_file_minidump", file);
+      })
+    ]
+
     if (this.memory) {
-      formData.append("memory_report", File.createFromFileName(this.memory.path));
+      promises.push(File.createFromFileName(this.memory.path).then(file => {
+        formData.append("memory_report", file);
+      }));
     }
+
     if (this.additionalDumps.length > 0) {
       let names = [];
       for (let i of this.additionalDumps) {
         names.push(i.name);
-        formData.append("upload_file_minidump_" + i.name,
-                        File.createFromFileName(i.dump.path));
+        promises.push(File.createFromFileName(i.dump.path).then(file => {
+          formData.append("upload_file_minidump_" + i.name, file);
+        }));
       }
     }
 
     let manager = Services.crashmanager;
     let submissionID = manager.generateSubmissionID();
 
     xhr.addEventListener("readystatechange", (evt) => {
       if (xhr.readyState == 4) {
@@ -323,17 +332,17 @@ Submitter.prototype = {
           } else {
             this.notifyStatus(FAILED);
             this.cleanup();
           }
         });
       }
     });
 
-    let p = Promise.resolve();
+    let p = Promise.all(promises);
     let id = this.id;
 
     if (this.recordSubmission) {
       p = manager.ensureCrashIsPresent(id).then(() => {
         return manager.addSubmissionAttempt(id, submissionID, new Date());
       });
     }
     p.then(() => { xhr.send(formData); });
--- a/toolkit/modules/PropertyListUtils.jsm
+++ b/toolkit/modules/PropertyListUtils.jsm
@@ -86,40 +86,47 @@ this.PropertyListUtils = Object.freeze({
     if (!(aFile instanceof Ci.nsILocalFile || aFile instanceof File))
       throw new Error("aFile is not a file object");
     if (typeof(aCallback) != "function")
       throw new Error("Invalid value for aCallback");
 
     // We guarantee not to throw directly for any other exceptions, and always
     // call aCallback.
     Services.tm.mainThread.dispatch(function() {
-      let file = aFile;
-      try {
-        if (file instanceof Ci.nsILocalFile) {
-          if (!file.exists())
-            throw new Error("The file pointed by aFile does not exist");
-
-          file = File.createFromNsIFile(file);
-        }
-
+      let self = this;
+      function readDOMFile(aFile) {
         let fileReader = new FileReader();
         let onLoadEnd = function() {
           let root = null;
           try {
             fileReader.removeEventListener("loadend", onLoadEnd);
             if (fileReader.readyState != fileReader.DONE)
               throw new Error("Could not read file contents: " + fileReader.error);
 
-            root = this._readFromArrayBufferSync(fileReader.result);
+            root = self._readFromArrayBufferSync(fileReader.result);
           } finally {
             aCallback(root);
           }
-        }.bind(this);
+        }
         fileReader.addEventListener("loadend", onLoadEnd);
-        fileReader.readAsArrayBuffer(file);
+        fileReader.readAsArrayBuffer(aFile);
+      }
+
+      try {
+        if (aFile instanceof Ci.nsILocalFile) {
+          if (!aFile.exists()) {
+            throw new Error("The file pointed by aFile does not exist");
+          }
+
+          File.createFromNsIFile(aFile).then(function(aFile) {
+            readDOMFile(aFile);
+          });
+          return;
+        }
+        readDOMFile(aFile);
       } catch (ex) {
         aCallback(null);
         throw ex;
       }
     }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
   },
 
   /**