Bug 1075302 - Allow Necko to do main thread I/O on remote blobs, r=khuey.
authorBen Turner <bent.mozilla@gmail.com>
Tue, 07 Oct 2014 12:26:59 -0700
changeset 209236 97febbb46ef483c73f29bd4a0296074d1e28bfa1
parent 209235 6525c7ee1f50addf145723c976dc353e4be9a00c
child 209237 38653b50f507926a1a2d8b9f51971e8e9aa8c285
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerskhuey
bugs1075302
milestone35.0a1
Bug 1075302 - Allow Necko to do main thread I/O on remote blobs, r=khuey.
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/test_blob_worker_xhr_post.html
dom/ipc/Blob.cpp
dom/ipc/BlobParent.h
dom/ipc/DOMTypes.ipdlh
ipc/glue/InputStreamParams.ipdlh
ipc/glue/InputStreamUtils.cpp
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -110,16 +110,18 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bfcache.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_archive.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_simple.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_worker_crash.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
+[test_blob_worker_xhr_post.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blocked_order.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_bug937006.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_clear.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_complex_keyPaths.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_blob_worker_xhr_post.html
@@ -0,0 +1,113 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function testSteps()
+  {
+    const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
+    const BLOB_TYPE = "text/plain";
+    const BLOB_SIZE = BLOB_DATA.join("").length;
+
+    info("Setting up");
+
+    let request = indexedDB.open(window.location.pathname, 1);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    let event = yield undefined;
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    ok(db, "Created database");
+
+    info("Creating objectStore");
+
+    let objectStore = db.createObjectStore("foo", { autoIncrement: true });
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    ok(true, "Opened database");
+
+    let blob = new Blob(BLOB_DATA, { type: BLOB_TYPE });
+
+    info("Adding blob to database");
+
+    objectStore = db.transaction("foo", "readwrite").objectStore("foo");
+    objectStore.add(blob).onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    let blobKey = event.target.result;
+    ok(blobKey, "Got a key for the blob");
+
+    info("Getting blob from the database");
+
+    objectStore = db.transaction("foo").objectStore("foo");
+    objectStore.get(blobKey).onsuccess = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    blob = event.target.result;
+
+    ok(blob instanceof Blob, "Got a blob");
+    is(blob.size, BLOB_SIZE, "Correct size");
+    is(blob.type, BLOB_TYPE, "Correct type");
+
+    let slice = blob.slice(0, BLOB_DATA[0].length, BLOB_TYPE);
+
+    ok(slice instanceof Blob, "Slice returned a blob");
+    is(slice.size, BLOB_DATA[0].length, "Correct size for slice");
+    is(slice.type, BLOB_TYPE, "Correct type for slice");
+
+    info("Sending slice to a worker");
+
+    function workerScript() {
+      onmessage = function(event) {
+        var blob = event.data;
+        var xhr = new XMLHttpRequest();
+        // We just want to make sure the error case doesn't fire; it's fine for
+        // us to just want a 404.
+        xhr.open('POST', 'http://mochi.test:8888/does-not-exist', true);
+        xhr.onload = function() {
+          postMessage({ status: xhr.status });
+        };
+        xhr.onerror = function() {
+          postMessage({ status: 'error' });
+        }
+        xhr.send(blob);
+      }
+    }
+
+    let workerScriptUrl =
+      URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
+
+    let xhrWorker = new Worker(workerScriptUrl);
+    xhrWorker.postMessage(slice);
+    xhrWorker.onmessage = grabEventAndContinueHandler;
+    event = yield undefined;
+
+    is(event.data.status, 404, "XHR generated the expected 404");
+    xhrWorker.terminate();
+
+    URL.revokeObjectURL(workerScriptUrl);
+
+    finishTest();
+    yield undefined;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -354,27 +354,29 @@ public:
   {
     MOZ_ASSERT(IsOnOwningThread());
   }
 
   void
   Serialize(InputStreamParams& aParams,
             FileDescriptorArray& /* aFileDescriptors */)
   {
+    MOZ_RELEASE_ASSERT(mBlobImpl);
+
     nsCOMPtr<nsIRemoteBlob> remote = do_QueryInterface(mBlobImpl);
     MOZ_ASSERT(remote);
-    MOZ_ASSERT(remote->GetBlobChild());
-
-    aParams = RemoteInputStreamParams(
-      nullptr /* sourceParent */,
-      remote->GetBlobChild() /* sourceChild */);
+
+    BlobChild* actor = remote->GetBlobChild();
+    MOZ_ASSERT(actor);
+
+    aParams = RemoteInputStreamParams(actor->ParentID());
   }
 
   bool
-  Deserialize(const InputStreamParams& aParams,
+  Deserialize(const InputStreamParams& /* aParams */,
               const FileDescriptorArray& /* aFileDescriptors */)
   {
     // See InputStreamUtils.cpp to see how deserialization of a
     // RemoteInputStream is special-cased.
     MOZ_CRASH("RemoteInputStream should never be deserialized");
   }
 
   void
@@ -414,25 +416,58 @@ public:
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
   NS_IMETHOD
   Available(uint64_t* aAvailable) MOZ_OVERRIDE
   {
-    // See large comment in FileInputStreamWrapper::Available.
-    if (IsOnOwningThread()) {
+    if (!IsOnOwningThread()) {
+      nsresult rv = BlockAndWaitForStream();
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = mStream->Available(aAvailable);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+#ifdef DEBUG
+    if (NS_IsMainThread()) {
+      NS_WARNING("Someone is trying to do main-thread I/O...");
+    }
+#endif
+
+    nsresult rv;
+
+    // See if we already have our real stream.
+    nsCOMPtr<nsIInputStream> inputStream;
+    {
+      MonitorAutoLock lock(mMonitor);
+
+      inputStream = mStream;
+    }
+
+    // If we do then just call through.
+    if (inputStream) {
+      rv = inputStream->Available(aAvailable);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      return NS_OK;
+    }
+
+    // If the stream is already closed then we can't do anything.
+    if (!mBlobImpl) {
       return NS_BASE_STREAM_CLOSED;
     }
 
-    nsresult rv = BlockAndWaitForStream();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mStream->Available(aAvailable);
+    // Otherwise fake it...
+    NS_WARNING("Available() called before real stream has been delivered, "
+               "guessing the amount of data available!");
+
+    rv = mBlobImpl->GetSize(aAvailable);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
   NS_IMETHOD
   Read(char* aBuffer, uint32_t aCount, uint32_t* aResult) MOZ_OVERRIDE
   {
@@ -685,43 +720,57 @@ public:
     DebugOnly<bool> isMutable;
     MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
     MOZ_ASSERT(!isMutable);
 
     return GetOrCreateInternal(aID,
                                aProcessID,
                                aBlobImpl,
                                /* aMayCreate */ true,
-                               /* aMayGet */ false);
+                               /* aMayGet */ false,
+                               /* aIgnoreProcessID */ false);
   }
 
   static already_AddRefed<IDTableEntry>
   Get(const nsID& aID, intptr_t aProcessID)
   {
     return GetOrCreateInternal(aID,
                                aProcessID,
                                nullptr,
                                /* aMayCreate */ false,
-                               /* aMayGet */ true);
+                               /* aMayGet */ true,
+                               /* aIgnoreProcessID */ false);
+  }
+
+  static already_AddRefed<IDTableEntry>
+  Get(const nsID& aID)
+  {
+    return GetOrCreateInternal(aID,
+                               0,
+                               nullptr,
+                               /* aMayCreate */ false,
+                               /* aMayGet */ true,
+                               /* aIgnoreProcessID */ true);
   }
 
   static already_AddRefed<IDTableEntry>
   GetOrCreate(const nsID& aID, intptr_t aProcessID, DOMFileImpl* aBlobImpl)
   {
     MOZ_ASSERT(aBlobImpl);
 
     DebugOnly<bool> isMutable;
     MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
     MOZ_ASSERT(!isMutable);
 
     return GetOrCreateInternal(aID,
                                aProcessID,
                                aBlobImpl,
                                /* aMayCreate */ true,
-                               /* aMayGet */ true);
+                               /* aMayGet */ true,
+                               /* aIgnoreProcessID */ false);
   }
 
   const nsID&
   ID() const
   {
     return mID;
   }
 
@@ -743,17 +792,18 @@ private:
   IDTableEntry(const nsID& aID, intptr_t aProcessID, DOMFileImpl* aBlobImpl);
   ~IDTableEntry();
 
   static already_AddRefed<IDTableEntry>
   GetOrCreateInternal(const nsID& aID,
                       intptr_t aProcessID,
                       DOMFileImpl* aBlobImpl,
                       bool aMayCreate,
-                      bool aMayGet);
+                      bool aMayGet,
+                      bool aIgnoreProcessID);
 };
 
 // Each instance of this class will be dispatched to the network stream thread
 // pool to run the first time where it will open the file input stream. It will
 // then dispatch itself back to the owning thread to send the child process its
 // response (assuming that the child has not crashed). The runnable will then
 // dispatch itself to the thread pool again in order to close the file input
 // stream.
@@ -2913,16 +2963,36 @@ BlobParent::Create(PBackgroundParent* aM
 {
   AssertCorrectThreadForManager(aManager);
   MOZ_ASSERT(aManager);
 
   return CreateFromParams(aManager, aParams);
 }
 
 // static
+already_AddRefed<DOMFileImpl>
+BlobParent::GetBlobImplForID(const nsID& aID)
+{
+  if (NS_WARN_IF(gProcessType != GeckoProcessType_Default)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  nsRefPtr<IDTableEntry> idTableEntry = IDTableEntry::Get(aID);
+  if (NS_WARN_IF(!idTableEntry)) {
+    return nullptr;
+  }
+
+  nsRefPtr<DOMFileImpl> blobImpl = idTableEntry->BlobImpl();
+  MOZ_ASSERT(blobImpl);
+
+  return blobImpl.forget();
+}
+
+// static
 template <class ParentManagerType>
 BlobParent*
 BlobParent::GetOrCreateFromImpl(ParentManagerType* aManager,
                                 DOMFileImpl* aBlobImpl)
 {
   AssertCorrectThreadForManager(aManager);
   MOZ_ASSERT(aManager);
   MOZ_ASSERT(aBlobImpl);
@@ -3545,17 +3615,18 @@ IDTableEntry::~IDTableEntry()
 
 // static
 already_AddRefed<BlobParent::IDTableEntry>
 BlobParent::
 IDTableEntry::GetOrCreateInternal(const nsID& aID,
                                   intptr_t aProcessID,
                                   DOMFileImpl* aBlobImpl,
                                   bool aMayCreate,
-                                  bool aMayGet)
+                                  bool aMayGet,
+                                  bool aIgnoreProcessID)
 {
   MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
   MOZ_ASSERT(sIDTableMutex);
   sIDTableMutex->AssertNotCurrentThreadOwns();
 
   nsRefPtr<IDTableEntry> entry;
 
   {
@@ -3573,17 +3644,17 @@ IDTableEntry::GetOrCreateInternal(const 
 
     if (entry) {
       MOZ_ASSERT_IF(aBlobImpl, entry->BlobImpl() == aBlobImpl);
 
       if (NS_WARN_IF(!aMayGet)) {
         return nullptr;
       }
 
-      if (NS_WARN_IF(entry->mProcessID != aProcessID)) {
+      if (!aIgnoreProcessID && NS_WARN_IF(entry->mProcessID != aProcessID)) {
         return nullptr;
       }
     } else {
       if (NS_WARN_IF(!aMayCreate)) {
         return nullptr;
       }
 
       MOZ_ASSERT(aBlobImpl);
--- a/dom/ipc/BlobParent.h
+++ b/dom/ipc/BlobParent.h
@@ -102,16 +102,19 @@ public:
          const ParentBlobConstructorParams& aParams);
 
   static void
   Destroy(PBlobParent* aActor)
   {
     delete static_cast<BlobParent*>(aActor);
   }
 
+  static already_AddRefed<DOMFileImpl>
+  GetBlobImplForID(const nsID& aID);
+
   bool
   HasManager() const
   {
     return mBackgroundManager || mContentManager;
   }
 
   PBackgroundParent*
   GetBackgroundManager() const
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -2,19 +2,16 @@
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBlob;
 include InputStreamParams;
 
-using struct nsID
-  from "nsID.h";
-
 using struct mozilla::void_t
   from "ipc/IPCMessageUtils.h";
 
 using struct mozilla::SerializedStructuredCloneBuffer
   from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace dom {
--- a/ipc/glue/InputStreamParams.ipdlh
+++ b/ipc/glue/InputStreamParams.ipdlh
@@ -2,16 +2,19 @@
  * 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/. */
 
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 include protocol PBlob;
 include protocol PFileDescriptorSet;
 
+using struct nsID
+  from "nsID.h";
+
 namespace mozilla {
 namespace ipc {
 
 struct StringInputStreamParams
 {
   nsCString data;
 };
 
@@ -34,17 +37,17 @@ struct MultiplexInputStreamParams
   InputStreamParams[] streams;
   uint32_t currentStream;
   nsresult status;
   bool startedReadingCurrent;
 };
 
 struct RemoteInputStreamParams
 {
-  PBlob remoteBlob;
+  nsID id;
 };
 
 // XXX This may only be used for same-process inter-thread communication! The
 //     value should be reinterpret_cast'd to nsIInputStream. It carries a
 //     reference.
 struct SameProcessInputStreamParams
 {
   intptr_t addRefedInputStream;
--- a/ipc/glue/InputStreamUtils.cpp
+++ b/ipc/glue/InputStreamUtils.cpp
@@ -102,27 +102,23 @@ DeserializeInputStream(const InputStream
 
     case InputStreamParams::TMultiplexInputStreamParams:
       serializable = do_CreateInstance(kMultiplexInputStreamCID);
       break;
 
     // When the input stream already exists in this process, all we need to do
     // is retrieve the original instead of sending any data over the wire.
     case InputStreamParams::TRemoteInputStreamParams: {
-      const RemoteInputStreamParams& params =
-          aParams.get_RemoteInputStreamParams();
+      if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) {
+        return nullptr;
+      }
 
-      nsRefPtr<DOMFileImpl> blobImpl;
-      if (params.remoteBlobParent()) {
-        blobImpl =
-          static_cast<BlobParent*>(params.remoteBlobParent())->GetBlobImpl();
-      } else {
-        blobImpl =
-          static_cast<BlobChild*>(params.remoteBlobChild())->GetBlobImpl();
-      }
+      const nsID& id = aParams.get_RemoteInputStreamParams().id();
+
+      nsRefPtr<DOMFileImpl> blobImpl = BlobParent::GetBlobImplForID(id);
 
       MOZ_ASSERT(blobImpl, "Invalid blob contents");
 
       // If fetching the internal stream fails, we ignore it and return a
       // null stream.
       nsCOMPtr<nsIInputStream> stream;
       nsresult rv = blobImpl->GetInternalStream(getter_AddRefs(stream));
       if (NS_FAILED(rv) || !stream) {