Bug 1257180 - patch 2 - Directory can be sent via postMessage(), r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 12 Apr 2016 08:51:04 -0400
changeset 316627 7c72bf95f312dec7cba18c32a33d9d3d428139e4
parent 316626 498c618afddf075175a8436b6775f0bfa764e9e0
child 316628 3c507567ab06559d715188a29fac85c9995160d2
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1257180
milestone48.0a1
Bug 1257180 - patch 2 - Directory can be sent via postMessage(), r=smaug
dom/base/StructuredCloneHolder.cpp
dom/base/StructuredCloneTags.h
dom/base/test/test_postMessages.html
dom/filesystem/GetDirectoryListingTask.cpp
dom/filesystem/OSFileSystem.cpp
dom/html/HTMLInputElement.cpp
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -6,16 +6,17 @@
 
 #include "StructuredCloneHolder.h"
 
 #include "ImageContainer.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/Directory.h"
+#include "mozilla/dom/DirectoryBinding.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/FileListBinding.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ImageDataBinding.h"
@@ -653,17 +654,17 @@ ReadBlob(JSContext* aCx,
   blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv);
   if (NS_WARN_IF(rv.Failed())) {
     rv.SuppressException();
     return nullptr;
   }
 
   MOZ_ASSERT(blobImpl);
 
-  // RefPtr<File> needs to go out of scope before toObjectOrNull() is
+  // RefPtr<File> needs to go out of scope before toObject() is
   // called because the static analysis thinks dereferencing XPCOM objects
   // can GC (because in some cases it can!), and a return statement with a
   // JSObject* type means that JSObject* is on the stack as a raw pointer
   // while destructors are running.
   JS::Rooted<JS::Value> val(aCx);
   {
     RefPtr<Blob> blob = Blob::Create(aHolder->ParentDuringRead(), blobImpl);
     if (!ToJSValue(aCx, blob, &val)) {
@@ -700,16 +701,90 @@ WriteBlob(JSStructuredCloneWriter* aWrit
                          aHolder->BlobImpls().Length())) {
     aHolder->BlobImpls().AppendElement(blobImpl);
     return true;
   }
 
   return false;
 }
 
+// A directory is serialized as:
+// - pair of ints: SCTAG_DOM_DIRECTORY, 0
+// - pair of ints: type (eDOMRootDirectory/eDOMNotRootDirectory) - path length
+// - path as string
+bool
+WriteDirectory(JSStructuredCloneWriter* aWriter,
+               Directory* aDirectory)
+{
+  MOZ_ASSERT(aWriter);
+  MOZ_ASSERT(aDirectory);
+
+  nsAutoString path;
+  aDirectory->GetFullRealPath(path);
+
+  size_t charSize = sizeof(nsString::char_type);
+  return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, 0) &&
+         JS_WriteUint32Pair(aWriter, (uint32_t)aDirectory->Type(),
+                            path.Length()) &&
+         JS_WriteBytes(aWriter, path.get(), path.Length() * charSize);
+}
+
+JSObject*
+ReadDirectory(JSContext* aCx,
+              JSStructuredCloneReader* aReader,
+              uint32_t aZero,
+              StructuredCloneHolder* aHolder)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aReader);
+  MOZ_ASSERT(aHolder);
+  MOZ_ASSERT(aZero == 0);
+
+  uint32_t directoryType, lengthOfString;
+  if (!JS_ReadUint32Pair(aReader, &directoryType, &lengthOfString)) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(directoryType == Directory::eDOMRootDirectory ||
+             directoryType == Directory::eNotDOMRootDirectory);
+
+  nsAutoString path;
+  path.SetLength(lengthOfString);
+  size_t charSize = sizeof(nsString::char_type);
+  if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(),
+                    lengthOfString * charSize)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
+                                      getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  // RefPtr<Directory> needs to go out of scope before toObject() is
+  // called because the static analysis thinks dereferencing XPCOM objects
+  // can GC (because in some cases it can!), and a return statement with a
+  // JSObject* type means that JSObject* is on the stack as a raw pointer
+  // while destructors are running.
+  JS::Rooted<JS::Value> val(aCx);
+  {
+    RefPtr<Directory> directory =
+      Directory::Create(aHolder->ParentDuringRead(), file,
+                        (Directory::DirectoryType) directoryType);
+
+    if (!ToJSValue(aCx, directory, &val)) {
+      return nullptr;
+    }
+  }
+
+  return &val.toObject();
+}
+
 // Read the WriteFileList for the format.
 JSObject*
 ReadFileList(JSContext* aCx,
              JSStructuredCloneReader* aReader,
              uint32_t aCount,
              StructuredCloneHolder* aHolder)
 {
   MOZ_ASSERT(aCx);
@@ -997,16 +1072,20 @@ StructuredCloneHolder::CustomReadHandler
                                          uint32_t aIndex)
 {
   MOZ_ASSERT(mSupportsCloning);
 
   if (aTag == SCTAG_DOM_BLOB) {
     return ReadBlob(aCx, aIndex, this);
   }
 
+  if (aTag == SCTAG_DOM_DIRECTORY) {
+    return ReadDirectory(aCx, aReader, aIndex, this);
+  }
+
   if (aTag == SCTAG_DOM_FILELIST) {
     return ReadFileList(aCx, aReader, aIndex, this);
   }
 
   if (aTag == SCTAG_DOM_FORMDATA) {
     return ReadFormData(aCx, aReader, aIndex, this);
   }
 
@@ -1037,16 +1116,24 @@ StructuredCloneHolder::CustomWriteHandle
   // See if this is a File/Blob object.
   {
     Blob* blob = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
       return WriteBlob(aWriter, blob, this);
     }
   }
 
+  // See if this is a Directory object.
+  {
+    Directory* directory = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, aObj, directory))) {
+      return WriteDirectory(aWriter, directory);
+    }
+  }
+
   // See if this is a FileList object.
   {
     FileList* fileList = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList)) &&
         (mSupportedContext == SameProcessSameThread ||
          fileList->ClonableToDifferentThreadOrProcess())) {
       return WriteFileList(aWriter, fileList, this);
     }
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -12,17 +12,16 @@
 namespace mozilla {
 namespace dom {
 
 // CHANGING THE ORDER/PLACEMENT OF EXISTING ENUM VALUES MAY BREAK INDEXEDDB.
 // PROCEED WITH EXTREME CAUTION.
 enum StructuredCloneTags {
   SCTAG_BASE = JS_SCTAG_USER_MIN,
 
-  // These tags are used only for main thread structured clone.
   SCTAG_DOM_BLOB,
 
   // This tag is obsolete and exists only for backwards compatibility with
   // existing IndexedDB databases.
   SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE,
 
   SCTAG_DOM_FILELIST,
   SCTAG_DOM_MUTABLEFILE,
@@ -48,15 +47,17 @@ enum StructuredCloneTags {
 
   SCTAG_DOM_FORMDATA,
 
   // This tag is for OffscreenCanvas.
   SCTAG_DOM_CANVAS,
 
   SCTAG_DOM_EXPANDED_PRINCIPAL,
 
+  SCTAG_DOM_DIRECTORY,
+
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // StructuredCloneTags_h__
--- a/dom/base/test/test_postMessages.html
+++ b/dom/base/test/test_postMessages.html
@@ -109,16 +109,44 @@ function create_fileList_forDir() {
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
   script.sendAsyncMessage("dir.open");
 }
 
+function create_directory() {
+  if (navigator.userAgent.toLowerCase().indexOf('Android') != -1) {
+    next();
+    return;
+  }
+
+  var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js");
+  var script = SpecialPowers.loadChromeScript(url);
+
+  function onOpened(message) {
+    var fileList = document.getElementById('fileList');
+    SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
+
+    fileList.getFilesAndDirectories().then(function(list) {
+      // Just a simple test
+      is(list.length, 1, "This list has 1 element");
+      ok(list[0] instanceof Directory, "We have a directory.");
+
+      clonableObjects.push(list[0]);
+      script.destroy();
+      next();
+    });
+  }
+
+  script.addMessageListener("dir.opened", onOpened);
+  script.sendAsyncMessage("dir.open");
+}
+
 function runTests(obj) {
   ok(('clonableObjects' in obj) &&
      ('transferableObjects' in obj) &&
      (obj.clonableObjects || obj.transferableObjects), "We must run some test!");
 
   // cloning tests
   new Promise(function(resolve, reject) {
     if (!obj.clonableObjects) {
@@ -517,16 +545,17 @@ function test_messagePort_inWorkers() {
       }
     });
   }
 }
 
 var tests = [
   create_fileList_forFile,
   create_fileList_forDir,
+  create_directory,
 
   test_windowToWindow,
   test_windowToIframe,
   test_windowToCrossOriginIframe,
 
   test_workers,
 
   test_broadcastChannel,
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -67,17 +67,16 @@ GetDirectoryListingTaskChild::GetDirecto
   , mType(aType)
 {
   MOZ_ASSERT(aFileSystem);
   aFileSystem->AssertIsOnOwningThread();
 }
 
 GetDirectoryListingTaskChild::~GetDirectoryListingTaskChild()
 {
-  MOZ_ASSERT(NS_IsMainThread());
   mFileSystem->AssertIsOnOwningThread();
 }
 
 already_AddRefed<Promise>
 GetDirectoryListingTaskChild::GetPromise()
 {
   mFileSystem->AssertIsOnOwningThread();
   return RefPtr<Promise>(mPromise).forget();
--- a/dom/filesystem/OSFileSystem.cpp
+++ b/dom/filesystem/OSFileSystem.cpp
@@ -54,17 +54,16 @@ OSFileSystem::Init(nsISupports* aParent)
   MOZ_ASSERT(obj);
 #endif
 }
 
 nsISupports*
 OSFileSystem::GetParentObject() const
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   return mParent;
 }
 
 void
 OSFileSystem::GetRootName(nsAString& aRetval) const
 {
   AssertIsOnOwningThread();
   aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
@@ -89,26 +88,23 @@ OSFileSystem::IsSafeDirectory(Directory*
   MOZ_CRASH("Don't use OSFileSystem with the Device Storage API");
   return true;
 }
 
 void
 OSFileSystem::Unlink()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
-
   mParent = nullptr;
 }
 
 void
 OSFileSystem::Traverse(nsCycleCollectionTraversalCallback &cb)
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
 
   OSFileSystem* tmp = this;
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent);
 }
 
 void
 OSFileSystem::SerializeDOMPath(nsAString& aOutput) const
 {
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -5037,23 +5037,16 @@ HTMLInputElement::GetFilesAndDirectories
   if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
                                  mozilla::fallible_t())) {
     p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
     return p.forget();
   }
 
   for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
     if (filesAndDirs[i].IsDirectory()) {
-#if defined(ANDROID) || defined(MOZ_B2G)
-      MOZ_ASSERT(false,
-                 "Directory picking should have been redirected to normal "
-                 "file picking for platforms that don't have a directory "
-                 "picker");
-#endif
-
       RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
 
       // In future we could refactor SetFilePickerFiltersFromAccept to return a
       // semicolon separated list of file extensions and include that in the
       // filter string passed here.
       directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
       filesAndDirsSeq[i].SetAsDirectory() = directory;
     } else {