Bug 1311466 - Part 8: Implement basic functionality for preprocessing results before they are sent for synchronous deserialization; r=asuth
authorJan Varga <jan.varga@gmail.com>
Tue, 25 Oct 2016 21:19:06 +0200
changeset 362310 3c3eced266e0a1072019dc78ffae248c99fe704e
parent 362309 8ddfb4a361d91b84495315c4d95ba7590a5d8227
child 362311 d0751b15d5790db842d77844ebc8f04d09ccbcd0
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-beta@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1311466
milestone52.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 1311466 - Part 8: Implement basic functionality for preprocessing results before they are sent for synchronous deserialization; r=asuth
dom/indexedDB/ActorsChild.cpp
dom/indexedDB/ActorsChild.h
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IndexedDatabase.h
dom/indexedDB/PBackgroundIDBRequest.ipdl
dom/indexedDB/test/file.js
dom/indexedDB/test/helpers.js
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/unit/test_wasm_put_get_values.js
dom/indexedDB/test/unit/xpcshell-head-parent-process.js
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -29,16 +29,18 @@
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsIBFCacheEntry.h"
 #include "nsIDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIEventTarget.h"
+#include "nsIFileStreams.h"
+#include "nsNetCID.h"
 #include "nsPIDOMWindow.h"
 #include "nsThreadUtils.h"
 #include "nsTraceRefcnt.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "ReportInternalError.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
@@ -585,32 +587,33 @@ protected:
   { }
 
   virtual bool
   Recv__delete__(const uint32_t& aPermission) override;
 };
 
 void
 DeserializeStructuredCloneFiles(
-                        IDBDatabase* aDatabase,
-                        const SerializedStructuredCloneReadInfo& aCloneReadInfo,
-                        nsTArray<StructuredCloneFile>& aFiles)
+                IDBDatabase* aDatabase,
+                const nsTArray<SerializedStructuredCloneFile>& aSerializedFiles,
+                const nsTArray<RefPtr<JS::WasmModule>>* aModules,
+                nsTArray<StructuredCloneFile>& aFiles)
 {
+  MOZ_ASSERT_IF(aModules, !aModules->IsEmpty());
   MOZ_ASSERT(aFiles.IsEmpty());
 
-  const nsTArray<SerializedStructuredCloneFile>& serializedFiles =
-    aCloneReadInfo.files();
-
-  if (!serializedFiles.IsEmpty()) {
-    const uint32_t count = serializedFiles.Length();
+  if (!aSerializedFiles.IsEmpty()) {
+    uint32_t moduleIndex = 0;
+
+    const uint32_t count = aSerializedFiles.Length();
     aFiles.SetCapacity(count);
 
     for (uint32_t index = 0; index < count; index++) {
       const SerializedStructuredCloneFile& serializedFile =
-        serializedFiles[index];
+        aSerializedFiles[index];
 
       const BlobOrMutableFile& blobOrMutableFile = serializedFile.file();
 
       switch (serializedFile.type()) {
         case StructuredCloneFile::eBlob: {
           MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::TPBlobChild);
 
           auto* actor =
@@ -682,16 +685,30 @@ DeserializeStructuredCloneFiles(
           MOZ_ASSERT(file);
 
           file->mType = StructuredCloneFile::eStructuredClone;
 
           break;
         }
 
         case StructuredCloneFile::eWasmBytecode: {
+          if (aModules) {
+            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
+
+            StructuredCloneFile* file = aFiles.AppendElement();
+            MOZ_ASSERT(file);
+
+            file->mType = serializedFile.type();
+
+            MOZ_ASSERT(moduleIndex < aModules->Length());
+            file->mWasmModule = aModules->ElementAt(moduleIndex);
+
+            break;
+          }
+
           MOZ_ASSERT(blobOrMutableFile.type() ==
                        BlobOrMutableFile::TPBlobChild);
 
           auto* actor =
             static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
 
           RefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
           MOZ_ASSERT(blobImpl);
@@ -705,16 +722,30 @@ DeserializeStructuredCloneFiles(
 
           file->mType = StructuredCloneFile::eWasmBytecode;
           file->mBlob.swap(blob);
 
           break;
         }
 
         case StructuredCloneFile::eWasmCompiled: {
+          if (aModules) {
+            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
+
+            StructuredCloneFile* file = aFiles.AppendElement();
+            MOZ_ASSERT(file);
+
+            file->mType = serializedFile.type();
+
+            MOZ_ASSERT(moduleIndex < aModules->Length());
+            file->mWasmModule = aModules->ElementAt(moduleIndex++);
+
+            break;
+          }
+
           MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t ||
                      blobOrMutableFile.type() ==
                        BlobOrMutableFile::TPBlobChild);
 
           switch (blobOrMutableFile.type()) {
             case BlobOrMutableFile::Tnull_t: {
               StructuredCloneFile* file = aFiles.AppendElement();
               MOZ_ASSERT(file);
@@ -905,16 +936,37 @@ DispatchSuccessEvent(ResultHelper* aResu
 
   if (transaction &&
       transaction->IsOpen() &&
       internalEvent->mFlags.mExceptionWasRaised) {
     transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
   }
 }
 
+PRFileDesc*
+GetFileDescriptorFromStream(nsIInputStream* aStream)
+{
+  MOZ_ASSERT(aStream);
+
+  nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream);
+  if (NS_WARN_IF(!fileMetadata)) {
+    return nullptr;
+  }
+
+  PRFileDesc* fileDesc;
+  nsresult rv = fileMetadata->GetFileDescriptor(&fileDesc);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(fileDesc);
+
+  return fileDesc;
+}
+
 class WorkerPermissionChallenge;
 
 // This class calles WorkerPermissionChallenge::OperationCompleted() in the
 // worker thread.
 class WorkerPermissionOperationCompleted final : public WorkerControlRunnable
 {
   RefPtr<WorkerPermissionChallenge> mChallenge;
 
@@ -1139,16 +1191,89 @@ WorkerPermissionRequestChildProcessActor
   MOZ_ASSERT(NS_IsMainThread());
   mChallenge->OperationCompleted();
   return true;
 }
 
 } // namespace
 
 /*******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+// CancelableRunnable is used to make workers happy.
+class BackgroundRequestChild::PreprocessHelper final
+  : public CancelableRunnable
+{
+  typedef std::pair<nsCOMPtr<nsIInputStream>,
+                    nsCOMPtr<nsIInputStream>> StreamPair;
+
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  nsTArray<StreamPair> mStreamPairs;
+  nsTArray<RefPtr<JS::WasmModule>> mModules;
+  BackgroundRequestChild* mActor;
+  nsresult mResultCode;
+
+public:
+  PreprocessHelper(BackgroundRequestChild* aActor)
+    : mOwningThread(NS_GetCurrentThread())
+    , mActor(aActor)
+    , mResultCode(NS_OK)
+  {
+    AssertIsOnOwningThread();
+    MOZ_ASSERT(aActor);
+    aActor->AssertIsOnOwningThread();
+  }
+
+  bool
+  IsOnOwningThread() const
+  {
+    MOZ_ASSERT(mOwningThread);
+
+    bool current;
+    return NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)) && current;
+  }
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    MOZ_ASSERT(IsOnOwningThread());
+  }
+
+  void
+  ClearActor()
+  {
+    AssertIsOnOwningThread();
+
+    mActor = nullptr;
+  }
+
+  nsresult
+  Init(const nsTArray<StructuredCloneFile>& aFiles);
+
+  nsresult
+  Dispatch();
+
+private:
+  ~PreprocessHelper()
+  { }
+
+  void
+  RunOnOwningThread();
+
+  nsresult
+  RunOnStreamTransportThread();
+
+  NS_DECL_NSIRUNNABLE
+
+  virtual nsresult
+  Cancel() override;
+};
+
+/*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 void
 PermissionRequestMainProcessHelper::OnPromptComplete(
                                                PermissionValue aPermissionValue)
 {
   MOZ_ASSERT(mActor);
@@ -2407,16 +2532,46 @@ BackgroundRequestChild::~BackgroundReque
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mTransaction);
 
   MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChild);
 }
 
 void
+BackgroundRequestChild::OnPreprocessFinished(
+                               const nsTArray<RefPtr<JS::WasmModule>>& aModules)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!aModules.IsEmpty());
+  MOZ_ASSERT(mPreprocessHelper);
+  MOZ_ASSERT(!mModules);
+
+  mModules = new nsTArray<RefPtr<JS::WasmModule>>;
+  *mModules = Move(aModules);
+
+  MOZ_ALWAYS_TRUE(SendContinue(ObjectStoreGetPreprocessResponse()));
+
+  mPreprocessHelper = nullptr;
+}
+
+void
+BackgroundRequestChild::OnPreprocessFailed(nsresult aErrorCode)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_FAILED(aErrorCode));
+  MOZ_ASSERT(mPreprocessHelper);
+  MOZ_ASSERT(!mModules);
+
+  MOZ_ALWAYS_TRUE(SendContinue(aErrorCode));
+
+  mPreprocessHelper = nullptr;
+}
+
+void
 BackgroundRequestChild::HandleResponse(nsresult aResponse)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NS_FAILED(aResponse));
   MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
   MOZ_ASSERT(mTransaction);
 
   DispatchErrorEvent(mRequest, aResponse, mTransaction);
@@ -2450,17 +2605,18 @@ BackgroundRequestChild::HandleResponse(
 
   // XXX Fix this somehow...
   auto& serializedCloneInfo =
     const_cast<SerializedStructuredCloneReadInfo&>(aResponse);
 
   StructuredCloneReadInfo cloneReadInfo(Move(serializedCloneInfo));
 
   DeserializeStructuredCloneFiles(mTransaction->Database(),
-                                  aResponse,
+                                  aResponse.files(),
+                                  mModules,
                                   cloneReadInfo.mFiles);
 
   ResultHelper helper(mRequest, mTransaction, &cloneReadInfo);
 
   DispatchSuccessEvent(&helper);
 }
 
 void
@@ -2482,17 +2638,20 @@ BackgroundRequestChild::HandleResponse(
       // XXX Fix this somehow...
       auto& serializedCloneInfo =
         const_cast<SerializedStructuredCloneReadInfo&>(aResponse[index]);
 
       StructuredCloneReadInfo* cloneReadInfo = cloneReadInfos.AppendElement();
 
       // Get the files
       nsTArray<StructuredCloneFile> files;
-      DeserializeStructuredCloneFiles(database, serializedCloneInfo, files);
+      DeserializeStructuredCloneFiles(database,
+                                      serializedCloneInfo.files(),
+                                      nullptr,
+                                      files);
 
       // Move relevant data into the cloneReadInfo
       *cloneReadInfo = Move(serializedCloneInfo);
       cloneReadInfo->mFiles = Move(files);
     }
   }
 
   ResultHelper helper(mRequest, mTransaction, &cloneReadInfos);
@@ -2524,16 +2683,22 @@ BackgroundRequestChild::HandleResponse(u
 
 void
 BackgroundRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
   MaybeCollectGarbageOnIPCMessage();
 
+  if (mPreprocessHelper) {
+    mPreprocessHelper->ClearActor();
+
+    mPreprocessHelper = nullptr;
+  }
+
   if (mTransaction) {
     mTransaction->AssertIsOnOwningThread();
 
     mTransaction->OnRequestFinished(/* aActorDestroyedNormally */
                                     aWhy == Deletion);
 #ifdef DEBUG
     mTransaction = nullptr;
 #endif
@@ -2624,16 +2789,228 @@ BackgroundRequestChild::Recv__delete__(c
 
   // Null this out so that we don't try to call OnRequestFinished() again in
   // ActorDestroy.
   mTransaction = nullptr;
 
   return true;
 }
 
+bool
+BackgroundRequestChild::RecvPreprocess(const PreprocessParams& aParams)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mTransaction);
+
+  MaybeCollectGarbageOnIPCMessage();
+
+  IDBDatabase* database = mTransaction->Database();
+
+  if (aParams.type() != PreprocessParams::TObjectStoreGetPreprocessParams) {
+    MOZ_ASSERT(false, "Fix me!");
+    return false;
+  }
+
+  ObjectStoreGetPreprocessParams params =
+    aParams.get_ObjectStoreGetPreprocessParams();
+
+  nsTArray<StructuredCloneFile> files;
+  DeserializeStructuredCloneFiles(database,
+                                  params.preprocessInfo().files(),
+                                  nullptr,
+                                  files);
+
+  mPreprocessHelper = new PreprocessHelper(this);
+
+  nsresult rv = mPreprocessHelper->Init(files);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return SendContinue(rv);
+  }
+
+  rv = mPreprocessHelper->Dispatch();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return SendContinue(rv);
+  }
+
+  return true;
+}
+
+nsresult
+BackgroundRequestChild::
+PreprocessHelper::Init(const nsTArray<StructuredCloneFile>& aFiles)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!aFiles.IsEmpty());
+
+  uint32_t count = aFiles.Length();
+
+  // We should receive even number of files.
+  MOZ_ASSERT(count % 2 == 0);
+
+  // Let's process it as pairs.
+  count = count / 2;
+
+  nsTArray<StreamPair> streamPairs;
+  for (uint32_t index = 0; index < count; index++) {
+    uint32_t bytecodeIndex = index * 2;
+    uint32_t compiledIndex = bytecodeIndex + 1;
+
+    const StructuredCloneFile& bytecodeFile = aFiles[bytecodeIndex];
+    const StructuredCloneFile& compiledFile = aFiles[compiledIndex];
+
+    MOZ_ASSERT(bytecodeFile.mType == StructuredCloneFile::eWasmBytecode);
+    MOZ_ASSERT(bytecodeFile.mBlob);
+    MOZ_ASSERT(compiledFile.mType == StructuredCloneFile::eWasmCompiled);
+
+    ErrorResult errorResult;
+
+    nsCOMPtr<nsIInputStream> bytecodeStream;
+    bytecodeFile.mBlob->GetInternalStream(getter_AddRefs(bytecodeStream),
+                                          errorResult);
+    if (NS_WARN_IF(errorResult.Failed())) {
+      return errorResult.StealNSResult();
+    }
+
+    nsCOMPtr<nsIInputStream> compiledStream;
+    if (compiledFile.mBlob) {
+      compiledFile.mBlob->GetInternalStream(getter_AddRefs(compiledStream),
+                                            errorResult);
+      if (NS_WARN_IF(errorResult.Failed())) {
+        return errorResult.StealNSResult();
+      }
+    }
+
+    streamPairs.AppendElement(StreamPair(bytecodeStream, compiledStream));
+  }
+
+  mStreamPairs = Move(streamPairs);
+
+  return NS_OK;
+}
+
+nsresult
+BackgroundRequestChild::
+PreprocessHelper::Dispatch()
+{
+  AssertIsOnOwningThread();
+
+  // The stream transport service is used for asynchronous processing. It has
+  // a threadpool with a high cap of 25 threads. Fortunately, the service can
+  // be used on workers too.
+  nsCOMPtr<nsIEventTarget> target =
+    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+  MOZ_ASSERT(target);
+
+  nsresult rv = target->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+BackgroundRequestChild::
+PreprocessHelper::RunOnOwningThread()
+{
+  AssertIsOnOwningThread();
+
+  if (mActor) {
+    if (NS_SUCCEEDED(mResultCode)) {
+      mActor->OnPreprocessFinished(mModules);
+    } else {
+      mActor->OnPreprocessFailed(mResultCode);
+    }
+  }
+}
+
+nsresult
+BackgroundRequestChild::
+PreprocessHelper::RunOnStreamTransportThread()
+{
+  MOZ_ASSERT(!IsOnOwningThread());
+  MOZ_ASSERT(!mStreamPairs.IsEmpty());
+  MOZ_ASSERT(mModules.IsEmpty());
+
+  const uint32_t count = mStreamPairs.Length();
+
+  for (uint32_t index = 0; index < count; index++) {
+    const StreamPair& streamPair = mStreamPairs[index];
+
+    const nsCOMPtr<nsIInputStream>& bytecodeStream = streamPair.first;
+
+    MOZ_ASSERT(bytecodeStream);
+
+    PRFileDesc* bytecodeFileDesc = GetFileDescriptorFromStream(bytecodeStream);
+    if (NS_WARN_IF(!bytecodeFileDesc)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    const nsCOMPtr<nsIInputStream>& compiledStream = streamPair.second;
+
+    PRFileDesc* compiledFileDesc;
+    if (compiledStream) {
+      compiledFileDesc = GetFileDescriptorFromStream(compiledStream);
+      if (NS_WARN_IF(!bytecodeFileDesc)) {
+        return NS_ERROR_FAILURE;
+      }
+    } else {
+      compiledFileDesc = nullptr;
+    }
+
+    JS::BuildIdCharVector buildId;
+    bool ok = GetBuildId(&buildId);
+    if (NS_WARN_IF(!ok)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<JS::WasmModule> module = JS::DeserializeWasmModule(bytecodeFileDesc,
+                                                              compiledFileDesc,
+                                                              Move(buildId),
+                                                              nullptr,
+                                                              0,
+                                                              0);
+    if (NS_WARN_IF(!module)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mModules.AppendElement(module);
+  }
+
+  mStreamPairs.Clear();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundRequestChild::
+PreprocessHelper::Run()
+{
+  if (IsOnOwningThread()) {
+    RunOnOwningThread();
+  } else {
+    nsresult rv = RunOnStreamTransportThread();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      MOZ_ASSERT(mResultCode == NS_OK);
+      mResultCode = rv;
+    }
+
+    MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundRequestChild::
+PreprocessHelper::Cancel()
+{
+  return NS_OK;
+}
+
 /*******************************************************************************
  * BackgroundCursorChild
  ******************************************************************************/
 
 // Does not need to be threadsafe since this only runs on one thread, but
 // inheriting from CancelableRunnable is easy.
 class BackgroundCursorChild::DelayedActionRunnable final
   : public CancelableRunnable
@@ -2821,17 +3198,18 @@ BackgroundCursorChild::HandleResponse(
   auto& responses =
     const_cast<nsTArray<ObjectStoreCursorResponse>&>(aResponses);
 
   for (ObjectStoreCursorResponse& response : responses) {
     StructuredCloneReadInfo cloneReadInfo(Move(response.cloneInfo()));
     cloneReadInfo.mDatabase = mTransaction->Database();
 
     DeserializeStructuredCloneFiles(mTransaction->Database(),
-                                    response.cloneInfo(),
+                                    response.cloneInfo().files(),
+                                    nullptr,
                                     cloneReadInfo.mFiles);
 
     RefPtr<IDBCursor> newCursor;
 
     if (mCursor) {
       mCursor->Reset(Move(response.key()), Move(cloneReadInfo));
     } else {
       newCursor = IDBCursor::Create(this,
@@ -2884,17 +3262,18 @@ BackgroundCursorChild::HandleResponse(co
 
   // XXX Fix this somehow...
   auto& response = const_cast<IndexCursorResponse&>(aResponse);
 
   StructuredCloneReadInfo cloneReadInfo(Move(response.cloneInfo()));
   cloneReadInfo.mDatabase = mTransaction->Database();
 
   DeserializeStructuredCloneFiles(mTransaction->Database(),
-                                  aResponse.cloneInfo(),
+                                  aResponse.cloneInfo().files(),
+                                  nullptr,
                                   cloneReadInfo.mFiles);
 
   RefPtr<IDBCursor> newCursor;
 
   if (mCursor) {
     mCursor->Reset(Move(response.key()),
                    Move(response.sortKey()),
                    Move(response.objectKey()),
--- a/dom/indexedDB/ActorsChild.h
+++ b/dom/indexedDB/ActorsChild.h
@@ -24,16 +24,20 @@
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
 class nsIEventTarget;
 struct nsID;
 struct PRThread;
 
+namespace JS {
+struct WasmModule;
+} // namespace JS
+
 namespace mozilla {
 namespace ipc {
 
 class BackgroundChildImpl;
 
 } // namespace ipc
 
 namespace dom {
@@ -635,28 +639,38 @@ private:
 class BackgroundRequestChild final
   : public BackgroundRequestChildBase
   , public PBackgroundIDBRequestChild
 {
   friend class BackgroundTransactionChild;
   friend class BackgroundVersionChangeTransactionChild;
   friend IDBTransaction;
 
+  class PreprocessHelper;
+
   RefPtr<IDBTransaction> mTransaction;
+  RefPtr<PreprocessHelper> mPreprocessHelper;
+  nsAutoPtr<nsTArray<RefPtr<JS::WasmModule>>> mModules;
 
 private:
   // Only created by IDBTransaction.
   explicit
   BackgroundRequestChild(IDBRequest* aRequest);
 
   // Only destroyed by BackgroundTransactionChild or
   // BackgroundVersionChangeTransactionChild.
   ~BackgroundRequestChild();
 
   void
+  OnPreprocessFinished(const nsTArray<RefPtr<JS::WasmModule>>& aModules);
+
+  void
+  OnPreprocessFailed(nsresult aErrorCode);
+
+  void
   HandleResponse(nsresult aResponse);
 
   void
   HandleResponse(const Key& aResponse);
 
   void
   HandleResponse(const nsTArray<Key>& aResponse);
 
@@ -673,16 +687,19 @@ private:
   HandleResponse(uint64_t aResponse);
 
   // IPDL methods are only called by IPDL.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual bool
   Recv__delete__(const RequestResponse& aResponse) override;
+
+  virtual bool
+  RecvPreprocess(const PreprocessParams& aParams) override;
 };
 
 class BackgroundCursorChild final
   : public PBackgroundIDBCursorChild
 {
   friend class BackgroundTransactionChild;
   friend class BackgroundVersionChangeTransactionChild;
 
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -5921,37 +5921,41 @@ protected:
   static uint64_t
   ReinterpretDoubleAsUInt64(double aDouble);
 
   static nsresult
   GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
                                           uint32_t aDataIndex,
                                           uint32_t aFileIdsIndex,
                                           FileManager* aFileManager,
-                                          StructuredCloneReadInfo* aInfo)
+                                          StructuredCloneReadInfo* aInfo,
+                                          bool* aHasWasm)
   {
     return GetStructuredCloneReadInfoFromSource(aStatement,
                                                 aDataIndex,
                                                 aFileIdsIndex,
                                                 aFileManager,
-                                                aInfo);
+                                                aInfo,
+                                                aHasWasm);
   }
 
   static nsresult
   GetStructuredCloneReadInfoFromValueArray(mozIStorageValueArray* aValues,
                                            uint32_t aDataIndex,
                                            uint32_t aFileIdsIndex,
                                            FileManager* aFileManager,
                                            StructuredCloneReadInfo* aInfo)
   {
+    bool dummy;
     return GetStructuredCloneReadInfoFromSource(aValues,
                                                 aDataIndex,
                                                 aFileIdsIndex,
                                                 aFileManager,
-                                                aInfo);
+                                                aInfo,
+                                                &dummy);
   }
 
   static nsresult
   BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
                           mozIStorageStatement* aStatement);
 
   static nsresult
   BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
@@ -6005,30 +6009,33 @@ protected:
 
 private:
   template <typename T>
   static nsresult
   GetStructuredCloneReadInfoFromSource(T* aSource,
                                        uint32_t aDataIndex,
                                        uint32_t aFileIdsIndex,
                                        FileManager* aFileManager,
-                                       StructuredCloneReadInfo* aInfo);
+                                       StructuredCloneReadInfo* aInfo,
+                                       bool* aHasWasm);
 
   static nsresult
   GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
                                      uint32_t aBlobDataLength,
                                      FileManager* aFileManager,
                                      const nsAString& aFileIds,
-                                     StructuredCloneReadInfo* aInfo);
+                                     StructuredCloneReadInfo* aInfo,
+                                     bool* aHasWasm);
 
   static nsresult
   GetStructuredCloneReadInfoFromExternalBlob(uint64_t aIntData,
                                              FileManager* aFileManager,
                                              const nsAString& aFileIds,
-                                             StructuredCloneReadInfo* aInfo);
+                                             StructuredCloneReadInfo* aInfo,
+                                             bool* aHasWasm);
 
   // Not to be overridden by subclasses.
   NS_DECL_MOZISTORAGEPROGRESSHANDLER
 };
 
 class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
 {
   mozIStorageConnection* mConnection;
@@ -6044,40 +6051,61 @@ public:
   nsresult
   Register(mozIStorageConnection* aConnection,
            DatabaseOperationBase* aDatabaseOp);
 };
 
 class TransactionDatabaseOperationBase
   : public DatabaseOperationBase
 {
+  enum class InternalState
+  {
+    Initial,
+    DatabaseWork,
+    SendingPreprocess,
+    WaitingForContinue,
+    SendingResults,
+    Completed
+  };
+
   RefPtr<TransactionBase> mTransaction;
   const int64_t mTransactionLoggingSerialNumber;
+  InternalState mInternalState;
   const bool mTransactionIsAborted;
 
 public:
   void
   AssertIsOnConnectionThread() const
 #ifdef DEBUG
   ;
 #else
   { }
 #endif
 
+  uint64_t
+  StartOnConnectionPool(const nsID& aBackgroundChildLoggingId,
+                        const nsACString& aDatabaseId,
+                        int64_t aLoggingSerialNumber,
+                        const nsTArray<nsString>& aObjectStoreNames,
+                        bool aIsWriteTransaction);
+
   void
   DispatchToConnectionPool();
 
   TransactionBase*
   Transaction() const
   {
     MOZ_ASSERT(mTransaction);
 
     return mTransaction;
   }
 
+  void
+  NoteContinueReceived();
+
   // May be overridden by subclasses if they need to perform work on the
   // background thread before being dispatched. Returning false will kill the
   // child actors and prevent dispatch.
   virtual bool
   Init(TransactionBase* aTransaction);
 
   // This callback will be called on the background thread before releasing the
   // final reference to this request object. Subclasses may perform any
@@ -6100,32 +6128,54 @@ protected:
 
   // Must be overridden in subclasses. Called on the target thread to allow the
   // subclass to perform necessary database or file operations. A successful
   // return value will trigger a SendSuccessResult callback on the background
   // thread while a failure value will trigger a SendFailureResult callback.
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) = 0;
 
+  // May be overriden in subclasses. Called on the background thread to decide
+  // if the subclass needs to send any preprocess info to the child actor.
+  virtual bool
+  HasPreprocessInfo();
+
+  // May be overriden in subclasses. Called on the background thread to allow
+  // the subclass to serialize its preprocess info and send it to the child
+  // actor. A successful return value will trigger a wait for a
+  // NoteContinueReceived callback on the background thread while a failure
+  // value will trigger a SendFailureResult callback.
+  virtual nsresult
+  SendPreprocessInfo();
+
   // Must be overridden in subclasses. Called on the background thread to allow
   // the subclass to serialize its results and send them to the child actor. A
   // failed return value will trigger a SendFailureResult callback.
   virtual nsresult
   SendSuccessResult() = 0;
 
   // Must be overridden in subclasses. Called on the background thread to allow
   // the subclass to send its failure code. Returning false will cause the
   // transaction to be aborted with aResultCode. Returning true will not cause
   // the transaction to be aborted.
   virtual bool
   SendFailureResult(nsresult aResultCode) = 0;
 
 private:
   void
-  RunOnOwningThread();
+  SendToConnectionPool();
+
+  void
+  SendPreprocess();
+
+  void
+  SendResults();
+
+  void
+  SendPreprocessInfoOrResults(bool aSendPreprocessInfo);
 
   // Not to be overridden by subclasses.
   NS_DECL_NSIRUNNABLE
 };
 
 class Factory final
   : public PBackgroundIDBFactoryParent
 {
@@ -6193,33 +6243,31 @@ private:
   virtual bool
   DeallocPBackgroundIDBDatabaseParent(PBackgroundIDBDatabaseParent* aActor)
                                       override;
 };
 
 class WaitForTransactionsHelper final
   : public Runnable
 {
-  nsCOMPtr<nsIEventTarget> mOwningThread;
   const nsCString mDatabaseId;
   nsCOMPtr<nsIRunnable> mCallback;
 
   enum class State
   {
     Initial = 0,
     WaitingForTransactions,
     WaitingForFileHandles,
     Complete
   } mState;
 
 public:
   WaitForTransactionsHelper(const nsCString& aDatabaseId,
                             nsIRunnable* aCallback)
-    : mOwningThread(NS_GetCurrentThread())
-    , mDatabaseId(aDatabaseId)
+    : mDatabaseId(aDatabaseId)
     , mCallback(aCallback)
     , mState(State::Initial)
   {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(!aDatabaseId.IsEmpty());
     MOZ_ASSERT(aCallback);
   }
 
@@ -8132,16 +8180,19 @@ private:
   SendSuccessResult() override;
 
   virtual bool
   SendFailureResult(nsresult aResultCode) override;
 
   // IPDL methods.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool
+  RecvContinue(const PreprocessResponse& aResponse) override;
 };
 
 class ObjectStoreAddOrPutRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   typedef mozilla::dom::quota::PersistenceType PersistenceType;
@@ -8286,16 +8337,17 @@ class ObjectStoreGetRequestOp final
 
   const uint32_t mObjectStoreId;
   RefPtr<Database> mDatabase;
   const OptionalKeyRange mOptionalKeyRange;
   AutoTArray<StructuredCloneReadInfo, 1> mResponse;
   PBackgroundParent* mBackgroundParent;
   const uint32_t mLimit;
   const bool mGetAll;
+  bool mHasWasm;
 
 private:
   // Only created by TransactionBase.
   ObjectStoreGetRequestOp(TransactionBase* aTransaction,
                           const RequestParams& aParams,
                           bool aGetAll);
 
   ~ObjectStoreGetRequestOp()
@@ -8303,16 +8355,22 @@ private:
 
   nsresult
   ConvertResponse(uint32_t aIndex,
                   SerializedStructuredCloneReadInfo& aSerializedInfo);
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
+  virtual bool
+  HasPreprocessInfo() override;
+
+  virtual nsresult
+  SendPreprocessInfo() override;
+
   virtual void
   GetResponse(RequestResponse& aResponse) override;
 };
 
 class ObjectStoreGetKeyRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
@@ -9642,19 +9700,21 @@ DeserializeStructuredCloneFile(FileManag
   aFile->mType = type;
 
   return NS_OK;
 }
 
 nsresult
 DeserializeStructuredCloneFiles(FileManager* aFileManager,
                                 const nsAString& aText,
-                                nsTArray<StructuredCloneFile>& aResult)
-{
-  MOZ_ASSERT(!IsOnBackgroundThread());
+                                nsTArray<StructuredCloneFile>& aResult,
+                                bool* aHasWasm)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aHasWasm);
 
   nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
     tokenizer(aText, ' ');
 
   nsAutoString token;
   nsresult rv;
   nsCOMPtr<nsIFile> directory;
 
@@ -9663,17 +9723,22 @@ DeserializeStructuredCloneFiles(FileMana
     MOZ_ASSERT(!token.IsEmpty());
 
     StructuredCloneFile* file = aResult.AppendElement();
     rv = DeserializeStructuredCloneFile(aFileManager, token, file);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    if (file->mType == StructuredCloneFile::eWasmCompiled) {
+    if (file->mType == StructuredCloneFile::eWasmBytecode) {
+      MOZ_ASSERT(file->mValid);
+
+      *aHasWasm = true;
+    }
+    else if (file->mType == StructuredCloneFile::eWasmCompiled) {
       if (!directory) {
         directory = aFileManager->GetCheckedDirectory();
         if (NS_WARN_IF(!directory)) {
           return NS_ERROR_FAILURE;
         }
       }
 
       const int64_t fileId = file->mFileInfo->Id();
@@ -9695,16 +9760,18 @@ DeserializeStructuredCloneFiles(FileMana
       bool ok = GetBuildId(&buildId);
       if (NS_WARN_IF(!ok)) {
         return NS_ERROR_FAILURE;
       }
 
       MOZ_ASSERT(file->mValid);
       file->mValid = JS::CompiledWasmModuleAssumptionsMatch(fileDesc,
                                                             Move(buildId));
+
+      *aHasWasm = true;
     }
   }
 
   return NS_OK;
 }
 
 bool
 GetDatabaseBaseFilename(const nsAString& aFilename,
@@ -9728,16 +9795,17 @@ GetDatabaseBaseFilename(const nsAString&
   return true;
 }
 
 nsresult
 SerializeStructuredCloneFiles(
                          PBackgroundParent* aBackgroundActor,
                          Database* aDatabase,
                          const nsTArray<StructuredCloneFile>& aFiles,
+                         bool aForPreprocess,
                          FallibleTArray<SerializedStructuredCloneFile>& aResult)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aBackgroundActor);
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(aResult.IsEmpty());
 
   if (aFiles.IsEmpty()) {
@@ -9756,16 +9824,22 @@ SerializeStructuredCloneFiles(
 
   if (NS_WARN_IF(!aResult.SetCapacity(count, fallible))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   for (uint32_t index = 0; index < count; index++) {
     const StructuredCloneFile& file = aFiles[index];
 
+    if (aForPreprocess &&
+        file.mType != StructuredCloneFile::eWasmBytecode &&
+        file.mType != StructuredCloneFile::eWasmCompiled) {
+      continue;
+    }
+
     const int64_t fileId = file.mFileInfo->Id();
     MOZ_ASSERT(fileId > 0);
 
     nsCOMPtr<nsIFile> nativeFile =
       fileManager->GetCheckedFileForId(directory, fileId);
     if (NS_WARN_IF(!nativeFile)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
@@ -9837,23 +9911,23 @@ SerializeStructuredCloneFiles(
         file->file() = null_t();
         file->type() = StructuredCloneFile::eStructuredClone;
 
         break;
       }
 
       case StructuredCloneFile::eWasmBytecode:
       case StructuredCloneFile::eWasmCompiled: {
-        if (file.mType == StructuredCloneFile::eWasmCompiled && !file.mValid) {
+        if (!aForPreprocess || !file.mValid) {
           SerializedStructuredCloneFile* serializedFile =
             aResult.AppendElement(fallible);
           MOZ_ASSERT(serializedFile);
 
           serializedFile->file() = null_t();
-          serializedFile->type() = StructuredCloneFile::eWasmCompiled;
+          serializedFile->type() = file.mType;
         } else {
           RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
                                                          file.mFileInfo,
                                                          /* aSnapshot */ false);
 
           PBlobParent* actor =
             BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor,
                                                           impl);
@@ -11452,17 +11526,18 @@ UpdateRefcountFunction::ProcessValue(moz
 
   nsString ids;
   rv = aValues->GetString(aIndex, ids);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsTArray<StructuredCloneFile> files;
-  rv = DeserializeStructuredCloneFiles(mFileManager, ids, files);
+  bool dummy;
+  rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, &dummy);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   for (uint32_t i = 0; i < files.Length(); i++) {
     const StructuredCloneFile& file = files[i];
 
     const int64_t id = file.mFileInfo->Id();
@@ -14385,22 +14460,21 @@ Database::RecvPBackgroundIDBTransactionC
     gConnectionPool = new ConnectionPool();
   }
 
   auto* transaction = static_cast<NormalTransaction*>(aActor);
 
   RefPtr<StartTransactionOp> startOp = new StartTransactionOp(transaction);
 
   uint64_t transactionId =
-    gConnectionPool->Start(GetLoggingInfo()->Id(),
-                           mMetadata->mDatabaseId,
-                           transaction->LoggingSerialNumber(),
-                           aObjectStoreNames,
-                           aMode != IDBTransaction::READ_ONLY,
-                           startOp);
+    startOp->StartOnConnectionPool(GetLoggingInfo()->Id(),
+                                   mMetadata->mDatabaseId,
+                                   transaction->LoggingSerialNumber(),
+                                   aObjectStoreNames,
+                                   aMode != IDBTransaction::READ_ONLY);
 
   transaction->SetActive(transactionId);
 
   if (NS_WARN_IF(!RegisterTransaction(transaction))) {
     IDB_REPORT_INTERNAL_ERR();
     transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, /* aForce */ false);
     return true;
   }
@@ -16588,16 +16662,17 @@ Cursor::SendResponseInternal(
                  aResponse.type() == CursorResponse::TIndexCursorResponse);
       MOZ_ASSERT(mDatabase);
       MOZ_ASSERT(mBackgroundParent);
 
       FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
       nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
                                                   mDatabase,
                                                   files,
+                                                  /* aForPreprocess */ false,
                                                   serializedFiles);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         aResponse = ClampResultCode(rv);
         break;
       }
 
       SerializedStructuredCloneReadInfo* serializedInfo = nullptr;
       switch (aResponse.type()) {
@@ -19264,17 +19339,18 @@ DatabaseOperationBase::ReinterpretDouble
 // static
 template <typename T>
 nsresult
 DatabaseOperationBase::GetStructuredCloneReadInfoFromSource(
                                                  T* aSource,
                                                  uint32_t aDataIndex,
                                                  uint32_t aFileIdsIndex,
                                                  FileManager* aFileManager,
-                                                 StructuredCloneReadInfo* aInfo)
+                                                 StructuredCloneReadInfo* aInfo,
+                                                 bool* aHasWasm)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(aSource);
   MOZ_ASSERT(aFileManager);
   MOZ_ASSERT(aInfo);
 
   int32_t columnType;
   nsresult rv = aSource->GetTypeOfIndex(aDataIndex, &columnType);
@@ -19307,47 +19383,50 @@ DatabaseOperationBase::GetStructuredClon
     rv = aSource->GetInt64(aDataIndex, reinterpret_cast<int64_t*>(&intData));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = GetStructuredCloneReadInfoFromExternalBlob(intData,
                                                     aFileManager,
                                                     fileIds,
-                                                    aInfo);
+                                                    aInfo,
+                                                    aHasWasm);
   } else {
     const uint8_t* blobData;
     uint32_t blobDataLength;
     nsresult rv =
       aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = GetStructuredCloneReadInfoFromBlob(blobData,
                                             blobDataLength,
                                             aFileManager,
                                             fileIds,
-                                            aInfo);
+                                            aInfo,
+                                            aHasWasm);
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
 DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob(
                                                  const uint8_t* aBlobData,
                                                  uint32_t aBlobDataLength,
                                                  FileManager* aFileManager,
                                                  const nsAString& aFileIds,
-                                                 StructuredCloneReadInfo* aInfo)
+                                                 StructuredCloneReadInfo* aInfo,
+                                                 bool* aHasWasm)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(aFileManager);
   MOZ_ASSERT(aInfo);
 
   PROFILER_LABEL("IndexedDB",
                  "DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob",
                  js::ProfileEntry::Category::STORAGE);
@@ -19373,47 +19452,53 @@ DatabaseOperationBase::GetStructuredClon
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   if (!aInfo->mData.WriteBytes(uncompressedBuffer, uncompressed.Length())) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (!aFileIds.IsVoid()) {
-    nsresult rv =
-      DeserializeStructuredCloneFiles(aFileManager, aFileIds, aInfo->mFiles);
+    nsresult rv = DeserializeStructuredCloneFiles(aFileManager,
+                                                  aFileIds,
+                                                  aInfo->mFiles,
+                                                  aHasWasm);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 // static
 nsresult
 DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob(
                                                  uint64_t aIntData,
                                                  FileManager* aFileManager,
                                                  const nsAString& aFileIds,
-                                                 StructuredCloneReadInfo* aInfo)
+                                                 StructuredCloneReadInfo* aInfo,
+                                                 bool* aHasWasm)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(aFileManager);
   MOZ_ASSERT(aInfo);
 
   PROFILER_LABEL(
             "IndexedDB",
             "DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob",
             js::ProfileEntry::Category::STORAGE);
 
   nsresult rv;
 
   if (!aFileIds.IsVoid()) {
-    rv = DeserializeStructuredCloneFiles(aFileManager, aFileIds, aInfo->mFiles);
+    rv = DeserializeStructuredCloneFiles(aFileManager,
+                                         aFileIds,
+                                         aInfo->mFiles,
+                                         aHasWasm);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   // Higher and lower 32 bits described
   // in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
   uint32_t index = uint32_t(aIntData & 0xFFFFFFFF);
@@ -21950,22 +22035,22 @@ OpenDatabaseOp::DispatchToWorkThread()
 
   if (!gConnectionPool) {
     gConnectionPool = new ConnectionPool();
   }
 
   RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
 
   uint64_t transactionId =
-    gConnectionPool->Start(backgroundChildLoggingId,
-                           mVersionChangeTransaction->DatabaseId(),
-                           loggingSerialNumber,
-                           objectStoreNames,
-                           /* aIsWriteTransaction */ true,
-                           versionChangeOp);
+    versionChangeOp->StartOnConnectionPool(
+                                        backgroundChildLoggingId,
+                                        mVersionChangeTransaction->DatabaseId(),
+                                        loggingSerialNumber,
+                                        objectStoreNames,
+                                        /* aIsWriteTransaction */ true);
 
   mVersionChangeOp = versionChangeOp;
 
   mVersionChangeTransaction->NoteActiveRequest();
   mVersionChangeTransaction->SetActive(transactionId);
 
   return NS_OK;
 }
@@ -23082,66 +23167,92 @@ VersionChangeOp::Run()
 }
 
 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
                                                   TransactionBase* aTransaction)
   : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
                           aTransaction->GetLoggingInfo()->NextRequestSN())
   , mTransaction(aTransaction)
   , mTransactionLoggingSerialNumber(aTransaction->LoggingSerialNumber())
+  , mInternalState(InternalState::Initial)
   , mTransactionIsAborted(aTransaction->IsAborted())
 {
   MOZ_ASSERT(aTransaction);
   MOZ_ASSERT(LoggingSerialNumber());
 }
 
 TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
                                                   TransactionBase* aTransaction,
                                                   uint64_t aLoggingSerialNumber)
   : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
                           aLoggingSerialNumber)
   , mTransaction(aTransaction)
   , mTransactionLoggingSerialNumber(aTransaction->LoggingSerialNumber())
+  , mInternalState(InternalState::Initial)
   , mTransactionIsAborted(aTransaction->IsAborted())
 {
   MOZ_ASSERT(aTransaction);
 }
 
 TransactionDatabaseOperationBase::~TransactionDatabaseOperationBase()
 {
+  MOZ_ASSERT(mInternalState == InternalState::Completed);
   MOZ_ASSERT(!mTransaction,
              "TransactionDatabaseOperationBase::Cleanup() was not called by a "
              "subclass!");
 }
 
 #ifdef DEBUG
 
 void
 TransactionDatabaseOperationBase::AssertIsOnConnectionThread() const
 {
   MOZ_ASSERT(mTransaction);
   mTransaction->AssertIsOnConnectionThread();
 }
 
 #endif // DEBUG
 
+uint64_t
+TransactionDatabaseOperationBase::StartOnConnectionPool(
+                                    const nsID& aBackgroundChildLoggingId,
+                                    const nsACString& aDatabaseId,
+                                    int64_t aLoggingSerialNumber,
+                                    const nsTArray<nsString>& aObjectStoreNames,
+                                    bool aIsWriteTransaction)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::Initial);
+
+  // Must set mInternalState before dispatching otherwise we will race with the
+  // connection thread.
+  mInternalState = InternalState::DatabaseWork;
+
+  return gConnectionPool->Start(aBackgroundChildLoggingId,
+                                aDatabaseId,
+                                aLoggingSerialNumber,
+                                aObjectStoreNames,
+                                aIsWriteTransaction,
+                                this);
+}
+
 void
 TransactionDatabaseOperationBase::DispatchToConnectionPool()
 {
   AssertIsOnOwningThread();
-
-  gConnectionPool->Dispatch(mTransaction->TransactionId(), this);
-
-  mTransaction->NoteActiveRequest();
+  MOZ_ASSERT(mInternalState == InternalState::Initial);
+
+  Unused << this->Run();
 }
 
 void
 TransactionDatabaseOperationBase::RunOnConnectionThread()
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(mInternalState == InternalState::DatabaseWork);
   MOZ_ASSERT(mTransaction);
   MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
 
   PROFILER_LABEL("IndexedDB",
                  "TransactionDatabaseOperationBase::RunOnConnectionThread",
                  js::ProfileEntry::Category::STORAGE);
 
   // There are several cases where we don't actually have to to any work here.
@@ -23198,87 +23309,187 @@ TransactionDatabaseOperationBase::RunOnC
 
         if (NS_FAILED(rv)) {
           mResultCode = rv;
         }
       }
     }
   }
 
+  // Must set mInternalState before dispatching otherwise we will race with the
+  // owning thread.
+  if (HasPreprocessInfo()) {
+    mInternalState = InternalState::SendingPreprocess;
+  } else {
+    mInternalState = InternalState::SendingResults;
+  }
+
   MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
 }
 
-void
-TransactionDatabaseOperationBase::RunOnOwningThread()
-{
-  AssertIsOnOwningThread();
+bool
+TransactionDatabaseOperationBase::HasPreprocessInfo()
+{
+  return false;
+}
+
+nsresult
+TransactionDatabaseOperationBase::SendPreprocessInfo()
+{
+  return NS_OK;
+}
+
+void
+TransactionDatabaseOperationBase::NoteContinueReceived()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue);
+
+  mInternalState = InternalState::SendingResults;
+
+  Unused << this->Run();
+}
+
+void
+TransactionDatabaseOperationBase::SendToConnectionPool()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::Initial);
+
+  // Must set mInternalState before dispatching otherwise we will race with the
+  // connection thread.
+  mInternalState = InternalState::DatabaseWork;
+
+  gConnectionPool->Dispatch(mTransaction->TransactionId(), this);
+
+  mTransaction->NoteActiveRequest();
+}
+
+void
+TransactionDatabaseOperationBase::SendPreprocess()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess);
+
+  SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ true);
+}
+
+void
+TransactionDatabaseOperationBase::SendResults()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::SendingResults);
+
+  SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ false);
+}
+
+void
+TransactionDatabaseOperationBase::SendPreprocessInfoOrResults(
+                                                       bool aSendPreprocessInfo)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess ||
+             mInternalState == InternalState::SendingResults);
   MOZ_ASSERT(mTransaction);
 
+  // Only needed if we're being called from within NoteContinueReceived() since
+  // this TransactionDatabaseOperationBase is only held alive by the IPDL.
+  // SendSuccessResult/SendFailureResult releases that last reference.
+  RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip;
+
   if (NS_WARN_IF(IsActorDestroyed())) {
     // Don't send any notifications if the actor was destroyed already.
     if (NS_SUCCEEDED(mResultCode)) {
       IDB_REPORT_INTERNAL_ERR();
       mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   } else {
+    if (!aSendPreprocessInfo) {
+      kungFuDeathGrip = this;
+    }
+
     if (mTransaction->IsInvalidated() || mTransaction->IsAborted()) {
       // Aborted transactions always see their requests fail with ABORT_ERR,
       // even if the request succeeded or failed with another error.
       mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
     } else if (NS_SUCCEEDED(mResultCode)) {
-      // This may release the IPDL reference.
-      mResultCode = SendSuccessResult();
+      if (aSendPreprocessInfo) {
+        // This should not release the IPDL reference.
+        mResultCode = SendPreprocessInfo();
+      } else {
+        // This may release the IPDL reference.
+        mResultCode = SendSuccessResult();
+      }
     }
 
     if (NS_FAILED(mResultCode)) {
       // This should definitely release the IPDL reference.
       if (!SendFailureResult(mResultCode)) {
         // Abort the transaction.
         mTransaction->Abort(mResultCode, /* aForce */ false);
       }
     }
   }
 
-  if (mLoggingSerialNumber) {
-    mTransaction->NoteFinishedRequest();
-  }
-
-  Cleanup();
+  if (aSendPreprocessInfo && NS_SUCCEEDED(mResultCode)) {
+    mInternalState = InternalState::WaitingForContinue;
+  } else {
+    if (mLoggingSerialNumber) {
+      mTransaction->NoteFinishedRequest();
+    }
+
+    Cleanup();
+
+    mInternalState = InternalState::Completed;
+  }
 }
 
 bool
 TransactionDatabaseOperationBase::Init(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mInternalState == InternalState::Initial);
   MOZ_ASSERT(aTransaction);
 
   return true;
 }
 
 void
 TransactionDatabaseOperationBase::Cleanup()
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mInternalState == InternalState::SendingResults);
   MOZ_ASSERT(mTransaction);
 
   mTransaction = nullptr;
 }
 
 NS_IMETHODIMP
 TransactionDatabaseOperationBase::Run()
 {
-  MOZ_ASSERT(mTransaction);
-
-  if (IsOnBackgroundThread()) {
-    RunOnOwningThread();
-  } else {
-    RunOnConnectionThread();
-  }
-
-  return NS_OK;
+  switch (mInternalState) {
+    case InternalState::Initial:
+      SendToConnectionPool();
+      return NS_OK;
+
+    case InternalState::DatabaseWork:
+      RunOnConnectionThread();
+      return NS_OK;
+
+    case InternalState::SendingPreprocess:
+      SendPreprocess();
+      return NS_OK;
+
+    case InternalState::SendingResults:
+      SendResults();
+      return NS_OK;
+
+    default:
+      MOZ_CRASH("Bad state!");
+  }
 }
 
 TransactionBase::
 CommitOp::CommitOp(TransactionBase* aTransaction, nsresult aResultCode)
   : DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
                           aTransaction->GetLoggingInfo()->NextRequestSN())
   , mTransaction(aTransaction)
   , mResultCode(aResultCode)
@@ -25465,16 +25676,38 @@ NormalTransactionOp::Cleanup()
 void
 NormalTransactionOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
   NoteActorDestroyed();
 }
 
+bool
+NormalTransactionOp::RecvContinue(const PreprocessResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  switch (aResponse.type()) {
+    case PreprocessResponse::Tnsresult:
+      mResultCode = aResponse.get_nsresult();
+      break;
+
+    case PreprocessResponse::TObjectStoreGetPreprocessResponse:
+      break;
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  NoteContinueReceived();
+
+  return true;
+}
+
 ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp(
                                                   TransactionBase* aTransaction,
                                                   const RequestParams& aParams)
   : NormalTransactionOp(aTransaction)
   , mParams(aParams.type() == RequestParams::TObjectStoreAddParams ?
               aParams.get_ObjectStoreAddParams().commonParams() :
               aParams.get_ObjectStorePutParams().commonParams())
   , mGroup(aTransaction->GetDatabase()->Group())
@@ -26328,16 +26561,17 @@ ObjectStoreGetRequestOp::ObjectStoreGetR
   , mOptionalKeyRange(aGetAll ?
                         aParams.get_ObjectStoreGetAllParams()
                                .optionalKeyRange() :
                         OptionalKeyRange(aParams.get_ObjectStoreGetParams()
                                                 .keyRange()))
   , mBackgroundParent(aTransaction->GetBackgroundParent())
   , mLimit(aGetAll ? aParams.get_ObjectStoreGetAllParams().limit() : 1)
   , mGetAll(aGetAll)
+  , mHasWasm(false)
 {
   MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetParams ||
              aParams.type() == RequestParams::TObjectStoreGetAllParams);
   MOZ_ASSERT(mObjectStoreId);
   MOZ_ASSERT(mDatabase);
   MOZ_ASSERT_IF(!aGetAll,
                 mOptionalKeyRange.type() ==
                   OptionalKeyRange::TSerializedKeyRange);
@@ -26354,16 +26588,17 @@ ObjectStoreGetRequestOp::ConvertResponse
   StructuredCloneReadInfo& info = mResponse[aIndex];
 
   aSerializedInfo.data().data = Move(info.mData);
 
   FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
   nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
                                               mDatabase,
                                               info.mFiles,
+                                              /* aForPreprocess */ false,
                                               serializedFiles);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(aSerializedInfo.files().IsEmpty());
 
   aSerializedInfo.files().SwapElements(serializedFiles);
@@ -26432,31 +26667,79 @@ ObjectStoreGetRequestOp::DoDatabaseWork(
   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
     StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement(fallible);
     if (NS_WARN_IF(!cloneInfo)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0,
                                                  mDatabase->GetFileManager(),
-                                                 cloneInfo);
+                                                 cloneInfo,
+                                                 &mHasWasm);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
 
   return NS_OK;
 }
 
+bool
+ObjectStoreGetRequestOp::HasPreprocessInfo()
+{
+  return mHasWasm;
+}
+
+nsresult
+ObjectStoreGetRequestOp::SendPreprocessInfo()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!IsActorDestroyed());
+  MOZ_ASSERT(!mResponse.IsEmpty());
+
+  if (mGetAll) {
+    MOZ_ASSERT(false, "Fix me!");
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  StructuredCloneReadInfo& cloneInfo = mResponse[0];
+
+  FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
+  nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
+                                              mDatabase,
+                                              cloneInfo.mFiles,
+                                              /* aForPreprocess */ true,
+                                              serializedFiles);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  WasmModulePreprocessInfo preprocessInfo;
+
+  MOZ_ASSERT(preprocessInfo.files().IsEmpty());
+
+  preprocessInfo.files().SwapElements(serializedFiles);
+
+  PreprocessParams params = ObjectStoreGetPreprocessParams(preprocessInfo);
+
+  if (NS_WARN_IF(!PBackgroundIDBRequestParent::SendPreprocess(params))) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  return NS_OK;
+}
+
 void
 ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse)
 {
   MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
 
   if (mGetAll) {
     aResponse = ObjectStoreGetAllResponse();
 
@@ -27030,19 +27313,21 @@ IndexGetRequestOp::DoDatabaseWork(Databa
 
   bool hasResult;
   while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
     StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement(fallible);
     if (NS_WARN_IF(!cloneInfo)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    bool dummy;
     rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0,
                                                  mDatabase->GetFileManager(),
-                                                 cloneInfo);
+                                                 cloneInfo,
+                                                 &dummy);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -27077,16 +27362,17 @@ IndexGetRequestOp::GetResponse(RequestRe
           fallibleCloneInfos[index];
 
         serializedInfo.data().data = Move(info.mData);
 
         FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
         nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
                                                     mDatabase,
                                                     info.mFiles,
+                                                    /* aForPreprocess */ false,
                                                     serializedFiles);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           aResponse = rv;
           return;
         }
 
         MOZ_ASSERT(serializedInfo.files().IsEmpty());
 
@@ -27112,16 +27398,17 @@ IndexGetRequestOp::GetResponse(RequestRe
 
     serializedInfo.data().data = Move(info.mData);
 
     FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
     nsresult rv =
       SerializeStructuredCloneFiles(mBackgroundParent,
                                     mDatabase,
                                     info.mFiles,
+                                    /* aForPreprocess */ false,
                                     serializedFiles);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aResponse = rv;
       return;
     }
 
     MOZ_ASSERT(serializedInfo.files().IsEmpty());
 
@@ -27401,21 +27688,23 @@ CursorOpBase::PopulateResponseFromStatem
   nsresult rv = mCursor->mKey.SetFromStatement(aStmt, 0);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   switch (mCursor->mType) {
     case OpenCursorParams::TObjectStoreOpenCursorParams: {
       StructuredCloneReadInfo cloneInfo;
+      bool dummy;
       rv = GetStructuredCloneReadInfoFromStatement(aStmt,
                                                    2,
                                                    1,
                                                    mCursor->mFileManager,
-                                                   &cloneInfo);
+                                                   &cloneInfo,
+                                                   &dummy);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       if (aInitializeResponse) {
         mResponse = nsTArray<ObjectStoreCursorResponse>();
       } else {
         MOZ_ASSERT(mResponse.type() ==
@@ -27444,21 +27733,23 @@ CursorOpBase::PopulateResponseFromStatem
       }
 
       rv = mCursor->mObjectKey.SetFromStatement(aStmt, 2);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       StructuredCloneReadInfo cloneInfo;
+      bool dummy;
       rv = GetStructuredCloneReadInfoFromStatement(aStmt,
                                                    4,
                                                    3,
                                                    mCursor->mFileManager,
-                                                   &cloneInfo);
+                                                   &cloneInfo,
+                                                   &dummy);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       MOZ_ASSERT(aInitializeResponse);
       mResponse = IndexCursorResponse();
 
       auto& response = mResponse.get_IndexCursorResponse();
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -185,35 +185,16 @@ GenerateRequest(JSContext* aCx, IDBObjec
 
   RefPtr<IDBRequest> request =
     IDBRequest::Create(aCx, aObjectStore, transaction->Database(), transaction);
   MOZ_ASSERT(request);
 
   return request.forget();
 }
 
-PRFileDesc*
-GetFileDescriptorFromStream(nsIInputStream* aStream)
-{
-  nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream);
-  if (NS_WARN_IF(!fileMetadata)) {
-    return nullptr;
-  }
-
-  PRFileDesc* fileDesc;
-  nsresult rv = fileMetadata->GetFileDescriptor(&fileDesc);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return nullptr;
-  }
-
-  MOZ_ASSERT(fileDesc);
-
-  return fileDesc;
-}
-
 bool
 StructuredCloneWriteCallback(JSContext* aCx,
                              JSStructuredCloneWriter* aWriter,
                              JS::Handle<JSObject*> aObj,
                              void* aClosure)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aWriter);
@@ -391,16 +372,19 @@ StructuredCloneWriteCallback(JSContext* 
 
     if (cloneWriteInfo->mFiles.Length() + 1 > size_t(UINT32_MAX)) {
       MOZ_ASSERT(false, "Fix the structured clone data to use a bigger type!");
       return false;
     }
 
     const uint32_t index = cloneWriteInfo->mFiles.Length();
 
+    // The ordering of the bytecode and compiled file is significant and must
+    // never be changed. These two files must always form a pair
+    // [eWasmBytecode, eWasmCompiled]. Everything else depends on it!
     if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_WASM, /* flags */ 0) ||
         !JS_WriteUint32Pair(aWriter, index, index + 1)) {
       return false;
     }
 
     StructuredCloneFile* newFile = cloneWriteInfo->mFiles.AppendElement();
     newFile->mBlob = bytecodeBlob;
     newFile->mType = StructuredCloneFile::eWasmBytecode;
@@ -740,76 +724,26 @@ public:
     }
 
     aResult.set(&wrappedFile.toObject());
     return true;
   }
 
   static bool
   CreateAndWrapWasmModule(JSContext* aCx,
-                          StructuredCloneFile& aBytecodeFile,
-                          StructuredCloneFile& aCompiledFile,
+                          StructuredCloneFile& aFile,
                           const WasmModuleData& aData,
                           JS::MutableHandle<JSObject*> aResult)
   {
     MOZ_ASSERT(aCx);
-    MOZ_ASSERT(aBytecodeFile.mType == StructuredCloneFile::eWasmBytecode);
-    MOZ_ASSERT(aBytecodeFile.mBlob);
-    MOZ_ASSERT(aCompiledFile.mType == StructuredCloneFile::eWasmCompiled);
-
-    ErrorResult errorResult;
-
-    nsCOMPtr<nsIInputStream> bytecodeStream;
-    aBytecodeFile.mBlob->GetInternalStream(getter_AddRefs(bytecodeStream),
-                                           errorResult);
-    if (NS_WARN_IF(errorResult.Failed())) {
-      return false;
-    }
-
-    PRFileDesc* bytecodeFileDesc = GetFileDescriptorFromStream(bytecodeStream);
-    if (NS_WARN_IF(!bytecodeFileDesc)) {
-      return false;
-    }
-
-    // The compiled stream must scoped here!
-    nsCOMPtr<nsIInputStream> compiledStream;
-
-    PRFileDesc* compiledFileDesc;
-    if (aCompiledFile.mBlob) {
-      aCompiledFile.mBlob->GetInternalStream(getter_AddRefs(compiledStream),
-                                             errorResult);
-      if (NS_WARN_IF(errorResult.Failed())) {
-        return false;
-      }
-
-      compiledFileDesc = GetFileDescriptorFromStream(compiledStream);
-      if (NS_WARN_IF(!compiledFileDesc)) {
-        return false;
-      }
-    } else {
-      compiledFileDesc = nullptr;
-    }
-
-    JS::BuildIdCharVector buildId;
-    bool ok = GetBuildId(&buildId);
-    if (NS_WARN_IF(!ok)) {
-      return false;
-    }
-
-    RefPtr<JS::WasmModule> module = JS::DeserializeWasmModule(bytecodeFileDesc,
-                                                              compiledFileDesc,
-                                                              Move(buildId),
-                                                              nullptr,
-                                                              0,
-                                                              0);
-    if (NS_WARN_IF(!module)) {
-      return false;
-    }
-
-    JS::Rooted<JSObject*> moduleObj(aCx, module->createObject(aCx));
+    MOZ_ASSERT(aFile.mType == StructuredCloneFile::eWasmCompiled);
+    MOZ_ASSERT(!aFile.mBlob);
+    MOZ_ASSERT(aFile.mWasmModule);
+
+    JS::Rooted<JSObject*> moduleObj(aCx, aFile.mWasmModule->createObject(aCx));
     if (NS_WARN_IF(!moduleObj)) {
       return false;
     }
 
     aResult.set(moduleObj);
     return true;
   }
 };
@@ -902,18 +836,17 @@ public:
     }
 
     aResult.set(obj);
     return true;
   }
 
   static bool
   CreateAndWrapWasmModule(JSContext* aCx,
-                          StructuredCloneFile& aBytecodeFile,
-                          StructuredCloneFile& aCompiledFile,
+                          StructuredCloneFile& aFile,
                           const WasmModuleData& aData,
                           JS::MutableHandle<JSObject*> aResult)
   {
     // Wasm module can't be used in index creation, so just make a dummy object.
     JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
     if (NS_WARN_IF(!obj)) {
       return false;
     }
@@ -975,24 +908,22 @@ public:
     }
 
     aResult.set(obj);
     return true;
   }
 
   static bool
   CreateAndWrapWasmModule(JSContext* aCx,
-                          StructuredCloneFile& aBytecodeFile,
-                          StructuredCloneFile& aCompiledFile,
+                          StructuredCloneFile& aFile,
                           const WasmModuleData& aData,
                           JS::MutableHandle<JSObject*> aResult)
   {
     MOZ_ASSERT(aCx);
-    MOZ_ASSERT(aBytecodeFile.mType == StructuredCloneFile::eBlob);
-    MOZ_ASSERT(aCompiledFile.mType == StructuredCloneFile::eBlob);
+    MOZ_ASSERT(aFile.mType == StructuredCloneFile::eBlob);
 
     MOZ_ASSERT(false, "This should never be possible!");
 
     return false;
   }
 };
 
 #endif // MOZ_B2G
@@ -1034,24 +965,20 @@ CommonStructuredCloneReadCallback(JSCont
       MOZ_ASSERT(!data.flags);
 
       if (data.bytecodeIndex >= cloneReadInfo->mFiles.Length() ||
           data.compiledIndex >= cloneReadInfo->mFiles.Length()) {
         MOZ_ASSERT(false, "Bad index value!");
         return nullptr;
       }
 
-      StructuredCloneFile& bytecodeFile =
-        cloneReadInfo->mFiles[data.bytecodeIndex];
-      StructuredCloneFile& compiledFile =
-        cloneReadInfo->mFiles[data.compiledIndex];
+      StructuredCloneFile& file = cloneReadInfo->mFiles[data.compiledIndex];
 
       if (NS_WARN_IF(!Traits::CreateAndWrapWasmModule(aCx,
-                                                      bytecodeFile,
-                                                      compiledFile,
+                                                      file,
                                                       data,
                                                       &result))) {
         return nullptr;
       }
 
       return result;
     }
 
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -6,16 +6,20 @@
 
 #ifndef mozilla_dom_indexeddatabase_h__
 #define mozilla_dom_indexeddatabase_h__
 
 #include "js/StructuredClone.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
+namespace JS {
+struct WasmModule;
+} // namespace JS
+
 namespace mozilla {
 namespace dom {
 
 class Blob;
 class IDBDatabase;
 class IDBMutableFile;
 
 namespace indexedDB {
@@ -31,16 +35,17 @@ struct StructuredCloneFile
     eStructuredClone,
     eWasmBytecode,
     eWasmCompiled,
     eEndGuard
   };
 
   RefPtr<Blob> mBlob;
   RefPtr<IDBMutableFile> mMutableFile;
+  RefPtr<JS::WasmModule> mWasmModule;
   RefPtr<FileInfo> mFileInfo;
   FileType mType;
   // This is currently specific to eWasmCompiled files.
   bool mValid;
 
   // In IndexedDatabaseInlines.h
   inline
   StructuredCloneFile();
--- a/dom/indexedDB/PBackgroundIDBRequest.ipdl
+++ b/dom/indexedDB/PBackgroundIDBRequest.ipdl
@@ -101,19 +101,58 @@ union RequestResponse
   ObjectStoreGetAllKeysResponse;
   IndexGetResponse;
   IndexGetKeyResponse;
   IndexGetAllResponse;
   IndexGetAllKeysResponse;
   IndexCountResponse;
 };
 
+struct WasmModulePreprocessInfo
+{
+  SerializedStructuredCloneFile[] files;
+};
+
+struct ObjectStoreGetPreprocessParams
+{
+  WasmModulePreprocessInfo preprocessInfo;
+};
+
+union PreprocessParams
+{
+  ObjectStoreGetPreprocessParams;
+};
+
+struct ObjectStoreGetPreprocessResponse
+{
+};
+
+// The nsresult is used if an error occurs for any preprocess request type.
+// The specific response types are sent on success.
+union PreprocessResponse
+{
+  nsresult;
+  ObjectStoreGetPreprocessResponse;
+};
+
 protocol PBackgroundIDBRequest
 {
   manager PBackgroundIDBTransaction or PBackgroundIDBVersionChangeTransaction;
 
+parent:
+  async Continue(PreprocessResponse response);
+
 child:
   async __delete__(RequestResponse response);
+
+  // Preprocess is used in cases where response processing needs to do something
+  // asynchronous off of the child actor's thread before returning the actual
+  // result to user code. This is necessary because RequestResponse processing
+  // occurs in __delete__ and the PBackgroundIDBRequest implementations'
+  // life-cycles are controlled by IPC and are not otherwise reference counted.
+  // By introducing the (optional) Preprocess/Continue steps reference counting
+  // or the introduction of additional runnables are avoided.
+  async Preprocess(PreprocessParams params);
 };
 
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
--- a/dom/indexedDB/test/file.js
+++ b/dom/indexedDB/test/file.js
@@ -80,27 +80,27 @@ function getNullBlob(size)
   return getBlob("binary/null", getView(size));
 }
 
 function getNullFile(name, size)
 {
   return getFile(name, "binary/null", getView(size));
 }
 
-function isWasmSupported()
+// This needs to be async to make it available on workers too.
+function getWasmBinary(text)
 {
-  let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
-  return testingFunctions.wasmIsSupported();
+  let binary = getWasmBinarySync(text);
+  SimpleTest.executeSoon(function() {
+    testGenerator.send(binary);
+  });
 }
 
-function getWasmModule(text)
+function getWasmModule(binary)
 {
-  let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
-  let wasmTextToBinary = SpecialPowers.unwrap(testingFunctions.wasmTextToBinary);
-  let binary = wasmTextToBinary(text);
   let module = new WebAssembly.Module(binary);
   return module;
 }
 
 function verifyBuffers(buffer1, buffer2)
 {
   ok(compareBuffers(buffer1, buffer2), "Correct buffer data");
 }
@@ -205,17 +205,17 @@ function verifyView(view1, view2)
 function verifyWasmModule(module1, module2)
 {
   let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
   let wasmExtractCode = SpecialPowers.unwrap(testingFunctions.wasmExtractCode);
   let exp1 = wasmExtractCode(module1);
   let exp2 = wasmExtractCode(module2);
   let code1 = exp1.code;
   let code2 = exp2.code;
-  ok(code1 instanceof Uint8Array, "Instance of Uint8Array");
+  todo(code1 instanceof Uint8Array, "Instance of Uint8Array");
   ok(code1.length == code2.length, "Correct length");
   verifyBuffers(code1, code2);
   continueToNextStep();
 }
 
 function grabFileUsageAndContinueHandler(request)
 {
   testGenerator.send(request.fileUsage);
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -71,17 +71,18 @@ function testHarnessSteps() {
   info("Pushing preferences");
 
   SpecialPowers.pushPrefEnv(
     {
       "set": [
         ["dom.indexedDB.testing", true],
         ["dom.indexedDB.experimental", true],
         ["dom.archivereader.enabled", true],
-        ["dom.workers.latestJSVersion", true]
+        ["dom.workers.latestJSVersion", true],
+        ["javascript.options.wasm", true]
       ]
     },
     nextTestHarnessStep
   );
   yield undefined;
 
   info("Pushing permissions");
 
@@ -140,17 +141,17 @@ function testHarnessSteps() {
           info(message.msg);
           break;
 
         case "ready":
           worker.postMessage({ op: "load", files: [ testScriptPath ] });
           break;
 
         case "loaded":
-          worker.postMessage({ op: "start" });
+          worker.postMessage({ op: "start", wasmSupported: isWasmSupported() });
           break;
 
         case "done":
           ok(true, "Worker finished");
           nextTestHarnessStep();
           break;
 
         case "expectUncaughtException":
@@ -158,16 +159,21 @@ function testHarnessSteps() {
           break;
 
         case "clearAllDatabases":
           clearAllDatabases(function(){
             worker.postMessage({ op: "clearAllDatabasesDone" });
           });
           break;
 
+        case "getWasmBinary":
+          worker.postMessage({ op: "getWasmBinaryDone",
+                               wasmBinary: getWasmBinarySync(message.text) });
+          break;
+
         default:
           ok(false,
              "Received a bad message from worker: " + JSON.stringify(message));
           nextTestHarnessStep();
       }
     };
 
     URL.revokeObjectURL(workerScriptURL);
@@ -343,19 +349,35 @@ function gc()
   SpecialPowers.forceCC();
 }
 
 function scheduleGC()
 {
   SpecialPowers.exactGC(continueToNextStep);
 }
 
+function isWasmSupported()
+{
+  let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
+  return testingFunctions.wasmIsSupported();
+}
+
+function getWasmBinarySync(text)
+{
+  let testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
+  let wasmTextToBinary = SpecialPowers.unwrap(testingFunctions.wasmTextToBinary);
+  let binary = wasmTextToBinary(text);
+  return binary;
+}
+
 function workerScript() {
   "use strict";
 
+  self.wasmSupported = false;
+
   self.repr = function(_thing_) {
     if (typeof(_thing_) == "undefined") {
       return "undefined";
     }
 
     let str;
 
     try {
@@ -533,39 +555,67 @@ function workerScript() {
     ok(false,
        "Worker: uncaught exception [" + _file_ + ":" + _line_ + "]: '" +
          _message_ + "'");
     self.finishTest();
     self.close();
     return true;
   };
 
+  self.isWasmSupported = function() {
+    return self.wasmSupported;
+  }
+
+  self.getWasmBinarySync = function(_text_) {
+    self.ok(false, "This can't be used on workers");
+  }
+
+  self.getWasmBinary = function(_text_) {
+    self.postMessage({ op: "getWasmBinary", text: _text_ });
+  }
+
+  self.getWasmModule = function(_binary_) {
+    let module = new WebAssembly.Module(_binary_);
+    return module;
+  }
+
+  self.verifyWasmModule = function(_module) {
+    self.todo(false, "Need a verifyWasmModule implementation on workers");
+    self.continueToNextStep();
+  }
+
   self.onmessage = function(_event_) {
     let message = _event_.data;
     switch (message.op) {
       case "load":
         info("Worker: loading " + JSON.stringify(message.files));
         self.importScripts(message.files);
         self.postMessage({ op: "loaded" });
         break;
 
       case "start":
+        self.wasmSupported = message.wasmSupported;
         executeSoon(function() {
           info("Worker: starting tests");
           testGenerator.next();
         });
         break;
 
       case "clearAllDatabasesDone":
         info("Worker: all databases are cleared");
         if (self._clearAllDatabasesCallback) {
           self._clearAllDatabasesCallback();
         }
         break;
 
+      case "getWasmBinaryDone":
+        info("Worker: get wasm binary done");
+        testGenerator.send(message.wasmBinary);
+        break;
+
       default:
         throw new Error("Received a bad message from parent: " +
                         JSON.stringify(message));
     }
   };
 
   self.postMessage({ op: "ready" });
 }
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -387,12 +387,11 @@ skip-if = (buildapp == 'b2g' && toolkit 
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_transaction_ordering.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_unique_index_update.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_view_put_get_values.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_wasm_put_get_values.html]
-#skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
-skip-if = true
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_serviceworker.html]
 skip-if = buildapp == 'b2g'
--- a/dom/indexedDB/test/unit/test_wasm_put_get_values.js
+++ b/dom/indexedDB/test/unit/test_wasm_put_get_values.js
@@ -1,39 +1,34 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-var disableWorkerTest = "Need a way to set temporary prefs from a worker";
-
 var testGenerator = testSteps();
 
 function testSteps()
 {
   const name =
     this.window ? window.location.pathname : "test_wasm_put_get_values.js";
 
   const objectStoreName = "Wasm";
 
   const wasmData = { key: 1, wasm: null };
 
-  if (this.window) {
-    SpecialPowers.pushPrefEnv({ "set": [["javascript.options.wasm", true]] },
-                              continueToNextStep);
-    yield undefined;
-  } else {
-    enableWasm();
-  }
-
   if (!isWasmSupported()) {
     finishTest();
     yield undefined;
   }
 
+  getWasmBinary('(module (func (nop)))');
+  let binary = yield undefined;
+
+  wasmData.wasm = getWasmModule(binary);
+
   info("Opening database");
 
   let request = indexedDB.open(name);
   request.onerror = errorHandler;
   request.onupgradeneeded = continueToNextStepSync;
   request.onsuccess = unexpectedSuccessHandler;
   yield undefined;
 
@@ -48,18 +43,16 @@ function testSteps()
   yield undefined;
 
   // success
   let db = request.result;
   db.onerror = errorHandler;
 
   info("Storing wasm");
 
-  wasmData.wasm = getWasmModule('(module (func (nop)))');
-
   let objectStore = db.transaction([objectStoreName], "readwrite")
                       .objectStore(objectStoreName);
   request = objectStore.add(wasmData.wasm, wasmData.key);
   request.onsuccess = continueToNextStepSync;
   yield undefined;
 
   is(request.result, wasmData.key, "Got correct key");
 
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -45,28 +45,30 @@ if (!this.runTest) {
   this.runTest = function()
   {
     if (SpecialPowers.isMainProcess()) {
       // XPCShell does not get a profile by default.
       do_get_profile();
 
       enableTesting();
       enableExperimental();
+      enableWasm();
     }
 
     Cu.importGlobalProperties(["indexedDB", "Blob", "File", "FileReader"]);
 
     do_test_pending();
     testGenerator.next();
   }
 }
 
 function finishTest()
 {
   if (SpecialPowers.isMainProcess()) {
+    resetWasm();
     resetExperimental();
     resetTesting();
 
     SpecialPowers.notifyObserversInParentProcess(null, "disk-space-watcher",
                                                  "free");
   }
 
   SpecialPowers.removeFiles();
@@ -209,16 +211,21 @@ function resetTesting()
   SpecialPowers.clearUserPref("dom.indexedDB.testing");
 }
 
 function enableWasm()
 {
   SpecialPowers.setBoolPref("javascript.options.wasm", true);
 }
 
+function resetWasm()
+{
+  SpecialPowers.clearUserPref("javascript.options.wasm");
+}
+
 function gc()
 {
   Cu.forceGC();
   Cu.forceCC();
 }
 
 function scheduleGC()
 {
@@ -369,20 +376,33 @@ function getFile(name, type, str)
 }
 
 function isWasmSupported()
 {
   let testingFunctions = Cu.getJSTestingFunctions();
   return testingFunctions.wasmIsSupported();
 }
 
-function getWasmModule(text)
+function getWasmBinarySync(text)
 {
   let testingFunctions = Cu.getJSTestingFunctions();
   let binary = testingFunctions.wasmTextToBinary(text);
+  return binary;
+}
+
+function getWasmBinary(text)
+{
+  let binary = getWasmBinarySync(text);
+  executeSoon(function() {
+    testGenerator.send(binary);
+  });
+}
+
+function getWasmModule(binary)
+{
   let module = new WebAssembly.Module(binary);
   return module;
 }
 
 function compareBuffers(buffer1, buffer2)
 {
   if (buffer1.byteLength != buffer2.byteLength) {
     return false;