Bug 1257180 - patch 1 - Directory clonable to workers, r=smaug
☠☠ backed out by 99c2637bbc26 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 12 Apr 2016 08:50:38 -0400
changeset 330680 b416fc68c0a2d4ea2640a31826233aa053c058c8
parent 330679 6c2df11a71b14819993bfe3f29cf8439551b802c
child 330681 83ce34cabf08a9b9d2ddc214c5cf880a81536107
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1257180
milestone48.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 1257180 - patch 1 - Directory clonable to workers, r=smaug
dom/base/FileList.cpp
dom/base/test/test_postMessages.html
dom/filesystem/Directory.cpp
dom/filesystem/Directory.h
dom/filesystem/FileSystemBase.h
dom/filesystem/FileSystemPermissionRequest.cpp
dom/filesystem/FileSystemPermissionRequest.h
dom/filesystem/FileSystemTaskBase.cpp
dom/filesystem/GetDirectoryListingTask.cpp
dom/filesystem/OSFileSystem.cpp
dom/filesystem/OSFileSystem.h
dom/filesystem/tests/mochitest.ini
dom/filesystem/tests/test_worker_basic.html
dom/filesystem/tests/worker_basic.js
dom/webidl/Directory.webidl
dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
dom/workers/test/test_worker_interfaces.js
--- a/dom/base/FileList.cpp
+++ b/dom/base/FileList.cpp
@@ -110,17 +110,18 @@ FileList::ToSequence(Sequence<OwningFile
     aSequence[i] = mFilesOrDirectories[i];
   }
 }
 
 bool
 FileList::ClonableToDifferentThreadOrProcess() const
 {
   for (uint32_t i = 0; i < mFilesOrDirectories.Length(); ++i) {
-    if (mFilesOrDirectories[i].IsDirectory()) {
+    if (mFilesOrDirectories[i].IsDirectory() &&
+        !mFilesOrDirectories[i].GetAsDirectory()->ClonableToDifferentThreadOrProcess()) {
       return false;
     }
   }
 
   return true;
 }
 
 } // namespace dom
--- a/dom/base/test/test_postMessages.html
+++ b/dom/base/test/test_postMessages.html
@@ -56,40 +56,40 @@ function compare(a, b) {
   }
 
   if (type != 'null') {
     is (a.toSource(), b.toSource(), 'Matching using toSource()');
   }
 }
 
 var clonableObjects = [
-  { crossThreads: true, data: 'hello world' },
-  { crossThreads: true, data: 123 },
-  { crossThreads: true, data: null },
-  { crossThreads: true, data: true },
-  { crossThreads: true, data: new Date() },
-  { crossThreads: true, data: [ 1, 'test', true, new Date() ] },
-  { crossThreads: true, data: { a: true, b:  null, c: new Date(), d: [ true, false, {} ] } },
-  { crossThreads: true, data: new Blob([123], { type: 'plain/text' }) },
-  { crossThreads: true, data: new ImageData(2, 2) },
+  'hello world',
+  123,
+  null,
+  true,
+  new Date(),
+  [ 1, 'test', true, new Date() ],
+  { a: true, b:  null, c: new Date(), d: [ true, false, {} ] },
+  new Blob([123], { type: 'plain/text' }),
+  new ImageData(2, 2),
 ];
 
 function create_fileList_forFile() {
   var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
     var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);
 
     // Just a simple test
     var domFile = fileList.files[0];
     is(domFile.name, "prefs.js", "fileName should be prefs.js");
 
-    clonableObjects.push({ crossThreads: true, data: fileList.files });
+    clonableObjects.push(fileList.files);
     script.destroy();
     next();
   }
 
   script.addMessageListener("file.opened", onOpened);
   script.sendAsyncMessage("file.open");
 }
 
@@ -100,17 +100,17 @@ function create_fileList_forDir() {
   function onOpened(message) {
     var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
 
     // Just a simple test
     is(fileList.files.length, 1, "Filelist has 1 element");
     ok(fileList.files[0] instanceof Directory, "We have a directory.");
 
-    clonableObjects.push({ crossThreads: false, data: fileList.files });
+    clonableObjects.push(fileList.files);
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
   script.sendAsyncMessage("dir.open");
 }
 
@@ -130,25 +130,18 @@ function runTests(obj) {
     function runClonableTest() {
       if (clonableObjectsId >= clonableObjects.length) {
         resolve();
         return;
       }
 
       var object = clonableObjects[clonableObjectsId++];
 
-      // If this test requires a cross-thread structured clone algorithm, maybe
-      // we have to skip it.
-      if (!object.crossThread && obj.crossThread) {
-        runClonableTest();
-        return;
-      }
-
-      obj.send(object.data, []).then(function(received) {
-        compare(received.data, object.data);
+      obj.send(object, []).then(function(received) {
+        compare(received.data, object);
         runClonableTest();
       });
     }
 
     runClonableTest();
   })
 
   // transfering tests
@@ -227,17 +220,16 @@ function test_windowToWindow() {
     let tmp = resolve;
     resolve = null;
     tmp({ data: e.data, ports: e.ports });
   }
 
   runTests({
     clonableObjects: true,
     transferableObjects: true,
-    crossThread: false,
     send: function(what, ports) {
       return new Promise(function(r, rr) {
         resolve = r;
 
         try {
           postMessage(what, '*', ports);
         } catch(e) {
           resolve = null;
@@ -281,17 +273,16 @@ function test_windowToIframeURL(url) {
   }
 
   var ifr = document.createElement('iframe');
   ifr.src = url;
   ifr.onload = function() {
     runTests({
       clonableObjects: true,
       transferableObjects: true,
-      crossThread: false,
       send: function(what, ports) {
         return new Promise(function(r, rr) {
           resolve = r;
           try {
             ifr.contentWindow.postMessage(what, '*', ports);
           } catch(e) {
             resolve = null;
             rr();
@@ -329,17 +320,16 @@ function test_workers() {
       let tmp = resolve;
       resolve = null;
       tmp({ data: e.data, ports: e.ports });
     }
 
     runTests({
       clonableObjects: true,
       transferableObjects: true,
-      crossThread: true,
       send: function(what, ports) {
         return new Promise(function(r, rr) {
           resolve = r;
           try {
             w.postMessage(what, ports);
           } catch(e) {
             resolve = null;
             rr();
@@ -373,17 +363,16 @@ function test_broadcastChannel() {
     let tmp = resolve;
     resolve = null;
     tmp({ data: e.data, ports: [] });
   }
 
   runTests({
     clonableObjects: true,
     transferableObjects: false,
-    crossThread: true,
     send: function(what, ports) {
       return new Promise(function(r, rr) {
         if (ports.length) {
           rr();
           return;
         }
 
         resolve = r;
@@ -419,17 +408,16 @@ function test_broadcastChannel_inWorkers
       let tmp = resolve;
       resolve = null;
       tmp({ data: e.data, ports: e.ports });
     }
 
     runTests({
       clonableObjects: true,
       transferableObjects: false,
-      crossThread: true,
       send: function(what, ports) {
         return new Promise(function(r, rr) {
           if (ports.length) {
             rr();
             return;
           }
 
           resolve = r;
@@ -461,17 +449,16 @@ function test_messagePort() {
     let tmp = resolve;
     resolve = null;
     tmp({ data: e.data, ports: e.ports });
   }
 
   runTests({
     clonableObjects: true,
     transferableObjects: true,
-    crossThread: true,
     send: function(what, ports) {
       return new Promise(function(r, rr) {
         resolve = r;
         try {
           mc.port1.postMessage(what, ports);
         } catch(e) {
           resolve = null;
           rr();
@@ -507,17 +494,16 @@ function test_messagePort_inWorkers() {
       let tmp = resolve;
       resolve = null;
       tmp({ data: e.data, ports: e.ports });
     }
 
     runTests({
       clonableObjects: true,
       transferableObjects: true,
-      crossThread: true,
       send: function(what, ports) {
         return new Promise(function(r, rr) {
           resolve = r;
           try {
             mc.port1.postMessage(what, ports);
           } catch(e) {
             resolve = null;
             rr();
--- a/dom/filesystem/Directory.cpp
+++ b/dom/filesystem/Directory.cpp
@@ -106,20 +106,31 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCA
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Directory)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Directory)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Directory)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-// static
-already_AddRefed<Promise>
+/* static */ bool
+Directory::DeviceStorageEnabled(JSContext* aCx, JSObject* aObj)
+{
+  if (!NS_IsMainThread()) {
+    return false;
+  }
+
+  return Preferences::GetBool("device.storage.enabled", false);
+}
+
+/* static */ already_AddRefed<Promise>
 Directory::GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aFileSystem);
 
   nsCOMPtr<nsIFile> path;
   aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aFileSystem->LocalOrDeviceStorageRootPath()),
                               true, getter_AddRefs(path));
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
@@ -134,17 +145,16 @@ Directory::GetRoot(FileSystemBase* aFile
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
 /* static */ already_AddRefed<Directory>
 Directory::Create(nsISupports* aParent, nsIFile* aFile,
                   DirectoryType aType, FileSystemBase* aFileSystem)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aParent);
   MOZ_ASSERT(aFile);
 
 #ifdef DEBUG
   bool isDir;
   nsresult rv = aFile->IsDirectory(&isDir);
   MOZ_ASSERT(NS_SUCCEEDED(rv) && isDir);
 
@@ -164,17 +174,16 @@ Directory::Create(nsISupports* aParent, 
 Directory::Directory(nsISupports* aParent,
                      nsIFile* aFile,
                      DirectoryType aType,
                      FileSystemBase* aFileSystem)
   : mParent(aParent)
   , mFile(aFile)
   , mType(aType)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aFile);
 
   // aFileSystem can be null. In this case we create a OSFileSystem when needed.
   if (aFileSystem) {
     // More likely, this is a OSFileSystem. This object keeps a reference of
     // mParent but it's not cycle collectable and to avoid manual
     // addref/release, it's better to have 1 object per directory. For this
     // reason we clone it here.
@@ -216,16 +225,19 @@ Directory::GetName(nsAString& aRetval, E
   aRv = mFile->GetLeafName(aRetval);
   NS_WARN_IF(aRv.Failed());
 }
 
 already_AddRefed<Promise>
 Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions,
                       ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
+
   RefPtr<Blob> blobData;
   InfallibleTArray<uint8_t> arrayData;
   bool replace = (aOptions.mIfExists == CreateIfExistsMode::Replace);
 
   // Get the file content.
   if (aOptions.mData.WasPassed()) {
     auto& data = aOptions.mData.Value();
     if (data.IsString()) {
@@ -263,16 +275,19 @@ Directory::CreateFile(const nsAString& a
   task->SetError(error);
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
 already_AddRefed<Promise>
 Directory::CreateDirectory(const nsAString& aPath, ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
+
   nsCOMPtr<nsIFile> realPath;
   nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath));
 
   RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
@@ -285,16 +300,19 @@ Directory::CreateDirectory(const nsAStri
   task->SetError(error);
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
 already_AddRefed<Promise>
 Directory::Get(const nsAString& aPath, ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
+
   nsCOMPtr<nsIFile> realPath;
   nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath));
 
   RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
@@ -308,29 +326,36 @@ Directory::Get(const nsAString& aPath, E
   task->SetError(error);
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
 already_AddRefed<Promise>
 Directory::Remove(const StringOrFileOrDirectory& aPath, ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
   return RemoveInternal(aPath, false, aRv);
 }
 
 already_AddRefed<Promise>
 Directory::RemoveDeep(const StringOrFileOrDirectory& aPath, ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
   return RemoveInternal(aPath, true, aRv);
 }
 
 already_AddRefed<Promise>
 Directory::RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive,
                           ErrorResult& aRv)
 {
+  // Only exposed for DeviceStorage.
+  MOZ_ASSERT(NS_IsMainThread());
+
   nsresult error = NS_OK;
   nsCOMPtr<nsIFile> realPath;
 
   // Check and get the target path.
 
   RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
@@ -477,10 +502,22 @@ Directory::DOMPathToRealPath(const nsASt
       return rv;
     }
   }
 
   file.forget(aFile);
   return NS_OK;
 }
 
+bool
+Directory::ClonableToDifferentThreadOrProcess() const
+{
+  // If we don't have a fileSystem we are going to create a OSFileSystem that is
+  // clonable everywhere.
+  if (!mFileSystem) {
+    return true;
+  }
+
+  return mFileSystem->ClonableToDifferentThreadOrProcess();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/Directory.h
+++ b/dom/filesystem/Directory.h
@@ -48,16 +48,19 @@ public:
       eFilePath,
       eDirectoryPath
     } mType;
   };
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Directory)
 
+  static bool
+  DeviceStorageEnabled(JSContext* aCx, JSObject* aObj);
+
   static already_AddRefed<Promise>
   GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv);
 
   enum DirectoryType {
     // When a directory is selected using a HTMLInputElement, that will be the
     // DOM root directory and its name will be '/'. All the sub directory will
     // be called with they real name. We use this enum to mark what we must
     // consider the '/' of this DOM filesystem.
@@ -139,16 +142,19 @@ public:
   FileSystemBase*
   GetFileSystem(ErrorResult& aRv);
 
   DirectoryType Type() const
   {
     return mType;
   }
 
+  bool
+  ClonableToDifferentThreadOrProcess() const;
+
 private:
   Directory(nsISupports* aParent,
             nsIFile* aFile, DirectoryType aType,
             FileSystemBase* aFileSystem = nullptr);
   ~Directory();
 
   /*
    * Convert relative DOM path to the absolute real path.
--- a/dom/filesystem/FileSystemBase.h
+++ b/dom/filesystem/FileSystemBase.h
@@ -122,16 +122,19 @@ public:
   // See how these 2 methods are used in FileSystemTaskChildBase.
 
   virtual bool
   NeedToGoToMainThread() const { return false; }
 
   virtual nsresult
   MainThreadWork() { return NS_ERROR_FAILURE; }
 
+  virtual bool
+  ClonableToDifferentThreadOrProcess() const { return false; }
+
   // CC methods
   virtual void Unlink() {}
   virtual void Traverse(nsCycleCollectionTraversalCallback &cb) {}
 
   void
   AssertIsOnOwningThread() const;
 
 protected:
--- a/dom/filesystem/FileSystemPermissionRequest.cpp
+++ b/dom/filesystem/FileSystemPermissionRequest.cpp
@@ -6,30 +6,127 @@
 #include "FileSystemPermissionRequest.h"
 
 #include "mozilla/dom/FileSystemBase.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/FileSystemUtils.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "nsIDocument.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsPIDOMWindow.h"
 #include "nsContentPermissionHelper.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+// This class takes care of the PBackground initialization and, once this step
+// is completed, it starts the task.
+class PBackgroundInitializer final : public nsIIPCBackgroundChildCreateCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+
+  static void
+  ScheduleTask(FileSystemTaskChildBase* aTask)
+  {
+    MOZ_ASSERT(aTask);
+    RefPtr<PBackgroundInitializer> pb = new PBackgroundInitializer(aTask);
+  }
+
+private:
+  explicit PBackgroundInitializer(FileSystemTaskChildBase* aTask)
+    : mTask(aTask)
+  {
+    MOZ_ASSERT(aTask);
+
+    PBackgroundChild* actor =
+      mozilla::ipc::BackgroundChild::GetForCurrentThread();
+    if (actor) {
+      ActorCreated(actor);
+    } else {
+      if (NS_WARN_IF(
+          !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
+        MOZ_CRASH();
+      }
+    }
+  }
+
+  ~PBackgroundInitializer()
+  {}
+
+  RefPtr<FileSystemTaskChildBase> mTask;
+};
+
+NS_IMPL_ISUPPORTS(PBackgroundInitializer,
+                  nsIIPCBackgroundChildCreateCallback)
+
+void
+PBackgroundInitializer::ActorFailed()
+{
+  MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+}
+
+void
+PBackgroundInitializer::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
+{
+  mTask->Start();
+}
+
+// This must be a CancelableRunnable because it can be dispatched to a worker
+// thread. But we don't care about the Cancel() because in that case, Run() is
+// not called and the task is deleted by the DTOR.
+class AsyncStartRunnable final : public nsCancelableRunnable
+{
+public:
+  explicit AsyncStartRunnable(FileSystemTaskChildBase* aTask)
+    : mTask(aTask)
+  {
+    MOZ_ASSERT(aTask);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    PBackgroundInitializer::ScheduleTask(mTask);
+    return NS_OK;
+  }
+
+private:
+  RefPtr<FileSystemTaskChildBase> mTask;
+};
+
+} // anonymous namespace
+
 NS_IMPL_ISUPPORTS(FileSystemPermissionRequest, nsIRunnable,
-                  nsIContentPermissionRequest,
-                  nsIIPCBackgroundChildCreateCallback)
+                  nsIContentPermissionRequest)
 
 /* static */ void
 FileSystemPermissionRequest::RequestForTask(FileSystemTaskChildBase* aTask)
 {
-  MOZ_ASSERT(aTask, "aTask should not be null!");
+  MOZ_ASSERT(aTask);
+
+  RefPtr<FileSystemBase> filesystem = aTask->GetFileSystem();
+  if (!filesystem) {
+    return;
+  }
+
+  if (filesystem->PermissionCheckType() == FileSystemBase::ePermissionCheckNotRequired) {
+    // Let's make the scheduling of this task asynchronous.
+    RefPtr<AsyncStartRunnable> runnable = new AsyncStartRunnable(aTask);
+    NS_DispatchToCurrentThread(runnable);
+    return;
+  }
+
+  // We don't need any permission check for the FileSystem API. If we are here
+  // it's because we are dealing with a DeviceStorage API that is main-thread
+  // only.
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<FileSystemPermissionRequest> request =
     new FileSystemPermissionRequest(aTask);
   NS_DispatchToCurrentThread(request);
 }
 
 FileSystemPermissionRequest::FileSystemPermissionRequest(FileSystemTaskChildBase* aTask)
@@ -151,36 +248,15 @@ FileSystemPermissionRequest::GetRequeste
   NS_ENSURE_ARG_POINTER(aRequester);
 
   nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
   requester.forget(aRequester);
   return NS_OK;
 }
 
 void
-FileSystemPermissionRequest::ActorFailed()
-{
-  MOZ_CRASH("Failed to create a PBackgroundChild actor!");
-}
-
-void
-FileSystemPermissionRequest::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
-{
-  mTask->Start();
-}
-
-void
 FileSystemPermissionRequest::ScheduleTask()
 {
-  PBackgroundChild* actor =
-    mozilla::ipc::BackgroundChild::GetForCurrentThread();
-  if (actor) {
-    ActorCreated(actor);
-  } else {
-    if (NS_WARN_IF(
-        !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
-      MOZ_CRASH();
-    }
-  }
+  PBackgroundInitializer::ScheduleTask(mTask);
 }
 
 } /* namespace dom */
 } /* namespace mozilla */
--- a/dom/filesystem/FileSystemPermissionRequest.h
+++ b/dom/filesystem/FileSystemPermissionRequest.h
@@ -5,49 +5,47 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_FileSystemPermissionRequest_h
 #define mozilla_dom_FileSystemPermissionRequest_h
 
 #include "nsAutoPtr.h"
 #include "nsIRunnable.h"
 #include "nsIContentPermissionPrompt.h"
-#include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsString.h"
 
 class nsPIDOMWindowInner;
 
 namespace mozilla {
 namespace dom {
 
 class FileSystemTaskChildBase;
 
 class FileSystemPermissionRequest final
   : public nsIContentPermissionRequest
   , public nsIRunnable
-  , public nsIIPCBackgroundChildCreateCallback
 {
 public:
   // Request permission for the given task.
   static void
   RequestForTask(FileSystemTaskChildBase* aTask);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSICONTENTPERMISSIONREQUEST
   NS_DECL_NSIRUNNABLE
-  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
 
 private:
   explicit FileSystemPermissionRequest(FileSystemTaskChildBase* aTask);
 
   ~FileSystemPermissionRequest();
 
   // Once the permission check has been done, we must run the task using IPC and
   // PBackground. This method checks if the PBackground thread is ready to
-  // receive the task and in case waits for ActorCreated() to be called.
+  // receive the task and in case waits for ActorCreated() to be called using
+  // the PBackgroundInitializer class (see FileSystemPermissionRequest.cpp).
   void
   ScheduleTask();
 
   nsCString mPermissionType;
   nsCString mPermissionAccess;
   RefPtr<FileSystemTaskChildBase> mTask;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
--- a/dom/filesystem/FileSystemTaskBase.cpp
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -70,28 +70,54 @@ DispatchToIOThread(nsIRunnable* aRunnabl
 
   nsCOMPtr<nsIEventTarget> target
     = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
   MOZ_ASSERT(target);
 
   return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
 }
 
+// This runnable is used when an error value is set before doing any real
+// operation on the I/O thread. In this case we skip all and we directly
+// communicate the error.
+class ErrorRunnable final : public nsCancelableRunnable
+{
+public:
+  explicit ErrorRunnable(FileSystemTaskChildBase* aTask)
+    : mTask(aTask)
+  {
+    MOZ_ASSERT(aTask);
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mTask->HasError());
+
+    mTask->HandlerCallback();
+    return NS_OK;
+  }
+
+private:
+  RefPtr<FileSystemTaskChildBase> mTask;
+};
+
 } // anonymous namespace
 
 /**
  * FileSystemTaskBase class
  */
 
 FileSystemTaskChildBase::FileSystemTaskChildBase(FileSystemBase* aFileSystem)
   : mErrorValue(NS_OK)
   , mFileSystem(aFileSystem)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
+  aFileSystem->AssertIsOnOwningThread();
 }
 
 FileSystemTaskChildBase::~FileSystemTaskChildBase()
 {
   mFileSystem->AssertIsOnOwningThread();
 }
 
 FileSystemBase*
@@ -102,19 +128,20 @@ FileSystemTaskChildBase::GetFileSystem()
 }
 
 void
 FileSystemTaskChildBase::Start()
 {
   mFileSystem->AssertIsOnOwningThread();
 
   if (HasError()) {
-    nsCOMPtr<nsIRunnable> runnable =
-      NS_NewRunnableMethod(this, &FileSystemTaskChildBase::HandlerCallback);
-    NS_DispatchToMainThread(runnable);
+    // In this case we don't want to use IPC at all.
+    RefPtr<ErrorRunnable> runnable = new ErrorRunnable(this);
+    nsresult rv = NS_DispatchToCurrentThread(runnable);
+    NS_WARN_IF(NS_FAILED(rv));
     return;
   }
 
   if (mFileSystem->IsShutdown()) {
     return;
   }
 
   nsAutoString serialization;
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -29,18 +29,18 @@ namespace dom {
 
 /* static */ already_AddRefed<GetDirectoryListingTaskChild>
 GetDirectoryListingTaskChild::Create(FileSystemBase* aFileSystem,
                                      nsIFile* aTargetPath,
                                      Directory::DirectoryType aType,
                                      const nsAString& aFilters,
                                      ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aFileSystem);
+  aFileSystem->AssertIsOnOwningThread();
 
   RefPtr<GetDirectoryListingTaskChild> task =
     new GetDirectoryListingTaskChild(aFileSystem, aTargetPath, aType, aFilters);
 
   // aTargetPath can be null. In this case SetError will be called.
 
   nsCOMPtr<nsIGlobalObject> globalObject =
     do_QueryInterface(aFileSystem->GetParentObject());
@@ -61,54 +61,55 @@ GetDirectoryListingTaskChild::GetDirecto
                                                            nsIFile* aTargetPath,
                                                            Directory::DirectoryType aType,
                                                            const nsAString& aFilters)
   : FileSystemTaskChildBase(aFileSystem)
   , mTargetPath(aTargetPath)
   , mFilters(aFilters)
   , mType(aType)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aFileSystem);
+  aFileSystem->AssertIsOnOwningThread();
 }
 
 GetDirectoryListingTaskChild::~GetDirectoryListingTaskChild()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  mFileSystem->AssertIsOnOwningThread();
 }
 
 already_AddRefed<Promise>
 GetDirectoryListingTaskChild::GetPromise()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  mFileSystem->AssertIsOnOwningThread();
   return RefPtr<Promise>(mPromise).forget();
 }
 
 FileSystemParams
 GetDirectoryListingTaskChild::GetRequestParams(const nsString& aSerializedDOMPath,
                                                ErrorResult& aRv) const
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  mFileSystem->AssertIsOnOwningThread();
 
   nsAutoString path;
   aRv = mTargetPath->GetPath(path);
   if (NS_WARN_IF(aRv.Failed())) {
     return FileSystemGetDirectoryListingParams();
   }
 
   return FileSystemGetDirectoryListingParams(aSerializedDOMPath, path,
                                              mType == Directory::eDOMRootDirectory,
                                              mFilters);
 }
 
 void
 GetDirectoryListingTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue,
                                                       ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  mFileSystem->AssertIsOnOwningThread();
   MOZ_ASSERT(aValue.type() ==
                FileSystemResponseValue::TFileSystemDirectoryListingResponse);
 
   FileSystemDirectoryListingResponse r = aValue;
   for (uint32_t i = 0; i < r.data().Length(); ++i) {
     const FileSystemDirectoryListingResponseData& data = r.data()[i];
 
     Directory::FileOrDirectoryPath element;
@@ -128,17 +129,18 @@ GetDirectoryListingTaskChild::SetSuccess
       return;
     }
   }
 }
 
 void
 GetDirectoryListingTaskChild::HandlerCallback()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
+  mFileSystem->AssertIsOnOwningThread();
+
   if (mFileSystem->IsShutdown()) {
     mPromise = nullptr;
     return;
   }
 
   if (HasError()) {
     mPromise->MaybeReject(mErrorValue);
     mPromise = nullptr;
--- a/dom/filesystem/OSFileSystem.cpp
+++ b/dom/filesystem/OSFileSystem.cpp
@@ -39,17 +39,16 @@ OSFileSystem::Clone()
 
   return fs.forget();
 }
 
 void
 OSFileSystem::Init(nsISupports* aParent)
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(!mParent, "No duple Init() calls");
   MOZ_ASSERT(aParent);
 
   mParent = aParent;
 
 #ifdef DEBUG
   nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(aParent);
   MOZ_ASSERT(obj);
--- a/dom/filesystem/OSFileSystem.h
+++ b/dom/filesystem/OSFileSystem.h
@@ -35,16 +35,19 @@ public:
   IsSafeFile(nsIFile* aFile) const override;
 
   virtual bool
   IsSafeDirectory(Directory* aDir) const override;
 
   virtual void
   SerializeDOMPath(nsAString& aOutput) const override;
 
+  virtual bool
+  ClonableToDifferentThreadOrProcess() const override { return true; }
+
   // CC methods
   virtual void Unlink() override;
   virtual void Traverse(nsCycleCollectionTraversalCallback &cb) override;
 
 private:
   virtual ~OSFileSystem() {}
 
   nsCOMPtr<nsISupports> mParent;
--- a/dom/filesystem/tests/mochitest.ini
+++ b/dom/filesystem/tests/mochitest.ini
@@ -1,5 +1,7 @@
 [DEFAULT]
 support-files =
   script_fileList.js
+  worker_basic.js
 
 [test_basic.html]
+[test_worker_basic.html]
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/tests/test_worker_basic.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Directory API in workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="fileList" type="file"></input>
+<script type="application/javascript;version=1.7">
+
+function create_fileList() {
+  var url = SimpleTest.getTestFileURL("script_fileList.js");
+  var script = SpecialPowers.loadChromeScript(url);
+
+  function onOpened(message) {
+    var fileList = document.getElementById('fileList');
+    SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+
+    // Just a simple test
+    is(fileList.files.length, 1, "Filelist has 1 element");
+    ok(fileList.files[0] instanceof Directory, "We have a directory.");
+
+    script.destroy();
+    next();
+  }
+
+  script.addMessageListener("dir.opened", onOpened);
+  script.sendAsyncMessage("dir.open", { path: 'ProfD' });
+}
+
+function test_worker() {
+  var fileList = document.getElementById('fileList');
+
+  var worker = new Worker('worker_basic.js');
+  worker.onmessage = function(e) {
+    if (e.data.type == 'finish') {
+      next();
+      return;
+    }
+
+    if (e.data.type == 'test') {
+      ok(e.data.test, e.data.message);
+    }
+  }
+
+  worker.postMessage(fileList.files);
+}
+
+var tests = [
+  create_fileList,
+  test_worker,
+];
+
+function next() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/tests/worker_basic.js
@@ -0,0 +1,58 @@
+function finish() {
+  postMessage({ type: 'finish' });
+}
+
+function ok(a, msg) {
+  postMessage({ type: 'test', test: !!a, message: msg });
+}
+
+function is(a, b, msg) {
+  ok(a === b, msg);
+}
+
+function isnot(a, b, msg) {
+  ok(a != b, msg);
+}
+
+function checkSubDir(dir) {
+  return dir.getFilesAndDirectories().then(
+    function(data) {
+      for (var i = 0; i < data.length; ++i) {
+        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
+        if (data[i] instanceof Directory) {
+          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
+          isnot(data[i].path, '/', "Subdirectory path should be called with the leafname");
+          isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path.");
+          is(data[i].path,dir.path + '/' + data[i].name, "Subdirectory path should be called parentdir.path + '/' + leafname");
+        }
+      }
+    }
+  );
+}
+
+onmessage = function(e) {
+  var fileList = e.data;
+  ok(fileList instanceof FileList, "This is a fileList.");
+  is(fileList.length, 1, "We want just 1 element.");
+  ok(fileList[0] instanceof Directory, "This is a directory.");
+
+  fileList[0].getFilesAndDirectories().then(
+    function(data) {
+      ok(data.length, "We should have some data.");
+      var promises = [];
+      for (var i = 0; i < data.length; ++i) {
+        ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
+        if (data[i] instanceof Directory) {
+          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
+          is(data[i].path, '/' + data[i].name, "Subdirectory path should be called '/' + leafname");
+          promises.push(checkSubDir(data[i]));
+        }
+      }
+
+      return Promise.all(promises);
+    },
+    function() {
+      ok(false, "Something when wrong");
+    }
+  ).then(finish);
+}
--- a/dom/webidl/Directory.webidl
+++ b/dom/webidl/Directory.webidl
@@ -10,17 +10,17 @@
  * path should be a descendent path like "path/to/file.txt" and not contain a
  * segment of ".." or ".". So the paths aren't allowed to walk up the directory
  * tree. For example, paths like "../foo", "..", "/foo/bar" or "foo/../bar" are
  * not allowed.
  *
  * http://w3c.github.io/filesystem-api/#idl-def-Directory
  * https://microsoftedge.github.io/directory-upload/proposal.html#directory-interface
  */
-[Exposed=Window]
+[Exposed=(Window,Worker)]
 interface Directory {
   /*
    * The leaf name of the directory.
    */
   [Throws]
   readonly attribute DOMString name;
 
   /*
@@ -34,72 +34,72 @@ interface Directory {
    * a new file to replace the existing one;
    * If 'ifExists' is 'replace', the path already exists, but is a directory,
    * createFile must fail.
    * Otherwise, if no other error occurs, createFile will create a new file.
    * The 'data' property contains the new file's content.
    * @return If succeeds, the promise is resolved with the new created
    * File object. Otherwise, rejected with a DOM error.
    */
-  [Pref="device.storage.enabled", NewObject]
+  [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject]
   Promise<File> createFile(DOMString path, optional CreateFileOptions options);
 
   /*
    * Creates a descendent directory. This method will create any intermediate
    * directories specified by the path segments.
    *
    * @param path The relative path of the new directory to current directory.
    * If path exists, createDirectory must fail.
    * @return If succeeds, the promise is resolved with the new created
    * Directory object. Otherwise, rejected with a DOM error.
    */
-  [Pref="device.storage.enabled", NewObject]
+  [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject]
   Promise<Directory> createDirectory(DOMString path);
 
   /*
    * Gets a descendent file or directory with the given path.
    *
    * @param path The descendent entry's relative path to current directory.
    * @return If the path exists and no error occurs, the promise is resolved
    * with a File or Directory object, depending on the entry's type. Otherwise,
    * rejected with a DOM error.
    */
-  [Pref="device.storage.enabled", NewObject]
+  [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject]
   Promise<(File or Directory)> get(DOMString path);
 
   /*
    * Deletes a file or an empty directory. The target must be a descendent of
    * current directory.
    * @param path If a DOM string is passed, it is the relative path of the
    * target. Otherwise, the File or Directory object of the target should be
    * passed.
    * @return If the target is a non-empty directory, or if deleting the target
    * fails, the promise is rejected with a DOM error. If the target did not
    * exist, the promise is resolved with boolean false. If the target did exist
    * and was successfully deleted, the promise is resolved with boolean true.
    */
-  [Pref="device.storage.enabled", NewObject]
+  [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject]
   Promise<boolean> remove((DOMString or File or Directory) path);
 
   /*
    * Deletes a file or a directory recursively. The target should be a
    * descendent of current directory.
    * @param path If a DOM string is passed, it is the relative path of the
    * target. Otherwise, the File or Directory object of the target should be
    * passed.
    * @return If the target exists, but deleting the target fails, the promise is
    * rejected with a DOM error. If the target did not exist, the promise is
    * resolved with boolean false. If the target did exist and was successfully
    * deleted, the promise is resolved with boolean true.
    */
-  [Pref="device.storage.enabled", NewObject]
+  [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject]
   Promise<boolean> removeDeep((DOMString or File or Directory) path);
 };
 
-[Exposed=Window]
+[Exposed=(Window,Worker)]
 partial interface Directory {
   // Already defined in the main interface declaration:
   //readonly attribute DOMString name;
 
   /*
    * The path of the Directory (includes both its basename and leafname).
    * The path begins with the name of the ancestor Directory that was
    * originally exposed to content (say via a directory picker) and traversed
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -92,16 +92,18 @@ var interfaceNamesInGlobalScope =
     "Crypto",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CustomEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStore", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStoreCursor", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "Directory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMCursor",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMError",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMException",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -90,16 +90,18 @@ var interfaceNamesInGlobalScope =
     "CustomEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DedicatedWorkerGlobalScope",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStore", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DataStoreCursor", b2g: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "Directory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMCursor",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMError",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMException",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DOMRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!